wip: update
This commit is contained in:
parent
4821fbae99
commit
81289d7e26
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"golang.org/x/exp/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -70,12 +71,22 @@ func makeLinkTestCase(input map[rune][]rune, overrides map[rune]common.Address)
|
|||
}
|
||||
}
|
||||
|
||||
func testLinkCase(t *testing.T, input map[rune][]rune, overrides map[rune]common.Address) {
|
||||
func testLinkCase(t *testing.T, input map[rune][]rune, overrides map[rune]struct{}) {
|
||||
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
||||
var testAddrNonce uint64
|
||||
|
||||
tc := makeLinkTestCase(input, overrides)
|
||||
alreadyDeployed := make(map[common.Address]struct{})
|
||||
|
||||
// generate deterministic addresses for the override set.
|
||||
rand.Seed(42)
|
||||
overrideAddrs := make(map[rune]common.Address)
|
||||
for contract, _ := range overrides {
|
||||
var addr common.Address
|
||||
rand.Read(addr[:])
|
||||
overrideAddrs[contract] = addr
|
||||
alreadyDeployed[addr] = struct{}{}
|
||||
}
|
||||
|
||||
tc := makeLinkTestCase(input, overrideAddrs)
|
||||
allContracts := make(map[rune]struct{})
|
||||
|
||||
for contract, deps := range input {
|
||||
|
@ -121,10 +132,15 @@ func testLinkCase(t *testing.T, input map[rune][]rune, overrides map[rune]common
|
|||
Pattern: pattern,
|
||||
})
|
||||
}
|
||||
|
||||
overridePatterns := make(map[string]common.Address)
|
||||
for pattern, override := range tc.overrides {
|
||||
overridePatterns[pattern] = override
|
||||
}
|
||||
deployParams := DeploymentParams{
|
||||
Contracts: contracts,
|
||||
Libraries: libs,
|
||||
Overrides: nil,
|
||||
Overrides: overridePatterns,
|
||||
}
|
||||
|
||||
res, err := LinkAndDeploy(deployParams, mockDeploy)
|
||||
|
@ -151,16 +167,16 @@ func TestContractLinking(t *testing.T) {
|
|||
testLinkCase(t, map[rune][]rune{
|
||||
'a': {'b', 'c', 'd', 'e'},
|
||||
'e': {'f', 'g', 'h', 'i'}},
|
||||
map[rune]common.Address{})
|
||||
map[rune]struct{}{})
|
||||
|
||||
testLinkCase(t, map[rune][]rune{
|
||||
'a': {'b', 'c', 'd', 'e'}},
|
||||
map[rune]common.Address{})
|
||||
map[rune]struct{}{})
|
||||
|
||||
// test single contract only without deps
|
||||
testLinkCase(t, map[rune][]rune{
|
||||
'a': {}},
|
||||
map[rune]common.Address{})
|
||||
map[rune]struct{}{})
|
||||
|
||||
// test that libraries at different levels of the tree can share deps,
|
||||
// and that these shared deps will only be deployed once.
|
||||
|
@ -168,17 +184,27 @@ func TestContractLinking(t *testing.T) {
|
|||
'a': {'b', 'c', 'd', 'e'},
|
||||
'e': {'f', 'g', 'h', 'i', 'm'},
|
||||
'i': {'j', 'k', 'l', 'm'}},
|
||||
map[rune]common.Address{})
|
||||
map[rune]struct{}{})
|
||||
|
||||
// test two contracts can be deployed which don't share deps
|
||||
testLinkCase(t, map[rune][]rune{
|
||||
'a': {'b', 'c', 'd', 'e'},
|
||||
'f': {'g', 'h', 'i', 'j'}},
|
||||
map[rune]common.Address{})
|
||||
map[rune]struct{}{})
|
||||
|
||||
// test two contracts can be deployed which share deps
|
||||
testLinkCase(t, map[rune][]rune{
|
||||
'a': {'b', 'c', 'd', 'e'},
|
||||
'f': {'g', 'c', 'd', 'j'}},
|
||||
map[rune]common.Address{})
|
||||
map[rune]struct{}{})
|
||||
|
||||
// test that one contract with overrides for all lib deps
|
||||
testLinkCase(t, map[rune][]rune{
|
||||
'a': {'b', 'c', 'd', 'e'}},
|
||||
map[rune]struct{}{'b': {}, 'c': {}, 'd': {}, 'e': {}})
|
||||
|
||||
// test deployment of a contract with overrides
|
||||
testLinkCase(t, map[rune][]rune{
|
||||
'a': {}},
|
||||
map[rune]struct{}{'a': {}})
|
||||
}
|
||||
|
|
|
@ -157,71 +157,108 @@ type DeploymentResult struct {
|
|||
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
|
||||
}
|
||||
}
|
||||
|
||||
type ContractDeployer interface {
|
||||
DeployContract(input []byte, deployer []byte) (common.Address, *types.Transaction, error)
|
||||
}
|
||||
|
||||
type depTreeBuilder struct {
|
||||
overrides map[string]common.Address
|
||||
}
|
||||
type depTreeNode struct {
|
||||
pattern string
|
||||
unlinkedCode string
|
||||
nodes []*depTreeNode
|
||||
}
|
||||
|
||||
func (d *depTreeBuilder) buildDepTree(contractBin string, libs map[string]string) *depTreeNode {
|
||||
node := depTreeNode{contractBin, contractBin, nil}
|
||||
|
||||
reMatchSpecificPattern, err := regexp.Compile("__\\$([a-f0-9]+)\\$__")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, match := range reMatchSpecificPattern.FindAllStringSubmatch(contractBin, -1) {
|
||||
pattern := match[1]
|
||||
if _, ok := d.overrides[pattern]; ok {
|
||||
continue
|
||||
}
|
||||
node.nodes = append(node.nodes, d.buildDepTree(libs[pattern], libs))
|
||||
}
|
||||
return &node
|
||||
}
|
||||
|
||||
type treeDeployer struct {
|
||||
deployedAddrs map[string]common.Address
|
||||
deployerTxs map[string]*types.Transaction
|
||||
inputs map[string][]byte
|
||||
deploy func(input, deployer []byte) (common.Address, *types.Transaction, error)
|
||||
err error
|
||||
}
|
||||
|
||||
func (d *treeDeployer) linkAndDeploy(node *depTreeNode) {
|
||||
for _, childNode := range node.nodes {
|
||||
d.linkAndDeploy(childNode)
|
||||
}
|
||||
// link in all node dependencies and produce the deployer bytecode
|
||||
deployerCode := node.unlinkedCode
|
||||
for _, child := range node.nodes {
|
||||
deployerCode = strings.ReplaceAll(deployerCode, child.pattern, d.deployedAddrs[child.pattern].String()[2:])
|
||||
}
|
||||
// deploy the contract.
|
||||
addr, tx, err := d.deploy(d.inputs[node.pattern], common.Hex2Bytes(deployerCode))
|
||||
if err != nil {
|
||||
d.err = err
|
||||
} else {
|
||||
d.deployedAddrs[node.pattern] = addr
|
||||
d.deployerTxs[node.pattern] = tx
|
||||
}
|
||||
}
|
||||
|
||||
func (d *treeDeployer) Result() (*DeploymentResult, error) {
|
||||
if d.err != nil {
|
||||
return nil, d.err
|
||||
}
|
||||
return &DeploymentResult{
|
||||
Txs: d.deployerTxs,
|
||||
Addrs: d.deployedAddrs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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 func(input, deployer []byte) (common.Address, *types.Transaction, error)) (res *DeploymentResult, err error) {
|
||||
libMetas := deployParams.Libraries
|
||||
overrides := deployParams.Overrides
|
||||
|
||||
res = &DeploymentResult{
|
||||
// re-express libraries as a map of pattern -> pre-link binary
|
||||
unlinkedLibs := make(map[string]string)
|
||||
for _, meta := range deployParams.Libraries {
|
||||
unlinkedLibs[meta.Pattern] = meta.Bin
|
||||
}
|
||||
accumRes := &DeploymentResult{
|
||||
Txs: make(map[string]*types.Transaction),
|
||||
Addrs: make(map[string]common.Address),
|
||||
}
|
||||
|
||||
// re-express libraries as a map of pattern -> pre-link binary
|
||||
pending := make(map[string]string)
|
||||
for _, meta := range libMetas {
|
||||
pending[meta.Pattern] = meta.Bin
|
||||
}
|
||||
|
||||
// initialize the set of already-deployed contracts with given override addresses
|
||||
deployed := make(map[string]common.Address)
|
||||
for pattern, deployAddr := range overrides {
|
||||
deployed[pattern] = deployAddr
|
||||
if _, ok := pending[pattern]; ok {
|
||||
delete(pending, pattern)
|
||||
}
|
||||
}
|
||||
|
||||
// link and deploy dynamic libraries
|
||||
for {
|
||||
var deployableDeps map[string]string
|
||||
pending, deployableDeps = linkLibs(pending, deployed)
|
||||
if len(deployableDeps) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
deployTxs, deployAddrs, err := deployLibs(deployableDeps, deploy)
|
||||
for pattern, addr := range deployAddrs {
|
||||
deployed[pattern] = addr
|
||||
res.Addrs[pattern] = addr
|
||||
res.Txs[pattern] = deployTxs[addr]
|
||||
}
|
||||
for _, contract := range deployParams.Contracts {
|
||||
treeBuilder := depTreeBuilder{deployParams.Overrides}
|
||||
tree := treeBuilder.buildDepTree(contract.Meta.Bin, unlinkedLibs)
|
||||
deployer := treeDeployer{deploy: deploy}
|
||||
deployer.linkAndDeploy(tree)
|
||||
res, err := deployer.Result()
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
return accumRes, err
|
||||
}
|
||||
accumRes.Accumulate(res)
|
||||
|
||||
// link and deploy contracts
|
||||
for _, contractParams := range deployParams.Contracts {
|
||||
linkedContract, err := linkContract(contractParams.Meta.Bin, deployed)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
contractAddr, contractTx, err := deployContract(contractParams.Input, linkedContract, deploy)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
res.Txs[contractParams.Meta.Pattern] = contractTx
|
||||
res.Addrs[contractParams.Meta.Pattern] = contractAddr
|
||||
}
|
||||
|
||||
return res, nil
|
||||
return accumRes, nil
|
||||
}
|
||||
|
||||
// TODO: this will be generated as part of the bindings, contain the ABI (or metadata object?) and errors
|
||||
|
|
Loading…
Reference in New Issue