Search code examples
ethereumrpcweb3jsjson-rpcmetamask

RPC call with Metamask


I want to create an RPC call (study purpose) from my app using metamask.

My method on my contract is:

function sayHelloMyName(string memory _name) external pure returns (string memory) {      
        require(bytes(_name).length != 0, "insert your name");
        return string(abi.encodePacked("hello ", _name));
    }

its hash is:

$ web3.utils.keccak256("sayHelloMyName(string)").substr(0, 10)
> '0x10a7b27a'

I want to pass my name foo where the hex decimal is 0x666f6f

web3.utils.toHex('foo')
'0x666f6f'

So my call is:

ethereum
    .request({
      method: 'eth_call',
      params: [
        {
          from: currentAccount,
          to: contractAddress,
          data: 0x10a7b27a0000000000000000000000000000000000000000000000000000000000666f6f
        }, "latest"],
    })
    .then((txHash) => {
      console.log(txHash);
      $('#info').html(txHash);
    })
    .catch((error) => {
      console.error;
      $('#info').text(JSON.stringify(error.message));
    });

where the data is the method signature, and my hex name and padding (total 32 bytes) Unfortunately, I get a revert of it.

{
    "jsonrpc": "2.0",
    "id": 1,
    "error": {
        "code": -32000,
        "message": "execution reverted"
    }
}

the code works, I can use it with web3 library.


Solution

  • Solidity string is encoded as a dynamic-length array of bytes including the first 32byte slot pointing to the location of the array beginning and the second 32byte slot containing the length of the string. Meaning: Not just the hex value of the string that you're passing.

    It works with web3js because the JS library decodes the hex back to string (web3 assumes the input is hex as it starts with 0x), and then encodes it correctly to the byte array while filling the data field.

    See output of this web3js snippet

    const data = web3.eth.abi.encodeFunctionCall({
        name: 'sayHelloMyName',
        type: 'function',
        inputs: [{
            type: 'string',
            name: '_name'
        }]
    }, [
        'foo',
    ]);
    console.log(data);
    

    that prints

    # formatted for readability
    0x10a7b27a
    0000000000000000000000000000000000000000000000000000000000000020
    0000000000000000000000000000000000000000000000000000000000000003
    666f6f0000000000000000000000000000000000000000000000000000000000
    
    • First slot is a pointer to the location where the array starts. In this case (hex20 == dec32) that's beginning of the second slot.
    • Second slot (value 3) is the length of the values
    • And the third slot contains the actual hex-encoded value of the string

    Solution: This long value (without the newlines) is what you need to pass instead.