aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPéter Szilágyi <peterke@gmail.com>2017-12-30 05:20:02 +0800
committerGitHub <noreply@github.com>2017-12-30 05:20:02 +0800
commitb9731767af634ff90ae9b387bd035d9bc4b128e3 (patch)
treeb9e05770b293a06310948f2f2261f918e8b7064d
parent36a10875c8c1a34aab851e3f65da48ccbb5367ce (diff)
downloaddexon-b9731767af634ff90ae9b387bd035d9bc4b128e3.tar
dexon-b9731767af634ff90ae9b387bd035d9bc4b128e3.tar.gz
dexon-b9731767af634ff90ae9b387bd035d9bc4b128e3.tar.bz2
dexon-b9731767af634ff90ae9b387bd035d9bc4b128e3.tar.lz
dexon-b9731767af634ff90ae9b387bd035d9bc4b128e3.tar.xz
dexon-b9731767af634ff90ae9b387bd035d9bc4b128e3.tar.zst
dexon-b9731767af634ff90ae9b387bd035d9bc4b128e3.zip
accounts/abi: handle named ouputs prefixed with underscores (#15766)
* accounts/abi: handle named ouputs prefixed with underscores * accounts/abi: handle collinding outputs for struct unpacks * accounts: handle purely underscore output names
-rw-r--r--accounts/abi/argument.go30
-rw-r--r--accounts/abi/bind/bind.go20
-rw-r--r--accounts/abi/bind/bind_test.go62
-rw-r--r--accounts/abi/unpack_test.go45
4 files changed, 153 insertions, 4 deletions
diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go
index ad17fbf2b..4540c342f 100644
--- a/accounts/abi/argument.go
+++ b/accounts/abi/argument.go
@@ -96,6 +96,20 @@ func (arguments Arguments) unpackTuple(v interface{}, output []byte) error {
if err := requireUnpackKind(value, typ, kind, arguments); err != nil {
return err
}
+ // If the output interface is a struct, make sure names don't collide
+ if kind == reflect.Struct {
+ exists := make(map[string]bool)
+ for _, arg := range arguments {
+ field := capitalise(arg.Name)
+ if field == "" {
+ return fmt.Errorf("abi: purely underscored output cannot unpack to struct")
+ }
+ if exists[field] {
+ return fmt.Errorf("abi: multiple outputs mapping to the same struct field '%s'", field)
+ }
+ exists[field] = true
+ }
+ }
// `i` counts the nonindexed arguments.
// `j` counts the number of complex types.
// both `i` and `j` are used to to correctly compute `data` offset.
@@ -123,10 +137,10 @@ func (arguments Arguments) unpackTuple(v interface{}, output []byte) error {
switch kind {
case reflect.Struct:
+ name := capitalise(arg.Name)
for j := 0; j < typ.NumField(); j++ {
- field := typ.Field(j)
// TODO read tags: `abi:"fieldName"`
- if field.Name == strings.ToUpper(arg.Name[:1])+arg.Name[1:] {
+ if typ.Field(j).Name == name {
if err := set(value.Field(j), reflectValue, arg); err != nil {
return err
}
@@ -222,3 +236,15 @@ func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) {
return ret, nil
}
+
+// capitalise makes the first character of a string upper case, also removing any
+// prefixing underscores from the variable names.
+func capitalise(input string) string {
+ for len(input) > 0 && input[0] == '_' {
+ input = input[1:]
+ }
+ if len(input) == 0 {
+ return ""
+ }
+ return strings.ToUpper(input[:1]) + input[1:]
+}
diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go
index 4dce79b77..8175e3cb9 100644
--- a/accounts/abi/bind/bind.go
+++ b/accounts/abi/bind/bind.go
@@ -304,8 +304,15 @@ var methodNormalizer = map[Lang]func(string) string{
LangJava: decapitalise,
}
-// capitalise makes the first character of a string upper case.
+// capitalise makes the first character of a string upper case, also removing any
+// prefixing underscores from the variable names.
func capitalise(input string) string {
+ for len(input) > 0 && input[0] == '_' {
+ input = input[1:]
+ }
+ if len(input) == 0 {
+ return ""
+ }
return strings.ToUpper(input[:1]) + input[1:]
}
@@ -315,15 +322,24 @@ func decapitalise(input string) string {
}
// structured checks whether a method has enough information to return a proper
-// Go struct ot if flat returns are needed.
+// Go struct or if flat returns are needed.
func structured(method abi.Method) bool {
if len(method.Outputs) < 2 {
return false
}
+ exists := make(map[string]bool)
for _, out := range method.Outputs {
+ // If the name is anonymous, we can't organize into a struct
if out.Name == "" {
return false
}
+ // If the field name is empty when normalized or collides (var, Var, _var, _Var),
+ // we can't organize into a struct
+ field := capitalise(out.Name)
+ if field == "" || exists[field] {
+ return false
+ }
+ exists[field] = true
}
return true
}
diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go
index 43ed53b92..896441d79 100644
--- a/accounts/abi/bind/bind_test.go
+++ b/accounts/abi/bind/bind_test.go
@@ -126,6 +126,7 @@ var bindTests = []struct {
{"type":"function","name":"namedOutput","constant":true,"inputs":[],"outputs":[{"name":"str","type":"string"}]},
{"type":"function","name":"anonOutput","constant":true,"inputs":[],"outputs":[{"name":"","type":"string"}]},
{"type":"function","name":"namedOutputs","constant":true,"inputs":[],"outputs":[{"name":"str1","type":"string"},{"name":"str2","type":"string"}]},
+ {"type":"function","name":"collidingOutputs","constant":true,"inputs":[],"outputs":[{"name":"str","type":"string"},{"name":"Str","type":"string"}]},
{"type":"function","name":"anonOutputs","constant":true,"inputs":[],"outputs":[{"name":"","type":"string"},{"name":"","type":"string"}]},
{"type":"function","name":"mixedOutputs","constant":true,"inputs":[],"outputs":[{"name":"","type":"string"},{"name":"str","type":"string"}]}
]
@@ -140,6 +141,7 @@ var bindTests = []struct {
str1, err = b.NamedOutput(nil)
str1, err = b.AnonOutput(nil)
res, _ := b.NamedOutputs(nil)
+ str1, str2, err = b.CollidingOutputs(nil)
str1, str2, err = b.AnonOutputs(nil)
str1, str2, err = b.MixedOutputs(nil)
@@ -447,6 +449,66 @@ var bindTests = []struct {
}
`,
},
+ {
+ `Underscorer`,
+ `
+ contract Underscorer {
+ function UnderscoredOutput() constant returns (int _int, string _string) {
+ return (314, "pi");
+ }
+ function LowerLowerCollision() constant returns (int _res, int res) {
+ return (1, 2);
+ }
+ function LowerUpperCollision() constant returns (int _res, int Res) {
+ return (1, 2);
+ }
+ function UpperLowerCollision() constant returns (int _Res, int res) {
+ return (1, 2);
+ }
+ function UpperUpperCollision() constant returns (int _Res, int Res) {
+ return (1, 2);
+ }
+ function PurelyUnderscoredOutput() constant returns (int _, int res) {
+ return (1, 2);
+ }
+ function AllPurelyUnderscoredOutput() constant returns (int _, int __) {
+ return (1, 2);
+ }
+ }
+ `, `6060604052341561000f57600080fd5b6103498061001e6000396000f300606060405260043610610083576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806303a592131461008857806367e6633d146100b85780639df484851461014d578063af7486ab1461017d578063b564b34d146101ad578063e02ab24d146101dd578063e409ca451461020d575b600080fd5b341561009357600080fd5b61009b61023d565b604051808381526020018281526020019250505060405180910390f35b34156100c357600080fd5b6100cb610252565b6040518083815260200180602001828103825283818151815260200191508051906020019080838360005b838110156101115780820151818401526020810190506100f6565b50505050905090810190601f16801561013e5780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b341561015857600080fd5b6101606102a0565b604051808381526020018281526020019250505060405180910390f35b341561018857600080fd5b6101906102b5565b604051808381526020018281526020019250505060405180910390f35b34156101b857600080fd5b6101c06102ca565b604051808381526020018281526020019250505060405180910390f35b34156101e857600080fd5b6101f06102df565b604051808381526020018281526020019250505060405180910390f35b341561021857600080fd5b6102206102f4565b604051808381526020018281526020019250505060405180910390f35b60008060016002819150809050915091509091565b600061025c610309565b61013a8090506040805190810160405280600281526020017f7069000000000000000000000000000000000000000000000000000000000000815250915091509091565b60008060016002819150809050915091509091565b60008060016002819150809050915091509091565b60008060016002819150809050915091509091565b60008060016002819150809050915091509091565b60008060016002819150809050915091509091565b6020604051908101604052806000815250905600a165627a7a72305820c11dcfa136fc7d182ee4d34f0b12d988496228f7e2d02d2b5376d996ca1743d00029`,
+ `[{"constant":true,"inputs":[],"name":"LowerUpperCollision","outputs":[{"name":"_res","type":"int256"},{"name":"Res","type":"int256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"UnderscoredOutput","outputs":[{"name":"_int","type":"int256"},{"name":"_string","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PurelyUnderscoredOutput","outputs":[{"name":"_","type":"int256"},{"name":"res","type":"int256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"UpperLowerCollision","outputs":[{"name":"_Res","type":"int256"},{"name":"res","type":"int256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"AllPurelyUnderscoredOutput","outputs":[{"name":"_","type":"int256"},{"name":"__","type":"int256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"UpperUpperCollision","outputs":[{"name":"_Res","type":"int256"},{"name":"Res","type":"int256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"LowerLowerCollision","outputs":[{"name":"_res","type":"int256"},{"name":"res","type":"int256"}],"payable":false,"stateMutability":"view","type":"function"}]`,
+ `
+ // Generate a new random account and a funded simulator
+ key, _ := crypto.GenerateKey()
+ auth := bind.NewKeyedTransactor(key)
+ sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}})
+
+ // Deploy a underscorer tester contract and execute a structured call on it
+ _, _, underscorer, err := DeployUnderscorer(auth, sim)
+ if err != nil {
+ t.Fatalf("Failed to deploy underscorer contract: %v", err)
+ }
+ sim.Commit()
+
+ // Verify that underscored return values correctly parse into structs
+ if res, err := underscorer.UnderscoredOutput(nil); err != nil {
+ t.Errorf("Failed to call constant function: %v", err)
+ } else if res.Int.Cmp(big.NewInt(314)) != 0 || res.String != "pi" {
+ t.Errorf("Invalid result, want: {314, \"pi\"}, got: %+v", res)
+ }
+ // Verify that underscored and non-underscored name collisions force tuple outputs
+ var a, b *big.Int
+
+ a, b, _ = underscorer.LowerLowerCollision(nil)
+ a, b, _ = underscorer.LowerUpperCollision(nil)
+ a, b, _ = underscorer.UpperLowerCollision(nil)
+ a, b, _ = underscorer.UpperUpperCollision(nil)
+ a, b, _ = underscorer.PurelyUnderscoredOutput(nil)
+ a, b, _ = underscorer.AllPurelyUnderscoredOutput(nil)
+
+ fmt.Println(a, b, err)
+ `,
+ },
}
// Tests that packages generated by the binder can be successfully compiled and
diff --git a/accounts/abi/unpack_test.go b/accounts/abi/unpack_test.go
index 391095073..4d7fe638c 100644
--- a/accounts/abi/unpack_test.go
+++ b/accounts/abi/unpack_test.go
@@ -259,6 +259,51 @@ var unpackTests = []unpackTest{
enc: "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003",
want: [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)},
},
+ // struct outputs
+ {
+ def: `[{"name":"int1","type":"int256"},{"name":"int2","type":"int256"}]`,
+ enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
+ want: struct {
+ Int1 *big.Int
+ Int2 *big.Int
+ }{big.NewInt(1), big.NewInt(2)},
+ },
+ {
+ def: `[{"name":"int","type":"int256"},{"name":"Int","type":"int256"}]`,
+ enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
+ want: struct {
+ Int1 *big.Int
+ Int2 *big.Int
+ }{},
+ err: "abi: multiple outputs mapping to the same struct field 'Int'",
+ },
+ {
+ def: `[{"name":"int","type":"int256"},{"name":"_int","type":"int256"}]`,
+ enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
+ want: struct {
+ Int1 *big.Int
+ Int2 *big.Int
+ }{},
+ err: "abi: multiple outputs mapping to the same struct field 'Int'",
+ },
+ {
+ def: `[{"name":"Int","type":"int256"},{"name":"_int","type":"int256"}]`,
+ enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
+ want: struct {
+ Int1 *big.Int
+ Int2 *big.Int
+ }{},
+ err: "abi: multiple outputs mapping to the same struct field 'Int'",
+ },
+ {
+ def: `[{"name":"Int","type":"int256"},{"name":"_","type":"int256"}]`,
+ enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
+ want: struct {
+ Int1 *big.Int
+ Int2 *big.Int
+ }{},
+ err: "abi: purely underscored output cannot unpack to struct",
+ },
}
func TestUnpack(t *testing.T) {