diff --git a/accounts/abi/bind/contract_linking_test.go b/accounts/abi/bind/contract_linking_test.go new file mode 100644 index 0000000000..5f82d813cc --- /dev/null +++ b/accounts/abi/bind/contract_linking_test.go @@ -0,0 +1,98 @@ +package bind + +import ( + v2 "github.com/ethereum/go-ethereum/accounts/abi/bind/v2" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "strings" + "testing" +) + +type linkTestCase struct { + // map of a library to the order in which its dependencies appear in the EVM bytecode. + codes map[string]string +} + +func makeLinkTestCase(input map[rune][]rune) *linkTestCase { + codes := make(map[string]string) + inputMap := make(map[rune]map[rune]struct{}) + + for contract, deps := range input { + inputMap[contract] = make(map[rune]struct{}) + for _, dep := range deps { + codes[string(contract)] = codes[string(contract)] + string(dep) + inputMap[contract][dep] = struct{}{} + } + } + return &linkTestCase{ + generateUnlinkedContracts(codes), + } +} + +func generateUnlinkedContracts(inputCodes map[string]string) map[string]string { + // map of solidity library pattern to unlinked code + codes := make(map[string]string) + + for name, code := range inputCodes { + var prelinkCode []string + for _, char := range code { + prelinkCode = append(prelinkCode, crypto.Keccak256Hash([]byte(string(char))).String()[2:36]) + } + pattern := crypto.Keccak256Hash([]byte(string(name))).String()[2:36] + codes[pattern] = strings.Join(prelinkCode, "") + } + return codes +} + +var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + +func testLinkCase(t *testing.T, input map[rune][]rune, overrides map[rune]common.Address) { + testAddr := crypto.PubkeyToAddress(testKey.PublicKey) + var testAddrNonce uint64 + + tc := makeLinkTestCase(input) + alreadyDeployed := make(map[common.Address]struct{}) + // TODO: include in link test case: set of contracts that we expect to be deployed at the end. + // generate this in makeLinkTestCase + // ^ overrides are not included in this case. + mockDeploy := func(input []byte, deployer []byte) (common.Address, *types.Transaction, error) { + contractAddr := crypto.CreateAddress(testAddr, testAddrNonce) + testAddrNonce++ + + // assert that this contract only references libs that are known to be deployed or in the override set + for i := 0; i < len(deployer)/20; i += 20 { + var dep common.Address + dep.SetBytes(deployer[i : i+20]) + if _, ok := alreadyDeployed[dep]; !ok { + t.Fatalf("reference to dependent contract that has not yet been deployed.") + } + } + alreadyDeployed[contractAddr] = struct{}{} + // we don't care about the txs themselves for the sake of the linking tests. so we can return nil for them in the mock deployer + return contractAddr, nil, nil + } + doTest := func() { + // convert the raw test case into working form + + deployParams := v2.DeploymentParams{ + Contracts: nil, + Libraries: nil, + Overrides: nil, + } + res, err := v2.LinkAndDeploy(deployParams, mockDeploy) + if err != nil { + t.Fatalf("got error from LinkAndDeploy: %v\n", err) + } + + // assert that res contains everything we expected to be deployed + } +} + +func TestContractLinking(t *testing.T) { + + //test-case specific values (TODO: move these, mockDeploy, doTest into their own routines). + + // input: a cycle of dependencies. + // expected: [{set of deps deployed first}, {set of deps deployed second}, ..., {contract(s) deployed}] +} diff --git a/accounts/abi/bind/v2/lib.go b/accounts/abi/bind/v2/lib.go index 3557818dec..84a03c0283 100644 --- a/accounts/abi/bind/v2/lib.go +++ b/accounts/abi/bind/v2/lib.go @@ -31,32 +31,31 @@ import ( // deployContract deploys a hex-encoded contract with the given constructor // input. It returns the deployment transaction, address on success. -func deployContract(backend bind.ContractBackend, auth *bind.TransactOpts, constructor []byte, contract string) (deploymentTx *types.Transaction, deploymentAddr common.Address, err error) { +func deployContract(constructor []byte, contract string, deploy func(input, deployer []byte) (common.Address, *types.Transaction, error)) (deploymentAddr common.Address, deploymentTx *types.Transaction, err error) { contractBinBytes, err := hex.DecodeString(contract[2:]) if err != nil { - return nil, common.Address{}, fmt.Errorf("contract bytecode is not a hex string: %s", contractBinBytes[2:]) + return common.Address{}, nil, fmt.Errorf("contract bytecode is not a hex string: %s", contractBinBytes[2:]) } - addr, tx, _, err := bind.DeployContractRaw(auth, contractBinBytes, backend, constructor) + addr, tx, err := deploy(constructor, contractBinBytes) if err != nil { - return nil, common.Address{}, fmt.Errorf("failed to deploy contract: %v", err) + return common.Address{}, nil, fmt.Errorf("failed to deploy contract: %v", err) } - return tx, addr, nil + return addr, tx, nil } // deployLibs iterates the set contracts (map of pattern to hex-encoded // contract deployer code). Each contract is deployed, and the // resulting addresses/deployment-txs are returned on success. -func deployLibs(backend bind.ContractBackend, auth *bind.TransactOpts, contracts map[string]string) (deploymentTxs map[common.Address]*types.Transaction, deployAddrs map[string]common.Address, err error) { +func deployLibs(contracts map[string]string, deploy func(input, deployer []byte) (common.Address, *types.Transaction, error)) (deploymentTxs map[common.Address]*types.Transaction, deployAddrs map[string]common.Address, err error) { deploymentTxs = make(map[common.Address]*types.Transaction) deployAddrs = make(map[string]common.Address) for pattern, contractBin := range contracts { - contractBinBytes, err := hex.DecodeString(contractBin[2:]) + contractDeployer, err := hex.DecodeString(contractBin[2:]) if err != nil { return deploymentTxs, deployAddrs, fmt.Errorf("contract bytecode is not a hex string: %s", contractBin[2:]) } - // TODO: can pass nil for constructor? - addr, tx, _, err := bind.DeployContractRaw(auth, contractBinBytes, backend, []byte{}) + addr, tx, err := deploy([]byte{}, contractDeployer) if err != nil { return deploymentTxs, deployAddrs, fmt.Errorf("failed to deploy contract: %v", err) } @@ -155,10 +154,14 @@ type DeploymentResult struct { Addrs map[string]common.Address } +type ContractDeployer interface { + DeployContract(input []byte, deployer []byte) (common.Address, *types.Transaction, error) +} + // LinkAndDeploy deploys a specified set of contracts and their dependent // libraries. If an error occurs, only contracts which were successfully // deployed are returned in the result. -func LinkAndDeploy(auth *bind.TransactOpts, backend bind.ContractBackend, deployParams DeploymentParams) (res *DeploymentResult, err error) { +func LinkAndDeploy(deployParams DeploymentParams, deploy func(input, deployer []byte) (common.Address, *types.Transaction, error)) (res *DeploymentResult, err error) { libMetas := deployParams.Libraries overrides := deployParams.Overrides @@ -188,7 +191,7 @@ func LinkAndDeploy(auth *bind.TransactOpts, backend bind.ContractBackend, deploy if len(deployableDeps) == 0 { break } - deployTxs, deployAddrs, err := deployLibs(backend, auth, deployableDeps) + deployTxs, deployAddrs, err := deployLibs(deployableDeps, deploy) for pattern, addr := range deployAddrs { deployed[pattern] = addr res.Addrs[pattern] = addr @@ -205,7 +208,7 @@ func LinkAndDeploy(auth *bind.TransactOpts, backend bind.ContractBackend, deploy if err != nil { return res, err } - contractTx, contractAddr, err := deployContract(backend, auth, contractParams.Input, linkedContract) + contractAddr, contractTx, err := deployContract(contractParams.Input, linkedContract, deploy) if err != nil { return res, err } diff --git a/accounts/abi/bind/v2/lib_test.go b/accounts/abi/bind/v2/lib_test.go index 90869f603f..f621762ccf 100644 --- a/accounts/abi/bind/v2/lib_test.go +++ b/accounts/abi/bind/v2/lib_test.go @@ -96,6 +96,13 @@ func testSetup() (*bind.TransactOpts, *backends.SimulatedBackend, error) { return opts, &bindBackend, nil } +func makeTestDeployer(auth *bind.TransactOpts, backend bind.ContractBackend) func(input, deployer []byte) (common.Address, *types.Transaction, error) { + return func(input, deployer []byte) (common.Address, *types.Transaction, error) { + addr, tx, _, err := bind.DeployContractRaw(auth, deployer, backend, input) + return addr, tx, err + } +} + // test that deploying a contract with library dependencies works, // verifying by calling method on the deployed contract. func TestDeploymentLibraries(t *testing.T) { @@ -124,7 +131,8 @@ func TestDeploymentLibraries(t *testing.T) { Libraries: nested_libraries.C1LibraryDeps, Overrides: nil, } - res, err := LinkAndDeploy(opts, bindBackend, deploymentParams) + + res, err := LinkAndDeploy(deploymentParams, makeTestDeployer(opts, bindBackend)) if err != nil { t.Fatalf("err: %+v\n", err) } @@ -181,7 +189,7 @@ func TestDeploymentWithOverrides(t *testing.T) { Libraries: nested_libraries.C1LibraryDeps, } - res, err := LinkAndDeploy(opts, bindBackend, deploymentParams) + res, err := LinkAndDeploy(deploymentParams, makeTestDeployer(opts, bindBackend)) if err != nil { t.Fatalf("err: %+v\n", err) } @@ -217,7 +225,7 @@ func TestDeploymentWithOverrides(t *testing.T) { Libraries: nil, Overrides: overrides, } - res, err = LinkAndDeploy(opts, bindBackend, deploymentParams) + res, err = LinkAndDeploy(deploymentParams, makeTestDeployer(opts, bindBackend)) if err != nil { t.Fatalf("err: %+v\n", err) } @@ -280,7 +288,7 @@ func TestEvents(t *testing.T) { }, } - res, err := LinkAndDeploy(txAuth, backend, deploymentParams) + res, err := LinkAndDeploy(deploymentParams, makeTestDeployer(txAuth, backend)) if err != nil { t.Fatalf("error deploying contract for testing: %v", err) }