Go-ethereum: Android: abi: cannot use slice as type string as argument / 'abi: cannot unmarshal string in to []interface {}'

Created on 19 Jul 2017  路  24Comments  路  Source: ethereum/go-ethereum

System information

Geth version: 1.6.6
Mobile: Android

Expected behaviour

Successful interaction with a deployed smart contract on the Rinkeby blockchain.

Actual behaviour

For setter (on contract):
'abi: cannot use slice as type string as argument'
For getter (on contract):
'abi: cannot unmarshal string in to []interface {}'

Steps to reproduce the behaviour

1.) Connect to Rinkeby Testnet via mobile
2.) Create an account via mobile
3.) Deploy a smart contract via desktop
4.) Try to interact w/ the smart contract via mobile

Android Code

final String address_string = "0x8607e627604495ae9812c22bb1c98bdcba581978";
String abi = "[{\"constant\":false,\"inputs\":[],\"name\":\"get_s\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"new_s\",\"type\":\"string\"}],\"name\":\"set_s\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"inputs\":[{\"name\":\"d_s\",\"type\":\"string\"}],\"payable\":false,\"type\":\"constructor\"}]";
Address address = Geth.newAddressFromHex(address_string);
BoundContract contract = Geth.bindContract(address, abi, ec);
CallOpts callOpts = Geth.newCallOpts();
callOpts.setContext(ctx);
callOpts.setGasLimit(31500);
System.out.println("OUTPUT: " + getString(contract, callOpts));

//Setter String to Test Contract
Interfaces params = Geth.newInterfaces(1);
Interface anInterface = Geth.newInterface();
anInterface.setString(teststring);
params.set(0,anInterface);
return contract.transact(opts, "set_s", params);

//Getter String from Test Contract
Interfaces args = Geth.newInterfaces(0);
Interfaces results = Geth.newInterfaces(1);
Interface result = Geth.newInterface();
result.setDefaultString();
results.set(0, result);
contract.call(opts, results, "get_s", args);
String string = results.get(0).getString();
return string;

Backtrace

W/System.err: go.Universe$proxyerror: abi: cannot unmarshal string in to []interface {} W/System.err: at org.ethereum.geth.BoundContract.call(Native Method) W/System.err: at andrewnguyen.test_geth.MainActivity.getString(MainActivity.java:170) W/System.err: at andrewnguyen.test_geth.MainActivity.smart_contract_interaction_get_string(MainActivity.java:157) W/System.err: at java.lang.reflect.Method.invoke(Native Method) W/System.err: at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288) W/System.err: at android.view.View.performClick(View.java:6213) W/System.err: at android.widget.TextView.performClick(TextView.java:11074) W/System.err: at android.view.View$PerformClick.run(View.java:23645) W/System.err: at android.os.Handler.handleCallback(Handler.java:751) W/System.err: at android.os.Handler.dispatchMessage(Handler.java:95) W/System.err: at android.os.Looper.loop(Looper.java:154) W/System.err: at android.app.ActivityThread.main(ActivityThread.java:6692) W/System.err: at java.lang.reflect.Method.invoke(Native Method) W/System.err: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1468) W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1358)

Contract

pragma solidity ^0.4.9;

contract echo {
   string s;

   function echo(string d_s) {
           s = d_s;
   }

   function set_s(string new_s) {
           s = new_s;
   }

   function get_s() returns (string) {
           return s;
    }
}

ABI Generated From remix.ethereum.org

[{"constant":false,"inputs":[],"name":"get_s","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"new_s","type":"string"}],"name":"set_s","outputs":[],"payable":false,"type":"function"},{"inputs":[{"name":"d_s","type":"string"}],"payable":false,"type":"constructor"}]

All 24 comments

solved?

Not yet...

Some more info I gathered from debugging:
1) I traced the call as follows:
- mobile/bind.go -> BoundContract.Call()
- accounts/abi/bind/base.go -> BoundContract.Call()
- accounts/abi/abi.go -> Pack() and Unpack()
- accounts/abi/reflect.go -> set()
2) For me, in the set() function in reflect.go, the src argument is a pointer, pointing to a struct and the dst argument is a slice.
3) The error is returned in the account/abi/reflect.go file, because none of the possible cases are fulfilled. The returned error is "abi: cannot unmarshal *big.Int in to []interface {}".
4) The EVM call is executed correctly (for a getter function), as expected (it returns a uint of value 1)

