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
This commit is contained in:
		
							parent
							
								
									36a10875c8
								
							
						
					
					
						commit
						b9731767af
					
				| @ -96,6 +96,20 @@ func (arguments Arguments) unpackTuple(v interface{}, output []byte) error { | |||||||
| 	if err := requireUnpackKind(value, typ, kind, arguments); err != nil { | 	if err := requireUnpackKind(value, typ, kind, arguments); err != nil { | ||||||
| 		return err | 		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.
 | 	// `i` counts the nonindexed arguments.
 | ||||||
| 	// `j` counts the number of complex types.
 | 	// `j` counts the number of complex types.
 | ||||||
| 	// both `i` and `j` are used to to correctly compute `data` offset.
 | 	// 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 { | 		switch kind { | ||||||
| 		case reflect.Struct: | 		case reflect.Struct: | ||||||
|  | 			name := capitalise(arg.Name) | ||||||
| 			for j := 0; j < typ.NumField(); j++ { | 			for j := 0; j < typ.NumField(); j++ { | ||||||
| 				field := typ.Field(j) |  | ||||||
| 				// TODO read tags: `abi:"fieldName"`
 | 				// 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 { | 					if err := set(value.Field(j), reflectValue, arg); err != nil { | ||||||
| 						return err | 						return err | ||||||
| 					} | 					} | ||||||
| @ -222,3 +236,15 @@ func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) { | |||||||
| 
 | 
 | ||||||
| 	return ret, nil | 	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:] | ||||||
|  | } | ||||||
|  | |||||||
| @ -304,8 +304,15 @@ var methodNormalizer = map[Lang]func(string) string{ | |||||||
| 	LangJava: decapitalise, | 	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 { | 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:] | 	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
 | // 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 { | func structured(method abi.Method) bool { | ||||||
| 	if len(method.Outputs) < 2 { | 	if len(method.Outputs) < 2 { | ||||||
| 		return false | 		return false | ||||||
| 	} | 	} | ||||||
|  | 	exists := make(map[string]bool) | ||||||
| 	for _, out := range method.Outputs { | 	for _, out := range method.Outputs { | ||||||
|  | 		// If the name is anonymous, we can't organize into a struct
 | ||||||
| 		if out.Name == "" { | 		if out.Name == "" { | ||||||
| 			return false | 			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 | 	return true | ||||||
| } | } | ||||||
|  | |||||||
| @ -126,6 +126,7 @@ var bindTests = []struct { | |||||||
| 				{"type":"function","name":"namedOutput","constant":true,"inputs":[],"outputs":[{"name":"str","type":"string"}]}, | 				{"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":"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":"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":"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"}]} | 				{"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.NamedOutput(nil) | ||||||
| 			 str1, err        = b.AnonOutput(nil) | 			 str1, err        = b.AnonOutput(nil) | ||||||
| 			 res, _          := b.NamedOutputs(nil) | 			 res, _          := b.NamedOutputs(nil) | ||||||
|  | 			 str1, str2, err  = b.CollidingOutputs(nil) | ||||||
| 			 str1, str2, err  = b.AnonOutputs(nil) | 			 str1, str2, err  = b.AnonOutputs(nil) | ||||||
| 			 str1, str2, err  = b.MixedOutputs(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
 | // Tests that packages generated by the binder can be successfully compiled and
 | ||||||
|  | |||||||
| @ -259,6 +259,51 @@ var unpackTests = []unpackTest{ | |||||||
| 		enc:  "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", | 		enc:  "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", | ||||||
| 		want: [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)}, | 		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) { | func TestUnpack(t *testing.T) { | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user