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/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"golang.org/x/exp/rand"
|
||||||
"testing"
|
"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)
|
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
||||||
var testAddrNonce uint64
|
var testAddrNonce uint64
|
||||||
|
|
||||||
tc := makeLinkTestCase(input, overrides)
|
|
||||||
alreadyDeployed := make(map[common.Address]struct{})
|
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{})
|
allContracts := make(map[rune]struct{})
|
||||||
|
|
||||||
for contract, deps := range input {
|
for contract, deps := range input {
|
||||||
|
@ -121,10 +132,15 @@ func testLinkCase(t *testing.T, input map[rune][]rune, overrides map[rune]common
|
||||||
Pattern: pattern,
|
Pattern: pattern,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
overridePatterns := make(map[string]common.Address)
|
||||||
|
for pattern, override := range tc.overrides {
|
||||||
|
overridePatterns[pattern] = override
|
||||||
|
}
|
||||||
deployParams := DeploymentParams{
|
deployParams := DeploymentParams{
|
||||||
Contracts: contracts,
|
Contracts: contracts,
|
||||||
Libraries: libs,
|
Libraries: libs,
|
||||||
Overrides: nil,
|
Overrides: overridePatterns,
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := LinkAndDeploy(deployParams, mockDeploy)
|
res, err := LinkAndDeploy(deployParams, mockDeploy)
|
||||||
|
@ -151,16 +167,16 @@ func TestContractLinking(t *testing.T) {
|
||||||
testLinkCase(t, map[rune][]rune{
|
testLinkCase(t, map[rune][]rune{
|
||||||
'a': {'b', 'c', 'd', 'e'},
|
'a': {'b', 'c', 'd', 'e'},
|
||||||
'e': {'f', 'g', 'h', 'i'}},
|
'e': {'f', 'g', 'h', 'i'}},
|
||||||
map[rune]common.Address{})
|
map[rune]struct{}{})
|
||||||
|
|
||||||
testLinkCase(t, map[rune][]rune{
|
testLinkCase(t, map[rune][]rune{
|
||||||
'a': {'b', 'c', 'd', 'e'}},
|
'a': {'b', 'c', 'd', 'e'}},
|
||||||
map[rune]common.Address{})
|
map[rune]struct{}{})
|
||||||
|
|
||||||
// test single contract only without deps
|
// test single contract only without deps
|
||||||
testLinkCase(t, map[rune][]rune{
|
testLinkCase(t, map[rune][]rune{
|
||||||
'a': {}},
|
'a': {}},
|
||||||
map[rune]common.Address{})
|
map[rune]struct{}{})
|
||||||
|
|
||||||
// test that libraries at different levels of the tree can share deps,
|
// test that libraries at different levels of the tree can share deps,
|
||||||
// and that these shared deps will only be deployed once.
|
// and that these shared deps will only be deployed once.
|
||||||
|
@ -168,17 +184,27 @@ func TestContractLinking(t *testing.T) {
|
||||||
'a': {'b', 'c', 'd', 'e'},
|
'a': {'b', 'c', 'd', 'e'},
|
||||||
'e': {'f', 'g', 'h', 'i', 'm'},
|
'e': {'f', 'g', 'h', 'i', 'm'},
|
||||||
'i': {'j', 'k', 'l', 'm'}},
|
'i': {'j', 'k', 'l', 'm'}},
|
||||||
map[rune]common.Address{})
|
map[rune]struct{}{})
|
||||||
|
|
||||||
// test two contracts can be deployed which don't share deps
|
// test two contracts can be deployed which don't share deps
|
||||||
testLinkCase(t, map[rune][]rune{
|
testLinkCase(t, map[rune][]rune{
|
||||||
'a': {'b', 'c', 'd', 'e'},
|
'a': {'b', 'c', 'd', 'e'},
|
||||||
'f': {'g', 'h', 'i', 'j'}},
|
'f': {'g', 'h', 'i', 'j'}},
|
||||||
map[rune]common.Address{})
|
map[rune]struct{}{})
|
||||||
|
|
||||||
// test two contracts can be deployed which share deps
|
// test two contracts can be deployed which share deps
|
||||||
testLinkCase(t, map[rune][]rune{
|
testLinkCase(t, map[rune][]rune{
|
||||||
'a': {'b', 'c', 'd', 'e'},
|
'a': {'b', 'c', 'd', 'e'},
|
||||||
'f': {'g', 'c', 'd', 'j'}},
|
'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
|
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 {
|
type ContractDeployer interface {
|
||||||
DeployContract(input []byte, deployer []byte) (common.Address, *types.Transaction, error)
|
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
|
// LinkAndDeploy deploys a specified set of contracts and their dependent
|
||||||
// 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 func(input, deployer []byte) (common.Address, *types.Transaction, error)) (res *DeploymentResult, err error) {
|
func LinkAndDeploy(deployParams DeploymentParams, deploy func(input, deployer []byte) (common.Address, *types.Transaction, error)) (res *DeploymentResult, err error) {
|
||||||
libMetas := deployParams.Libraries
|
// re-express libraries as a map of pattern -> pre-link binary
|
||||||
overrides := deployParams.Overrides
|
unlinkedLibs := make(map[string]string)
|
||||||
|
for _, meta := range deployParams.Libraries {
|
||||||
res = &DeploymentResult{
|
unlinkedLibs[meta.Pattern] = meta.Bin
|
||||||
|
}
|
||||||
|
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 _, contract := range deployParams.Contracts {
|
||||||
// re-express libraries as a map of pattern -> pre-link binary
|
treeBuilder := depTreeBuilder{deployParams.Overrides}
|
||||||
pending := make(map[string]string)
|
tree := treeBuilder.buildDepTree(contract.Meta.Bin, unlinkedLibs)
|
||||||
for _, meta := range libMetas {
|
deployer := treeDeployer{deploy: deploy}
|
||||||
pending[meta.Pattern] = meta.Bin
|
deployer.linkAndDeploy(tree)
|
||||||
}
|
res, err := deployer.Result()
|
||||||
|
|
||||||
// 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]
|
|
||||||
}
|
|
||||||
if err != nil {
|
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 accumRes, nil
|
||||||
return res, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this will be generated as part of the bindings, contain the ABI (or metadata object?) and errors
|
// TODO: this will be generated as part of the bindings, contain the ABI (or metadata object?) and errors
|
||||||
|
|
Loading…
Reference in New Issue