msg="sending {\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"eth_call\",\"params\":[{\"data\":\"0x3bc36a2d\",\"from\":\"0x0000000000000000000000000000000000000000\",\"to\":\"0x978d6147f6b2a5088f5b24f79a4f952f7cbc6f56\"},\"latest\"]}"

msg="<-readResp: response {\"jsonrpc\":\"2.0\",\"id\":2,\"result\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"}"

Has anyone already found the specific problem, or a fix?

I used this workaround for returning a string from a contract call command:

https://github.com/cameronvoell/go-ethereum/commit/65987efac70c8af7ec9923107d94a9275d21fdd1

Hi @Rylo4

https://github.com/ethereum/go-ethereum/blob/master/accounts/abi/abi.go#L107

Can you try to replace from "if len(method.Outputs) > 1" to "if len(method.Outputs) >= 1", then rebuild android package again?

@nthtson Thanks, this fixes the issue in my comment. Does this change have implications for the full version of geth?

@Rylo4 I think it's ok for your purposes now. Anyway, keep an eye on this issue.

FYI, I also reported another issue about Contract ABI https://github.com/ethereum/go-ethereum/issues/14363#issuecomment-320412358

The Geth mobile swift has the same problem

abi: cannot unmarshal *big.Int in to []interface {}.

Hi @erichin

I have the same problem. Have you solved it ?

@nthtson @Rylo4 So the problem still exists. At least on iOS version. @nthtson ur comments for the fix might not valid anymore as abi.go implementation changed.

@timoninn Nop, but i use to fix it by the way of @nthtson commented on Aug 17. But now the abi.go implementation has changed as @DHOscar say

i have the same problem
abi: cannot unmarshal *big.Int in to []interface {}.

is it solved?

Same problem here, on iOS, with geth 1.7.2.
If I try to get balance from a contract, the exception is exactly same.
When I try to get the owner of the contract, then the error is abi: cannot unmarshal common.Address in to []interface {}

Both cases RPC return correctly.
For getOwner case:

msg="sending {\"jsonrpc\":\"2.0\",\"id\":18,\"method\":\"eth_call\",\"params\":[{\"data\":\"0x893d20e8\",\"from\":\"0x0000000000000000000000000000000000000000\",\"to\":\"0x3fe80503073df564fcd29031a1161ef53bfb429d\"},\"latest\"]}"

msg="<-readResp: response {\"jsonrpc\":\"2.0\",\"id\":18,\"result\":\"0x000000000000000000000000ddcaeba5e0feb704b649d36d3d631a060eca6f2f\"}"

Yes,my code is :

Address address = new Address("0x43ee79e379e7b78d871100ed696e803e7893b644");
String abiJSON = "[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_addr","type":"address"}],"name":"getNonce","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"founder","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"version","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"allocateEndBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"},{"name":"_v","type":"uint8"},{"name":"_r","type":"bytes32"},{"name":"_s","type":"bytes32"}],"name":"approveProxy","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_owners","type":"address[]"},{"name":"_values","type":"uint256[]"}],"name":"allocateTokens","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"},{"name":"_extraData","type":"bytes"}],"name":"approveAndCallcode","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"},{"name":"_extraData","type":"bytes"}],"name":"approveAndCall","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"allocateStartBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"},{"name":"_feeUgt","type":"uint256"},{"name":"_v","type":"uint8"},{"name":"_r","type":"bytes32"},{"name":"_s","type":"bytes32"}],"name":"transferProxy","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":false,"stateMutability":"nonpayable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_owner","type":"address"},{"indexed":true,"name":"_spender","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Approval","type":"event"}]";
CallOpts opts = Geth.newCallOpts();
opts.setContext(ctx);
BoundContract contract = Geth.bindContract(address,abiJSON,client);
Interfaces results = Geth.newInterfaces(1);
Interface result = Geth.newInterface();
result.setDefaultBigInt();
results.set(0, result);
Interfaces params = Geth.newInterfaces(1);
Interface anInterface = Geth.newInterface();
anInterface.setAddress(new 
geth.Address("0x62a1056fcf3dbb4305a00b0686e9a96b0b7e4bfa"));
params.set(0,anInterface);
contract.call(opts,results,"balanceOf",params);
BigInt balance = results.get(0).getBigInt();

