diff --git a/accounts/abi/bind/v2/contract_linking_test.go b/accounts/abi/bind/v2/contract_linking_test.go index 517afbaa10..af22b1998f 100644 --- a/accounts/abi/bind/v2/contract_linking_test.go +++ b/accounts/abi/bind/v2/contract_linking_test.go @@ -6,6 +6,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "golang.org/x/exp/rand" "testing" ) @@ -70,12 +71,22 @@ func makeLinkTestCase(input map[rune][]rune, overrides map[rune]common.Address) } } -func testLinkCase(t *testing.T, input map[rune][]rune, overrides map[rune]common.Address) { +func testLinkCase(t *testing.T, input map[rune][]rune, overrides map[rune]struct{}) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) var testAddrNonce uint64 - - tc := makeLinkTestCase(input, overrides) alreadyDeployed := make(map[common.Address]struct{}) + + // generate deterministic addresses for the override set. + rand.Seed(42) + overrideAddrs := make(map[rune]common.Address) + for contract, _ := range overrides { + var addr common.Address + rand.Read(addr[:]) + overrideAddrs[contract] = addr + alreadyDeployed[addr] = struct{}{} + } + + tc := makeLinkTestCase(input, overrideAddrs) allContracts := make(map[rune]struct{}) for contract, deps := range input { @@ -121,10 +132,15 @@ func testLinkCase(t *testing.T, input map[rune][]rune, overrides map[rune]common Pattern: pattern, }) } + + overridePatterns := make(map[string]common.Address) + for pattern, override := range tc.overrides { + overridePatterns[pattern] = override + } deployParams := DeploymentParams{ Contracts: contracts, Libraries: libs, - Overrides: nil, + Overrides: overridePatterns, } res, err := LinkAndDeploy(deployParams, mockDeploy) @@ -151,16 +167,16 @@ func TestContractLinking(t *testing.T) { testLinkCase(t, map[rune][]rune{ 'a': {'b', 'c', 'd', 'e'}, 'e': {'f', 'g', 'h', 'i'}}, - map[rune]common.Address{}) + map[rune]struct{}{}) testLinkCase(t, map[rune][]rune{ 'a': {'b', 'c', 'd', 'e'}}, - map[rune]common.Address{}) + map[rune]struct{}{}) // test single contract only without deps testLinkCase(t, map[rune][]rune{ 'a': {}}, - map[rune]common.Address{}) + map[rune]struct{}{}) // test that libraries at different levels of the tree can share deps, // and that these shared deps will only be deployed once. @@ -168,17 +184,27 @@ func TestContractLinking(t *testing.T) { 'a': {'b', 'c', 'd', 'e'}, 'e': {'f', 'g', 'h', 'i', 'm'}, 'i': {'j', 'k', 'l', 'm'}}, - map[rune]common.Address{}) + map[rune]struct{}{}) // 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]common.Address{}) + map[rune]struct{}{}) // 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]common.Address{}) + map[rune]struct{}{}) + + // test that 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': {}}) + + // test deployment of a contract with overrides + testLinkCase(t, map[rune][]rune{ + 'a': {}}, + map[rune]struct{}{'a': {}}) } diff --git a/accounts/abi/bind/v2/lib.go b/accounts/abi/bind/v2/lib.go index 0db3fd3191..15664c38c8 100644 --- a/accounts/abi/bind/v2/lib.go +++ b/accounts/abi/bind/v2/lib.go @@ -157,71 +157,108 @@ type DeploymentResult struct { Addrs map[string]common.Address } +func (d *DeploymentResult) Accumulate(other *DeploymentResult) { + for pattern, tx := range other.Txs { + d.Txs[pattern] = tx + } + for pattern, addr := range other.Addrs { + d.Addrs[pattern] = addr + } +} + type ContractDeployer interface { DeployContract(input []byte, deployer []byte) (common.Address, *types.Transaction, error) } +type depTreeBuilder struct { + overrides map[string]common.Address +} +type depTreeNode struct { + pattern string + unlinkedCode string + nodes []*depTreeNode +} + +func (d *depTreeBuilder) buildDepTree(contractBin string, libs map[string]string) *depTreeNode { + node := depTreeNode{contractBin, contractBin, nil} + + reMatchSpecificPattern, err := regexp.Compile("__\\$([a-f0-9]+)\\$__") + if err != nil { + panic(err) + } + for _, match := range reMatchSpecificPattern.FindAllStringSubmatch(contractBin, -1) { + pattern := match[1] + if _, ok := d.overrides[pattern]; ok { + continue + } + node.nodes = append(node.nodes, d.buildDepTree(libs[pattern], libs)) + } + return &node +} + +type treeDeployer struct { + deployedAddrs map[string]common.Address + deployerTxs map[string]*types.Transaction + inputs map[string][]byte + deploy func(input, deployer []byte) (common.Address, *types.Transaction, error) + err error +} + +func (d *treeDeployer) linkAndDeploy(node *depTreeNode) { + for _, childNode := range node.nodes { + d.linkAndDeploy(childNode) + } + // link in all node dependencies and produce the deployer bytecode + deployerCode := node.unlinkedCode + for _, child := range node.nodes { + deployerCode = strings.ReplaceAll(deployerCode, child.pattern, d.deployedAddrs[child.pattern].String()[2:]) + } + // deploy the contract. + addr, tx, err := d.deploy(d.inputs[node.pattern], common.Hex2Bytes(deployerCode)) + if err != nil { + d.err = err + } else { + d.deployedAddrs[node.pattern] = addr + d.deployerTxs[node.pattern] = tx + } +} + +func (d *treeDeployer) Result() (*DeploymentResult, error) { + if d.err != nil { + return nil, d.err + } + return &DeploymentResult{ + Txs: d.deployerTxs, + Addrs: d.deployedAddrs, + }, nil +} + // 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(deployParams DeploymentParams, deploy func(input, deployer []byte) (common.Address, *types.Transaction, error)) (res *DeploymentResult, err error) { - libMetas := deployParams.Libraries - overrides := deployParams.Overrides - - res = &DeploymentResult{ + // re-express libraries as a map of pattern -> pre-link binary + unlinkedLibs := make(map[string]string) + for _, meta := range deployParams.Libraries { + unlinkedLibs[meta.Pattern] = meta.Bin + } + accumRes := &DeploymentResult{ Txs: make(map[string]*types.Transaction), Addrs: make(map[string]common.Address), } - - // re-express libraries as a map of pattern -> pre-link binary - pending := make(map[string]string) - for _, meta := range libMetas { - pending[meta.Pattern] = meta.Bin - } - - // initialize the set of already-deployed contracts with given override addresses - deployed := make(map[string]common.Address) - for pattern, deployAddr := range overrides { - deployed[pattern] = deployAddr - if _, ok := pending[pattern]; ok { - delete(pending, pattern) - } - } - - // link and deploy dynamic libraries - for { - var deployableDeps map[string]string - pending, deployableDeps = linkLibs(pending, deployed) - if len(deployableDeps) == 0 { - break - } - - deployTxs, deployAddrs, err := deployLibs(deployableDeps, deploy) - for pattern, addr := range deployAddrs { - deployed[pattern] = addr - res.Addrs[pattern] = addr - res.Txs[pattern] = deployTxs[addr] - } + for _, contract := range deployParams.Contracts { + treeBuilder := depTreeBuilder{deployParams.Overrides} + tree := treeBuilder.buildDepTree(contract.Meta.Bin, unlinkedLibs) + deployer := treeDeployer{deploy: deploy} + deployer.linkAndDeploy(tree) + res, err := deployer.Result() if err != nil { - return res, err + return accumRes, err } - } + accumRes.Accumulate(res) - // link and deploy contracts - for _, contractParams := range deployParams.Contracts { - linkedContract, err := linkContract(contractParams.Meta.Bin, deployed) - if err != nil { - return res, err - } - contractAddr, contractTx, err := deployContract(contractParams.Input, linkedContract, deploy) - if err != nil { - return res, err - } - res.Txs[contractParams.Meta.Pattern] = contractTx - res.Addrs[contractParams.Meta.Pattern] = contractAddr } - - return res, nil + return accumRes, nil } // TODO: this will be generated as part of the bindings, contain the ABI (or metadata object?) and errors