ABI encodeFunctionCall
maxence-charriere opened this issue · 9 comments
Hello,
Could it be possible to get the web3 encodeFunctionCall
?
=> https://web3js.readthedocs.io/en/v1.5.2/web3-eth-abi.html#encodefunctioncall
I tried to make something but I did not completely get how to format dynamic types.
It would be very helpful if you can provide an out-of-the-box function that does this.
I was actually able to get what I wanted with this:
func ContractFunc(def string, args ...interface{}) (string, error) {
var b strings.Builder
m, err := abi.NewMethod(def)
if err != nil {
return "", errors.New("parsing method failed").
Tag("definition", def).
Wrap(err)
}
b.WriteString(fmt.Sprintf("0x%x", m.ID()))
s, err := m.Inputs.Encode(args)
if err != nil {
return "", errors.New("encoding arguments failed").
Tag("definition", def).
Wrap(err)
}
b.WriteString(fmt.Sprintf("%x", s))
return b.String(), nil
}
Thanks for this awesome package!
I will open the issue again because I think it is a good idea to include a helper method in Method to make the encoding and decoding, something like this:
m, err := abi.NewMethod()
input, err := m.Encode(args)
...
val, err := m.Decode(output)
After being able to format properly a method call, I tried to decode a result.
ABI function:
function getTileIdsByPublisher(address publisher) view returns (uint256[])
Result of the function:
0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003
My code to decode the return value:
func DecodeContractFuncResult(def string, data string) (interface{}, error) {
m, err := abi.NewMethod(def)
if err != nil {
return nil, errors.New("parsing method failed").
Tag("definition", def).
Wrap(err)
}
res, err := m.Outputs.Decode([]byte(data))
if err != nil {
return nil, errors.New("decoding method result failed").
Tag("method", def).
Wrap(err)
}
return res, nil
}
I got this error:
decoding method result failed:
method: function getTileIdsByPublisher(address publisher) view returns (uint256[])
error: offset larger than int64: 3472328296227680304
@ferranbt Any idea why this fails?
It looks like the return data from the smart contract do not correspond to the output (uint256[]).
It does, I unit tested it with javascript. Also got it from a javascript client.
Hey @ferranbt, I made a unit test to compare what the smart contract is returning vs what type encode for uint256[]
returns:
func TestEncodeWithType(t *testing.T) {
utests := []struct {
scenario string
typ string
val interface{}
expected string
}{
{
scenario: "empty uint256[]",
typ: "uint256[]",
val: []uint{},
expected: "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000",
},
{
scenario: "uint256[] with 1 element",
typ: "uint256[]",
val: []uint{1},
expected: "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001",
},
}
for _, u := range utests {
t.Run(u.scenario, func(t *testing.T) {
typ, err := abi.NewType(u.typ)
require.NoError(t, err)
data, err := typ.Encode(u.val)
require.NoError(t, err)
require.Equal(t, u.expected, fmt.Sprintf("0x%x", data))
})
}
}
Output:
▶ go test
--- FAIL: TestEncodeWithType (0.00s)
--- FAIL: TestEncodeWithType/empty_uint256[] (0.00s)
contract_test.go:93:
Error Trace: contract_test.go:93
Error: Not equal:
expected: "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000"
actual : "0x0000000000000000000000000000000000000000000000000000000000000000"
Diff:
--- Expected
+++ Actual
@@ -1 +1 @@
-0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000
+0x0000000000000000000000000000000000000000000000000000000000000000
Test: TestEncodeWithType/empty_uint256[]
--- FAIL: TestEncodeWithType/uint256[]_with_1_element (0.00s)
contract_test.go:93:
Error Trace: contract_test.go:93
Error: Not equal:
expected: "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001"
actual : "0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001"
Diff:
--- Expected
+++ Actual
@@ -1 +1 @@
-0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001
+0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001
Test: TestEncodeWithType/uint256[]_with_1_element
FAIL
exit status 1
FAIL github.com/maxence-charriere/nftile/pkg/eth 0.295s
Look like the one returned by the smart contract has some header data.
Note that I was able to decode the smart contract function return with web3js decodeParameter and retrieve the expected array.
The solidity version I use is 0.8.4
.
I also made a test to try encoding from a method and noticed it was failing:
func TestEncodeFromMethod(t *testing.T) {
utests := []struct {
scenario string
val []uint
}{
{
scenario: "encode empty slice",
val: []uint{},
},
{
scenario: "encode slice with 1 element",
val: []uint{1},
},
}
for _, u := range utests {
t.Run(u.scenario, func(t *testing.T) {
f := "function getTileIdsByPublisher(address publisher) view returns (uint256[])"
m, err := abi.NewMethod(f)
require.NoError(t, err)
_, err = m.Outputs.Encode([]uint{1})
require.NoError(t, err)
})
}
}
Output:
--- FAIL: TestEncodeFromMethod (0.00s)
--- FAIL: TestEncodeFromMethod/encode_empty_slice (0.00s)
contract_test.go:120:
Error Trace: contract_test.go:120
Error: Received unexpected error:
failed to encode uint as Slice
Test: TestEncodeFromMethod/encode_empty_slice
--- FAIL: TestEncodeFromMethod/encode_slice_with_1_element (0.00s)
contract_test.go:120:
Error Trace: contract_test.go:120
Error: Received unexpected error:
failed to encode uint as Slice
Test: TestEncodeFromMethod/encode_slice_with_1_element
FAIL
exit status 1
FAIL github.com/maxence-charriere/nftile/pkg/eth 2.366s
Look like the one returned by the smart contract has some header data.
I think your input is "tuple(uint256[])" while the other one is "uint256[]", the tuple encoding is the extra header.
I also made a test to try encoding from a method and noticed it was failing:
For "uint256" you have to use big.Int library. Though I will open an issue to use also any other uint type as well if this is a confusion.
This library uses integration tests with real smart contracts deployed in a local network to validate the abi encodings. I just added uint256[] to the tests and it works fine:
https://github.com/umbracle/go-web3/blob/fix-encode-uint256-slice/abi/encoding_test.go#L126
This is the smart contract it tests against:
pragma solidity ^0.5.5;
pragma experimental ABIEncoderV2;
contract Sample {
// structs
function set(uint256[] memory arg0) public view returns (uint256[] memory) {
return (arg0);
}
}
Reopening to track the []uint256
issue.