accounts/abi/bind: correctly handle structs used only as constructor params (#23940)
The `structs` map is populated by iterating over all methods except the constructor, which results in a nil-pointer dereference. I've first reproduced the problem with a new test and then implemented the fix. Co-authored-by: Arran Schlosberg <me@arranschlosberg.com>
This commit is contained in:
		| @@ -1911,6 +1911,50 @@ var bindTests = []struct { | ||||
| 		nil, | ||||
| 		nil, | ||||
| 	}, | ||||
| 	{ | ||||
| 		name: `ConstructorWithStructParam`, | ||||
| 		contract: ` | ||||
| 		pragma solidity >=0.8.0 <0.9.0; | ||||
| 		 | ||||
| 		contract ConstructorWithStructParam { | ||||
| 			struct StructType { | ||||
| 				uint256 field; | ||||
| 			} | ||||
| 		 | ||||
| 			constructor(StructType memory st) {} | ||||
| 		} | ||||
| 		`, | ||||
| 		bytecode: []string{`0x608060405234801561001057600080fd5b506040516101c43803806101c48339818101604052810190610032919061014a565b50610177565b6000604051905090565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6100958261004c565b810181811067ffffffffffffffff821117156100b4576100b361005d565b5b80604052505050565b60006100c7610038565b90506100d3828261008c565b919050565b6000819050919050565b6100eb816100d8565b81146100f657600080fd5b50565b600081519050610108816100e2565b92915050565b60006020828403121561012457610123610047565b5b61012e60206100bd565b9050600061013e848285016100f9565b60008301525092915050565b6000602082840312156101605761015f610042565b5b600061016e8482850161010e565b91505092915050565b603f806101856000396000f3fe6080604052600080fdfea2646970667358221220cdffa667affecefac5561f65f4a4ba914204a8d4eb859d8cd426fb306e5c12a364736f6c634300080a0033`}, | ||||
| 		abi:      []string{`[{"inputs":[{"components":[{"internalType":"uint256","name":"field","type":"uint256"}],"internalType":"struct ConstructorWithStructParam.StructType","name":"st","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"}]`}, | ||||
| 		imports: ` | ||||
| 			"math/big" | ||||
|  | ||||
| 			"github.com/ethereum/go-ethereum/accounts/abi/bind" | ||||
| 			"github.com/ethereum/go-ethereum/accounts/abi/bind/backends" | ||||
| 			"github.com/ethereum/go-ethereum/core" | ||||
| 			"github.com/ethereum/go-ethereum/crypto" | ||||
| 			"github.com/ethereum/go-ethereum/eth/ethconfig" | ||||
| 		`, | ||||
| 		tester: ` | ||||
| 			var ( | ||||
| 				key, _  = crypto.GenerateKey() | ||||
| 				user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) | ||||
| 				sim     = backends.NewSimulatedBackend(core.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil) | ||||
| 			) | ||||
| 			defer sim.Close() | ||||
|  | ||||
| 			_, tx, _, err := DeployConstructorWithStructParam(user, sim, ConstructorWithStructParamStructType{Field: big.NewInt(42)}) | ||||
| 			if err != nil { | ||||
| 				t.Fatalf("DeployConstructorWithStructParam() got err %v; want nil err", err) | ||||
| 			} | ||||
| 			sim.Commit() | ||||
| 			 | ||||
| 			if _, err = bind.WaitDeployed(nil, sim, tx); err != nil { | ||||
| 				t.Logf("Deployment tx: %+v", tx) | ||||
| 				t.Errorf("bind.WaitDeployed(nil, %T, <deployment tx>) got err %v; want nil err", sim, err) | ||||
| 			} | ||||
| 		`, | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| // Tests that packages generated by the binder can be successfully compiled and | ||||
| @@ -1934,22 +1978,23 @@ func TestGolangBindings(t *testing.T) { | ||||
| 	} | ||||
| 	// Generate the test suite for all the contracts | ||||
| 	for i, tt := range bindTests { | ||||
| 		var types []string | ||||
| 		if tt.types != nil { | ||||
| 			types = tt.types | ||||
| 		} else { | ||||
| 			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, tt.aliases) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("test %d: failed to generate binding: %v", i, err) | ||||
| 		} | ||||
| 		if err = ioutil.WriteFile(filepath.Join(pkg, strings.ToLower(tt.name)+".go"), []byte(bind), 0600); err != nil { | ||||
| 			t.Fatalf("test %d: failed to write binding: %v", i, err) | ||||
| 		} | ||||
| 		// Generate the test file with the injected test code | ||||
| 		code := fmt.Sprintf(` | ||||
| 		t.Run(tt.name, func(t *testing.T) { | ||||
| 			var types []string | ||||
| 			if tt.types != nil { | ||||
| 				types = tt.types | ||||
| 			} else { | ||||
| 				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, tt.aliases) | ||||
| 			if err != nil { | ||||
| 				t.Fatalf("test %d: failed to generate binding: %v", i, err) | ||||
| 			} | ||||
| 			if err = ioutil.WriteFile(filepath.Join(pkg, strings.ToLower(tt.name)+".go"), []byte(bind), 0600); err != nil { | ||||
| 				t.Fatalf("test %d: failed to write binding: %v", i, err) | ||||
| 			} | ||||
| 			// Generate the test file with the injected test code | ||||
| 			code := fmt.Sprintf(` | ||||
| 			package bindtest | ||||
|  | ||||
| 			import ( | ||||
| @@ -1961,9 +2006,10 @@ func TestGolangBindings(t *testing.T) { | ||||
| 				%s | ||||
| 			} | ||||
| 		`, tt.imports, tt.name, tt.tester) | ||||
| 		if err := ioutil.WriteFile(filepath.Join(pkg, strings.ToLower(tt.name)+"_test.go"), []byte(code), 0600); err != nil { | ||||
| 			t.Fatalf("test %d: failed to write tests: %v", i, err) | ||||
| 		} | ||||
| 			if err := ioutil.WriteFile(filepath.Join(pkg, strings.ToLower(tt.name)+"_test.go"), []byte(code), 0600); err != nil { | ||||
| 				t.Fatalf("test %d: failed to write tests: %v", i, err) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| 	// Convert the package to go modules and use the current source for go-ethereum | ||||
| 	moder := exec.Command(gocmd, "mod", "init", "bindtest") | ||||
|   | ||||
		Reference in New Issue
	
	Block a user