From 7455b91800d4f1d209f0690ce7b544a219f3501e Mon Sep 17 00:00:00 2001 From: Daniel Liu Date: Thu, 12 Dec 2024 17:11:08 +0800 Subject: [PATCH] accounts/abi/bind: accept function ptr parameter (#19755) --- accounts/abi/bind/bind.go | 8 +++-- accounts/abi/bind/bind_test.go | 61 +++++++++++++++++++++++++++++++++- accounts/abi/bind/template.go | 10 ++++++ cmd/abigen/main.go | 4 ++- common/compiler/helpers.go | 7 ++-- common/compiler/solidity.go | 4 ++- 6 files changed, 85 insertions(+), 9 deletions(-) diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index ca9e3e8b52..e6312657bd 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -43,7 +43,7 @@ const ( // to be used as is in client code, but rather as an intermediate struct which // enforces compile time type safety and naming convention opposed to having to // manually maintain hard coded strings that break on runtime. -func Bind(types []string, abis []string, bytecodes []string, pkg string, lang Lang) (string, error) { +func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang) (string, error) { // Process each individual contract requested binding contracts := make(map[string]*tmplContract) @@ -124,6 +124,9 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La Transacts: transacts, Events: events, } + if len(fsigs) > i { + contracts[types[i]].FuncSigs = fsigs[i] + } } // Generate the contract template data content and render it data := &tmplData{ @@ -178,8 +181,7 @@ func bindBasicTypeGo(kind abi.Type) string { case abi.BytesTy: return "[]byte" case abi.FunctionTy: - // todo(rjl493456442) - return "" + return "[24]byte" default: // string, bool types return kind.String() diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index 55116c5a10..8a73fc8b64 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -36,6 +36,7 @@ var bindTests = []struct { abi string imports string tester string + fsigs []map[string]string }{ // Test that the binding is available in combined and separate forms too { @@ -55,6 +56,7 @@ var bindTests = []struct { t.Fatalf("transactor binding (%v) nil or error (%v) not nil", b, nil) } `, + nil, }, // Test that all the official sample contracts bind correctly { @@ -68,6 +70,7 @@ var bindTests = []struct { t.Fatalf("binding (%v) nil or error (%v) not nil", b, nil) } `, + nil, }, { `Crowdsale`, @@ -80,6 +83,7 @@ var bindTests = []struct { t.Fatalf("binding (%v) nil or error (%v) not nil", b, nil) } `, + nil, }, { `DAO`, @@ -92,6 +96,7 @@ var bindTests = []struct { t.Fatalf("binding (%v) nil or error (%v) not nil", b, nil) } `, + nil, }, // Test that named and anonymous inputs are handled correctly { @@ -125,6 +130,7 @@ var bindTests = []struct { fmt.Println(err) }`, + nil, }, // Test that named and anonymous outputs are handled correctly { @@ -161,6 +167,7 @@ var bindTests = []struct { fmt.Println(str1, str2, res.Str1, res.Str2, err) }`, + nil, }, // Tests that named, anonymous and indexed events are handled correctly { @@ -226,6 +233,7 @@ var bindTests = []struct { if _, ok := reflect.TypeOf(&EventChecker{}).MethodByName("FilterAnonymous"); ok { t.Errorf("binding has disallowed method (FilterAnonymous)") }`, + nil, }, // Test that contract interactions (deploy, transact and call) generate working code { @@ -283,6 +291,7 @@ var bindTests = []struct { t.Fatalf("Transact string mismatch: have '%s', want 'Transact string'", str) } `, + nil, }, // Tests that plain values can be properly returned and deserialized { @@ -324,6 +333,7 @@ var bindTests = []struct { t.Fatalf("Retrieved value mismatch: have %v/%v, want %v/%v", str, num, "Hi", 1) } `, + nil, }, // Tests that tuples can be properly returned and deserialized { @@ -365,6 +375,7 @@ var bindTests = []struct { t.Fatalf("Retrieved value mismatch: have %v/%v, want %v/%v", res.A, res.B, "Hi", 1) } `, + nil, }, // Tests that arrays/slices can be properly returned and deserialized. // Only addresses are tested, remainder just compiled to keep the test small. @@ -418,6 +429,7 @@ var bindTests = []struct { t.Fatalf("Slice return mismatch: have %v, want %v", out, []common.Address{auth.From, common.Address{}}) } `, + nil, }, // Tests that anonymous default methods can be correctly invoked { @@ -464,6 +476,7 @@ var bindTests = []struct { t.Fatalf("Address mismatch: have %v, want %v", caller, auth.From) } `, + nil, }, // Tests that non-existent contracts are reported as such (though only simulator test) { @@ -498,6 +511,7 @@ var bindTests = []struct { t.Fatalf("Error mismatch: have %v, want %v", err, bind.ErrNoCode) } `, + nil, }, // Tests that gas estimation works for contracts with weird gas mechanics too. { @@ -549,6 +563,7 @@ var bindTests = []struct { t.Fatalf("Field mismatch: have %v, want %v", field, "automatic") } `, + nil, }, // Test that constant functions can be called from an (optional) specified address { @@ -598,6 +613,7 @@ var bindTests = []struct { } } `, + nil, }, // Tests that methods and returns with underscores inside work correctly. { @@ -673,6 +689,7 @@ var bindTests = []struct { fmt.Println(a, b, err) `, + nil, }, // Tests that logs can be successfully filtered and decoded. { @@ -855,6 +872,7 @@ var bindTests = []struct { case <-time.After(250 * time.Millisecond): } `, + nil, }, { `DeeplyNestedArray`, @@ -931,6 +949,47 @@ var bindTests = []struct { t.Fatalf("Retrieved value does not match expected value! got: %d, expected: %d. %v", retrievedArr[4][3][2], testArr[4][3][2], err) } `, + nil, + }, + { + `CallbackParam`, + ` + contract FunctionPointerTest { + function test(function(uint256) external callback) external { + callback(1); + } + } + `, + `608060405234801561001057600080fd5b5061015e806100206000396000f3fe60806040526004361061003b576000357c010000000000000000000000000000000000000000000000000000000090048063d7a5aba214610040575b600080fd5b34801561004c57600080fd5b506100be6004803603602081101561006357600080fd5b810190808035806c0100000000000000000000000090049068010000000000000000900463ffffffff1677ffffffffffffffffffffffffffffffffffffffffffffffff169091602001919093929190939291905050506100c0565b005b818160016040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b15801561011657600080fd5b505af115801561012a573d6000803e3d6000fd5b50505050505056fea165627a7a7230582062f87455ff84be90896dbb0c4e4ddb505c600d23089f8e80a512548440d7e2580029`, + `[ + { + "constant": false, + "inputs": [ + { + "name": "callback", + "type": "function" + } + ], + "name": "test", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + ]`, + ` + "strings" + `, + ` + if strings.Compare("test(function)", CallbackParamFuncSigs["d7a5aba2"]) != 0 { + t.Fatalf("") + } + `, + []map[string]string{ + { + "test(function)": "d7a5aba2", + }, + }, }, } @@ -957,7 +1016,7 @@ func TestGolangBindings(t *testing.T) { // Generate the test suite for all the contracts for i, tt := range bindTests { // Generate the binding and create a Go source file in the workspace - bind, err := Bind([]string{tt.name}, []string{tt.abi}, []string{tt.bytecode}, "bindtest", LangGo) + bind, err := Bind([]string{tt.name}, []string{tt.abi}, []string{tt.bytecode}, tt.fsigs, "bindtest", LangGo) if err != nil { t.Fatalf("test %d: failed to generate binding: %v", i, err) } diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go index ad3cb74f95..f65c16bdcd 100644 --- a/accounts/abi/bind/template.go +++ b/accounts/abi/bind/template.go @@ -29,6 +29,7 @@ type tmplContract struct { Type string // Type name of the main contract binding InputABI string // JSON ABI used as the input to generate the binding from InputBin string // Optional EVM bytecode used to denetare deploy code from + FuncSigs map[string]string // Optional map: string signature -> 4-byte signature Constructor abi.Method // Contract constructor for deploy parametrization Calls map[string]*tmplMethod // Contract calls that only read state data Transacts map[string]*tmplMethod // Contract calls that write state data @@ -91,6 +92,15 @@ var ( // {{.Type}}ABI is the input ABI used to generate the binding from. const {{.Type}}ABI = "{{.InputABI}}" + {{if $contract.FuncSigs}} + // {{.Type}}FuncSigs maps the 4-byte function signature to its string representation. + var {{.Type}}FuncSigs = map[string]string{ + {{range $strsig, $binsig := .FuncSigs}} + "{{$binsig}}": "{{$strsig}}", + {{end}} + } + {{end}} + {{if .InputBin}} // {{.Type}}Bin is the compiled bytecode used for deploying new contracts. const {{.Type}}Bin = ` + "`" + `{{.InputBin}}` + "`" + ` diff --git a/cmd/abigen/main.go b/cmd/abigen/main.go index 7a696fce41..b6b1533554 100644 --- a/cmd/abigen/main.go +++ b/cmd/abigen/main.go @@ -135,6 +135,7 @@ func abigen(c *cli.Context) error { abis []string bins []string types []string + sigs []map[string]string ) if c.String(solFlag.Name) != "" || c.String(vyFlag.Name) != "" || c.String(abiFlag.Name) == "-" { // Generate the list of types to exclude from binding @@ -179,6 +180,7 @@ func abigen(c *cli.Context) error { } abis = append(abis, string(abi)) bins = append(bins, contract.Code) + sigs = append(sigs, contract.Hashes) nameParts := strings.Split(name, ":") types = append(types, nameParts[len(nameParts)-1]) @@ -209,7 +211,7 @@ func abigen(c *cli.Context) error { types = append(types, kind) } // Generate the contract binding - code, err := bind.Bind(types, abis, bins, c.String(pkgFlag.Name), lang) + code, err := bind.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), lang) if err != nil { fmt.Printf("Failed to generate ABI binding: %v\n", err) os.Exit(-1) diff --git a/common/compiler/helpers.go b/common/compiler/helpers.go index c2d4a20e36..59d242af3d 100644 --- a/common/compiler/helpers.go +++ b/common/compiler/helpers.go @@ -27,9 +27,10 @@ var versionRegexp = regexp.MustCompile(`([0-9]+)\.([0-9]+)\.([0-9]+)`) // Contract contains information about a compiled contract, alongside its code and runtime code. type Contract struct { - Code string `json:"code"` - RuntimeCode string `json:"runtime-code"` - Info ContractInfo `json:"info"` + Code string `json:"code"` + RuntimeCode string `json:"runtime-code"` + Info ContractInfo `json:"info"` + Hashes map[string]string `json:"hashes"` } // ContractInfo contains information about a compiled contract, including access diff --git a/common/compiler/solidity.go b/common/compiler/solidity.go index 7ed9c2633e..56e01ee334 100644 --- a/common/compiler/solidity.go +++ b/common/compiler/solidity.go @@ -39,6 +39,7 @@ type solcOutput struct { BinRuntime string `json:"bin-runtime"` SrcMapRuntime string `json:"srcmap-runtime"` Bin, SrcMap, Abi, Devdoc, Userdoc, Metadata string + Hashes map[string]string } Version string } @@ -49,7 +50,7 @@ func (s *Solidity) makeArgs() []string { "--optimize", // code optimizer switched on } if s.Major > 0 || s.Minor > 4 || s.Patch > 6 { - p[1] += ",metadata" + p[1] += ",metadata,hashes" } return p } @@ -161,6 +162,7 @@ func ParseCombinedJSON(combinedJSON []byte, source string, languageVersion strin contracts[name] = &Contract{ Code: "0x" + info.Bin, RuntimeCode: "0x" + info.BinRuntime, + Hashes: info.Hashes, Info: ContractInfo{ Source: source, Language: "Solidity",