From 4fc62e94b388082bd5c3b35a8f924eea34c6c201 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Thu, 28 Nov 2024 18:59:12 +0700 Subject: [PATCH] add errors to emitted code. --- accounts/abi/bind/bind.go | 47 +++++++++++ accounts/abi/bind/template.go | 6 ++ accounts/abi/bind/testdata/v2/bindings.go | 0 .../abi/bind/testdata/v2/events/bindings.go | 2 +- .../testdata/v2/nested_libraries/bindings.go | 2 +- .../testdata/v2/return_structs/bindings.go | 2 +- .../bind/testdata/v2/solc_errors/bindings.go | 81 +++++++++++++++++++ .../testdata/v2/solc_errors/combined-abi.json | 1 + .../bind/testdata/v2/solc_errors/contract.sol | 15 ++++ accounts/abi/bind/v2/lib.go | 2 +- accounts/abi/bind/v2/lib_test.go | 55 ++++++++++++- 11 files changed, 208 insertions(+), 5 deletions(-) create mode 100644 accounts/abi/bind/testdata/v2/bindings.go create mode 100644 accounts/abi/bind/testdata/v2/solc_errors/bindings.go create mode 100644 accounts/abi/bind/testdata/v2/solc_errors/combined-abi.json create mode 100644 accounts/abi/bind/testdata/v2/solc_errors/contract.sol diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index e4bdf41f1e..59b04926e8 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -189,6 +189,7 @@ func bind(types []string, abis []string, bytecodes []string, fsigs []map[string] calls = make(map[string]*tmplMethod) transacts = make(map[string]*tmplMethod) events = make(map[string]*tmplEvent) + errors = make(map[string]*tmplError) fallback *tmplMethod receive *tmplMethod @@ -304,6 +305,51 @@ func bind(types []string, abis []string, bytecodes []string, fsigs []map[string] // Append the event to the accumulator list events[original.Name] = &tmplEvent{Original: original, Normalized: normalized} } + for _, original := range evmABI.Errors { + // TODO: I copied this from events. I think it should be correct but not totally sure + // even if it is correct, should consider deduplicating this into its own function. + + // Normalize the event for capital cases and non-anonymous outputs + normalized := original + + // Ensure there is no duplicated identifier + normalizedName := methodNormalizer(alias(aliases, original.Name)) + // Name shouldn't start with a digit. It will make the generated code invalid. + if len(normalizedName) > 0 && unicode.IsDigit(rune(normalizedName[0])) { + normalizedName = fmt.Sprintf("E%s", normalizedName) + normalizedName = abi.ResolveNameConflict(normalizedName, func(name string) bool { + _, ok := eventIdentifiers[name] + return ok + }) + } + if eventIdentifiers[normalizedName] { + return nil, fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName) + } + eventIdentifiers[normalizedName] = true + normalized.Name = normalizedName + + used := make(map[string]bool) + normalized.Inputs = make([]abi.Argument, len(original.Inputs)) + copy(normalized.Inputs, original.Inputs) + for j, input := range normalized.Inputs { + if input.Name == "" || isKeyWord(input.Name) { + normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) + } + // Event is a bit special, we need to define event struct in binding, + // ensure there is no camel-case-style name conflict. + for index := 0; ; index++ { + if !used[capitalise(normalized.Inputs[j].Name)] { + used[capitalise(normalized.Inputs[j].Name)] = true + break + } + normalized.Inputs[j].Name = fmt.Sprintf("%s%d", normalized.Inputs[j].Name, index) + } + if hasStruct(input.Type) { + bindStructType(input.Type, structs) + } + } + errors[original.Name] = &tmplError{Original: original, Normalized: normalized} + } // Add two special fallback functions if they exist if evmABI.HasFallback() { fallback = &tmplMethod{Original: evmABI.Fallback} @@ -324,6 +370,7 @@ func bind(types []string, abis []string, bytecodes []string, fsigs []map[string] Events: events, Libraries: make(map[string]string), AllLibraries: make(map[string]string), + Errors: errors, } // Function 4-byte signatures are stored in the same sequence diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go index 7dce6f9558..7481bf06f6 100644 --- a/accounts/abi/bind/template.go +++ b/accounts/abi/bind/template.go @@ -46,6 +46,7 @@ type tmplContract struct { Libraries map[string]string // Same as tmplData, but filtered to only keep direct deps that the contract needs AllLibraries map[string]string // same as Libraries, but all direct/indirect library dependencies Library bool // Indicator whether the contract is a library + Errors map[string]*tmplError } // tmplMethod is a wrapper around an abi.Method that contains a few preprocessed @@ -63,6 +64,11 @@ type tmplEvent struct { Normalized abi.Event // Normalized version of the parsed fields } +type tmplError struct { + Original abi.Error + Normalized abi.Error +} + // tmplField is a wrapper around a struct field with binding language // struct type definition and relative filed name. type tmplField struct { diff --git a/accounts/abi/bind/testdata/v2/bindings.go b/accounts/abi/bind/testdata/v2/bindings.go new file mode 100644 index 0000000000..e69de29bb2 diff --git a/accounts/abi/bind/testdata/v2/events/bindings.go b/accounts/abi/bind/testdata/v2/events/bindings.go index a2e93d6996..6c8b6cd16b 100644 --- a/accounts/abi/bind/testdata/v2/events/bindings.go +++ b/accounts/abi/bind/testdata/v2/events/bindings.go @@ -13,7 +13,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" ) -// Reference imports to suppress errors if they are not otherwise used. +// Reference imports to suppress solc_errors if they are not otherwise used. var ( _ = errors.New _ = big.NewInt diff --git a/accounts/abi/bind/testdata/v2/nested_libraries/bindings.go b/accounts/abi/bind/testdata/v2/nested_libraries/bindings.go index 267862d194..cf5206c2a2 100644 --- a/accounts/abi/bind/testdata/v2/nested_libraries/bindings.go +++ b/accounts/abi/bind/testdata/v2/nested_libraries/bindings.go @@ -13,7 +13,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" ) -// Reference imports to suppress errors if they are not otherwise used. +// Reference imports to suppress solc_errors if they are not otherwise used. var ( _ = errors.New _ = big.NewInt diff --git a/accounts/abi/bind/testdata/v2/return_structs/bindings.go b/accounts/abi/bind/testdata/v2/return_structs/bindings.go index 49cec76960..959da73e96 100644 --- a/accounts/abi/bind/testdata/v2/return_structs/bindings.go +++ b/accounts/abi/bind/testdata/v2/return_structs/bindings.go @@ -13,7 +13,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" ) -// Reference imports to suppress errors if they are not otherwise used. +// Reference imports to suppress solc_errors if they are not otherwise used. var ( _ = errors.New _ = big.NewInt diff --git a/accounts/abi/bind/testdata/v2/solc_errors/bindings.go b/accounts/abi/bind/testdata/v2/solc_errors/bindings.go new file mode 100644 index 0000000000..50e01a49a9 --- /dev/null +++ b/accounts/abi/bind/testdata/v2/solc_errors/bindings.go @@ -0,0 +1,81 @@ +// Code generated via abigen V2 - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package solc_errors + +import ( + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = abi.ConvertType +) + +var CLibraryDeps = []*bind.MetaData{} + +// TODO: convert this type to value type after everything works. +// CMetaData contains all meta data concerning the C contract. +var CMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"arg1\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"arg2\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"arg3\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"arg4\",\"type\":\"bool\"}],\"name\":\"BadThing\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"Foo\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"}]", + Pattern: "55ef3c19a0ab1c1845f9e347540c1e51f5", + Bin: "0x6080604052348015600e575f80fd5b506101148061001c5f395ff3fe6080604052348015600e575f80fd5b50600436106026575f3560e01c8063bfb4ebcf14602a575b5f80fd5b60306032565b005b5f600160025f6040517fbb6a82f1000000000000000000000000000000000000000000000000000000008152600401606c949392919060a3565b60405180910390fd5b5f819050919050565b6085816075565b82525050565b5f8115159050919050565b609d81608b565b82525050565b5f60808201905060b45f830187607e565b60bf6020830186607e565b60ca6040830185607e565b60d560608301846096565b9594505050505056fea26469706673582212205ce065ab1cfe16beba2b766e14009fc67ac66c214872149c889f0589720b870a64736f6c634300081a0033", +} + +// C is an auto generated Go binding around an Ethereum contract. +type C struct { + abi abi.ABI +} + +// NewC creates a new instance of C. +func NewC() (*C, error) { + parsed, err := CMetaData.GetAbi() + if err != nil { + return nil, err + } + return &C{abi: *parsed}, nil +} + +func (_C *C) PackConstructor() ([]byte, error) { + return _C.abi.Pack("") +} + +// Foo is a free data retrieval call binding the contract method 0xbfb4ebcf. +// +// Solidity: function Foo() pure returns() +func (_C *C) PackFoo() ([]byte, error) { + return _C.abi.Pack("Foo") +} + +// CBadThing represents a BadThing error raised by the C contract. +type CBadThing struct { + Arg1 *big.Int + Arg2 *big.Int + Arg3 *big.Int + Arg4 bool +} + +func CBadThingErrorID() common.Hash { + return common.HexToHash("0xbb6a82f123854747ef4381e30e497f934a3854753fec99a69c35c30d4b46714d") +} + +func (_C *C) UnpackBadThingError(raw []byte) (*CBadThing, error) { + errName := "BadThing" + out := new(CBadThing) + if err := _C.abi.UnpackIntoInterface(out, errName, raw); err != nil { + return nil, err + } + return out, nil +} + diff --git a/accounts/abi/bind/testdata/v2/solc_errors/combined-abi.json b/accounts/abi/bind/testdata/v2/solc_errors/combined-abi.json new file mode 100644 index 0000000000..e9f5d2e00f --- /dev/null +++ b/accounts/abi/bind/testdata/v2/solc_errors/combined-abi.json @@ -0,0 +1 @@ +{"contracts":{"contract.sol:C":{"abi":[{"inputs":[{"internalType":"uint256","name":"arg1","type":"uint256"},{"internalType":"uint256","name":"arg2","type":"uint256"},{"internalType":"uint256","name":"arg3","type":"uint256"},{"internalType":"bool","name":"arg4","type":"bool"}],"name":"BadThing","type":"error"},{"inputs":[],"name":"Foo","outputs":[],"stateMutability":"pure","type":"function"}],"bin":"6080604052348015600e575f80fd5b506101148061001c5f395ff3fe6080604052348015600e575f80fd5b50600436106026575f3560e01c8063bfb4ebcf14602a575b5f80fd5b60306032565b005b5f600160025f6040517fbb6a82f1000000000000000000000000000000000000000000000000000000008152600401606c949392919060a3565b60405180910390fd5b5f819050919050565b6085816075565b82525050565b5f8115159050919050565b609d81608b565b82525050565b5f60808201905060b45f830187607e565b60bf6020830186607e565b60ca6040830185607e565b60d560608301846096565b9594505050505056fea26469706673582212205ce065ab1cfe16beba2b766e14009fc67ac66c214872149c889f0589720b870a64736f6c634300081a0033"}},"version":"0.8.26+commit.8a97fa7a.Darwin.appleclang"} diff --git a/accounts/abi/bind/testdata/v2/solc_errors/contract.sol b/accounts/abi/bind/testdata/v2/solc_errors/contract.sol new file mode 100644 index 0000000000..aa2218cdc3 --- /dev/null +++ b/accounts/abi/bind/testdata/v2/solc_errors/contract.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +error BadThing(uint256 arg1, uint256 arg2, uint256 arg3, bool arg4); + +contract C { + function Foo() public pure { + revert BadThing({ + arg1: uint256(0), + arg2: uint256(1), + arg3: uint256(2), + arg4: false + }); + } +} \ No newline at end of file diff --git a/accounts/abi/bind/v2/lib.go b/accounts/abi/bind/v2/lib.go index 3e677d6ce0..eaef2832ea 100644 --- a/accounts/abi/bind/v2/lib.go +++ b/accounts/abi/bind/v2/lib.go @@ -278,7 +278,7 @@ type EventIterator[T any] struct { unpack func(*types.Log) (*T, error) // Unpack function for the event logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination + sub ethereum.Subscription // Subscription for solc_errors, completion and termination done bool // Whether the subscription completed delivering logs fail error // Occurred error to stop iteration } diff --git a/accounts/abi/bind/v2/lib_test.go b/accounts/abi/bind/v2/lib_test.go index d8b0c5ee42..eb6bf6ae09 100644 --- a/accounts/abi/bind/v2/lib_test.go +++ b/accounts/abi/bind/v2/lib_test.go @@ -25,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/accounts/abi/bind/testdata/v2/events" "github.com/ethereum/go-ethereum/accounts/abi/bind/testdata/v2/nested_libraries" + "github.com/ethereum/go-ethereum/accounts/abi/bind/testdata/v2/solc_errors" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" @@ -98,8 +99,60 @@ func testSetup() (*bind.TransactOpts, *backends.SimulatedBackend, error) { } // test deployment and interaction for a basic contract with no library deps -func TestDeployment(t *testing.T) { +func TestErrors(t *testing.T) { + opts, bindBackend, err := testSetup() + if err != nil { + t.Fatalf("err setting up test: %v", err) + } + defer bindBackend.Backend.Close() + deploymentParams := DeploymentParams{ + Contracts: []ContractDeployParams{ + { + Meta: solc_errors.CMetaData, + }, + }, + } + res, err := LinkAndDeploy(opts, bindBackend, deploymentParams) + if err != nil { + t.Fatalf("err: %+v\n", err) + } + bindBackend.Commit() + + if len(res.Addrs) != 1 { + t.Fatalf("deployment should have generated 1 addresses. got %d", len(res.Addrs)) + } + for _, tx := range res.Txs { + _, err = bind.WaitDeployed(context.Background(), bindBackend, tx) + if err != nil { + t.Fatalf("error deploying library: %+v", err) + } + } + c, err := solc_errors.NewC() + if err != nil { + t.Fatalf("err is %v", err) + } + doInput, err := c.PackFoo() + if err != nil { + t.Fatalf("pack function input err: %v\n", doInput) + } + + cABI, err := nested_libraries.C1MetaData.GetAbi() + if err != nil { + t.Fatalf("error getting abi object: %v", err) + } + contractAddr := res.Addrs[solc_errors.CMetaData.Pattern] + boundC := bind.NewBoundContract(contractAddr, *cABI, bindBackend, bindBackend, bindBackend) + callOpts := &bind.CallOpts{ + From: common.Address{}, + Context: context.Background(), + } + callRes, err := boundC.CallRaw(callOpts, doInput) + if err != nil { + fmt.Println(callRes) + t.Fatalf("err calling contract: %v", err) + } + _ = callRes } // test that deploying a contract with library dependencies works,