Skip to content

Commit

Permalink
move contract dep tree, link-and-deploy to bind package. This is so t…
Browse files Browse the repository at this point in the history
…hat bind can build the dependency tree for each library to populate their exported deps.
  • Loading branch information
jwasinger committed Dec 15, 2024
1 parent 68f4019 commit e140c3d
Show file tree
Hide file tree
Showing 6 changed files with 291 additions and 282 deletions.
64 changes: 19 additions & 45 deletions accounts/abi/bind/bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,51 +404,6 @@ func bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
contracts[types[i]].Library = ok
}

// compute the full set of libraries that each contract depends on.
for _, contract := range contracts {
if contract.Library {
continue
}
// recursively traverse the library dependency graph
// of the contract, flattening it into a set.
//
// For abigenv2, we do not generate contract deploy
// methods (which in v1 recursively deploy their
// library dependencies). So, the entire set of
// library dependencies is required, and we will
// determine the order to deploy and link them at
// runtime.
var findDeps func(contract *tmplContract) map[string]struct{}
findDeps = func(contract *tmplContract) map[string]struct{} {
// 1) match all libraries that this contract depends on
re, err := regexp.Compile(`__\$([a-f0-9]+)\$__`)
if err != nil {
panic(err)
}
libBin := contracts[contract.Type].InputBin
matches := re.FindAllStringSubmatch(libBin, -1)
result := make(map[string]struct{})

// 2) recurse, gathering nested library dependencies
for _, match := range matches {
pattern := match[1]
result[pattern] = struct{}{}
depContract := contracts[libs[pattern]]
for subPattern, _ := range findDeps(depContract) {
result[subPattern] = struct{}{}
}
}
return result
}
// take the set of library patterns, convert it to a map of type -> pattern
deps := findDeps(contract)
contract.AllLibraries = make(map[string]string)
for contractPattern, _ := range deps {
contractType := libs[contractPattern]
contract.AllLibraries[contractType] = contractPattern
}
}

// map of contract name -> pattern
invertedLibs := make(map[string]string)
// assume that this is invertible/onto because I assume library names are unique now
Expand All @@ -457,6 +412,25 @@ func bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
invertedLibs[name] = pattern
}

contractsBins := make(map[string]string)
for typ, contract := range 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 {
continue
}
subDepType := libs[subDepPattern]
contracts[contractType].AllLibraries[subDepType] = subDepPattern
}
}

// Generate the contract template data content and render it
data := &tmplData{
Package: pkg,
Expand Down
242 changes: 242 additions & 0 deletions accounts/abi/bind/dep_tree.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
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
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,12 @@
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package v2
package bind

import (
"fmt"
"testing"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
Expand Down Expand Up @@ -87,6 +86,8 @@ func makeLinkTestCase(input map[rune][]rune, overrides map[rune]common.Address)
}
}

var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")

type linkTestCaseInput struct {
input map[rune][]rune
overrides map[rune]struct{}
Expand Down Expand Up @@ -141,10 +142,10 @@ func testLinkCase(t *testing.T, tcInput linkTestCaseInput) {
deployParams DeploymentParams
)
for pattern, bin := range tc.contractCodes {
deployParams.Contracts = append(deployParams.Contracts, &bind.MetaData{Pattern: pattern, Bin: "0x" + bin})
deployParams.Contracts = append(deployParams.Contracts, &MetaData{Pattern: pattern, Bin: "0x" + bin})
}
for pattern, bin := range tc.libCodes {
deployParams.Contracts = append(deployParams.Contracts, &bind.MetaData{
deployParams.Contracts = append(deployParams.Contracts, &MetaData{
Bin: "0x" + bin,
Pattern: pattern,
})
Expand Down
Loading

0 comments on commit e140c3d

Please sign in to comment.