The RPC retun correctly:
{"jsonrpc":"2.0","id":3,"result":"0x000000000000000000000000000000000000000000041e8e57486d2ec0800000"}"
but the abi error return : go.Universe$proxyerror: abi: cannot unmarshal *big.Int in to []interface {}

Finally i found the wrong place about golang ethereum source code /accounts/abi/reflect.go

func set(dst, src reflect.Value, output Argument) error {
dstType := dst.Type()
srcType := src.Type()

switch {
case dstType.AssignableTo(src.Type()):
    dst.Set(src)
case dstType.Kind() == reflect.Array && srcType.Kind() == reflect.Slice:
    if dst.Len() < output.Type.SliceSize {
        return fmt.Errorf("abi: cannot unmarshal src (len=%d) in to dst (len=%d)", output.Type.SliceSize, dst.Len())
    }
    reflect.Copy(dst, src)
case dstType.Kind() == reflect.Interface:
    dst.Set(src)
case dstType.Kind() == reflect.Ptr:
    return set(dst.Elem(), src, output)
default:
    return fmt.Errorf("abi: cannot unmarshal %v in to %v", src.Type(), dst.Type())
}
return nil

}

Same problem Android version

abi: cannot unmarshal *big.Int in to []interface {}

Any solution ?

Quickly check the go source code. Compare the original call method implementation in base.go:

func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string, params ...interface{}) error

And mobile wrapper in bind.go:

func (c *BoundContract) Call(opts *CallOpts, out *Interfaces, method string, args *Interfaces) error {
    results := make([]interface{}, len(out.objects))
    copy(results, out.objects)
    if err := c.contract.Call(&opts.opts, &results, method, args.objects...); err != nil {
        return err
    }
    copy(out.objects, results)
    return nil
}

The input param result type for original call method should be interface{}. But in the mobile wrapper, we try to use an array/slice.

So if the smart contract always returjn a signal value, I think we can fix the wrapper implementation to make it work. But for sure this is not the right solution.

I modify the go source code belllow and bulid for new geth.aar, it can solve the problem ,but i think it is not the good solution.

func (c *BoundContract) Call(opts *CallOpts, out *Interfaces, method string, args *Interfaces) error {
// results := make([]interface{}, len(out.objects))
// copy(results, out.objects)
if err := c.contract.Call(&opts.opts, out.objects[0], method, args.objects...); err != nil {
    return err
}
// copy(out.objects, results)
return nil

}

@cytl5yy Totally agreed. I just test it and it works for the signal return value. Not test the array yet. But for sure it should not work for the tuple.

I would say this is the problem. Although I have no good solution for it.

Yes ,signal value is good for this solution, so if you want to work for single and tuple value ,you must modify /accounts/abi/method.go souce code. when i have time i will try to solve it.

Unrelated to this issue, @DHOscar and @cytl5yy, do you guys know how to make a signer when call transfer/transaction on iOS? I only found an Android example, and follow that logic on iOS, it just crashed.

go.Universe$proxyerror: "setText" abi: cannot use slice as type string as argument
Any solution, guys?

What I did is cherry-pick the patch from @Orenzak #15402 and build from source. That works well.

Indeed what I did. I think the fix works but CI give problem. Hope @OrenZak will fix it soon. As my project don't need to work with the most current client, I just cherry-pick it.

I think that鈥檚 CI鈥檚 problem, but apparently people don鈥檛 care about mobile, even a CLI issse (like attach should ignore 鈥攄atadir) can have higher priority and get merged quickly.

Was this page helpful?
0 / 5 - 0 ratings