rewrite contract link/deploy again

This commit is contained in:
Jared Wasinger 2024-12-10 12:23:23 +07:00 committed by Felix Lange
parent e4c01633a4
commit 60fc16dea8
3 changed files with 80 additions and 74 deletions

View File

@ -120,17 +120,13 @@ func testLinkCase(t *testing.T, tcInput linkTestCaseInput) {
} }
var ( var (
contracts []ContractDeployParams deployParams DeploymentParams
libs []*bind.MetaData
) )
for pattern, bin := range tc.contractCodes { for pattern, bin := range tc.contractCodes {
contracts = append(contracts, ContractDeployParams{ deployParams.Contracts = append(deployParams.Contracts, &bind.MetaData{Pattern: pattern, Bin: "0x" + bin})
Meta: &bind.MetaData{Pattern: pattern, Bin: "0x" + bin},
Input: nil,
})
} }
for pattern, bin := range tc.libCodes { for pattern, bin := range tc.libCodes {
libs = append(libs, &bind.MetaData{ deployParams.Contracts = append(deployParams.Contracts, &bind.MetaData{
Bin: "0x" + bin, Bin: "0x" + bin,
Pattern: pattern, Pattern: pattern,
}) })
@ -140,11 +136,7 @@ func testLinkCase(t *testing.T, tcInput linkTestCaseInput) {
for pattern, override := range tc.overrides { for pattern, override := range tc.overrides {
overridePatterns[pattern] = override overridePatterns[pattern] = override
} }
deployParams := DeploymentParams{ deployParams.Overrides = overridePatterns
Contracts: contracts,
Libraries: libs,
Overrides: overridePatterns,
}
res, err := LinkAndDeploy(deployParams, mockDeploy) res, err := LinkAndDeploy(deployParams, mockDeploy)
if err != nil { if err != nil {
@ -160,11 +152,6 @@ func testLinkCase(t *testing.T, tcInput linkTestCaseInput) {
t.Fatalf("expected contract %s was not deployed\n", string(contract)) t.Fatalf("expected contract %s was not deployed\n", string(contract))
} }
} }
// note that the link-and-deploy functionality assumes that the combined-abi is well-formed.
// test-case ideas:
// * libraries that are disjount from the rest of dep graph (they don't get deployed)
} }
func TestContractLinking(t *testing.T) { func TestContractLinking(t *testing.T) {
@ -263,7 +250,7 @@ func TestContractLinking(t *testing.T) {
'f': {}, 'g': {}, 'c': {}, 'd': {}, 'h': {}, 'f': {}, 'g': {}, 'c': {}, 'd': {}, 'h': {},
}}) }})
// test nested libraries that share deps at different levels of the tree.. with override. // test nested libraries that share deps at different levels of the tree... with override.
testLinkCase(t, linkTestCaseInput{ testLinkCase(t, linkTestCaseInput{
map[rune][]rune{ map[rune][]rune{
'a': {'b', 'c', 'd', 'e'}, 'a': {'b', 'c', 'd', 'e'},

View File

@ -39,12 +39,8 @@ type ContractDeployParams struct {
// set of contracts, their dependency libraries. It takes an optional override // set of contracts, their dependency libraries. It takes an optional override
// list to specify libraries that have already been deployed on-chain. // list to specify libraries that have already been deployed on-chain.
type DeploymentParams struct { type DeploymentParams struct {
// Contracts is the set of contract deployment parameters for contracts Contracts []*bind.MetaData
// that are about to be deployed. Inputs map[string][]byte
Contracts []ContractDeployParams
// Libraries is a map of pattern to metadata for library contracts that
// are to be deployed.
Libraries []*bind.MetaData
// Overrides is an optional map of pattern to deployment address. // Overrides is an optional map of pattern to deployment address.
// Contracts/libraries that refer to dependencies in the override // Contracts/libraries that refer to dependencies in the override
// set are linked to the provided address (an already-deployed contract). // set are linked to the provided address (an already-deployed contract).
@ -69,13 +65,15 @@ func (d *DeploymentResult) Accumulate(other *DeploymentResult) {
} }
} }
type ContractDeployer interface {
DeployContract(input []byte, deployer []byte) (common.Address, *types.Transaction, error)
}
type depTreeBuilder struct { type depTreeBuilder struct {
overrides map[string]common.Address overrides map[string]common.Address
libs map[string]string // 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{}
} }
type depTreeNode struct { type depTreeNode struct {
pattern string pattern string
@ -83,27 +81,58 @@ type depTreeNode struct {
nodes []*depTreeNode nodes []*depTreeNode
} }
func (d *depTreeBuilder) buildDepTree(pattern string, contractBin string) *depTreeNode { func (d *depTreeBuilder) buildDepTrees(pattern, contract string) {
node := depTreeNode{pattern, contractBin, nil}
reMatchSpecificPattern, err := regexp.Compile("__\\$([a-f0-9]+)\\$__") reMatchSpecificPattern, err := regexp.Compile("__\\$([a-f0-9]+)\\$__")
if err != nil { if err != nil {
panic(err) panic(err)
} }
for _, match := range reMatchSpecificPattern.FindAllStringSubmatch(contractBin, -1) { node := &depTreeNode{
pattern := match[1] pattern: pattern,
if _, ok := d.overrides[pattern]; ok { unlinkedCode: contract,
}
for _, match := range reMatchSpecificPattern.FindAllStringSubmatch(contract, -1) {
depPattern := match[1]
if _, ok := d.subtrees[depPattern]; ok {
continue continue
} }
node.nodes = append(node.nodes, d.buildDepTree(pattern, d.libs[pattern])) delete(d.roots, depPattern)
d.buildDepTrees(depPattern, d.contracts[depPattern])
node.nodes = append(node.nodes, d.subtrees[depPattern])
} }
return &node d.subtrees[pattern] = node
}
func (d *depTreeBuilder) BuildDepTrees() (roots []*depTreeNode) {
for pattern, _ := range d.contracts {
d.roots[pattern] = struct{}{}
}
for pattern, contract := range d.contracts {
if _, ok := d.subtrees[pattern]; ok {
continue
}
reMatchSpecificPattern, err := regexp.Compile("__\\$([a-f0-9]+)\\$__")
if err != nil {
panic(err)
}
for _, match := range reMatchSpecificPattern.FindAllStringSubmatch(contract, -1) {
depPattern := match[1]
delete(d.roots, depPattern)
if _, ok := d.subtrees[depPattern]; ok {
continue
}
d.buildDepTrees(depPattern, d.contracts[depPattern])
}
}
for pattern, _ := range d.roots {
roots = append(roots, d.subtrees[pattern])
}
return roots
} }
type treeDeployer struct { type treeDeployer struct {
deployedAddrs map[string]common.Address deployedAddrs map[string]common.Address
deployerTxs map[string]*types.Transaction deployerTxs map[string]*types.Transaction
inputs map[string][]byte input map[string][]byte // map of the root contract pattern to the constructor input (if there is any)
deploy func(input, deployer []byte) (common.Address, *types.Transaction, error) deploy func(input, deployer []byte) (common.Address, *types.Transaction, error)
err error err error
} }
@ -114,11 +143,13 @@ func (d *treeDeployer) linkAndDeploy(node *depTreeNode) {
} }
// link in all node dependencies and produce the deployer bytecode // link in all node dependencies and produce the deployer bytecode
deployerCode := node.unlinkedCode deployerCode := node.unlinkedCode
for _, child := range node.nodes { for _, child := range node.nodes {
deployerCode = strings.ReplaceAll(deployerCode, child.pattern, d.deployedAddrs[child.pattern].String()[2:]) deployerCode = strings.ReplaceAll(deployerCode, "__$"+child.pattern+"$__", strings.ToLower(d.deployedAddrs[child.pattern].String()[2:]))
} }
// deploy the contract. // deploy the contract.
addr, tx, err := d.deploy(d.inputs[node.pattern], common.Hex2Bytes(deployerCode)) addr, tx, err := d.deploy(d.input[node.pattern], common.Hex2Bytes(deployerCode))
if err != nil { if err != nil {
d.err = err d.err = err
} else { } else {
@ -141,32 +172,34 @@ func (d *treeDeployer) Result() (*DeploymentResult, error) {
// 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) {
// re-express libraries as a map of pattern -> pre-link binary unlinkedContracts := make(map[string]string)
unlinkedLibs := make(map[string]string)
for _, meta := range deployParams.Libraries {
unlinkedLibs[meta.Pattern] = meta.Bin
}
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 _, contract := range deployParams.Contracts { for _, meta := range deployParams.Contracts {
if _, ok := deployParams.Overrides[contract.Meta.Pattern]; ok { unlinkedContracts[meta.Pattern] = meta.Bin
continue
} }
treeBuilder := depTreeBuilder{deployParams.Overrides, unlinkedLibs} // TODO: instantiate this using constructor
tree := treeBuilder.buildDepTree(contract.Meta.Pattern, contract.Meta.Bin) treeBuilder := depTreeBuilder{
overrides: deployParams.Overrides,
contracts: unlinkedContracts,
}
deps := treeBuilder.BuildDepTrees()
for _, tr := range deps {
// TODO: instantiate deployer with its tree?
deployer := treeDeployer{ deployer := treeDeployer{
deploy: deploy, deploy: deploy,
deployedAddrs: make(map[string]common.Address), deployedAddrs: make(map[string]common.Address),
deployerTxs: make(map[string]*types.Transaction)} deployerTxs: make(map[string]*types.Transaction),
deployer.linkAndDeploy(tree) input: map[string][]byte{tr.pattern: deployParams.Inputs[tr.pattern]}}
deployer.linkAndDeploy(tr)
res, err := deployer.Result() res, err := deployer.Result()
if err != nil { if err != nil {
return accumRes, err return accumRes, err
} }
accumRes.Accumulate(res) accumRes.Accumulate(res)
} }
return accumRes, nil return accumRes, nil
} }

View File

@ -122,13 +122,8 @@ func TestDeploymentLibraries(t *testing.T) {
t.Fatalf("failed to pack constructor: %v", err) t.Fatalf("failed to pack constructor: %v", err)
} }
deploymentParams := DeploymentParams{ deploymentParams := DeploymentParams{
Contracts: []ContractDeployParams{ Contracts: append(nested_libraries.C1LibraryDeps, nested_libraries.C1MetaData),
{ Inputs: map[string][]byte{nested_libraries.C1MetaData.Pattern: constructorInput},
Meta: nested_libraries.C1MetaData,
Input: constructorInput,
},
},
Libraries: nested_libraries.C1LibraryDeps,
Overrides: nil, Overrides: nil,
} }
@ -186,7 +181,7 @@ func TestDeploymentWithOverrides(t *testing.T) {
// deploy some library deps // deploy some library deps
deploymentParams := DeploymentParams{ deploymentParams := DeploymentParams{
Libraries: nested_libraries.C1LibraryDeps, Contracts: nested_libraries.C1LibraryDeps,
} }
res, err := LinkAndDeploy(deploymentParams, makeTestDeployer(opts, bindBackend)) res, err := LinkAndDeploy(deploymentParams, makeTestDeployer(opts, bindBackend))
@ -216,13 +211,8 @@ func TestDeploymentWithOverrides(t *testing.T) {
overrides := res.Addrs overrides := res.Addrs
// deploy the contract // deploy the contract
deploymentParams = DeploymentParams{ deploymentParams = DeploymentParams{
Contracts: []ContractDeployParams{ Contracts: []*bind.MetaData{nested_libraries.C1MetaData},
{ Inputs: map[string][]byte{nested_libraries.C1MetaData.Pattern: constructorInput},
Meta: nested_libraries.C1MetaData,
Input: constructorInput,
},
},
Libraries: nil,
Overrides: overrides, Overrides: overrides,
} }
res, err = LinkAndDeploy(deploymentParams, makeTestDeployer(opts, bindBackend)) res, err = LinkAndDeploy(deploymentParams, makeTestDeployer(opts, bindBackend))
@ -281,11 +271,7 @@ func TestEvents(t *testing.T) {
} }
deploymentParams := DeploymentParams{ deploymentParams := DeploymentParams{
Contracts: []ContractDeployParams{ Contracts: []*bind.MetaData{events.CMetaData},
{
Meta: events.CMetaData,
},
},
} }
res, err := LinkAndDeploy(deploymentParams, makeTestDeployer(txAuth, backend)) res, err := LinkAndDeploy(deploymentParams, makeTestDeployer(txAuth, backend))