From ce993c0b3d74a686818f0aea787cbaa586219af2 Mon Sep 17 00:00:00 2001 From: Daniel Liu Date: Mon, 6 Jan 2025 09:12:27 +0800 Subject: [PATCH] accounts/abi/bind, cmd/abigen: implement alias for abigen (#20244) --- accounts/abi/bind/bind.go | 41 ++++++++++++++++++--- accounts/abi/bind/bind_test.go | 66 +++++++++++++++++++++++++++++++++- cmd/abigen/main.go | 31 ++++++++++++---- 3 files changed, 127 insertions(+), 11 deletions(-) diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index d0ffb40aa96e..6a28ad0f7718 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -44,7 +44,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, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string) (string, error) { +func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string) (string, error) { // Process each individual contract requested binding contracts := make(map[string]*tmplContract) @@ -71,12 +71,29 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] transacts = make(map[string]*tmplMethod) events = make(map[string]*tmplEvent) structs = make(map[string]*tmplStruct) + + // identifiers are used to detect duplicated identifier of function + // and event. For all calls, transacts and events, abigen will generate + // corresponding bindings. However we have to ensure there is no + // identifier coliision in the bindings of these categories. + callIdentifiers = make(map[string]bool) + transactIdentifiers = make(map[string]bool) + eventIdentifiers = make(map[string]bool) ) for _, original := range evmABI.Methods { // Normalize the method for capital cases and non-anonymous inputs/outputs normalized := original - normalized.Name = methodNormalizer[lang](original.Name) - + normalizedName := methodNormalizer[lang](alias(aliases, original.Name)) + // Ensure there is no duplicated identifier + var identifiers = callIdentifiers + if !original.Const { + identifiers = transactIdentifiers + } + if identifiers[normalizedName] { + return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName) + } + identifiers[normalizedName] = true + normalized.Name = normalizedName normalized.Inputs = make([]abi.Argument, len(original.Inputs)) copy(normalized.Inputs, original.Inputs) for j, input := range normalized.Inputs { @@ -111,7 +128,14 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] } // Normalize the event for capital cases and non-anonymous outputs normalized := original - normalized.Name = methodNormalizer[lang](original.Name) + + // Ensure there is no duplicated identifier + normalizedName := methodNormalizer[lang](alias(aliases, original.Name)) + if eventIdentifiers[normalizedName] { + return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName) + } + eventIdentifiers[normalizedName] = true + normalized.Name = normalizedName normalized.Inputs = make([]abi.Argument, len(original.Inputs)) copy(normalized.Inputs, original.Inputs) @@ -316,6 +340,15 @@ var namedType = map[Lang]func(string, abi.Type) string{ LangGo: func(string, abi.Type) string { panic("this shouldn't be needed") }, } +// alias returns an alias of the given string based on the aliasing rules +// or returns itself if no rule is matched. +func alias(aliases map[string]string, n string) string { + if alias, exist := aliases[n]; exist { + return alias + } + return n +} + // methodNormalizer is a name transformer that modifies Solidity method names to // conform to target language naming concentions. var methodNormalizer = map[Lang]func(string) string{ diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index 244fa5c28731..d6b35156ea92 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -38,6 +38,7 @@ var bindTests = []struct { tester string fsigs []map[string]string libs map[string]string + aliases map[string]string types []string }{ // Test that the binding is available in combined and separate forms too @@ -61,6 +62,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, // Test that all the official sample contracts bind correctly { @@ -77,6 +79,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, { `Crowdsale`, @@ -92,6 +95,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, { `DAO`, @@ -107,6 +111,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, // Test that named and anonymous inputs are handled correctly { @@ -143,6 +148,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, // Test that named and anonymous outputs are handled correctly { @@ -182,6 +188,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, // Tests that named, anonymous and indexed events are handled correctly { @@ -250,6 +257,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, // Test that contract interactions (deploy, transact and call) generate working code { @@ -312,6 +320,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, // Tests that plain values can be properly returned and deserialized { @@ -358,6 +367,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, // Tests that tuples can be properly returned and deserialized { @@ -404,6 +414,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, // Tests that arrays/slices can be properly returned and deserialized. // Only addresses are tested, remainder just compiled to keep the test small. @@ -462,6 +473,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, // Tests that anonymous default methods can be correctly invoked { @@ -513,6 +525,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, // Tests that non-existent contracts are reported as such (though only simulator test) { @@ -552,6 +565,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, // Tests that gas estimation works for contracts with weird gas mechanics too. { @@ -608,6 +622,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, // Test that constant functions can be called from an (optional) specified address { @@ -663,6 +678,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, // Tests that methods and returns with underscores inside work correctly. { @@ -743,6 +759,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, // Tests that logs can be successfully filtered and decoded. { @@ -965,6 +982,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, { `DeeplyNestedArray`, @@ -1046,6 +1064,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, { `CallbackParam`, @@ -1088,6 +1107,51 @@ var bindTests = []struct { }, nil, nil, + nil, + }, + { + "IdentifierCollision", + ` + pragma solidity >=0.4.19 <0.6.0; + + contract IdentifierCollision { + uint public _myVar; + + function MyVar() public view returns (uint) { + return _myVar; + } + } + `, + []string{"60806040523480156100115760006000fd5b50610017565b60c3806100256000396000f3fe608060405234801560105760006000fd5b506004361060365760003560e01c806301ad4d8714603c5780634ef1f0ad146058576036565b60006000fd5b60426074565b6040518082815260200191505060405180910390f35b605e607d565b6040518082815260200191505060405180910390f35b60006000505481565b60006000600050549050608b565b9056fea265627a7a7231582067c8d84688b01c4754ba40a2a871cede94ea1f28b5981593ab2a45b46ac43af664736f6c634300050c0032"}, + []string{`[{"constant":true,"inputs":[],"name":"MyVar","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"_myVar","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}]`}, + ` + "math/big" + + "github.com/XinFinOrg/XDPoSChain/accounts/abi/bind" + "github.com/XinFinOrg/XDPoSChain/accounts/abi/bind/backends" + "github.com/XinFinOrg/XDPoSChain/crypto" + "github.com/XinFinOrg/XDPoSChain/core" + "github.com/XinFinOrg/XDPoSChain/params" + `, + ` + // Initialize test accounts + key, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(key.PublicKey) + + // Deploy registrar contract + sim := backends.NewXDCSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(10000000000000000)}}, 10000000, params.TestXDPoSMockChainConfig) + defer sim.Close() + + transactOpts := bind.NewKeyedTransactor(key) + _, _, _, err := DeployIdentifierCollision(transactOpts, sim) + if err != nil { + t.Fatalf("failed to deploy contract: %v", err) + } + `, + nil, + nil, + map[string]string{"_myVar": "pubVar"}, // alias MyVar to PubVar + nil, }, } @@ -1120,7 +1184,7 @@ func TestGolangBindings(t *testing.T) { types = []string{tt.name} } // Generate the binding and create a Go source file in the workspace - bind, err := Bind(types, tt.abi, tt.bytecode, tt.fsigs, "bindtest", LangGo, tt.libs) + bind, err := Bind(types, tt.abi, tt.bytecode, tt.fsigs, "bindtest", LangGo, tt.libs, tt.aliases) if err != nil { t.Fatalf("test %d: failed to generate binding: %v", i, err) } diff --git a/cmd/abigen/main.go b/cmd/abigen/main.go index 9cdc47d61ff1..b7ba99f10621 100644 --- a/cmd/abigen/main.go +++ b/cmd/abigen/main.go @@ -21,6 +21,7 @@ import ( "fmt" "io" "os" + "regexp" "strings" "github.com/XinFinOrg/XDPoSChain/accounts/abi/bind" @@ -74,6 +75,10 @@ var ( Usage: "Destination language for the bindings (go)", Value: "go", } + aliasFlag = &cli.StringFlag{ + Name: "alias", + Usage: "Comma separated aliases for function and event renaming, e.g. foo=bar", + } ) func init() { @@ -88,6 +93,7 @@ func init() { pkgFlag, outFlag, langFlag, + aliasFlag, } app.Action = abigen } @@ -100,11 +106,12 @@ func abigen(c *cli.Context) error { lang := bind.LangGo // If the entire solidity code was specified, build and bind based on that var ( - abis []string - bins []string - types []string - sigs []map[string]string - libs = make(map[string]string) + abis []string + bins []string + types []string + sigs []map[string]string + libs = make(map[string]string) + aliases = make(map[string]string) ) if c.String(abiFlag.Name) != "" { // Load up the ABI, optional bytecode and type name from the parameters @@ -176,8 +183,20 @@ func abigen(c *cli.Context) error { libs[libPattern] = nameParts[len(nameParts)-1] } } + // Extract all aliases from the flags + if c.IsSet(aliasFlag.Name) { + // We support multi-versions for aliasing + // e.g. + // foo=bar,foo2=bar2 + // foo:bar,foo2:bar2 + re := regexp.MustCompile(`(?:(\w+)[:=](\w+))`) + submatches := re.FindAllStringSubmatch(c.String(aliasFlag.Name), -1) + for _, match := range submatches { + aliases[match[1]] = match[2] + } + } // Generate the contract binding - code, err := bind.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), lang, libs) + code, err := bind.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), lang, libs, aliases) if err != nil { utils.Fatalf("Failed to generate ABI binding: %v", err) }