fix test cases

This commit is contained in:
Jared Wasinger 2024-12-09 13:56:53 +07:00 committed by Felix Lange
parent 81289d7e26
commit 463c3b6dd4
2 changed files with 81 additions and 130 deletions

View File

@ -71,10 +71,10 @@ func makeLinkTestCase(input map[rune][]rune, overrides map[rune]common.Address)
} }
} }
func testLinkCase(t *testing.T, input map[rune][]rune, overrides map[rune]struct{}) { func testLinkCase(t *testing.T, input map[rune][]rune, overrides map[rune]struct{}, expectDeployed map[rune]struct{}) {
testAddr := crypto.PubkeyToAddress(testKey.PublicKey) testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
var testAddrNonce uint64 var testAddrNonce uint64
alreadyDeployed := make(map[common.Address]struct{}) overridesAddrs := make(map[common.Address]struct{})
// generate deterministic addresses for the override set. // generate deterministic addresses for the override set.
rand.Seed(42) rand.Seed(42)
@ -83,7 +83,7 @@ func testLinkCase(t *testing.T, input map[rune][]rune, overrides map[rune]struct
var addr common.Address var addr common.Address
rand.Read(addr[:]) rand.Read(addr[:])
overrideAddrs[contract] = addr overrideAddrs[contract] = addr
alreadyDeployed[addr] = struct{}{} overridesAddrs[addr] = struct{}{}
} }
tc := makeLinkTestCase(input, overrideAddrs) tc := makeLinkTestCase(input, overrideAddrs)
@ -96,9 +96,6 @@ func testLinkCase(t *testing.T, input map[rune][]rune, overrides map[rune]struct
} }
} }
// TODO: include in link test case: set of contracts that we expect to be deployed at the end.
// generate this in makeLinkTestCase
// ^ overrides are not included in this case.
mockDeploy := func(input []byte, deployer []byte) (common.Address, *types.Transaction, error) { mockDeploy := func(input []byte, deployer []byte) (common.Address, *types.Transaction, error) {
contractAddr := crypto.CreateAddress(testAddr, testAddrNonce) contractAddr := crypto.CreateAddress(testAddr, testAddrNonce)
testAddrNonce++ testAddrNonce++
@ -107,11 +104,11 @@ func testLinkCase(t *testing.T, input map[rune][]rune, overrides map[rune]struct
for i := 0; i < len(deployer)/20; i += 20 { for i := 0; i < len(deployer)/20; i += 20 {
var dep common.Address var dep common.Address
dep.SetBytes(deployer[i : i+20]) dep.SetBytes(deployer[i : i+20])
if _, ok := alreadyDeployed[dep]; !ok { if _, ok := overridesAddrs[dep]; !ok {
t.Fatalf("reference to dependent contract that has not yet been deployed: %x\n", dep) t.Fatalf("reference to dependent contract that has not yet been deployed: %x\n", dep)
} }
} }
alreadyDeployed[contractAddr] = struct{}{} overridesAddrs[contractAddr] = struct{}{}
// we don't care about the txs themselves for the sake of the linking tests. so we can return nil for them in the mock deployer // we don't care about the txs themselves for the sake of the linking tests. so we can return nil for them in the mock deployer
return contractAddr, nil, nil return contractAddr, nil, nil
} }
@ -148,13 +145,14 @@ func testLinkCase(t *testing.T, input map[rune][]rune, overrides map[rune]struct
t.Fatalf("got error from LinkAndDeploy: %v\n", err) t.Fatalf("got error from LinkAndDeploy: %v\n", err)
} }
// TODO: assert that the result consists of the input contracts minus the overrides. if len(res.Addrs) != len(expectDeployed) {
t.Fatalf("got %d deployed contracts. expected %d.\n", len(res.Addrs), len(expectDeployed))
if len(res.Addrs) != len(allContracts)-len(overrides) { }
for val, _ := range allContracts { for contract, _ := range expectDeployed {
fmt.Println(string(val)) pattern := crypto.Keccak256Hash([]byte(string(contract))).String()[2:36]
if _, ok := res.Addrs[pattern]; !ok {
t.Fatalf("expected contract %s was not deployed\n", string(contract))
} }
t.Fatalf("expected %d contracts to be deployed. got %d\n", len(allContracts)-len(overrides), len(res.Addrs))
} }
// note that the link-and-deploy functionality assumes that the combined-abi is well-formed. // note that the link-and-deploy functionality assumes that the combined-abi is well-formed.
@ -164,19 +162,29 @@ func testLinkCase(t *testing.T, input map[rune][]rune, overrides map[rune]struct
} }
func TestContractLinking(t *testing.T) { func TestContractLinking(t *testing.T) {
testLinkCase(t, map[rune][]rune{
'a': {'b', 'c', 'd', 'e'},
'e': {'f', 'g', 'h', 'i'}},
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]struct{}{}) map[rune]struct{}{},
map[rune]struct{}{
'a': {}, 'b': {}, 'c': {}, 'd': {}, 'e': {},
})
testLinkCase(t, map[rune][]rune{
'a': {'b', 'c', 'd', 'e'},
'e': {'f', 'g', 'h', 'i'}},
map[rune]struct{}{},
map[rune]struct{}{
'a': {}, 'b': {}, 'c': {}, 'd': {}, 'e': {}, 'f': {}, 'g': {}, 'h': {}, 'i': {},
})
// 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]struct{}{}) map[rune]struct{}{},
map[rune]struct{}{
'a': {},
})
// 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.
@ -184,27 +192,60 @@ 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]struct{}{}) map[rune]struct{}{},
map[rune]struct{}{
'a': {}, 'b': {}, 'c': {}, 'd': {}, 'e': {}, 'f': {}, 'g': {}, 'h': {}, 'i': {}, 'j': {}, 'k': {}, 'l': {}, 'm': {},
})
// 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]struct{}{}) map[rune]struct{}{},
map[rune]struct{}{
'a': {}, 'b': {}, 'c': {}, 'd': {}, 'e': {}, 'f': {}, 'g': {}, 'h': {}, 'i': {}, 'j': {},
})
// 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', 'h'}},
map[rune]struct{}{}) map[rune]struct{}{},
map[rune]struct{}{
'a': {}, 'b': {}, 'c': {}, 'd': {}, 'e': {}, 'f': {}, 'g': {}, 'h': {},
})
// test that one contract with overrides for all lib deps // test one contract with overrides for all lib deps
testLinkCase(t, map[rune][]rune{ testLinkCase(t, map[rune][]rune{
'a': {'b', 'c', 'd', 'e'}}, 'a': {'b', 'c', 'd', 'e'}},
map[rune]struct{}{'b': {}, 'c': {}, 'd': {}, 'e': {}}) map[rune]struct{}{'b': {}, 'c': {}, 'd': {}, 'e': {}},
map[rune]struct{}{
'a': {},
})
// test one contract with overrides for some lib deps
testLinkCase(t, map[rune][]rune{
'a': {'b', 'c'}},
map[rune]struct{}{'b': {}, 'c': {}},
map[rune]struct{}{
'a': {},
})
// test deployment of a contract with overrides // test deployment of a contract with overrides
testLinkCase(t, map[rune][]rune{ testLinkCase(t, map[rune][]rune{
'a': {}}, 'a': {}},
map[rune]struct{}{'a': {}}) map[rune]struct{}{'a': {}},
map[rune]struct{}{})
// two contracts share some dependencies. one contract is marked as an override. all dependencies for the non-override
// contract will be deployed
testLinkCase(t, map[rune][]rune{
'a': {'b', 'c', 'd', 'e'},
'f': {'g', 'c', 'd', 'h'}},
map[rune]struct{}{'a': {}},
map[rune]struct{}{
'f': {}, 'g': {}, 'c': {}, 'd': {}, 'h': {},
})
// TODO: same as the above case but nested one level of dependencies deep (?)
} }

