diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index 69e3b94bb3..54f4b7989e 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -95,6 +95,7 @@ type MetaData struct { ABI string ab *abi.ABI Pattern string + Deps []*MetaData } func (m *MetaData) GetAbi() (*abi.ABI, error) { diff --git a/accounts/abi/bind/bindv2.go b/accounts/abi/bind/bindv2.go index 72095934e7..66f4391f78 100644 --- a/accounts/abi/bind/bindv2.go +++ b/accounts/abi/bind/bindv2.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/ethereum/go-ethereum/accounts/abi" "go/format" + "regexp" "strings" "text/template" "unicode" @@ -190,6 +191,17 @@ func (cb *contractBinder) bindError(original abi.Error) error { return nil } +func parseLibraryDeps(unlinkedCode string) (res []string) { + reMatchSpecificPattern, err := regexp.Compile(`__\$([a-f0-9]+)\$__`) + if err != nil { + panic(err) + } + for _, match := range reMatchSpecificPattern.FindAllStringSubmatch(unlinkedCode, -1) { + res = append(res, match[1]) + } + return res +} + // BindV2 generates a Go wrapper around a contract ABI. This wrapper isn't meant // to be used as is in client code, but rather as an intermediate struct which // enforces compile time type safety and naming convention as opposed to having to @@ -271,23 +283,9 @@ func BindV2(types []string, abis []string, bytecodes []string, pkg string, libs Structs: b.structs, } - contractsBins := make(map[string]string) for typ, contract := range data.Contracts { - pattern := invertedLibs[typ] - contractsBins[pattern] = contract.InputBin - } - builder := newDepTreeBuilder(nil, contractsBins) - roots, deps := builder.BuildDepTrees() - allNodes := append(roots, deps...) - for _, dep := range allNodes { - contractType := libs[dep.pattern] - for subDepPattern, _ := range dep.Flatten() { - if subDepPattern == dep.pattern { - // don't include the dep as a dependency of itself - continue - } - subDepType := libs[subDepPattern] - data.Contracts[contractType].Libraries[subDepType] = subDepPattern + for _, depPattern := range parseLibraryDeps(contract.InputBin) { + data.Contracts[typ].Libraries[libs[depPattern]] = depPattern } } buffer := new(bytes.Buffer) diff --git a/accounts/abi/bind/dep_tree.go b/accounts/abi/bind/dep_tree.go index 7bc3d39621..072e716368 100644 --- a/accounts/abi/bind/dep_tree.go +++ b/accounts/abi/bind/dep_tree.go @@ -3,7 +3,7 @@ package bind import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" - "regexp" + "maps" "strings" ) @@ -45,100 +45,6 @@ func (d *DeploymentResult) Accumulate(other *DeploymentResult) { maps.Copy(d.Addrs, other.Addrs) } -// depTreeBuilder turns a set of unlinked contracts libraries into a set of one -// or more dependency trees. -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 -} - -// Flatten returns the subtree into a map of pattern -> unlinked contract bytecode. -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 -} - -// buildDepTrees is the internal version of BuildDepTrees that recursively calls itself. -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 build 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. It returns nodes that are roots of a dependency tree and nodes that aren't. -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{}{} - } - 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{}), - } -} - // DeployFn deploys a contract given a deployer and optional input. It returns // the address and a pending transaction, or an error if the deployment failed. type DeployFn func(input, deployer []byte) (common.Address, *types.Transaction, error) @@ -154,37 +60,34 @@ type depTreeDeployer struct { // linkAndDeploy recursively deploys a contract and its dependencies: 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) error { +func (d *depTreeDeployer) linkAndDeploy(metadata *MetaData) error { // don't deploy contracts specified as overrides. don't deploy their dependencies. - if node.overrideAddr != nil { + if _, ok := d.deployedAddrs[metadata.Pattern]; ok { return nil } // if this contract/library depends on other libraries deploy them (and their dependencies) first - for _, childNode := range node.children { - if err := d.linkAndDeploy(childNode); err != nil { + for _, dep := range metadata.Deps { + if err := d.linkAndDeploy(dep); err != nil { return err } } // 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 { - linkAddr := d.deployedAddrs[child.pattern] - if child.overrideAddr != nil { - linkAddr = *child.overrideAddr - } - deployerCode = strings.ReplaceAll(deployerCode, "__$"+child.pattern+"$__", strings.ToLower(linkAddr.String()[2:])) + deployerCode := metadata.Bin + for _, dep := range metadata.Deps { + linkAddr := d.deployedAddrs[dep.Pattern] + deployerCode = strings.ReplaceAll(deployerCode, "__$"+dep.Pattern+"$__", strings.ToLower(linkAddr.String()[2:])) } // Finally, deploy the contract. - addr, tx, err := d.deploy(d.input[node.pattern], common.Hex2Bytes(deployerCode)) + addr, tx, err := d.deploy(d.input[metadata.Pattern], common.Hex2Bytes(deployerCode)) if err != nil { return err } - d.deployedAddrs[node.pattern] = addr - d.deployerTxs[node.pattern] = tx + d.deployedAddrs[metadata.Pattern] = addr + d.deployerTxs[metadata.Pattern] = tx return nil } @@ -207,23 +110,16 @@ func newDepTreeDeployer(deploy DeployFn) *depTreeDeployer { // 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) + deployer := newDepTreeDeployer(deploy) + for _, contract := range deployParams.contracts { if deployParams.inputs != nil { - deployer.input = map[string][]byte{tr.pattern: deployParams.inputs[tr.pattern]} + deployer.input = map[string][]byte{contract.Pattern: deployParams.inputs[contract.Pattern]} } - err := deployer.linkAndDeploy(tr) + err := deployer.linkAndDeploy(contract) res := deployer.result() accumRes.Accumulate(res) if err != nil { diff --git a/accounts/abi/bind/dep_tree_test.go b/accounts/abi/bind/dep_tree_test.go index 87390bb033..12f4c87f6a 100644 --- a/accounts/abi/bind/dep_tree_test.go +++ b/accounts/abi/bind/dep_tree_test.go @@ -94,6 +94,36 @@ type linkTestCaseInput struct { expectDeployed map[rune]struct{} } +// linkDeps will return a set of root dependencies and their sub-dependencies connected via the Deps field +func linkDeps(deps map[string]*MetaData) []*MetaData { + roots := make(map[string]struct{}) + for pattern, _ := range deps { + roots[pattern] = struct{}{} + } + + connectedDeps := make(map[string]MetaData) + for pattern, dep := range deps { + connectedDeps[pattern] = __linkDeps(*dep, deps, &roots) + } + rootMetadatas := []*MetaData{} + for pattern, _ := range roots { + dep := connectedDeps[pattern] + rootMetadatas = append(rootMetadatas, &dep) + } + return rootMetadatas +} + +func __linkDeps(metadata MetaData, depMap map[string]*MetaData, roots *map[string]struct{}) MetaData { + linked := metadata + depPatterns := parseLibraryDeps(metadata.Bin) + for _, pattern := range depPatterns { + delete(*roots, pattern) + connectedDep := __linkDeps(*depMap[pattern], depMap, roots) + linked.Deps = append(linked.Deps, &connectedDep) + } + return linked +} + func testLinkCase(t *testing.T, tcInput linkTestCaseInput) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) var testAddrNonce uint64 @@ -138,24 +168,26 @@ func testLinkCase(t *testing.T, tcInput linkTestCaseInput) { return contractAddr, nil, nil } - var contracts []*MetaData + var contracts map[string]*MetaData overrides := make(map[string]common.Address) for pattern, bin := range tc.contractCodes { - contracts = append(contracts, &MetaData{Pattern: pattern, Bin: "0x" + bin}) + contracts[pattern] = &MetaData{Pattern: pattern, Bin: "0x" + bin} } for pattern, bin := range tc.libCodes { - contracts = append(contracts, &MetaData{ + contracts[pattern] = &MetaData{ Bin: "0x" + bin, Pattern: pattern, - }) + } } + contractsList := linkDeps(contracts) + for pattern, override := range tc.overrides { overrides[pattern] = override } - deployParams := NewDeploymentParams(contracts, nil, overrides) + deployParams := NewDeploymentParams(contractsList, nil, overrides) res, err := LinkAndDeploy(deployParams, mockDeploy) if err != nil { t.Fatalf("got error from LinkAndDeploy: %v\n", err) diff --git a/accounts/abi/bind/source2.go.tpl b/accounts/abi/bind/source2.go.tpl index 89e4673def..7b60484189 100644 --- a/accounts/abi/bind/source2.go.tpl +++ b/accounts/abi/bind/source2.go.tpl @@ -33,12 +33,6 @@ var ( {{end}} {{range $contract := .Contracts}} - var {{$contract.Type}}LibraryDeps = []*bind.MetaData{ - {{range $name, $pattern := .Libraries -}} - {{$name}}MetaData, - {{ end}} - } - // TODO: convert this type to value type after everything works. // {{.Type}}MetaData contains all meta data concerning the {{.Type}} contract. var {{.Type}}MetaData = &bind.MetaData{ @@ -47,6 +41,9 @@ var ( {{if .InputBin -}} Bin: "0x{{.InputBin}}", {{end}} + {{if .Libraries -}}{{range $name, $pattern := .Libraries}} + {{$name}}MetaData, + {{end}}{{end}} } // {{.Type}} is an auto generated Go binding around an Ethereum contract.