diff --git a/accounts/abi/bind/v2/contract_linking_test.go b/accounts/abi/bind/v2/contract_linking_test.go index ab102e12bd..81266800dc 100644 --- a/accounts/abi/bind/v2/contract_linking_test.go +++ b/accounts/abi/bind/v2/contract_linking_test.go @@ -120,17 +120,13 @@ func testLinkCase(t *testing.T, tcInput linkTestCaseInput) { } var ( - contracts []ContractDeployParams - libs []*bind.MetaData + deployParams DeploymentParams ) for pattern, bin := range tc.contractCodes { - contracts = append(contracts, ContractDeployParams{ - Meta: &bind.MetaData{Pattern: pattern, Bin: "0x" + bin}, - Input: nil, - }) + deployParams.Contracts = append(deployParams.Contracts, &bind.MetaData{Pattern: pattern, Bin: "0x" + bin}) } for pattern, bin := range tc.libCodes { - libs = append(libs, &bind.MetaData{ + deployParams.Contracts = append(deployParams.Contracts, &bind.MetaData{ Bin: "0x" + bin, Pattern: pattern, }) @@ -140,11 +136,7 @@ func testLinkCase(t *testing.T, tcInput linkTestCaseInput) { for pattern, override := range tc.overrides { overridePatterns[pattern] = override } - deployParams := DeploymentParams{ - Contracts: contracts, - Libraries: libs, - Overrides: overridePatterns, - } + deployParams.Overrides = overridePatterns res, err := LinkAndDeploy(deployParams, mockDeploy) if err != nil { @@ -160,11 +152,6 @@ func testLinkCase(t *testing.T, tcInput linkTestCaseInput) { t.Fatalf("expected contract %s was not deployed\n", string(contract)) } } - - // note that the link-and-deploy functionality assumes that the combined-abi is well-formed. - - // test-case ideas: - // * libraries that are disjount from the rest of dep graph (they don't get deployed) } func TestContractLinking(t *testing.T) { @@ -263,7 +250,7 @@ func TestContractLinking(t *testing.T) { 'f': {}, 'g': {}, 'c': {}, 'd': {}, 'h': {}, }}) - // test nested libraries that share deps at different levels of the tree.. with override. + // test nested libraries that share deps at different levels of the tree... with override. testLinkCase(t, linkTestCaseInput{ map[rune][]rune{ 'a': {'b', 'c', 'd', 'e'}, diff --git a/accounts/abi/bind/v2/lib.go b/accounts/abi/bind/v2/lib.go index dd7a7b8279..b3a064ba24 100644 --- a/accounts/abi/bind/v2/lib.go +++ b/accounts/abi/bind/v2/lib.go @@ -39,12 +39,8 @@ type ContractDeployParams struct { // set of contracts, their dependency libraries. It takes an optional override // list to specify libraries that have already been deployed on-chain. type DeploymentParams struct { - // Contracts is the set of contract deployment parameters for contracts - // that are about to be deployed. - Contracts []ContractDeployParams - // Libraries is a map of pattern to metadata for library contracts that - // are to be deployed. - Libraries []*bind.MetaData + Contracts []*bind.MetaData + Inputs map[string][]byte // Overrides is an optional map of pattern to deployment address. // Contracts/libraries that refer to dependencies in the override // set are linked to the provided address (an already-deployed contract). @@ -69,13 +65,15 @@ func (d *DeploymentResult) Accumulate(other *DeploymentResult) { } } -type ContractDeployer interface { - DeployContract(input []byte, deployer []byte) (common.Address, *types.Transaction, error) -} - type depTreeBuilder struct { overrides map[string]common.Address - libs map[string]string + // map of pattern to unlinked contract bytecode (for libraries or contracts) + contracts map[string]string + // map of pattern to subtree represented by contract + subtrees map[string]*depTreeNode + + // map of nodes that aren't referenced by other dependencies (these can be libraries too if user is doing lib-only deployment) + roots map[string]struct{} } type depTreeNode struct { pattern string @@ -83,27 +81,58 @@ type depTreeNode struct { nodes []*depTreeNode } -func (d *depTreeBuilder) buildDepTree(pattern string, contractBin string) *depTreeNode { - node := depTreeNode{pattern, contractBin, nil} - +func (d *depTreeBuilder) buildDepTrees(pattern, contract string) { 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 { + node := &depTreeNode{ + pattern: pattern, + unlinkedCode: contract, + } + for _, match := range reMatchSpecificPattern.FindAllStringSubmatch(contract, -1) { + depPattern := match[1] + if _, ok := d.subtrees[depPattern]; ok { continue } - node.nodes = append(node.nodes, d.buildDepTree(pattern, d.libs[pattern])) + delete(d.roots, depPattern) + d.buildDepTrees(depPattern, d.contracts[depPattern]) + node.nodes = append(node.nodes, d.subtrees[depPattern]) } - return &node + d.subtrees[pattern] = node +} + +func (d *depTreeBuilder) BuildDepTrees() (roots []*depTreeNode) { + for pattern, _ := range d.contracts { + d.roots[pattern] = struct{}{} + } + for pattern, contract := range d.contracts { + if _, ok := d.subtrees[pattern]; ok { + continue + } + reMatchSpecificPattern, err := regexp.Compile("__\\$([a-f0-9]+)\\$__") + if err != nil { + panic(err) + } + for _, match := range reMatchSpecificPattern.FindAllStringSubmatch(contract, -1) { + depPattern := match[1] + delete(d.roots, depPattern) + if _, ok := d.subtrees[depPattern]; ok { + continue + } + d.buildDepTrees(depPattern, d.contracts[depPattern]) + } + } + for pattern, _ := range d.roots { + roots = append(roots, d.subtrees[pattern]) + } + return roots } type treeDeployer struct { deployedAddrs map[string]common.Address deployerTxs map[string]*types.Transaction - inputs map[string][]byte + input map[string][]byte // map of the root contract pattern to the constructor input (if there is any) deploy func(input, deployer []byte) (common.Address, *types.Transaction, error) err error } @@ -114,11 +143,13 @@ func (d *treeDeployer) linkAndDeploy(node *depTreeNode) { } // 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:]) + deployerCode = strings.ReplaceAll(deployerCode, "__$"+child.pattern+"$__", strings.ToLower(d.deployedAddrs[child.pattern].String()[2:])) } + // deploy the contract. - addr, tx, err := d.deploy(d.inputs[node.pattern], common.Hex2Bytes(deployerCode)) + addr, tx, err := d.deploy(d.input[node.pattern], common.Hex2Bytes(deployerCode)) if err != nil { d.err = err } else { @@ -141,32 +172,34 @@ func (d *treeDeployer) Result() (*DeploymentResult, error) { // 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) { - // 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 - } + unlinkedContracts := make(map[string]string) accumRes := &DeploymentResult{ Txs: make(map[string]*types.Transaction), Addrs: make(map[string]common.Address), } - for _, contract := range deployParams.Contracts { - if _, ok := deployParams.Overrides[contract.Meta.Pattern]; ok { - continue - } - treeBuilder := depTreeBuilder{deployParams.Overrides, unlinkedLibs} - tree := treeBuilder.buildDepTree(contract.Meta.Pattern, contract.Meta.Bin) + for _, meta := range deployParams.Contracts { + unlinkedContracts[meta.Pattern] = meta.Bin + } + // TODO: instantiate this using constructor + treeBuilder := depTreeBuilder{ + overrides: deployParams.Overrides, + contracts: unlinkedContracts, + } + + deps := treeBuilder.BuildDepTrees() + for _, tr := range deps { + // TODO: instantiate deployer with its tree? deployer := treeDeployer{ deploy: deploy, deployedAddrs: make(map[string]common.Address), - deployerTxs: make(map[string]*types.Transaction)} - deployer.linkAndDeploy(tree) + deployerTxs: make(map[string]*types.Transaction), + input: map[string][]byte{tr.pattern: deployParams.Inputs[tr.pattern]}} + deployer.linkAndDeploy(tr) res, err := deployer.Result() if err != nil { return accumRes, err } accumRes.Accumulate(res) - } return accumRes, nil } diff --git a/accounts/abi/bind/v2/lib_test.go b/accounts/abi/bind/v2/lib_test.go index f621762ccf..27b346f73d 100644 --- a/accounts/abi/bind/v2/lib_test.go +++ b/accounts/abi/bind/v2/lib_test.go @@ -122,13 +122,8 @@ func TestDeploymentLibraries(t *testing.T) { t.Fatalf("failed to pack constructor: %v", err) } deploymentParams := DeploymentParams{ - Contracts: []ContractDeployParams{ - { - Meta: nested_libraries.C1MetaData, - Input: constructorInput, - }, - }, - Libraries: nested_libraries.C1LibraryDeps, + Contracts: append(nested_libraries.C1LibraryDeps, nested_libraries.C1MetaData), + Inputs: map[string][]byte{nested_libraries.C1MetaData.Pattern: constructorInput}, Overrides: nil, } @@ -186,7 +181,7 @@ func TestDeploymentWithOverrides(t *testing.T) { // deploy some library deps deploymentParams := DeploymentParams{ - Libraries: nested_libraries.C1LibraryDeps, + Contracts: nested_libraries.C1LibraryDeps, } res, err := LinkAndDeploy(deploymentParams, makeTestDeployer(opts, bindBackend)) @@ -216,13 +211,8 @@ func TestDeploymentWithOverrides(t *testing.T) { overrides := res.Addrs // deploy the contract deploymentParams = DeploymentParams{ - Contracts: []ContractDeployParams{ - { - Meta: nested_libraries.C1MetaData, - Input: constructorInput, - }, - }, - Libraries: nil, + Contracts: []*bind.MetaData{nested_libraries.C1MetaData}, + Inputs: map[string][]byte{nested_libraries.C1MetaData.Pattern: constructorInput}, Overrides: overrides, } res, err = LinkAndDeploy(deploymentParams, makeTestDeployer(opts, bindBackend)) @@ -281,11 +271,7 @@ func TestEvents(t *testing.T) { } deploymentParams := DeploymentParams{ - Contracts: []ContractDeployParams{ - { - Meta: events.CMetaData, - }, - }, + Contracts: []*bind.MetaData{events.CMetaData}, } res, err := LinkAndDeploy(deploymentParams, makeTestDeployer(txAuth, backend))