Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

accounts/abi/bind, cmd/abigen: implement alias for abigen #20244

Merged
merged 7 commits into from
Nov 14, 2019
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 40 additions & 4 deletions accounts/abi/bind/bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -74,12 +74,31 @@ 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.
//
// The key of identifiers map is the concat of function name and function type.
identifiers = make(map[string]bool)
gballet marked this conversation as resolved.
Show resolved Hide resolved
)
for _, original := range evmABI.Methods {
// Normalize the method for capital cases and non-anonymous inputs/outputs
normalized := original
normalized.Name = methodNormalizer[lang](original.Name)

// Ensure there is no duplicated identifier
normalizedName := methodNormalizer[lang](alias(aliases, original.Name))
id := normalizedName
if original.Const {
id += "call"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of having string concatenations, how about using three different identifier maps, transactIdentifiers, callIdentifiers and eventIdentifiers ?

} else {
id += "transact"
}
if identifiers[id] {
return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName)
}
identifiers[id] = true
normalized.Name = normalizedName
normalized.Inputs = make([]abi.Argument, len(original.Inputs))
copy(normalized.Inputs, original.Inputs)
for j, input := range normalized.Inputs {
Expand Down Expand Up @@ -114,7 +133,15 @@ 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))
id := normalizedName + "event"
if identifiers[id] {
return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName)
}
identifiers[id] = true
normalized.Name = normalizedName

normalized.Inputs = make([]abi.Argument, len(original.Inputs))
copy(normalized.Inputs, original.Inputs)
Expand Down Expand Up @@ -483,6 +510,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{
Expand Down
70 changes: 68 additions & 2 deletions accounts/abi/bind/bind_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -61,6 +62,7 @@ var bindTests = []struct {
nil,
nil,
nil,
nil,
},
// Test that all the official sample contracts bind correctly
{
Expand All @@ -77,6 +79,7 @@ var bindTests = []struct {
nil,
nil,
nil,
nil,
},
{
`Crowdsale`,
Expand All @@ -92,6 +95,7 @@ var bindTests = []struct {
nil,
nil,
nil,
nil,
},
{
`DAO`,
Expand All @@ -107,6 +111,7 @@ var bindTests = []struct {
nil,
nil,
nil,
nil,
},
// Test that named and anonymous inputs are handled correctly
{
Expand Down Expand Up @@ -143,6 +148,7 @@ var bindTests = []struct {
nil,
nil,
nil,
nil,
},
// Test that named and anonymous outputs are handled correctly
{
Expand Down Expand Up @@ -182,6 +188,7 @@ var bindTests = []struct {
nil,
nil,
nil,
nil,
},
// Tests that named, anonymous and indexed events are handled correctly
{
Expand Down Expand Up @@ -250,6 +257,7 @@ var bindTests = []struct {
nil,
nil,
nil,
nil,
},
// Test that contract interactions (deploy, transact and call) generate working code
{
Expand Down Expand Up @@ -311,6 +319,7 @@ var bindTests = []struct {
nil,
nil,
nil,
nil,
},
// Tests that plain values can be properly returned and deserialized
{
Expand Down Expand Up @@ -356,6 +365,7 @@ var bindTests = []struct {
nil,
nil,
nil,
nil,
},
// Tests that tuples can be properly returned and deserialized
{
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -458,6 +469,7 @@ var bindTests = []struct {
nil,
nil,
nil,
nil,
},
// Tests that anonymous default methods can be correctly invoked
{
Expand Down Expand Up @@ -508,6 +520,7 @@ var bindTests = []struct {
nil,
nil,
nil,
nil,
},
// Tests that non-existent contracts are reported as such (though only simulator test)
{
Expand Down Expand Up @@ -547,6 +560,7 @@ var bindTests = []struct {
nil,
nil,
nil,
nil,
},
// Tests that gas estimation works for contracts with weird gas mechanics too.
{
Expand Down Expand Up @@ -602,6 +616,7 @@ var bindTests = []struct {
nil,
nil,
nil,
nil,
},
// Test that constant functions can be called from an (optional) specified address
{
Expand Down Expand Up @@ -655,6 +670,7 @@ var bindTests = []struct {
nil,
nil,
nil,
nil,
},
// Tests that methods and returns with underscores inside work correctly.
{
Expand Down Expand Up @@ -734,6 +750,7 @@ var bindTests = []struct {
nil,
nil,
nil,
nil,
},
// Tests that logs can be successfully filtered and decoded.
{
Expand Down Expand Up @@ -955,6 +972,7 @@ var bindTests = []struct {
nil,
nil,
nil,
nil,
},
{
`DeeplyNestedArray`,
Expand Down Expand Up @@ -1035,6 +1053,7 @@ var bindTests = []struct {
nil,
nil,
nil,
nil,
},
{
`CallbackParam`,
Expand Down Expand Up @@ -1076,6 +1095,7 @@ var bindTests = []struct {
},
nil,
nil,
nil,
}, {
`Tuple`,
`
Expand Down Expand Up @@ -1219,6 +1239,7 @@ var bindTests = []struct {
nil,
nil,
nil,
nil,
},
{
`UseLibrary`,
Expand Down Expand Up @@ -1287,6 +1308,7 @@ var bindTests = []struct {
map[string]string{
"b98c933f0a6ececcd167bd4f9d3299b1a0": "Math",
},
nil,
[]string{"UseLibrary", "Math"},
}, {
"Overload",
Expand Down Expand Up @@ -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
gballet marked this conversation as resolved.
Show resolved Hide resolved
nil,
},
}

Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down
31 changes: 25 additions & 6 deletions cmd/abigen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
"io/ioutil"
"os"
"regexp"
"strings"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
Expand Down Expand Up @@ -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() {
Expand All @@ -120,6 +125,7 @@ func init() {
pkgFlag,
outFlag,
langFlag,
aliasFlag,
}
app.Action = utils.MigrateFlags(abigen)
cli.CommandHelpTemplate = commandHelperTemplate
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down