Ethers.js: Using the ABI coder to encode and decode eth_call

Created on 24 Jun 2018  路  24Comments  路  Source: ethers-io/ethers.js

https://github.com/ethjs/ethjs-abi says that it is from ethers.js, but it is fairly out of date and doesn't support structs. I'm using ethers.js elsewhere in my app and would like to leverage its ability to decode tuple[] parameters. Is there somewhere in the library where I can do something like the following?

const result = ethers.?.decodeParams(abi, encodedResultOfEthCall)
discussion

Most helpful comment

Reading this again, I think what you want already exists. :)

Does this do what you want?

var abi = [ { name: 'foo', type: 'function', inputs: [ { type: 'uint256' } ], outputs: [ { type: 'uint8' }] } ];
var iface = new ethers.Interface(abi)

// Example
var calldata = iface.functions.foo.encode(42);
// "0x2fbebd38000000000000000000000000000000000000000000000000000000000000002a"

// Parsing call response
var response = "0x000000000000000000000000000000000000000000000000000000000000002b";
var result = iface.functions.foo.decode(response);
// 43

All 24 comments

I'm also looking for a replacement for ethers.?.encodeMethod(abiFunction, parameters)

Looks like no such thing exists. Feature request: Expose parseParams, as it is a necessary primitive to create a function signature hash from an ABI.

Heya!

The ethjs library, should not be used, as it uses out-of-date versions of the library and introduces security issues in other parts of the library. The author just cut and paste an assortment of ethers.js into their library, and I do not believe maintains it.

If you use the new 4.0 (TypeScript branch):

var fragment = ethers.utils.parseParamType("tuple(uint a, address b, tuple(bytes c) d) e");
console.log(fragment);
{ type: 'tuple',
  name: 'e',
  components: 
   [ { type: 'uint256', name: 'a' },
     { type: 'address', name: 'b' },
     { type: 'tuple', name: 'd', components: [Array] } ] }

Notice that it handles tuples, array, etc. but also converts uint to uint256. It should make it much easier to write tools.

It can be done in v3 as well, but it is more complicated and error-prone; let me know if you need this way and I'll post it.

I think you may have misunderstood what I'm looking for (based on your answer). I have an ABI object to start with (from Solidity compiler output), and I need to ABI encode some JavaScript parameters using that ABI object (so I can pass them to an eth_call as the data after the signature hash, or decode the result of an eth_call using that ABI. It looks like your code snippit is to take a string and turn it into an ABI object.

I have looked through the code and found where you do this, but the functions that do this work are not exported, thus not available to library users. Here are the sections I was hoping to find exposed in a nice API:
https://github.com/ethers-io/ethers.js/blob/typescript/contracts/interface.js#L264-L287
https://github.com/ethers-io/ethers.js/blob/typescript/contracts/interface.js#L29-L55

One has a comment indicating there are perhaps plans to expose something nicer in the future.

Oh! Sorry, yes, I misunderstood.

I believe you should be able to use:

var abiCoder = ethers.utils.defaultAbiCoder;
var data = abiCoder.encode(abi.foobar.inputs, values);
var calldata = ...
var decodedResponse = abiCoder.decode(abi.foobar.outputs, calldata);

I can expose a method to encode the sighash part too, since I think that is the only part you are then missing.

Reading this again, I think what you want already exists. :)

Does this do what you want?

var abi = [ { name: 'foo', type: 'function', inputs: [ { type: 'uint256' } ], outputs: [ { type: 'uint8' }] } ];
var iface = new ethers.Interface(abi)

// Example
var calldata = iface.functions.foo.encode(42);
// "0x2fbebd38000000000000000000000000000000000000000000000000000000000000002a"

// Parsing call response
var response = "0x000000000000000000000000000000000000000000000000000000000000002b";
var result = iface.functions.foo.decode(response);
// 43

Ooh, that does look like exactly what I want. I'll give it a try. Thanks!

When I ran this piece of code, it tells me that:

Uncaught TypeError: iface.functions.foo.encode is not a function

The error can be seen here: https://codepen.io/asiankingofwhales/pen/YOpYyL

Are you using v3 branch? Try using v4 branch.

npm install ethers@next

And try the code again:

const ethers = require('ethers');

var abi = [{ name: 'foo', type: 'function', inputs: [{ type: 'uint256' }], outputs: [{ type: 'uint8' }] }];
var iface = new ethers.Interface(abi)

// Example
var calldata = iface.functions.foo.encode([42]);
// "0x2fbebd38000000000000000000000000000000000000000000000000000000000000002a"

// Parsing call response
var response = "0x000000000000000000000000000000000000000000000000000000000000002b";
var result = iface.functions.foo.decode(response);
// [ 43 ]

V4 branch works. Thank you

When I ran this piece of code,it tells me that:

Uncaught TypeError: ethers.Interface is not a constructor
at <anonymous>:1:13

I import ethers by this way

In v4, it has been moved to ethers.utils.Interface. That should fix that problem. :)

So how should I user abicoder to general the code for the current version锛燂紵
I really appreciate it if you can write a demo to me

I can add an example to the docs tomorrow, but here is the documentation to get started: https://docs.ethers.io/ethers.js/html/api-advanced.html#interface

I鈥檒l update this ticket once I鈥檝e added some sample code to the docs. :)

Thank you so much,bro

Hello! Hoping this thread is OK for my question :)

I have the following:

let contract = new ethers.Contract(testContractAddress, abi, customHttpProvider);
let contractWithSigner = contract.connect(wallet);

const args = Object.values({
    propertyOne: 1236,
    propertyTwo: 2
});

