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)
var testAddrNonce uint64
alreadyDeployed := make(map[common.Address]struct{})
overridesAddrs := make(map[common.Address]struct{})
// generate deterministic addresses for the override set.
rand.Seed(42)
@ -83,7 +83,7 @@ func testLinkCase(t *testing.T, input map[rune][]rune, overrides map[rune]struct
var addr common.Address
rand.Read(addr[:])
overrideAddrs[contract] = addr
alreadyDeployed[addr] = struct{}{}
overridesAddrs[addr] = struct{}{}
}
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) {
contractAddr := crypto.CreateAddress(testAddr, 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 {
var dep common.Address
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)
}
}
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
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)
}
// TODO: assert that the result consists of the input contracts minus the overrides.
if len(res.Addrs) != len(allContracts)-len(overrides) {
for val, _ := range allContracts {
fmt.Println(string(val))
if len(res.Addrs) != len(expectDeployed) {
t.Fatalf("got %d deployed contracts. expected %d.\n", len(res.Addrs), len(expectDeployed))
}
for contract, _ := range expectDeployed {
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.
@ -164,19 +162,29 @@ func testLinkCase(t *testing.T, input map[rune][]rune, overrides map[rune]struct
}
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{
'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
testLinkCase(t, map[rune][]rune{
'a': {}},
map[rune]struct{}{})
map[rune]struct{}{},
map[rune]struct{}{
'a': {},
})
// test that libraries at different levels of the tree can share deps,
// and that these shared deps will only be deployed once.
@ -184,27 +192,60 @@ func TestContractLinking(t *testing.T) {
'a': {'b', 'c', 'd', 'e'},
'e': {'f', 'g', 'h', 'i', '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
testLinkCase(t, map[rune][]rune{
'a': {'b', 'c', 'd', 'e'},
'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
testLinkCase(t, map[rune][]rune{
'a': {'b', 'c', 'd', 'e'},
'f': {'g', 'c', 'd', 'j'}},
map[rune]struct{}{})
'f': {'g', 'c', 'd', 'h'}},
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{
'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
testLinkCase(t, map[rune][]rune{
'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
import (
"encoding/hex"
"fmt"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
@ -29,101 +27,6 @@ import (
"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:
// the metdata and constructor input (which can be nil if no input is specified).
type ContractDeployParams struct {
@ -172,6 +75,7 @@ type ContractDeployer interface {
type depTreeBuilder struct {
overrides map[string]common.Address
libs map[string]string
}
type depTreeNode struct {
pattern string
@ -179,8 +83,8 @@ type depTreeNode struct {
nodes []*depTreeNode
}
func (d *depTreeBuilder) buildDepTree(contractBin string, libs map[string]string) *depTreeNode {
node := depTreeNode{contractBin, contractBin, nil}
func (d *depTreeBuilder) buildDepTree(pattern string, contractBin string) *depTreeNode {
node := depTreeNode{pattern, contractBin, nil}
reMatchSpecificPattern, err := regexp.Compile("__\\$([a-f0-9]+)\\$__")
if err != nil {
@ -191,7 +95,7 @@ func (d *depTreeBuilder) buildDepTree(contractBin string, libs map[string]string
if _, ok := d.overrides[pattern]; ok {
continue
}
node.nodes = append(node.nodes, d.buildDepTree(libs[pattern], libs))
node.nodes = append(node.nodes, d.buildDepTree(pattern, d.libs[pattern]))
}
return &node
}
@ -247,9 +151,15 @@ func LinkAndDeploy(deployParams DeploymentParams, deploy func(input, deployer []
Addrs: make(map[string]common.Address),
}
for _, contract := range deployParams.Contracts {
treeBuilder := depTreeBuilder{deployParams.Overrides}
tree := treeBuilder.buildDepTree(contract.Meta.Bin, unlinkedLibs)
deployer := treeDeployer{deploy: deploy}
if _, ok := deployParams.Overrides[contract.Meta.Pattern]; ok {
continue
}
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)
res, err := deployer.Result()
if err != nil {