From a87658abe1ce7598b67003c7e636b7b68f2fb9f4 Mon Sep 17 00:00:00 2001 From: luka-ciric-ethernal Date: Thu, 17 Oct 2024 10:00:47 +0200 Subject: [PATCH 1/7] Update Solidity version to 0.8.19 --- abi/testing.go | 20 ++++++++++---------- compiler/fixtures/ballot.sol | 5 +++-- compiler/fixtures/simple_auction.sol | 15 ++++++++------- compiler/solidity_test.go | 10 +++++----- scripts/setup-ci.sh | 2 +- testutil/contract.go | 3 +-- 6 files changed, 28 insertions(+), 27 deletions(-) diff --git a/abi/testing.go b/abi/testing.go index fbd59eca..9a2ca4b4 100644 --- a/abi/testing.go +++ b/abi/testing.go @@ -191,16 +191,16 @@ func (g *generateContractImpl) run(t *Type) string { body = append(body, fmt.Sprintf("arg%d", indx)) } - contractTemplate := `pragma solidity ^0.5.5; -pragma experimental ABIEncoderV2; - -contract Sample { - // structs - %s - function set(%s) public view returns (%s) { - return (%s); - } -}` + contractTemplate := + `pragma solidity ^0.8.19; + + contract Sample { + // structs + %s + function set(%s) public view returns (%s) { + return (%s); + } + }` contract := fmt.Sprintf( contractTemplate, diff --git a/compiler/fixtures/ballot.sol b/compiler/fixtures/ballot.sol index 515babef..580aa3e2 100644 --- a/compiler/fixtures/ballot.sol +++ b/compiler/fixtures/ballot.sol @@ -1,4 +1,5 @@ -pragma solidity >=0.4.22 <0.6.0; +/* SPDX-License-Identifier: UNLICENSED */ +pragma solidity >=0.4.22 <0.8.29; /// @title Voting with delegation. contract Ballot { @@ -28,7 +29,7 @@ contract Ballot { Proposal[] public proposals; /// Create a new ballot to choose one of `proposalNames`. - constructor(bytes32[] memory proposalNames) public { + constructor(bytes32[] memory proposalNames) { chairperson = msg.sender; voters[chairperson].weight = 1; diff --git a/compiler/fixtures/simple_auction.sol b/compiler/fixtures/simple_auction.sol index aff2425d..b11d292f 100644 --- a/compiler/fixtures/simple_auction.sol +++ b/compiler/fixtures/simple_auction.sol @@ -1,4 +1,5 @@ -pragma solidity >=0.4.22 <0.6.0; +/* SPDX-License-Identifier: UNLICENSED */ +pragma solidity >=0.4.22 <0.8.29; contract SimpleAuction { // Parameters of the auction. Times are either @@ -33,9 +34,9 @@ contract SimpleAuction { constructor( uint _biddingTime, address payable _beneficiary - ) public { + ) { beneficiary = _beneficiary; - auctionEndTime = now + _biddingTime; + auctionEndTime = block.timestamp + _biddingTime; } /// Bid on the auction with the value sent @@ -52,7 +53,7 @@ contract SimpleAuction { // Revert the call if the bidding // period is over. require( - now <= auctionEndTime, + block.timestamp <= auctionEndTime, "Auction already ended." ); @@ -77,7 +78,7 @@ contract SimpleAuction { } /// Withdraw a bid that was overbid. - function withdraw() public returns (bool) { + function withdraw() public payable returns (bool) { uint amount = pendingReturns[msg.sender]; if (amount > 0) { // It is important to set this to zero because the recipient @@ -85,7 +86,7 @@ contract SimpleAuction { // before `send` returns. pendingReturns[msg.sender] = 0; - if (!msg.sender.send(amount)) { + if (!payable(msg.sender).send(amount)) { // No need to call throw here, just reset the amount owing pendingReturns[msg.sender] = amount; return false; @@ -111,7 +112,7 @@ contract SimpleAuction { // external contracts. // 1. Conditions - require(now >= auctionEndTime, "Auction not yet ended."); + require(block.timestamp >= auctionEndTime, "Auction not yet ended."); require(!ended, "auctionEnd has already been called."); // 2. Effects diff --git a/compiler/solidity_test.go b/compiler/solidity_test.go index 715287c4..ac62b20e 100644 --- a/compiler/solidity_test.go +++ b/compiler/solidity_test.go @@ -28,7 +28,7 @@ func init() { panic(err) } // solc folder does not exists - if err := DownloadSolidity("0.5.5", solcDir, false); err != nil { + if err := DownloadSolidity("0.8.19", solcDir, false); err != nil { panic(err) } } @@ -134,13 +134,13 @@ func TestDownloadSolidityCompiler(t *testing.T) { } defer os.RemoveAll(dst1) - if err := DownloadSolidity("0.5.5", dst1, true); err != nil { + if err := DownloadSolidity("0.8.19", dst1, true); err != nil { t.Fatal(err) } if existsSolidity(t, filepath.Join(dst1, "solidity")) { t.Fatal("it should not exist") } - if !existsSolidity(t, filepath.Join(dst1, "solidity-0.5.5")) { + if !existsSolidity(t, filepath.Join(dst1, "solidity-0.8.19")) { t.Fatal("it should exist") } @@ -150,13 +150,13 @@ func TestDownloadSolidityCompiler(t *testing.T) { } defer os.RemoveAll(dst2) - if err := DownloadSolidity("0.5.5", dst2, false); err != nil { + if err := DownloadSolidity("0.8.19", dst2, false); err != nil { t.Fatal(err) } if !existsSolidity(t, filepath.Join(dst2, "solidity")) { t.Fatal("it should exist") } - if existsSolidity(t, filepath.Join(dst2, "solidity-0.5.5")) { + if existsSolidity(t, filepath.Join(dst2, "solidity-0.8.19")) { t.Fatal("it should not exist") } } diff --git a/scripts/setup-ci.sh b/scripts/setup-ci.sh index b0eb5f73..93b3c0ef 100755 --- a/scripts/setup-ci.sh +++ b/scripts/setup-ci.sh @@ -3,7 +3,7 @@ # set -o errexit install_solidity() { - VERSION="0.5.5" + VERSION="0.8.19" DOWNLOAD=https://github.com/ethereum/solidity/releases/download/v${VERSION}/solc-static-linux curl -L $DOWNLOAD > /tmp/solc diff --git a/testutil/contract.go b/testutil/contract.go index 3088351b..25f99ffe 100644 --- a/testutil/contract.go +++ b/testutil/contract.go @@ -38,8 +38,7 @@ func (c *Contract) GetEvent(name string) *Event { // Print prints the contract func (c *Contract) Print() string { - str := "pragma solidity ^0.5.5;\n" - str += "pragma experimental ABIEncoderV2;\n" + str := "pragma solidity ^0.8.19;\n" str += "\n" str += "contract Sample {\n" From b14808b1923879b8baf2f48e780565a46e429753 Mon Sep 17 00:00:00 2001 From: luka-ciric-ethernal Date: Thu, 17 Oct 2024 17:46:03 +0200 Subject: [PATCH 2/7] Refactor Solidity version handling and ABI parsing, adding tests for solidity contract & encoding/decoding In new version we use Solidity 0.8.19 we get for ABI real ABI structure, not string as it was at 0.5.5 version --- abi/abi.go | 89 +++++++++++++++++++++++++ abi/encoding_test.go | 71 +++++++++++++++++++- abi/topics_test.go | 2 +- compiler/solidity.go | 22 ++++++- compiler/solidity_test.go | 2 +- contract/contract_test.go | 132 +++++++++++++++++++++++++++++++++++--- 6 files changed, 305 insertions(+), 13 deletions(-) diff --git a/abi/abi.go b/abi/abi.go index 0c4c5eac..349b7d28 100644 --- a/abi/abi.go +++ b/abi/abi.go @@ -11,6 +11,7 @@ import ( "sync" "github.com/Ethernal-Tech/ethgo" + "github.com/Ethernal-Tech/ethgo/compiler" "golang.org/x/crypto/sha3" ) @@ -87,6 +88,94 @@ func NewABI(s string) (*ABI, error) { return NewABIFromReader(bytes.NewReader([]byte(s))) } +func toArgStr(args []compiler.IOField) []*ArgumentStr { + res := make([]*ArgumentStr, len(args)) + for i, arg := range args { + res[i] = &ArgumentStr{ + Name: arg.Name, + Type: arg.Type, + Indexed: arg.Indexed, + Components: toArgStr(arg.Components), + InternalType: arg.InternalType, + } + } + + return res +} + +func NewABIFromSlice(abi []compiler.AbiField) (*ABI, error) { + a := &ABI{} + + for _, field := range abi { + switch field.Type { + case "constructor": + if a.Constructor != nil { + return nil, fmt.Errorf("multiple constructor declaration") + } + input, err := NewTupleTypeFromArgs(toArgStr(field.Inputs)) + if err != nil { + panic(err) + } + a.Constructor = &Method{ + Inputs: input, + } + + case "function", "": + c := field.Constant + if field.StateMutability == "view" || field.StateMutability == "pure" { + c = true + } + + inputs, err := NewTupleTypeFromArgs(toArgStr(field.Inputs)) + if err != nil { + panic(err) + } + outputs, err := NewTupleTypeFromArgs(toArgStr(field.Outputs)) + if err != nil { + panic(err) + } + method := &Method{ + Name: field.Name, + Const: c, + Inputs: inputs, + Outputs: outputs, + } + a.addMethod(method) + + case "event": + input, err := NewTupleTypeFromArgs(toArgStr(field.Inputs)) + if err != nil { + panic(err) + } + event := &Event{ + Name: field.Name, + Anonymous: field.Anonymous, + Inputs: input, + } + a.addEvent(event) + + case "error": + input, err := NewTupleTypeFromArgs(toArgStr(field.Inputs)) + if err != nil { + panic(err) + } + errObj := &Error{ + Name: field.Name, + Inputs: input, + } + a.addError(errObj) + + case "fallback": + case "receive": + // do nothing + + default: + return nil, fmt.Errorf("unknown field type '%s'", field.Type) + } + } + return a, nil +} + // MustNewABI returns a parsed ABI contract or panics if fails func MustNewABI(s string) *ABI { a, err := NewABI(s) diff --git a/abi/encoding_test.go b/abi/encoding_test.go index 6eb171f6..cfa7e7c1 100644 --- a/abi/encoding_test.go +++ b/abi/encoding_test.go @@ -14,6 +14,7 @@ import ( "github.com/Ethernal-Tech/ethgo" "github.com/Ethernal-Tech/ethgo/compiler" "github.com/Ethernal-Tech/ethgo/testutil" + "github.com/stretchr/testify/assert" ) func mustDecodeHex(str string) []byte { @@ -666,7 +667,7 @@ func testTypeWithContract(t *testing.T, server *testutil.TestServer, typ *Type) return fmt.Errorf("Expected the contract to be called Sample") } - abi, err := NewABI(string(solcContract.Abi)) + abi, err := NewABIFromSlice(solcContract.Abi) if err != nil { return err } @@ -760,3 +761,71 @@ func TestEncodingStruct_camcelCase(t *testing.T) { t.Fatal("bad") } } + +func TestEncodingStruct_dynamicTuple(t *testing.T) { + typ := MustNewType("tuple(address aa, uint32[] b)") + + type Obj struct { + A ethgo.Address `abi:"aa"` + B []uint32 + } + obj := Obj{ + A: ethgo.Address{0x1}, + B: []uint32{1, 2}, + } + + encoded, err := typ.Encode(&obj) + if err != nil { + t.Fatal(err) + } + + var obj2 Obj + if err := typ.DecodeStruct(encoded, &obj2); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(obj, obj2) { + t.Fatal("bad") + } + + g := &generateContractImpl{} + source := g.run(typ) + + output, err := compiler.NewSolidityCompiler("solc").CompileCode(source) + assert.NoError(t, err) + + solcContract, ok := output.Contracts[":Sample"] + assert.True(t, ok) + + abi, err := NewABIFromSlice(solcContract.Abi) + assert.NoError(t, err) + + binBuf, err := hex.DecodeString(solcContract.Bin) + assert.NoError(t, err) + + txn := ðgo.Transaction{ + Input: binBuf, + } + + server := testutil.NewTestServer(t) + receipt, err := server.SendTxn(txn) + assert.NoError(t, err) + + method, ok := abi.Methods["set"] + assert.True(t, ok) + + encoded, err = typ.Encode(obj) + assert.NoError(t, err) + + data := append(method.ID(), encoded...) + + res, err := server.Call(ðgo.CallMsg{ + To: &receipt.ContractAddress, + Data: data, + }) + assert.NoError(t, err) + assert.Equal(t, encodeHex(data[4:]), res) + + err = typ.DecodeStruct(data[4:], &obj2) + assert.NoError(t, err) + assert.Equal(t, obj, obj2) +} diff --git a/abi/topics_test.go b/abi/topics_test.go index 1d420120..72a9bd39 100644 --- a/abi/topics_test.go +++ b/abi/topics_test.go @@ -106,7 +106,7 @@ func TestIntegrationTopics(t *testing.T) { require.NoError(t, err) // read the abi - abi, err := NewABI(artifact.Abi) + abi, err := NewABIFromSlice(artifact.Abi) assert.NoError(t, err) // parse the logs diff --git a/compiler/solidity.go b/compiler/solidity.go index 8fe6b50f..38b0ce68 100644 --- a/compiler/solidity.go +++ b/compiler/solidity.go @@ -23,8 +23,26 @@ type Source struct { AST map[string]interface{} } +type IOField struct { + Name string `json:"name"` + Type string `json:"type"` + Indexed bool `json:"indexed"` + Components []IOField `json:"components"` + InternalType string `json:"internalType"` +} + +type AbiField struct { + Type string `json:"type"` + Name string `json:"name"` + Inputs []IOField `json:"inputs"` + Outputs []IOField `json:"outputs"` + StateMutability string `json:"stateMutability"` + Anonymous bool `json:"anonymous"` + Constant bool `json:"constant"` +} + type Artifact struct { - Abi string + Abi []AbiField `json:"abi"` Bin string BinRuntime string `json:"bin-runtime"` SrcMap string `json:"srcmap"` @@ -83,7 +101,7 @@ func (s *Solidity) compileImpl(code string, files ...string) (*Output, error) { cmd.Stderr = &stderr if err := cmd.Run(); err != nil { - return nil, fmt.Errorf("failed to compile: %s", string(stderr.Bytes())) + return nil, fmt.Errorf("failed to compile: %s", stderr.String()) } var output *Output diff --git a/compiler/solidity_test.go b/compiler/solidity_test.go index ac62b20e..ab6756f1 100644 --- a/compiler/solidity_test.go +++ b/compiler/solidity_test.go @@ -119,7 +119,7 @@ func existsSolidity(t *testing.T, path string) bool { cmd.Stderr = &stderr cmd.Stdout = &stdout if err := cmd.Run(); err != nil { - t.Fatalf("solidity version failed: %s", string(stderr.Bytes())) + t.Fatalf("solidity version failed: %s", stderr.String()) } if len(stdout.Bytes()) == 0 { t.Fatal("empty output") diff --git a/contract/contract_test.go b/contract/contract_test.go index 2afa64cc..7b19efb6 100644 --- a/contract/contract_test.go +++ b/contract/contract_test.go @@ -28,7 +28,7 @@ func TestContract_NoInput(t *testing.T) { contract, addr, err := s.DeployContract(cc) require.NoError(t, err) - abi0, err := abi.NewABI(contract.Abi) + abi0, err := abi.NewABIFromSlice(contract.Abi) assert.NoError(t, err) p, _ := jsonrpc.NewClient(s.HTTPAddr()) @@ -58,7 +58,7 @@ func TestContract_IO(t *testing.T) { contract, addr, err := s.DeployContract(cc) require.NoError(t, err) - abi, err := abi.NewABI(contract.Abi) + abi, err := abi.NewABIFromSlice(contract.Abi) assert.NoError(t, err) c := NewContract(addr, abi, WithJsonRPCEndpoint(s.HTTPAddr())) @@ -83,7 +83,7 @@ func TestContract_From(t *testing.T) { contract, addr, err := s.DeployContract(cc) require.NoError(t, err) - abi, err := abi.NewABI(contract.Abi) + abi, err := abi.NewABIFromSlice(contract.Abi) assert.NoError(t, err) from := ethgo.Address{0x1} @@ -109,7 +109,7 @@ func TestContract_Deploy(t *testing.T) { artifact, err := cc.Compile() assert.NoError(t, err) - abi, err := abi.NewABI(artifact.Abi) + abi, err := abi.NewABIFromSlice(artifact.Abi) assert.NoError(t, err) bin, err := hex.DecodeString(artifact.Bin) @@ -146,7 +146,7 @@ func TestContract_Transaction(t *testing.T) { artifact, addr, err := s.DeployContract(cc) require.NoError(t, err) - abi, err := abi.NewABI(artifact.Abi) + abi, err := abi.NewABIFromSlice(artifact.Abi) assert.NoError(t, err) // send multiple transactions @@ -187,7 +187,7 @@ func TestContract_CallAtBlock(t *testing.T) { artifact, addr, err := s.DeployContract(cc) require.NoError(t, err) - abi, err := abi.NewABI(artifact.Abi) + abi, err := abi.NewABIFromSlice(artifact.Abi) assert.NoError(t, err) contract := NewContract(addr, abi, WithJsonRPCEndpoint(s.HTTPAddr()), WithSender(key)) @@ -240,7 +240,7 @@ func TestContract_SendValueContractCall(t *testing.T) { artifact, addr, err := s.DeployContract(cc) require.NoError(t, err) - abi, err := abi.NewABI(artifact.Abi) + abi, err := abi.NewABIFromSlice(artifact.Abi) assert.NoError(t, err) contract := NewContract(addr, abi, WithJsonRPCEndpoint(s.HTTPAddr()), WithSender(key)) @@ -275,7 +275,7 @@ func TestContract_EIP1559(t *testing.T) { artifact, addr, err := s.DeployContract(cc) require.NoError(t, err) - abi, err := abi.NewABI(artifact.Abi) + abi, err := abi.NewABIFromSlice(artifact.Abi) assert.NoError(t, err) client, _ := jsonrpc.NewClient(s.HTTPAddr()) @@ -299,3 +299,119 @@ func TestContract_EIP1559(t *testing.T) { assert.NotZero(t, txnObj.MaxFeePerGas) assert.NotZero(t, txnObj.MaxPriorityFeePerGas) } + +func TestContract_CallFunctionsWithArgs(t *testing.T) { + s := testutil.NewTestServer(t) + + cc := &testutil.Contract{} + cc.AddCallback(func() string { + return ` + function bar(bytes3[2] memory) public pure {} + function baz(uint32 x, bool y) public pure returns (bool r) { r = x > 32 || y; } + function sam(bytes memory, bool, uint[] memory) public pure {} + ` + }) + + contract, addr, err := s.DeployContract(cc) + require.NoError(t, err) + + abi, err := abi.NewABIFromSlice(contract.Abi) + assert.NoError(t, err) + + c := NewContract(addr, abi, WithJsonRPCEndpoint(s.HTTPAddr())) + + method := c.GetABI().GetMethod("bar") + encoded, err := method.Encode([]interface{}{[2][3]byte{{'a', 'b', 'c'}, {'d', 'e', 'f'}}}) + assert.NoError(t, err) + + expected, err := hex.DecodeString("fce353f661626300000000000000000000000000000000000000000000000000000000006465660000000000000000000000000000000000000000000000000000000000") + assert.NoError(t, err) + assert.Equal(t, encoded, expected) + + method = c.GetABI().GetMethod("baz") + encoded, err = method.Encode([]interface{}{uint32(69), true}) + assert.NoError(t, err) + + expected, err = hex.DecodeString("cdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001") + assert.NoError(t, err) + assert.Equal(t, encoded, expected) + + method = c.GetABI().GetMethod("sam") + encoded, err = method.Encode([]interface{}{[]byte("dave"), true, []uint{1, 2, 3}}) + assert.NoError(t, err) + + expected, err = hex.DecodeString("a5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003") + assert.NoError(t, err) + assert.Equal(t, encoded, expected) +} + +func TestContract_ReturnStruct(t *testing.T) { + s := testutil.NewTestServer(t) + + cc := &testutil.Contract{} + cc.AddCallback(func() string { + return ` + struct S { uint32 a; uint32 b; } + function foo() public pure returns (S memory) { + return S(1, 2); + } + + struct T { uint32[] a; uint32 b; } + function bar() public pure returns (T memory) { + uint32[] memory arr = new uint32[](3); + arr[0] = 1; + arr[1] = 2; + arr[2] = 3; + return T(arr, 4); + } + + function baz(T memory t) public returns (T memory) { return t; } + ` + }) + + ss := map[string]interface{}{ + "a": uint32(1), + "b": uint32(2), + } + + tt := map[string]interface{}{ + "a": []uint32{1, 2, 3}, + "b": uint32(4), + } + + contract, addr, err := s.DeployContract(cc) + require.NoError(t, err) + + abi, err := abi.NewABIFromSlice(contract.Abi) + assert.NoError(t, err) + + c := NewContract(addr, abi, WithJsonRPCEndpoint(s.HTTPAddr())) + + method := c.GetABI().GetMethod("foo") + resp, err := c.CallInternal(method, ethgo.Latest) + assert.NoError(t, err) + + res, err := method.Decode(resp) + assert.NoError(t, err) + assert.Equal(t, res["0"], ss) + + method = c.GetABI().GetMethod("bar") + resp, err = c.CallInternal(method, ethgo.Latest) + assert.NoError(t, err) + + res, err = method.Decode(resp) + assert.NoError(t, err) + assert.Equal(t, res["0"], tt) + + method = c.GetABI().GetMethod("baz") + resp, err = c.CallInternal(method, ethgo.Latest, tt) + assert.NoError(t, err) + + encoded, err := method.Encode([]interface{}{tt}) + assert.NoError(t, err) + assert.Equal(t, resp, encoded[4:]) // skip the method id prefix + + res, err = method.Decode(resp) + assert.NoError(t, err) + assert.Equal(t, res["0"], tt) +} From 22695b1a1c92a34e9e0b5b95a88d5c1090bd29b5 Mon Sep 17 00:00:00 2001 From: luka-ciric-ethernal Date: Fri, 18 Oct 2024 13:02:39 +0200 Subject: [PATCH 3/7] Refactor ABI parsing to use compiler.IOField instead of custom ArgumentStr struct --- abi/abi.go | 48 ++++++++++----------------------------- abi/encoding_test.go | 12 +++++----- abi/type.go | 13 ++++++----- abi/type_test.go | 45 ++++++++++++++++++------------------ compiler/solidity.go | 24 ++++++++++---------- compiler/solidity_test.go | 2 +- 6 files changed, 61 insertions(+), 83 deletions(-) diff --git a/abi/abi.go b/abi/abi.go index 349b7d28..542cdf2d 100644 --- a/abi/abi.go +++ b/abi/abi.go @@ -88,21 +88,6 @@ func NewABI(s string) (*ABI, error) { return NewABIFromReader(bytes.NewReader([]byte(s))) } -func toArgStr(args []compiler.IOField) []*ArgumentStr { - res := make([]*ArgumentStr, len(args)) - for i, arg := range args { - res[i] = &ArgumentStr{ - Name: arg.Name, - Type: arg.Type, - Indexed: arg.Indexed, - Components: toArgStr(arg.Components), - InternalType: arg.InternalType, - } - } - - return res -} - func NewABIFromSlice(abi []compiler.AbiField) (*ABI, error) { a := &ABI{} @@ -112,7 +97,7 @@ func NewABIFromSlice(abi []compiler.AbiField) (*ABI, error) { if a.Constructor != nil { return nil, fmt.Errorf("multiple constructor declaration") } - input, err := NewTupleTypeFromArgs(toArgStr(field.Inputs)) + input, err := NewTupleTypeFromFields(field.Inputs) if err != nil { panic(err) } @@ -126,11 +111,11 @@ func NewABIFromSlice(abi []compiler.AbiField) (*ABI, error) { c = true } - inputs, err := NewTupleTypeFromArgs(toArgStr(field.Inputs)) + inputs, err := NewTupleTypeFromFields(field.Inputs) if err != nil { panic(err) } - outputs, err := NewTupleTypeFromArgs(toArgStr(field.Outputs)) + outputs, err := NewTupleTypeFromFields(field.Outputs) if err != nil { panic(err) } @@ -143,7 +128,7 @@ func NewABIFromSlice(abi []compiler.AbiField) (*ABI, error) { a.addMethod(method) case "event": - input, err := NewTupleTypeFromArgs(toArgStr(field.Inputs)) + input, err := NewTupleTypeFromFields(field.Inputs) if err != nil { panic(err) } @@ -155,7 +140,7 @@ func NewABIFromSlice(abi []compiler.AbiField) (*ABI, error) { a.addEvent(event) case "error": - input, err := NewTupleTypeFromArgs(toArgStr(field.Inputs)) + input, err := NewTupleTypeFromFields(field.Inputs) if err != nil { panic(err) } @@ -203,8 +188,8 @@ func (a *ABI) UnmarshalJSON(data []byte) error { Constant bool Anonymous bool StateMutability string - Inputs []*ArgumentStr - Outputs []*ArgumentStr + Inputs []*compiler.IOField + Outputs []*compiler.IOField } if err := json.Unmarshal(data, &fields); err != nil { @@ -217,7 +202,7 @@ func (a *ABI) UnmarshalJSON(data []byte) error { if a.Constructor != nil { return fmt.Errorf("multiple constructor declaration") } - input, err := NewTupleTypeFromArgs(field.Inputs) + input, err := NewTupleTypeFromFields(field.Inputs) if err != nil { panic(err) } @@ -231,11 +216,11 @@ func (a *ABI) UnmarshalJSON(data []byte) error { c = true } - inputs, err := NewTupleTypeFromArgs(field.Inputs) + inputs, err := NewTupleTypeFromFields(field.Inputs) if err != nil { panic(err) } - outputs, err := NewTupleTypeFromArgs(field.Outputs) + outputs, err := NewTupleTypeFromFields(field.Outputs) if err != nil { panic(err) } @@ -248,7 +233,7 @@ func (a *ABI) UnmarshalJSON(data []byte) error { a.addMethod(method) case "event": - input, err := NewTupleTypeFromArgs(field.Inputs) + input, err := NewTupleTypeFromFields(field.Inputs) if err != nil { panic(err) } @@ -260,7 +245,7 @@ func (a *ABI) UnmarshalJSON(data []byte) error { a.addEvent(event) case "error": - input, err := NewTupleTypeFromArgs(field.Inputs) + input, err := NewTupleTypeFromFields(field.Inputs) if err != nil { panic(err) } @@ -497,15 +482,6 @@ func buildSignature(name string, typ *Type) string { return fmt.Sprintf("%v(%v)", name, strings.Join(types, ",")) } -// ArgumentStr encodes a type object -type ArgumentStr struct { - Name string - Type string - Indexed bool - Components []*ArgumentStr - InternalType string -} - var keccakPool = sync.Pool{ New: func() interface{} { return sha3.NewLegacyKeccak256() diff --git a/abi/encoding_test.go b/abi/encoding_test.go index cfa7e7c1..d7a181eb 100644 --- a/abi/encoding_test.go +++ b/abi/encoding_test.go @@ -504,13 +504,13 @@ func TestEncodingBestEffort(t *testing.T) { func TestEncodingArguments(t *testing.T) { cases := []struct { - Arg *ArgumentStr + Arg *compiler.IOField Input interface{} }{ { - &ArgumentStr{ + &compiler.IOField{ Type: "tuple", - Components: []*ArgumentStr{ + Components: []*compiler.IOField{ { Name: "", Type: "int32", @@ -527,9 +527,9 @@ func TestEncodingArguments(t *testing.T) { }, }, { - &ArgumentStr{ + &compiler.IOField{ Type: "tuple", - Components: []*ArgumentStr{ + Components: []*compiler.IOField{ { Name: "a", Type: "int32", @@ -551,7 +551,7 @@ func TestEncodingArguments(t *testing.T) { for _, c := range cases { t.Run("", func(t *testing.T) { - tt, err := NewTypeFromArgument(c.Arg) + tt, err := NewTypeFromField(c.Arg) if err != nil { t.Fatal(err) } diff --git a/abi/type.go b/abi/type.go index 221278a5..b7e458ad 100644 --- a/abi/type.go +++ b/abi/type.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/Ethernal-Tech/ethgo" + "github.com/Ethernal-Tech/ethgo/compiler" ) // batch of predefined reflect types @@ -115,10 +116,10 @@ func NewTupleType(inputs []*TupleElem) *Type { } } -func NewTupleTypeFromArgs(inputs []*ArgumentStr) (*Type, error) { +func NewTupleTypeFromFields(inputs []*compiler.IOField) (*Type, error) { elems := []*TupleElem{} for _, i := range inputs { - typ, err := NewTypeFromArgument(i) + typ, err := NewTypeFromField(i) if err != nil { return nil, err } @@ -255,7 +256,7 @@ func (t *Type) isDynamicType() bool { return t.kind == KindString || t.kind == KindBytes || t.kind == KindSlice || (t.kind == KindArray && t.elem.isDynamicType()) } -func parseType(arg *ArgumentStr) (string, error) { +func parseType(arg *compiler.IOField) (string, error) { if !strings.HasPrefix(arg.Type, "tuple") { return arg.Type, nil } @@ -280,8 +281,8 @@ func parseType(arg *ArgumentStr) (string, error) { return fmt.Sprintf("tuple(%s)%s", strings.Join(str, ","), strings.TrimPrefix(arg.Type, "tuple")), nil } -// NewTypeFromArgument parses an abi type from an argument -func NewTypeFromArgument(arg *ArgumentStr) (*Type, error) { +// NewTypeFromField parses an abi type from an argument +func NewTypeFromField(arg *compiler.IOField) (*Type, error) { str, err := parseType(arg) if err != nil { return nil, err @@ -299,7 +300,7 @@ func NewTypeFromArgument(arg *ArgumentStr) (*Type, error) { return typ, nil } -func fillIn(typ *Type, arg *ArgumentStr) error { +func fillIn(typ *Type, arg *compiler.IOField) error { typ.itype = arg.InternalType if len(arg.Components) == 0 { diff --git a/abi/type_test.go b/abi/type_test.go index d9865c4c..d3c92a48 100644 --- a/abi/type_test.go +++ b/abi/type_test.go @@ -4,6 +4,7 @@ import ( "reflect" "testing" + "github.com/Ethernal-Tech/ethgo/compiler" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -11,7 +12,7 @@ import ( func TestType(t *testing.T) { cases := []struct { s string - a *ArgumentStr + a *compiler.IOField t *Type r string err bool @@ -111,9 +112,9 @@ func TestType(t *testing.T) { }, { s: "tuple(int64 indexed arg0)", - a: &ArgumentStr{ + a: &compiler.IOField{ Type: "tuple", - Components: []*ArgumentStr{ + Components: []*compiler.IOField{ { Name: "arg0", Type: "int64", @@ -139,9 +140,9 @@ func TestType(t *testing.T) { }, { s: "tuple(int64 arg_0)[2]", - a: &ArgumentStr{ + a: &compiler.IOField{ Type: "tuple[2]", - Components: []*ArgumentStr{ + Components: []*compiler.IOField{ { Name: "arg_0", Type: "int64", @@ -170,9 +171,9 @@ func TestType(t *testing.T) { }, { s: "tuple(int64 a)[]", - a: &ArgumentStr{ + a: &compiler.IOField{ Type: "tuple[]", - Components: []*ArgumentStr{ + Components: []*compiler.IOField{ { Name: "a", Type: "int64", @@ -200,9 +201,9 @@ func TestType(t *testing.T) { }, { s: "tuple(int32 indexed arg0,tuple(int32 c) b_2)", - a: &ArgumentStr{ + a: &compiler.IOField{ Type: "tuple", - Components: []*ArgumentStr{ + Components: []*compiler.IOField{ { Name: "arg0", Type: "int32", @@ -211,7 +212,7 @@ func TestType(t *testing.T) { { Name: "b_2", Type: "tuple", - Components: []*ArgumentStr{ + Components: []*compiler.IOField{ { Name: "c", Type: "int32", @@ -255,9 +256,9 @@ func TestType(t *testing.T) { }, { s: "tuple()", - a: &ArgumentStr{ + a: &compiler.IOField{ Type: "tuple", - Components: []*ArgumentStr{}, + Components: []*compiler.IOField{}, }, t: &Type{ kind: KindTuple, @@ -268,12 +269,12 @@ func TestType(t *testing.T) { { // hidden tuple token s: "tuple((int32))", - a: &ArgumentStr{ + a: &compiler.IOField{ Type: "tuple", - Components: []*ArgumentStr{ + Components: []*compiler.IOField{ { Type: "tuple", - Components: []*ArgumentStr{ + Components: []*compiler.IOField{ { Type: "int32", }, @@ -344,7 +345,7 @@ func TestType(t *testing.T) { } assert.Equal(t, expected, e0.Format(true)) - e1, err := NewTypeFromArgument(c.a) + e1, err := NewTypeFromField(c.a) if err != nil { t.Fatal(err) } @@ -365,12 +366,12 @@ func TestType(t *testing.T) { } func TestTypeArgument_InternalFields(t *testing.T) { - arg := &ArgumentStr{ + arg := &compiler.IOField{ Type: "tuple", - Components: []*ArgumentStr{ + Components: []*compiler.IOField{ { Type: "tuple[]", - Components: []*ArgumentStr{ + Components: []*compiler.IOField{ { Type: "int32", InternalType: "c", @@ -381,7 +382,7 @@ func TestTypeArgument_InternalFields(t *testing.T) { }, } - res, err := NewTypeFromArgument(arg) + res, err := NewTypeFromField(arg) require.NoError(t, err) require.Equal(t, res.tuple[0].Elem.itype, "b") @@ -432,8 +433,8 @@ func TestSize(t *testing.T) { } } -func simpleType(s string) *ArgumentStr { - return &ArgumentStr{ +func simpleType(s string) *compiler.IOField { + return &compiler.IOField{ Type: s, } } diff --git a/compiler/solidity.go b/compiler/solidity.go index 38b0ce68..d0c65f08 100644 --- a/compiler/solidity.go +++ b/compiler/solidity.go @@ -24,21 +24,21 @@ type Source struct { } type IOField struct { - Name string `json:"name"` - Type string `json:"type"` - Indexed bool `json:"indexed"` - Components []IOField `json:"components"` - InternalType string `json:"internalType"` + Name string `json:"name"` + Type string `json:"type"` + Indexed bool `json:"indexed"` + Components []*IOField `json:"components"` + InternalType string `json:"internalType"` } type AbiField struct { - Type string `json:"type"` - Name string `json:"name"` - Inputs []IOField `json:"inputs"` - Outputs []IOField `json:"outputs"` - StateMutability string `json:"stateMutability"` - Anonymous bool `json:"anonymous"` - Constant bool `json:"constant"` + Type string `json:"type"` + Name string `json:"name"` + Inputs []*IOField `json:"inputs"` + Outputs []*IOField `json:"outputs"` + StateMutability string `json:"stateMutability"` + Anonymous bool `json:"anonymous"` + Constant bool `json:"constant"` } type Artifact struct { diff --git a/compiler/solidity_test.go b/compiler/solidity_test.go index ab6756f1..1e5f5209 100644 --- a/compiler/solidity_test.go +++ b/compiler/solidity_test.go @@ -19,7 +19,7 @@ var ( ) func init() { - _, err := os.Stat(solcDir) + _, err := os.Stat(solcPath) if err == nil { // already exists return From 52058b3b6214824d61cd0a86b23c80565cae3369 Mon Sep 17 00:00:00 2001 From: luka-ciric-ethernal Date: Mon, 21 Oct 2024 17:04:41 +0200 Subject: [PATCH 4/7] instead of panic returning errors and changed shorthand for fatal errors in testing --- abi/abi.go | 11 ++++++----- abi/encoding_test.go | 11 +++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/abi/abi.go b/abi/abi.go index 542cdf2d..462a3cba 100644 --- a/abi/abi.go +++ b/abi/abi.go @@ -88,6 +88,7 @@ func NewABI(s string) (*ABI, error) { return NewABIFromReader(bytes.NewReader([]byte(s))) } +// NewABIFromSlice returns a parsed ABI struct from a slice of ABI fields that represent the contract func NewABIFromSlice(abi []compiler.AbiField) (*ABI, error) { a := &ABI{} @@ -99,7 +100,7 @@ func NewABIFromSlice(abi []compiler.AbiField) (*ABI, error) { } input, err := NewTupleTypeFromFields(field.Inputs) if err != nil { - panic(err) + return nil, err } a.Constructor = &Method{ Inputs: input, @@ -113,11 +114,11 @@ func NewABIFromSlice(abi []compiler.AbiField) (*ABI, error) { inputs, err := NewTupleTypeFromFields(field.Inputs) if err != nil { - panic(err) + return nil, err } outputs, err := NewTupleTypeFromFields(field.Outputs) if err != nil { - panic(err) + return nil, err } method := &Method{ Name: field.Name, @@ -130,7 +131,7 @@ func NewABIFromSlice(abi []compiler.AbiField) (*ABI, error) { case "event": input, err := NewTupleTypeFromFields(field.Inputs) if err != nil { - panic(err) + return nil, err } event := &Event{ Name: field.Name, @@ -142,7 +143,7 @@ func NewABIFromSlice(abi []compiler.AbiField) (*ABI, error) { case "error": input, err := NewTupleTypeFromFields(field.Inputs) if err != nil { - panic(err) + return nil, err } errObj := &Error{ Name: field.Name, diff --git a/abi/encoding_test.go b/abi/encoding_test.go index d7a181eb..7ba5d55c 100644 --- a/abi/encoding_test.go +++ b/abi/encoding_test.go @@ -15,6 +15,7 @@ import ( "github.com/Ethernal-Tech/ethgo/compiler" "github.com/Ethernal-Tech/ethgo/testutil" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func mustDecodeHex(str string) []byte { @@ -775,14 +776,12 @@ func TestEncodingStruct_dynamicTuple(t *testing.T) { } encoded, err := typ.Encode(&obj) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) var obj2 Obj - if err := typ.DecodeStruct(encoded, &obj2); err != nil { - t.Fatal(err) - } + err = typ.DecodeStruct(encoded, &obj2) + require.NoError(t, err) + if !reflect.DeepEqual(obj, obj2) { t.Fatal("bad") } From 488658d35fb51c1f6aefbe4d61d54acf8a0135e3 Mon Sep 17 00:00:00 2001 From: luka-ciric-ethernal Date: Tue, 22 Oct 2024 10:03:58 +0200 Subject: [PATCH 5/7] solidity version set as constant --- abi/encoding_test.go | 44 +++++++++++++++++++++++++++++++++++++++ abi/testing.go | 4 +++- compiler/solidity.go | 4 ++++ compiler/solidity_test.go | 10 ++++----- testutil/contract.go | 2 +- 5 files changed, 57 insertions(+), 7 deletions(-) diff --git a/abi/encoding_test.go b/abi/encoding_test.go index 7ba5d55c..f6455a5f 100644 --- a/abi/encoding_test.go +++ b/abi/encoding_test.go @@ -335,6 +335,49 @@ func TestEncoding(t *testing.T) { } } +func TestEncoding2(t *testing.T) { + cases := []struct { + Type string + Input interface{} + }{ + { + "tuple(bytes a)[]", + []map[string]interface{}{ + { + "a": []byte{ + 0xff, + 0xff, + }, + }, + { + "a": []byte{ + 0xff, + 0xff, + }, + }, + }, + }, + } + + server := testutil.NewTestServer(t) + + for _, c := range cases { + c := c + t.Run(c.Type, func(t *testing.T) { + t.Parallel() + + tt, err := NewType(c.Type) + if err != nil { + t.Fatal(err) + } + + if err := testEncodeDecode(t, server, tt, c.Input); err != nil { + t.Fatal(err) + } + }) + } +} + func TestEncodingBestEffort(t *testing.T) { strAddress := "0xdbb881a51CD4023E4400CEF3ef73046743f08da3" ethAddress := ethgo.HexToAddress(strAddress) @@ -566,6 +609,7 @@ func TestEncodingArguments(t *testing.T) { func testEncodeDecode(t *testing.T, server *testutil.TestServer, tt *Type, input interface{}) error { res1, err := Encode(input, tt) + fmt.Println(res1) if err != nil { return err } diff --git a/abi/testing.go b/abi/testing.go index 9a2ca4b4..023fa50d 100644 --- a/abi/testing.go +++ b/abi/testing.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/Ethernal-Tech/ethgo" + "github.com/Ethernal-Tech/ethgo/compiler" ) func randomInt(min, max int) int { @@ -192,7 +193,7 @@ func (g *generateContractImpl) run(t *Type) string { } contractTemplate := - `pragma solidity ^0.8.19; + `pragma solidity ^%s; contract Sample { // structs @@ -204,6 +205,7 @@ func (g *generateContractImpl) run(t *Type) string { contract := fmt.Sprintf( contractTemplate, + compiler.SolcVersion, strings.Join(g.structs, "\n"), strings.Join(input, ","), strings.Join(output, ","), diff --git a/compiler/solidity.go b/compiler/solidity.go index d0c65f08..459cf36f 100644 --- a/compiler/solidity.go +++ b/compiler/solidity.go @@ -13,6 +13,10 @@ import ( "strings" ) +const ( + SolcVersion = "0.8.19" +) + type Output struct { Contracts map[string]*Artifact Sources map[string]*Source diff --git a/compiler/solidity_test.go b/compiler/solidity_test.go index 1e5f5209..aae2722d 100644 --- a/compiler/solidity_test.go +++ b/compiler/solidity_test.go @@ -28,7 +28,7 @@ func init() { panic(err) } // solc folder does not exists - if err := DownloadSolidity("0.8.19", solcDir, false); err != nil { + if err := DownloadSolidity(SolcVersion, solcDir, false); err != nil { panic(err) } } @@ -134,13 +134,13 @@ func TestDownloadSolidityCompiler(t *testing.T) { } defer os.RemoveAll(dst1) - if err := DownloadSolidity("0.8.19", dst1, true); err != nil { + if err := DownloadSolidity(SolcVersion, dst1, true); err != nil { t.Fatal(err) } if existsSolidity(t, filepath.Join(dst1, "solidity")) { t.Fatal("it should not exist") } - if !existsSolidity(t, filepath.Join(dst1, "solidity-0.8.19")) { + if !existsSolidity(t, filepath.Join(dst1, "solidity-"+SolcVersion)) { t.Fatal("it should exist") } @@ -150,13 +150,13 @@ func TestDownloadSolidityCompiler(t *testing.T) { } defer os.RemoveAll(dst2) - if err := DownloadSolidity("0.8.19", dst2, false); err != nil { + if err := DownloadSolidity(SolcVersion, dst2, false); err != nil { t.Fatal(err) } if !existsSolidity(t, filepath.Join(dst2, "solidity")) { t.Fatal("it should exist") } - if existsSolidity(t, filepath.Join(dst2, "solidity-0.8.19")) { + if existsSolidity(t, filepath.Join(dst2, "solidity-"+SolcVersion)) { t.Fatal("it should not exist") } } diff --git a/testutil/contract.go b/testutil/contract.go index 25f99ffe..a08f6c18 100644 --- a/testutil/contract.go +++ b/testutil/contract.go @@ -38,7 +38,7 @@ func (c *Contract) GetEvent(name string) *Event { // Print prints the contract func (c *Contract) Print() string { - str := "pragma solidity ^0.8.19;\n" + str := "pragma solidity ^" + compiler.SolcVersion + ";\n" str += "\n" str += "contract Sample {\n" From df2563f98b47d28c9eb63fb072093643f4022f35 Mon Sep 17 00:00:00 2001 From: luka-ciric-ethernal Date: Tue, 22 Oct 2024 10:15:29 +0200 Subject: [PATCH 6/7] deleting irrelevant test --- abi/encoding_test.go | 43 ------------------------------------------- 1 file changed, 43 deletions(-) diff --git a/abi/encoding_test.go b/abi/encoding_test.go index f6455a5f..37b653b5 100644 --- a/abi/encoding_test.go +++ b/abi/encoding_test.go @@ -335,49 +335,6 @@ func TestEncoding(t *testing.T) { } } -func TestEncoding2(t *testing.T) { - cases := []struct { - Type string - Input interface{} - }{ - { - "tuple(bytes a)[]", - []map[string]interface{}{ - { - "a": []byte{ - 0xff, - 0xff, - }, - }, - { - "a": []byte{ - 0xff, - 0xff, - }, - }, - }, - }, - } - - server := testutil.NewTestServer(t) - - for _, c := range cases { - c := c - t.Run(c.Type, func(t *testing.T) { - t.Parallel() - - tt, err := NewType(c.Type) - if err != nil { - t.Fatal(err) - } - - if err := testEncodeDecode(t, server, tt, c.Input); err != nil { - t.Fatal(err) - } - }) - } -} - func TestEncodingBestEffort(t *testing.T) { strAddress := "0xdbb881a51CD4023E4400CEF3ef73046743f08da3" ethAddress := ethgo.HexToAddress(strAddress) From 648afbdf8ae063e3a969f341311dc3a30f559ddc Mon Sep 17 00:00:00 2001 From: luka-ciric-ethernal Date: Tue, 22 Oct 2024 10:16:40 +0200 Subject: [PATCH 7/7] removing irrelevant printing --- abi/encoding_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/abi/encoding_test.go b/abi/encoding_test.go index 37b653b5..7ba5d55c 100644 --- a/abi/encoding_test.go +++ b/abi/encoding_test.go @@ -566,7 +566,6 @@ func TestEncodingArguments(t *testing.T) { func testEncodeDecode(t *testing.T, server *testutil.TestServer, tt *Type, input interface{}) error { res1, err := Encode(input, tt) - fmt.Println(res1) if err != nil { return err }