mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-02 01:53:48 +00:00
wip: embed library relations in metadata
This commit is contained in:
parent
c89c24ce37
commit
eb10c47793
5 changed files with 71 additions and 147 deletions
|
|
@ -95,6 +95,7 @@ type MetaData struct {
|
|||
ABI string
|
||||
ab *abi.ABI
|
||||
Pattern string
|
||||
Deps []*MetaData
|
||||
}
|
||||
|
||||
func (m *MetaData) GetAbi() (*abi.ABI, error) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue