diff --git a/accounts/abi/bind/v2/contract_linking_test.go b/accounts/abi/bind/v2/contract_linking_test.go index af22b1998f..ecdccbfb3a 100644 --- a/accounts/abi/bind/v2/contract_linking_test.go +++ b/accounts/abi/bind/v2/contract_linking_test.go @@ -71,10 +71,10 @@ func makeLinkTestCase(input map[rune][]rune, overrides map[rune]common.Address) } } -func testLinkCase(t *testing.T, input map[rune][]rune, overrides map[rune]struct{}) { +func testLinkCase(t *testing.T, input map[rune][]rune, overrides map[rune]struct{}, expectDeployed map[rune]struct{}) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) var testAddrNonce uint64 - alreadyDeployed := make(map[common.Address]struct{}) + overridesAddrs := make(map[common.Address]struct{}) // generate deterministic addresses for the override set. rand.Seed(42) @@ -83,7 +83,7 @@ func testLinkCase(t *testing.T, input map[rune][]rune, overrides map[rune]struct var addr common.Address rand.Read(addr[:]) overrideAddrs[contract] = addr - alreadyDeployed[addr] = struct{}{} + overridesAddrs[addr] = struct{}{} } tc := makeLinkTestCase(input, overrideAddrs) @@ -96,9 +96,6 @@ func testLinkCase(t *testing.T, input map[rune][]rune, overrides map[rune]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++ @@ -107,11 +104,11 @@ func testLinkCase(t *testing.T, input map[rune][]rune, overrides map[rune]struct for i := 0; i < len(deployer)/20; i += 20 { var dep common.Address dep.SetBytes(deployer[i : i+20]) - if _, ok := alreadyDeployed[dep]; !ok { + if _, ok := overridesAddrs[dep]; !ok { t.Fatalf("reference to dependent contract that has not yet been deployed: %x\n", dep) } } - alreadyDeployed[contractAddr] = struct{}{} + overridesAddrs[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 } @@ -148,13 +145,14 @@ func testLinkCase(t *testing.T, input map[rune][]rune, overrides map[rune]struct t.Fatalf("got error from LinkAndDeploy: %v\n", err) } - // TODO: assert that the result consists of the input contracts minus the overrides. - - if len(res.Addrs) != len(allContracts)-len(overrides) { - for val, _ := range allContracts { - fmt.Println(string(val)) + if len(res.Addrs) != len(expectDeployed) { + t.Fatalf("got %d deployed contracts. expected %d.\n", len(res.Addrs), len(expectDeployed)) + } + for contract, _ := range expectDeployed { + pattern := crypto.Keccak256Hash([]byte(string(contract))).String()[2:36] + if _, ok := res.Addrs[pattern]; !ok { + t.Fatalf("expected contract %s was not deployed\n", string(contract)) } - t.Fatalf("expected %d contracts to be deployed. got %d\n", len(allContracts)-len(overrides), len(res.Addrs)) } // note that the link-and-deploy functionality assumes that the combined-abi is well-formed. @@ -164,19 +162,29 @@ func testLinkCase(t *testing.T, input map[rune][]rune, overrides map[rune]struct } func TestContractLinking(t *testing.T) { - testLinkCase(t, map[rune][]rune{ - 'a': {'b', 'c', 'd', 'e'}, - 'e': {'f', 'g', 'h', 'i'}}, - map[rune]struct{}{}) testLinkCase(t, map[rune][]rune{ 'a': {'b', 'c', 'd', 'e'}}, - map[rune]struct{}{}) + map[rune]struct{}{}, + map[rune]struct{}{ + 'a': {}, 'b': {}, 'c': {}, 'd': {}, 'e': {}, + }) + + testLinkCase(t, map[rune][]rune{ + 'a': {'b', 'c', 'd', 'e'}, + 'e': {'f', 'g', 'h', 'i'}}, + map[rune]struct{}{}, + map[rune]struct{}{ + 'a': {}, 'b': {}, 'c': {}, 'd': {}, 'e': {}, 'f': {}, 'g': {}, 'h': {}, 'i': {}, + }) // test single contract only without deps testLinkCase(t, map[rune][]rune{ 'a': {}}, - map[rune]struct{}{}) + map[rune]struct{}{}, + map[rune]struct{}{ + 'a': {}, + }) // test that libraries at different levels of the tree can share deps, // and that these shared deps will only be deployed once. @@ -184,27 +192,60 @@ func TestContractLinking(t *testing.T) { 'a': {'b', 'c', 'd', 'e'}, 'e': {'f', 'g', 'h', 'i', 'm'}, 'i': {'j', 'k', 'l', 'm'}}, - map[rune]struct{}{}) + map[rune]struct{}{}, + map[rune]struct{}{ + 'a': {}, 'b': {}, 'c': {}, 'd': {}, 'e': {}, 'f': {}, 'g': {}, 'h': {}, 'i': {}, 'j': {}, 'k': {}, 'l': {}, 'm': {}, + }) // test two contracts can be deployed which don't share deps testLinkCase(t, map[rune][]rune{ 'a': {'b', 'c', 'd', 'e'}, 'f': {'g', 'h', 'i', 'j'}}, - map[rune]struct{}{}) + map[rune]struct{}{}, + map[rune]struct{}{ + 'a': {}, 'b': {}, 'c': {}, 'd': {}, 'e': {}, 'f': {}, 'g': {}, 'h': {}, 'i': {}, 'j': {}, + }) // test two contracts can be deployed which share deps testLinkCase(t, map[rune][]rune{ 'a': {'b', 'c', 'd', 'e'}, - 'f': {'g', 'c', 'd', 'j'}}, - map[rune]struct{}{}) + 'f': {'g', 'c', 'd', 'h'}}, + map[rune]struct{}{}, + map[rune]struct{}{ + 'a': {}, 'b': {}, 'c': {}, 'd': {}, 'e': {}, 'f': {}, 'g': {}, 'h': {}, + }) - // test that one contract with overrides for all lib deps + // test one contract with overrides for all lib deps testLinkCase(t, map[rune][]rune{ 'a': {'b', 'c', 'd', 'e'}}, - map[rune]struct{}{'b': {}, 'c': {}, 'd': {}, 'e': {}}) + map[rune]struct{}{'b': {}, 'c': {}, 'd': {}, 'e': {}}, + map[rune]struct{}{ + 'a': {}, + }) + + // test one contract with overrides for some lib deps + testLinkCase(t, map[rune][]rune{ + 'a': {'b', 'c'}}, + map[rune]struct{}{'b': {}, 'c': {}}, + map[rune]struct{}{ + 'a': {}, + }) // test deployment of a contract with overrides testLinkCase(t, map[rune][]rune{ 'a': {}}, - map[rune]struct{}{'a': {}}) + map[rune]struct{}{'a': {}}, + map[rune]struct{}{}) + + // two contracts share some dependencies. one contract is marked as an override. all dependencies for the non-override + // contract will be deployed + testLinkCase(t, map[rune][]rune{ + 'a': {'b', 'c', 'd', 'e'}, + 'f': {'g', 'c', 'd', 'h'}}, + map[rune]struct{}{'a': {}}, + map[rune]struct{}{ + 'f': {}, 'g': {}, 'c': {}, 'd': {}, 'h': {}, + }) + + // TODO: same as the above case but nested one level of dependencies deep (?) } diff --git a/accounts/abi/bind/v2/lib.go b/accounts/abi/bind/v2/lib.go index 15664c38c8..dd7a7b8279 100644 --- a/accounts/abi/bind/v2/lib.go +++ b/accounts/abi/bind/v2/lib.go @@ -17,8 +17,6 @@ package v2 import ( - "encoding/hex" - "fmt" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -29,101 +27,6 @@ import ( "strings" ) -// deployContract deploys a hex-encoded contract with the given constructor -// input. It returns the deployment transaction, address on success. -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 common.Address{}, nil, fmt.Errorf("contract bytecode is not a hex string: %s", contractBinBytes[2:]) - } - addr, tx, err := deploy(constructor, contractBinBytes) - if err != nil { - return common.Address{}, nil, fmt.Errorf("failed to deploy contract: %v", err) - } - 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(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 { - contractDeployer, err := hex.DecodeString(contractBin[2:]) - if err != nil { - return deploymentTxs, deployAddrs, fmt.Errorf("contract bytecode is not a hex string: %s", contractBin[2:]) - } - addr, tx, err := deploy([]byte{}, contractDeployer) - if err != nil { - return deploymentTxs, deployAddrs, fmt.Errorf("failed to deploy contract: %v", err) - } - deploymentTxs[addr] = tx - deployAddrs[pattern] = addr - } - - return deploymentTxs, deployAddrs, nil -} - -// linkContract takes an unlinked contract deployer hex-encoded code, a map of -// already-deployed library dependencies, replaces references to deployed library -// dependencies in the contract code, and returns the contract deployment bytecode on -// success. -func linkContract(contract string, linkedLibs map[string]common.Address) (deployableContract string, err error) { - reMatchSpecificPattern, err := regexp.Compile("__\\$([a-f0-9]+)\\$__") - if err != nil { - return "", err - } - // link in any library the contract depends on - for _, match := range reMatchSpecificPattern.FindAllStringSubmatch(contract, -1) { - matchingPattern := match[1] - addr := linkedLibs[matchingPattern] - contract = strings.ReplaceAll(contract, "__$"+matchingPattern+"$__", addr.String()[2:]) - } - return contract, nil -} - -// linkLibs iterates the set of dependencies that have yet to be -// linked/deployed (pending), replacing references to library dependencies -// (i.e. mutating pending) if those dependencies are fully linked/deployed -// (in 'linked'). -// -// contracts that have become fully linked in the current invocation are -// returned. -func linkLibs(pending map[string]string, linked map[string]common.Address) (newPending map[string]string, deployableDeps map[string]string) { - newPending = make(map[string]string) - reMatchSpecificPattern, err := regexp.Compile("__\\$([a-f0-9]+)\\$__") - if err != nil { - panic(err) - } - reMatchAnyPattern, err := regexp.Compile("__\\$.*\\$__") - if err != nil { - panic(err) - } - deployableDeps = make(map[string]string) - - for pattern, dep := range pending { - newPending[pattern] = dep - // link references to dependent libraries that have been deployed - for _, match := range reMatchSpecificPattern.FindAllStringSubmatch(newPending[pattern], -1) { - matchingPattern := match[1] - addr, ok := linked[matchingPattern] - if !ok { - continue - } - - newPending[pattern] = strings.ReplaceAll(newPending[pattern], "__$"+matchingPattern+"$__", addr.String()[2:]) - } - // if the library code became fully linked, move it from pending->linked. - if !reMatchAnyPattern.MatchString(newPending[pattern]) { - deployableDeps[pattern] = newPending[pattern] - delete(newPending, pattern) - } - } - return newPending, deployableDeps -} - // ContractDeployParams represents state needed to deploy a contract: // the metdata and constructor input (which can be nil if no input is specified). type ContractDeployParams struct { @@ -172,6 +75,7 @@ type ContractDeployer interface { type depTreeBuilder struct { overrides map[string]common.Address + libs map[string]string } type depTreeNode struct { pattern string @@ -179,8 +83,8 @@ type depTreeNode struct { nodes []*depTreeNode } -func (d *depTreeBuilder) buildDepTree(contractBin string, libs map[string]string) *depTreeNode { - node := depTreeNode{contractBin, contractBin, nil} +func (d *depTreeBuilder) buildDepTree(pattern string, contractBin string) *depTreeNode { + node := depTreeNode{pattern, contractBin, nil} reMatchSpecificPattern, err := regexp.Compile("__\\$([a-f0-9]+)\\$__") if err != nil { @@ -191,7 +95,7 @@ func (d *depTreeBuilder) buildDepTree(contractBin string, libs map[string]string if _, ok := d.overrides[pattern]; ok { continue } - node.nodes = append(node.nodes, d.buildDepTree(libs[pattern], libs)) + node.nodes = append(node.nodes, d.buildDepTree(pattern, d.libs[pattern])) } return &node } @@ -247,9 +151,15 @@ func LinkAndDeploy(deployParams DeploymentParams, deploy func(input, deployer [] Addrs: make(map[string]common.Address), } for _, contract := range deployParams.Contracts { - treeBuilder := depTreeBuilder{deployParams.Overrides} - tree := treeBuilder.buildDepTree(contract.Meta.Bin, unlinkedLibs) - deployer := treeDeployer{deploy: deploy} + if _, ok := deployParams.Overrides[contract.Meta.Pattern]; ok { + continue + } + treeBuilder := depTreeBuilder{deployParams.Overrides, unlinkedLibs} + tree := treeBuilder.buildDepTree(contract.Meta.Pattern, contract.Meta.Bin) + deployer := treeDeployer{ + deploy: deploy, + deployedAddrs: make(map[string]common.Address), + deployerTxs: make(map[string]*types.Transaction)} deployer.linkAndDeploy(tree) res, err := deployer.Result() if err != nil {