package bind import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "regexp" "strings" ) // 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 { Meta *MetaData // Input is the ABI-encoded constructor input for the contract deployment. Input []byte } // DeploymentParams represents parameters needed to deploy a // 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 []*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). Overrides map[string]common.Address } // DeploymentResult contains the relevant information from the deployment of // multiple contracts: their deployment txs and addresses. type DeploymentResult struct { // map of contract library pattern -> deploy transaction Txs map[string]*types.Transaction // map of contract library pattern -> deployed address 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 } } // depTreeBuilder turns a set of unlinked contracts and their dependent libraries into a collection of trees // representing the relation of their dependencies. type depTreeBuilder struct { overrides map[string]common.Address // 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{} } // depTreeNode represents a node (contract) in a dependency tree. it contains its unlinked code, and references to any // library contracts that it requires. If it is specified as an override, it contains the address where it has already // been deployed at. type depTreeNode struct { pattern string unlinkedCode string children []*depTreeNode overrideAddr *common.Address } // returns the subtree as a map of pattern -> unlinked contract bytecode. it excludes the code of the top-level // node. func (n *depTreeNode) Flatten() (res map[string]string) { res = map[string]string{n.pattern: n.unlinkedCode} for _, child := range n.children { subtree := child.Flatten() for k, v := range subtree { res[k] = v } } return res } func (d *depTreeBuilder) buildDepTrees(pattern, contract string) { // if the node is in the subtree set already, it has already been fully recursed/built so we can bail out. if _, ok := d.subtrees[pattern]; ok { return } node := &depTreeNode{ pattern: pattern, unlinkedCode: contract, } if addr, ok := d.overrides[pattern]; ok { node.overrideAddr = &addr } // iterate each referenced library in the unlinked code, recurse and built its subtree. reMatchSpecificPattern, err := regexp.Compile(`__\$([a-f0-9]+)\$__`) if err != nil { panic(err) } for _, match := range reMatchSpecificPattern.FindAllStringSubmatch(contract, -1) { depPattern := match[1] d.buildDepTrees(depPattern, d.contracts[depPattern]) node.children = append(node.children, d.subtrees[depPattern]) // this library can't be a root dependency if it is referenced by other contracts. delete(d.roots, depPattern) } d.subtrees[pattern] = node } // BuildDepTrees will compute a set of dependency trees from a set of unlinked contracts. The root of each tree // corresponds to a contract/library that is not referenced as a dependency anywhere else. Children of each node are // its library dependencies. func (d *depTreeBuilder) BuildDepTrees() (roots []*depTreeNode, nonRoots []*depTreeNode) { // before the trees of dependencies are known, consider that any provided contract could be a root. for pattern, _ := range d.contracts { d.roots[pattern] = struct{}{} } // recursively build each part of the dependency subtree by starting at for pattern, contract := range d.contracts { d.buildDepTrees(pattern, contract) } for pattern, _ := range d.contracts { if _, ok := d.roots[pattern]; ok { roots = append(roots, d.subtrees[pattern]) } else { nonRoots = append(nonRoots, d.subtrees[pattern]) } } return roots, nonRoots } func newDepTreeBuilder(overrides map[string]common.Address, contracts map[string]string) *depTreeBuilder { return &depTreeBuilder{ overrides: overrides, contracts: contracts, subtrees: make(map[string]*depTreeNode), roots: make(map[string]struct{}), } } type deployFn func(input, deployer []byte) (common.Address, *types.Transaction, error) // depTreeDeployer is responsible for taking a dependency, deploying-and-linking its components in the proper // order. A depTreeDeployer cannot be used after calling LinkAndDeploy other than to retrieve the deployment result. type depTreeDeployer struct { deployedAddrs map[string]common.Address deployerTxs map[string]*types.Transaction input map[string][]byte // map of the root contract pattern to the constructor input (if there is any) deploy deployFn err error } // linkAndDeploy recursively deploys a contract/library: starting by linking/deploying its dependencies. // The deployment result (deploy addresses/txs or an error) is stored in the depTreeDeployer object. func (d *depTreeDeployer) linkAndDeploy(node *depTreeNode) { // short-circuit further deployment of contracts if a previous deployment encountered an error. if d.err != nil { return } // don't deploy contracts specified as overrides. don't deploy their dependencies. if node.overrideAddr != nil { return } // if this contract/library depends on other libraries deploy them (and their dependencies) first for _, childNode := range node.children { d.linkAndDeploy(childNode) } // if we just deployed any prerequisite contracts, link their deployed addresses into the bytecode to produce // a deployer bytecode for this contract. deployerCode := node.unlinkedCode for _, child := range node.children { var linkAddr common.Address if child.overrideAddr != nil { linkAddr = *child.overrideAddr } else { linkAddr = d.deployedAddrs[child.pattern] } deployerCode = strings.ReplaceAll(deployerCode, "__$"+child.pattern+"$__", strings.ToLower(linkAddr.String()[2:])) } // Finally, deploy the contract. addr, tx, err := d.deploy(d.input[node.pattern], common.Hex2Bytes(deployerCode)) if err != nil { d.err = err } else { d.deployedAddrs[node.pattern] = addr d.deployerTxs[node.pattern] = tx } } // result returns a result for this deployment, or an error if it failed. func (d *depTreeDeployer) result() (*DeploymentResult, error) { if d.err != nil { return nil, d.err } return &DeploymentResult{ Txs: d.deployerTxs, Addrs: d.deployedAddrs, }, nil } func newDepTreeDeployer(deploy deployFn) *depTreeDeployer { return &depTreeDeployer{ deploy: deploy, deployedAddrs: make(map[string]common.Address), deployerTxs: make(map[string]*types.Transaction)} } // 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 deployFn) (res *DeploymentResult, err error) { unlinkedContracts := make(map[string]string) accumRes := &DeploymentResult{ Txs: make(map[string]*types.Transaction), Addrs: make(map[string]common.Address), } for _, meta := range deployParams.Contracts { unlinkedContracts[meta.Pattern] = meta.Bin[2:] } treeBuilder := newDepTreeBuilder(deployParams.Overrides, unlinkedContracts) deps, _ := treeBuilder.BuildDepTrees() for _, tr := range deps { deployer := newDepTreeDeployer(deploy) if deployParams.Inputs != nil { deployer.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 }