let iface = new ethers.utils.Interface(abi)
let calldata = iface.functions.myFunction.encode(...args);

let transaction = {
    nonce: 0,
    gasLimit: 3000000,
    to: testContractAddress,
    data: calldata
};

let signPromise = await wallet.sign(transaction);

And I'm receiving an error:

message:"invalid input argument (arg=undefined, reason="types/values length mismatch", value={"types":[{"name":"propertyOne","type":"uint256"},{"name":"propertyTwo","type":"uint256"}],"values":1236}, version=4.0.26)"
reason:"types/values length mismatch"

I understand that is expecting more values for the length or parameters the function uses, but I also tried pass them individually and got the next error:

message:"Cannot read property 'length' of undefined"
stack:"TypeError: Cannot read property 'length' of undefined\n    at _FunctionDescription.encode (/node_modules/ethers/utils/interface.js:80:42)\n    at signTransactionTest (/testWithTransactionInsteadOfContract.js:50:59)\n    at processTicksAndRejections (internal/process/next_tick.js:81:5)"

What am I doing wrong? :disappointed:
Thanks in advance!

I think you need to get rid of the "..." in front of the args. I鈥檓 not at my computer right now though, so I can鈥檛 test it out. Let me know if that doesn鈥檛 help and I鈥檒l try out the code once I鈥檓 back at my computer. :)

Aren't you a genius? Thanks! Worked perfectly ... I did not try that option :woman_facepalming:

Haha! Awesome. Nah, I just stare at it all day long... every.... day. :)

TypeError: Cannot read property 'encode' of undefined. All examples are not working with v5

In v5, Interface is no longer a meta-class, which should make interacting with it much easier, but will require your code to change.

See: https://docs.ethers.io/v5/api/utils/abi/interface/

So, for example, to encode function data, you now use:

const abi = [
    "function transfer(address to, uint value)"
];
const iface = new Interface(abi);
const data = iface.encodeFunctionData("transfer", (someAddress, someValue));

You can also now perform encoding and decoding of both data and results (in v4, you could only encode data and only decode results).

:)

I'm having a similar issue. Using V5 under NodeJS

const fillInterface = new ethers.utils.Interface(FILL_ORDER_ABI);        // function abi
const data =  fillInterface.encodeFunctionData("fillOrder",[orderTuple, takerAssetFillAmount, signature])

_Error: no matching function (argument="name", value="fillOrder", code=INVALID_ARGUMENT, version=abi/5.0.9)_

The ABI

{
                           "constant":false,"inputs":[
                               {"components":[
                                   {"internalType":"address","name":"makerAddress","type":"address"},
                                   {"internalType":"address","name":"takerAddress","type":"address"},
                                   {"internalType":"address","name":"feeRecipientAddress","type":"address"},
                                   {"internalType":"address","name":"senderAddress","type":"address"},
                                   {"internalType":"uint256","name":"makerAssetAmount","type":"uint256"},
                                   {"internalType":"uint256","name":"takerAssetAmount","type":"uint256"},
                                   {"internalType":"uint256","name":"makerFee","type":"uint256"},
                                   {"internalType":"uint256","name":"takerFee","type":"uint256"},
                                   {"internalType":"uint256","name":"expirationTimeSeconds","type":"uint256"},
                                   {"internalType":"uint256","name":"salt","type":"uint256"},
                                   {"internalType":"bytes","name":"makerAssetData","type":"bytes"},
                                   {"internalType":"bytes","name":"takerAssetData","type":"bytes"},
                                   {"internalType":"bytes","name":"makerFeeAssetData","type":"bytes"},
                                   {"internalType":"bytes","name":"takerFeeAssetData","type":"bytes"}
                                   ],
                                   "internalType":"struct LibOrder.Order","name":"order","type":"tuple"
                                },
                                {"internalType":"uint256","name":"takerAssetFillAmount","type":"uint256"},
                                {"internalType":"bytes","name":"signature","type":"bytes"}
                            ],
                            "name":"fillOrder",
                            "outputs":[
                                {"components":[
                                    {"internalType":"uint256","name":"makerAssetFilledAmount","type":"uint256"},
                                    {"internalType":"uint256","name":"takerAssetFilledAmount","type":"uint256"},
                                    {"internalType":"uint256","name":"makerFeePaid","type":"uint256"},
                                    {"internalType":"uint256","name":"takerFeePaid","type":"uint256"},
                                    {"internalType":"uint256","name":"protocolFeePaid","type":"uint256"}
                                ],
                                "internalType":"struct LibFillResults.FillResults",
                                "name":"fillResults","type":"tuple"}
                            ],"payable":true,
                            "stateMutability":"payable",
                            "type":"function"
                        }

Not sure why it wont see the function. This ABI was working with WEB3

@Madeindreams Are there multiple functions named fillOrder? If so, you will need to use the qualified name to access it, for example:

contract["fillOrder(address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes)"](makerAddr, takerAddr, feeAddr, sender, makerAmnt, takerAmnt, makerFee, takerFee, expires, salt, makerData, takerData, eakerDataFee, takerDataFee);

There may be typos in that, but that should give you the general idea. Another option is to exclude the conflicting fragments from the ABI, which is what I usually recommend. It makes life much simpler (and the code size much smaller) to only include parts of the contract you actually end up using. :)

Let me know if that helps.


Continuing in your cross-post, #1196. :)

The ABI only contain 1 function. fillOrder. From what i can read in the ABI its a component named fillOrder with an input and an output. I don't think there is a function name in my ABI. Anyway I can call it without it's name?

Was this page helpful?
0 / 5 - 0 ratings