View File

@ -17,8 +17,6 @@
package v2 package v2
import ( import (
"encoding/hex"
"fmt"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
@ -29,101 +27,6 @@ import (
"strings" "strings"
) )
// deployContract deploys a hex-encoded contract with the given constructor
// input. It returns the deployment transaction, address on success.
func deployContract(constructor []byte, contract string, deploy func(input, deployer []byte) (common.Address, *types.Transaction, error)) (deploymentAddr common.Address, deploymentTx *types.Transaction, err error) {
contractBinBytes, err := hex.DecodeString(contract[2:])
if err != nil {
return common.Address{}, nil, fmt.Errorf("contract bytecode is not a hex string: %s", contractBinBytes[2:])
}
addr, tx, err := deploy(constructor, contractBinBytes)
if err != nil {
return common.Address{}, nil, fmt.Errorf("failed to deploy contract: %v", err)
}
return addr, tx, nil
}
// deployLibs iterates the set contracts (map of pattern to hex-encoded
// contract deployer code). Each contract is deployed, and the
// resulting addresses/deployment-txs are returned on success.
func deployLibs(contracts map[string]string, deploy func(input, deployer []byte) (common.Address, *types.Transaction, error)) (deploymentTxs map[common.Address]*types.Transaction, deployAddrs map[string]common.Address, err error) {
deploymentTxs = make(map[common.Address]*types.Transaction)
deployAddrs = make(map[string]common.Address)
for pattern, contractBin := range contracts {
contractDeployer, err := hex.DecodeString(contractBin[2:])
if err != nil {
return deploymentTxs, deployAddrs, fmt.Errorf("contract bytecode is not a hex string: %s", contractBin[2:])
}
addr, tx, err := deploy([]byte{}, contractDeployer)
if err != nil {
return deploymentTxs, deployAddrs, fmt.Errorf("failed to deploy contract: %v", err)
}
deploymentTxs[addr] = tx
deployAddrs[pattern] = addr
}
return deploymentTxs, deployAddrs, nil
}
// linkContract takes an unlinked contract deployer hex-encoded code, a map of
// already-deployed library dependencies, replaces references to deployed library
// dependencies in the contract code, and returns the contract deployment bytecode on
// success.
func linkContract(contract string, linkedLibs map[string]common.Address) (deployableContract string, err error) {
reMatchSpecificPattern, err := regexp.Compile("__\\$([a-f0-9]+)\\$__")
if err != nil {
return "", err
}
// link in any library the contract depends on
for _, match := range reMatchSpecificPattern.FindAllStringSubmatch(contract, -1) {
matchingPattern := match[1]
addr := linkedLibs[matchingPattern]
contract = strings.ReplaceAll(contract, "__$"+matchingPattern+"$__", addr.String()[2:])
}
return contract, nil
}
// linkLibs iterates the set of dependencies that have yet to be
// linked/deployed (pending), replacing references to library dependencies
// (i.e. mutating pending) if those dependencies are fully linked/deployed
// (in 'linked').
//
// contracts that have become fully linked in the current invocation are
// returned.
func linkLibs(pending map[string]string, linked map[string]common.Address) (newPending map[string]string, deployableDeps map[string]string) {
newPending = make(map[string]string)
reMatchSpecificPattern, err := regexp.Compile("__\\$([a-f0-9]+)\\$__")
if err != nil {
panic(err)
}
reMatchAnyPattern, err := regexp.Compile("__\\$.*\\$__")
if err != nil {
panic(err)
}
deployableDeps = make(map[string]string)
for pattern, dep := range pending {
newPending[pattern] = dep
// link references to dependent libraries that have been deployed
for _, match := range reMatchSpecificPattern.FindAllStringSubmatch(newPending[pattern], -1) {
matchingPattern := match[1]
addr, ok := linked[matchingPattern]
if !ok {
continue
}
newPending[pattern] = strings.ReplaceAll(newPending[pattern], "__$"+matchingPattern+"$__", addr.String()[2:])
}
// if the library code became fully linked, move it from pending->linked.
if !reMatchAnyPattern.MatchString(newPending[pattern]) {
deployableDeps[pattern] = newPending[pattern]
delete(newPending, pattern)
}
}
return newPending, deployableDeps
}
// ContractDeployParams represents state needed to deploy a contract: // ContractDeployParams represents state needed to deploy a contract:
// the metdata and constructor input (which can be nil if no input is specified). // the metdata and constructor input (which can be nil if no input is specified).
type ContractDeployParams struct { type ContractDeployParams struct {
@ -172,6 +75,7 @@ type ContractDeployer interface {
type depTreeBuilder struct { type depTreeBuilder struct {
overrides map[string]common.Address overrides map[string]common.Address
libs map[string]string
} }
type depTreeNode struct { type depTreeNode struct {
pattern string pattern string
@ -179,8 +83,8 @@ type depTreeNode struct {
nodes []*depTreeNode nodes []*depTreeNode
} }
func (d *depTreeBuilder) buildDepTree(contractBin string, libs map[string]string) *depTreeNode { func (d *depTreeBuilder) buildDepTree(pattern string, contractBin string) *depTreeNode {
node := depTreeNode{contractBin, contractBin, nil} 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 {
@ -191,7 +95,7 @@ func (d *depTreeBuilder) buildDepTree(contractBin string, libs map[string]string
if _, ok := d.overrides[pattern]; ok { if _, ok := d.overrides[pattern]; ok {
continue continue
} }
node.nodes = append(node.nodes, d.buildDepTree(libs[pattern], libs)) node.nodes = append(node.nodes, d.buildDepTree(pattern, d.libs[pattern]))
} }
return &node return &node
} }
@ -247,9 +151,15 @@ func LinkAndDeploy(deployParams DeploymentParams, deploy func(input, deployer []
Addrs: make(map[string]common.Address), Addrs: make(map[string]common.Address),
} }
for _, contract := range deployParams.Contracts { for _, contract := range deployParams.Contracts {
treeBuilder := depTreeBuilder{deployParams.Overrides} if _, ok := deployParams.Overrides[contract.Meta.Pattern]; ok {
tree := treeBuilder.buildDepTree(contract.Meta.Bin, unlinkedLibs) continue
deployer := treeDeployer{deploy: deploy} }
treeBuilder := depTreeBuilder{deployParams.Overrides, unlinkedLibs}
tree := treeBuilder.buildDepTree(contract.Meta.Pattern, contract.Meta.Bin)
deployer := treeDeployer{
deploy: deploy,
deployedAddrs: make(map[string]common.Address),
deployerTxs: make(map[string]*types.Transaction)}
deployer.linkAndDeploy(tree) deployer.linkAndDeploy(tree)
res, err := deployer.Result() res, err := deployer.Result()
if err != nil { if err != nil {