mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-15 08:23:46 +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
|
ABI string
|
||||||
ab *abi.ABI
|
ab *abi.ABI
|
||||||
Pattern string
|
Pattern string
|
||||||
|
Deps []*MetaData
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MetaData) GetAbi() (*abi.ABI, error) {
|
func (m *MetaData) GetAbi() (*abi.ABI, error) {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
"go/format"
|
"go/format"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
@ -190,6 +191,17 @@ func (cb *contractBinder) bindError(original abi.Error) error {
|
||||||
return nil
|
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
|
// 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
|
// 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
|
// 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,
|
Structs: b.structs,
|
||||||
}
|
}
|
||||||
|
|
||||||
contractsBins := make(map[string]string)
|
|
||||||
for typ, contract := range data.Contracts {
|
for typ, contract := range data.Contracts {
|
||||||
pattern := invertedLibs[typ]
|
for _, depPattern := range parseLibraryDeps(contract.InputBin) {
|
||||||
contractsBins[pattern] = contract.InputBin
|
data.Contracts[typ].Libraries[libs[depPattern]] = depPattern
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buffer := new(bytes.Buffer)
|
buffer := new(bytes.Buffer)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package bind
|
||||||
import (
|
import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"regexp"
|
"maps"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -45,100 +45,6 @@ func (d *DeploymentResult) Accumulate(other *DeploymentResult) {
|
||||||
maps.Copy(d.Addrs, other.Addrs)
|
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
|
// 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.
|
// the address and a pending transaction, or an error if the deployment failed.
|
||||||
type DeployFn func(input, deployer []byte) (common.Address, *types.Transaction, error)
|
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.
|
// 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.
|
// 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.
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// if this contract/library depends on other libraries deploy them (and their dependencies) first
|
// if this contract/library depends on other libraries deploy them (and their dependencies) first
|
||||||
for _, childNode := range node.children {
|
for _, dep := range metadata.Deps {
|
||||||
if err := d.linkAndDeploy(childNode); err != nil {
|
if err := d.linkAndDeploy(dep); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if we just deployed any prerequisite contracts, link their deployed addresses into the bytecode to produce
|
// if we just deployed any prerequisite contracts, link their deployed addresses into the bytecode to produce
|
||||||
// a deployer bytecode for this contract.
|
// a deployer bytecode for this contract.
|
||||||
deployerCode := node.unlinkedCode
|
deployerCode := metadata.Bin
|
||||||
for _, child := range node.children {
|
for _, dep := range metadata.Deps {
|
||||||
linkAddr := d.deployedAddrs[child.pattern]
|
linkAddr := d.deployedAddrs[dep.Pattern]
|
||||||
if child.overrideAddr != nil {
|
deployerCode = strings.ReplaceAll(deployerCode, "__$"+dep.Pattern+"$__", strings.ToLower(linkAddr.String()[2:]))
|
||||||
linkAddr = *child.overrideAddr
|
|
||||||
}
|
|
||||||
deployerCode = strings.ReplaceAll(deployerCode, "__$"+child.pattern+"$__", strings.ToLower(linkAddr.String()[2:]))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, deploy the contract.
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.deployedAddrs[node.pattern] = addr
|
d.deployedAddrs[metadata.Pattern] = addr
|
||||||
d.deployerTxs[node.pattern] = tx
|
d.deployerTxs[metadata.Pattern] = tx
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,23 +110,16 @@ func newDepTreeDeployer(deploy DeployFn) *depTreeDeployer {
|
||||||
// libraries. If an error occurs, only contracts which were successfully
|
// libraries. If an error occurs, only contracts which were successfully
|
||||||
// deployed are returned in the result.
|
// deployed are returned in the result.
|
||||||
func LinkAndDeploy(deployParams *DeploymentParams, deploy DeployFn) (res *DeploymentResult, err error) {
|
func LinkAndDeploy(deployParams *DeploymentParams, deploy DeployFn) (res *DeploymentResult, err error) {
|
||||||
unlinkedContracts := make(map[string]string)
|
|
||||||
accumRes := &DeploymentResult{
|
accumRes := &DeploymentResult{
|
||||||
Txs: make(map[string]*types.Transaction),
|
Txs: make(map[string]*types.Transaction),
|
||||||
Addrs: make(map[string]common.Address),
|
Addrs: make(map[string]common.Address),
|
||||||
}
|
}
|
||||||
for _, meta := range deployParams.contracts {
|
deployer := newDepTreeDeployer(deploy)
|
||||||
unlinkedContracts[meta.Pattern] = meta.Bin[2:]
|
for _, contract := range deployParams.contracts {
|
||||||
}
|
|
||||||
treeBuilder := newDepTreeBuilder(deployParams.overrides, unlinkedContracts)
|
|
||||||
deps, _ := treeBuilder.BuildDepTrees()
|
|
||||||
|
|
||||||
for _, tr := range deps {
|
|
||||||
deployer := newDepTreeDeployer(deploy)
|
|
||||||
if deployParams.inputs != nil {
|
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()
|
res := deployer.result()
|
||||||
accumRes.Accumulate(res)
|
accumRes.Accumulate(res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,36 @@ type linkTestCaseInput struct {
|
||||||
expectDeployed map[rune]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) {
|
func testLinkCase(t *testing.T, tcInput linkTestCaseInput) {
|
||||||
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
||||||
var testAddrNonce uint64
|
var testAddrNonce uint64
|
||||||
|
|
@ -138,24 +168,26 @@ func testLinkCase(t *testing.T, tcInput linkTestCaseInput) {
|
||||||
return contractAddr, nil, nil
|
return contractAddr, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var contracts []*MetaData
|
var contracts map[string]*MetaData
|
||||||
overrides := make(map[string]common.Address)
|
overrides := make(map[string]common.Address)
|
||||||
|
|
||||||
for pattern, bin := range tc.contractCodes {
|
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 {
|
for pattern, bin := range tc.libCodes {
|
||||||
contracts = append(contracts, &MetaData{
|
contracts[pattern] = &MetaData{
|
||||||
Bin: "0x" + bin,
|
Bin: "0x" + bin,
|
||||||
Pattern: pattern,
|
Pattern: pattern,
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contractsList := linkDeps(contracts)
|
||||||
|
|
||||||
for pattern, override := range tc.overrides {
|
for pattern, override := range tc.overrides {
|
||||||
overrides[pattern] = override
|
overrides[pattern] = override
|
||||||
}
|
}
|
||||||
|
|
||||||
deployParams := NewDeploymentParams(contracts, nil, overrides)
|
deployParams := NewDeploymentParams(contractsList, nil, overrides)
|
||||||
res, err := LinkAndDeploy(deployParams, mockDeploy)
|
res, err := LinkAndDeploy(deployParams, mockDeploy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("got error from LinkAndDeploy: %v\n", err)
|
t.Fatalf("got error from LinkAndDeploy: %v\n", err)
|
||||||
|
|
|
||||||
|
|
@ -33,12 +33,6 @@ var (
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{range $contract := .Contracts}}
|
{{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.
|
// TODO: convert this type to value type after everything works.
|
||||||
// {{.Type}}MetaData contains all meta data concerning the {{.Type}} contract.
|
// {{.Type}}MetaData contains all meta data concerning the {{.Type}} contract.
|
||||||
var {{.Type}}MetaData = &bind.MetaData{
|
var {{.Type}}MetaData = &bind.MetaData{
|
||||||
|
|
@ -47,6 +41,9 @@ var (
|
||||||
{{if .InputBin -}}
|
{{if .InputBin -}}
|
||||||
Bin: "0x{{.InputBin}}",
|
Bin: "0x{{.InputBin}}",
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{if .Libraries -}}{{range $name, $pattern := .Libraries}}
|
||||||
|
{{$name}}MetaData,
|
||||||
|
{{end}}{{end}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// {{.Type}} is an auto generated Go binding around an Ethereum contract.
|
// {{.Type}} is an auto generated Go binding around an Ethereum contract.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue