From f8a95d996fbe66e460747c963bf30c59eb49021c Mon Sep 17 00:00:00 2001 From: gary rong Date: Thu, 14 Nov 2019 15:26:10 +0800 Subject: [PATCH] accounts/abi/bind, cmd/abigen: implement alias for abigen (#20244) * accounts/abi/bind, cmd/abigen: implement alias for abigen * accounts/abi/bind: minor fixes * accounts/abi/bind: address comments * cmd/abigen: address comments * accounts/abi/bind: print error log when identifier collision * accounts/abi/bind: address comments * accounts/abi/bind: address comment --- accounts/abi/bind/bind.go | 41 ++++++++++++++++++-- accounts/abi/bind/bind_test.go | 70 +++++++++++++++++++++++++++++++++- cmd/abigen/main.go | 31 ++++++++++++--- 3 files changed, 130 insertions(+), 12 deletions(-) diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index 7bda997a6..e51b44563 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -47,7 +47,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) @@ -74,12 +74,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 { @@ -114,7 +131,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) @@ -483,6 +507,15 @@ func namedTypeJava(javaKind string, solKind abi.Type) string { } } +// 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 1db568283..333b1d5a8 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 { @@ -311,6 +319,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, // Tests that plain values can be properly returned and deserialized { @@ -356,6 +365,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, // Tests that tuples can be properly returned and deserialized { @@ -401,6 +411,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. @@ -458,6 +469,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, // Tests that anonymous default methods can be correctly invoked { @@ -508,6 +520,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, // Tests that non-existent contracts are reported as such (though only simulator test) { @@ -547,6 +560,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, // Tests that gas estimation works for contracts with weird gas mechanics too. { @@ -602,6 +616,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, // Test that constant functions can be called from an (optional) specified address { @@ -655,6 +670,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, // Tests that methods and returns with underscores inside work correctly. { @@ -734,6 +750,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, // Tests that logs can be successfully filtered and decoded. { @@ -955,6 +972,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, { `DeeplyNestedArray`, @@ -1035,6 +1053,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, { `CallbackParam`, @@ -1076,6 +1095,7 @@ var bindTests = []struct { }, nil, nil, + nil, }, { `Tuple`, ` @@ -1219,6 +1239,7 @@ var bindTests = []struct { nil, nil, nil, + nil, }, { `UseLibrary`, @@ -1287,6 +1308,7 @@ var bindTests = []struct { map[string]string{ "b98c933f0a6ececcd167bd4f9d3299b1a0": "Math", }, + nil, []string{"UseLibrary", "Math"}, }, { "Overload", @@ -1381,6 +1403,50 @@ var bindTests = []struct { nil, 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/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/core" + `, + ` + // Initialize test accounts + key, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(key.PublicKey) + + // Deploy registrar contract + sim := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(1000000000)}}, 10000000) + 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, }, } @@ -1412,7 +1478,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) } @@ -1835,7 +1901,7 @@ public class Test { }, } for i, c := range cases { - binding, err := Bind([]string{c.name}, []string{c.abi}, []string{c.bytecode}, nil, "bindtest", LangJava, nil) + binding, err := Bind([]string{c.name}, []string{c.abi}, []string{c.bytecode}, nil, "bindtest", LangJava, nil, nil) 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 769811500..659cf1b4c 100644 --- a/cmd/abigen/main.go +++ b/cmd/abigen/main.go @@ -21,6 +21,7 @@ import ( "fmt" "io/ioutil" "os" + "regexp" "strings" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -103,6 +104,10 @@ var ( Usage: "Destination language for the bindings (go, java, objc)", Value: "go", } + aliasFlag = cli.StringFlag{ + Name: "alias", + Usage: "Comma separated aliases for function and event renaming, e.g. foo=bar", + } ) func init() { @@ -120,6 +125,7 @@ func init() { pkgFlag, outFlag, langFlag, + aliasFlag, } app.Action = utils.MigrateFlags(abigen) cli.CommandHelpTemplate = commandHelperTemplate @@ -144,11 +150,12 @@ func abigen(c *cli.Context) error { } // 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.GlobalString(abiFlag.Name) != "" { // Load up the ABI, optional bytecode and type name from the parameters @@ -232,8 +239,20 @@ func abigen(c *cli.Context) error { libs[libPattern] = nameParts[len(nameParts)-1] } } + // Extract all aliases from the flags + if c.GlobalIsSet(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.GlobalString(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.GlobalString(pkgFlag.Name), lang, libs) + code, err := bind.Bind(types, abis, bins, sigs, c.GlobalString(pkgFlag.Name), lang, libs, aliases) if err != nil { utils.Fatalf("Failed to generate ABI binding: %v", err) }