trustwallet / wallet-core

Cross-platform, cross-blockchain wallet library.
https://developer.trustwallet.com/wallet-core
Apache License 2.0
2.86k stars 1.6k forks source link

Unexpected EthereumAbiValue.decodeValue output for strings #2554

Closed nikitaeverywhere closed 2 years ago

nikitaeverywhere commented 2 years ago

Related: https://github.com/trustwallet/wallet-core/discussions/2526

Hello! Say we have "Tether USD" string encoded in the JSON RPC response:

POST https://eth.public-rpc.com { "jsonrpc": "2.0", "id": 0, "method": "eth_call", "params": [ { "to": "0xdac17f958d2ee523a2206206994597c13d831ec7", "data": "0x06fdde03" }, "latest" ] }

{
    "jsonrpc": "2.0",
    "id": 0,
    "result": "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a5465746865722055534400000000000000000000000000000000000000000000"
}

The result decoding is easy in JavaScript, check this: https://codepen.io/ZitRos/pen/ExLaByJ

const res = new Web3(window.ethereum).eth.abi.decodeParameters([{
      type: 'string',
      name: 'myString'
  }], '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a5465746865722055534400000000000000000000000000000000000000000000');
console.log(res[0]); // "Tether USD"

However when we try the same using EthereumAbiValue.decodeValue with "string" it doesn't work (returns an empty string). Uint though works well.

image

What is wrong?

Same thing if you take this string from the test and insert it to JavaScript code: it can't decode it. You can quickly check it here.

new Web3(window.ethereum).eth.abi.decodeParameters([{type: 'string',name: 'test'}], "0x 000000000000000000000000000000000000000000000000000000000000002c48656c6c6f20576f726c64212020202048656c6c6f20576f726c64212020202048656c6c6f20576f726c64210000000000000000000000000000000000000000")
// Throws an error

Can anyone suggest what is wrong with decodeParameters and string? Or how to properly decode "Tether USD" using wallet-core from the RPC call above?

Thank you.

Milerius commented 2 years ago

Related: #2526

Hello! Say we have "Tether USD" string encoded in the JSON RPC response:

POST https://eth.public-rpc.com { "jsonrpc": "2.0", "id": 0, "method": "eth_call", "params": [ { "to": "0xdac17f958d2ee523a2206206994597c13d831ec7", "data": "0x06fdde03" }, "latest" ] }

{
    "jsonrpc": "2.0",
    "id": 0,
    "result": "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a5465746865722055534400000000000000000000000000000000000000000000"
}

The result decoding is easy in JavaScript, check this: https://codepen.io/ZitRos/pen/ExLaByJ

const res = new Web3(window.ethereum).eth.abi.decodeParameters([{
      type: 'string',
      name: 'myString'
  }], '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a5465746865722055534400000000000000000000000000000000000000000000');
console.log(res[0]); // "Tether USD"

However when we try the same using EthereumAbiValue.decodeValue with "string" it doesn't work (returns an empty string). Uint though works well.

image

What is wrong?

Same thing if you take this string from the test and insert it to JavaScript code: it can't decode it. You can quickly check it here.

new Web3(window.ethereum).eth.abi.decodeParameters([{type: 'string',name: 'test'}], "0x 000000000000000000000000000000000000000000000000000000000000002c48656c6c6f20576f726c64212020202048656c6c6f20576f726c64212020202048656c6c6f20576f726c64210000000000000000000000000000000000000000")
// Throws an error

Can anyone suggest what is wrong with decodeParameters and string? Or how to properly decode "Tether USD" using wallet-core from the RPC call above?

Thank you.

I do not think it's a bug, there is a confusion between decoding a parameter and a value here, the test you are pointing out is decoding a value of type string, not a full parameter including the type - if you were decoding the full parameter the result would be 0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002c48656c6c6f20576f726c64212020202048656c6c6f20576f726c64212020202048656c6c6f20576f726c64210000000000000000000000000000000000000000

source: https://abi.hashex.org/

I do think you are looking for: https://github.com/trustwallet/wallet-core/blob/1114cf79bf23d8125c717924738522669adfd77f/src/Ethereum/ABI/Parameters.h#L65

js solution:

try {
  const res = new Web3(window.ethereum).eth.abi.decodeParameters([{
      type: 'string',
      name: 'myString'
  }], '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002c48656c6c6f20576f726c64212020202048656c6c6f20576f726c64212020202048656c6c6f20576f726c64210000000000000000000000000000000000000000');

  document.body.textContent = res[0]; 
} catch (e) {
  document.body.textContent = e.toString();
}

cpp solution:

TEST(EthereumAbi, EncodeParamsStringSimple) {
    auto p = Parameters(std::vector<std::shared_ptr<ParamBase>>{
    std::make_shared<ParamString>("Hello World!    Hello World!    Hello World!")});
    Data encoded;
    p.encode(encoded);
    ASSERT_EQ(hex(encoded), "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002c48656c6c6f20576f726c64212020202048656c6c6f20576f726c64212020202048656c6c6f20576f726c64210000000000000000000000000000000000000000");
}
Milerius commented 2 years ago

Please feel free to re-open if you have any questions.

nikitaeverywhere commented 2 years ago

Thanks a ton @Milerius! This explanation sound to me, I spend some time to understand it. However I still struggle to understand how to parse the response from JSON PRC; decoding plain RPC response 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a5465746865722055534400000000000000000000000000000000000000000000 doesn't work, how should I transform it to be parseable?

EthereumAbiValue.decodeValue(hexStringToByteArray(encodedString), "string") // ""
optout21 commented 2 years ago

To decode, one needs to know the paramter types. The eth_call returns an encoded data, and its structure depends on the contract called. So you need to know the parameter structure returned by the called contract -- this is documentation of the contract. Then construct the parameter structure, and then it can decode (like here https://github.com/trustwallet/wallet-core/blob/master/tests/Ethereum/AbiTests.cpp#L933)

It is not possible to decode without knowing upfront the order and types of the parameters.

optout21 commented 2 years ago

Here's C code for decoding, a set of one string parameter:

        auto p = Parameters(std::vector<std::shared_ptr<ParamBase>>{std::make_shared<ParamString>()});
        size_t offset = 0;
        p.decode(parse_hex("0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a5465746865722055534400000000000000000000000000000000000000000000"), offset);
        std::cout << "Value: '" << dynamic_cast<ParamString*>(p.getParam(0).get())->getVal() << "' \n";
stosabon commented 2 years ago

@catenocrypt Is there something similar in wallet core for Android? Don't see it.