staging changes for linking tests. rework LinkAndDeploy to decouple logic of contract deployment, by passing a callback that does it.

This commit is contained in:
Jared Wasinger 2024-12-05 17:32:24 +07:00 committed by Felix Lange
parent ba68e747b5
commit c8b17ca9c4
3 changed files with 125 additions and 16 deletions

View file

@ -0,0 +1,98 @@
package bind
import (
v2 "github.com/ethereum/go-ethereum/accounts/abi/bind/v2"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"strings"
"testing"
)
type linkTestCase struct {
// map of a library to the order in which its dependencies appear in the EVM bytecode.
codes map[string]string
}
func makeLinkTestCase(input map[rune][]rune) *linkTestCase {
codes := make(map[string]string)
inputMap := make(map[rune]map[rune]struct{})
for contract, deps := range input {
inputMap[contract] = make(map[rune]struct{})
for _, dep := range deps {
codes[string(contract)] = codes[string(contract)] + string(dep)
inputMap[contract][dep] = struct{}{}
}
}
return &linkTestCase{
generateUnlinkedContracts(codes),
}
}
func generateUnlinkedContracts(inputCodes map[string]string) map[string]string {
// map of solidity library pattern to unlinked code
codes := make(map[string]string)
for name, code := range inputCodes {
var prelinkCode []string
for _, char := range code {
prelinkCode = append(prelinkCode, crypto.Keccak256Hash([]byte(string(char))).String()[2:36])
}
pattern := crypto.Keccak256Hash([]byte(string(name))).String()[2:36]
codes[pattern] = strings.Join(prelinkCode, "")
}
return codes
}
var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
func testLinkCase(t *testing.T, input map[rune][]rune, overrides map[rune]common.Address) {
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
var testAddrNonce uint64
tc := makeLinkTestCase(input)
alreadyDeployed := make(map[common.Address]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++
// assert that this contract only references libs that are known to be deployed or in the override set
for i := 0; i < len(deployer)/20; i += 20 {
var dep common.Address
dep.SetBytes(deployer[i : i+20])
if _, ok := alreadyDeployed[dep]; !ok {
t.Fatalf("reference to dependent contract that has not yet been deployed.")
}
}
alreadyDeployed[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
}
doTest := func() {
// convert the raw test case into working form
deployParams := v2.DeploymentParams{
Contracts: nil,
Libraries: nil,
Overrides: nil,
}
res, err := v2.LinkAndDeploy(deployParams, mockDeploy)
if err != nil {
t.Fatalf("got error from LinkAndDeploy: %v\n", err)
}
// assert that res contains everything we expected to be deployed
}
}
func TestContractLinking(t *testing.T) {
//test-case specific values (TODO: move these, mockDeploy, doTest into their own routines).
// input: a cycle of dependencies.
// expected: [{set of deps deployed first}, {set of deps deployed second}, ..., {contract(s) deployed}]
}

View file

@ -31,32 +31,31 @@ import (
// deployContract deploys a hex-encoded contract with the given constructor
// input. It returns the deployment transaction, address on success.
func deployContract(backend bind.ContractBackend, auth *bind.TransactOpts, constructor []byte, contract string) (deploymentTx *types.Transaction, deploymentAddr common.Address, err error) {
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 nil, common.Address{}, fmt.Errorf("contract bytecode is not a hex string: %s", contractBinBytes[2:])
return common.Address{}, nil, fmt.Errorf("contract bytecode is not a hex string: %s", contractBinBytes[2:])
}
addr, tx, _, err := bind.DeployContractRaw(auth, contractBinBytes, backend, constructor)
addr, tx, err := deploy(constructor, contractBinBytes)
if err != nil {
return nil, common.Address{}, fmt.Errorf("failed to deploy contract: %v", err)
return common.Address{}, nil, fmt.Errorf("failed to deploy contract: %v", err)
}
return tx, addr, nil
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(backend bind.ContractBackend, auth *bind.TransactOpts, contracts map[string]string) (deploymentTxs map[common.Address]*types.Transaction, deployAddrs map[string]common.Address, err error) {
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 {
contractBinBytes, err := hex.DecodeString(contractBin[2:])
contractDeployer, err := hex.DecodeString(contractBin[2:])
if err != nil {
return deploymentTxs, deployAddrs, fmt.Errorf("contract bytecode is not a hex string: %s", contractBin[2:])
}
// TODO: can pass nil for constructor?
addr, tx, _, err := bind.DeployContractRaw(auth, contractBinBytes, backend, []byte{})
addr, tx, err := deploy([]byte{}, contractDeployer)
if err != nil {
return deploymentTxs, deployAddrs, fmt.Errorf("failed to deploy contract: %v", err)
}
@ -155,10 +154,14 @@ type DeploymentResult struct {
Addrs map[string]common.Address
}
type ContractDeployer interface {
DeployContract(input []byte, deployer []byte) (common.Address, *types.Transaction, error)
}
// 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(auth *bind.TransactOpts, backend bind.ContractBackend, deployParams DeploymentParams) (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
overrides := deployParams.Overrides
@ -188,7 +191,7 @@ func LinkAndDeploy(auth *bind.TransactOpts, backend bind.ContractBackend, deploy
if len(deployableDeps) == 0 {
break
}
deployTxs, deployAddrs, err := deployLibs(backend, auth, deployableDeps)
deployTxs, deployAddrs, err := deployLibs(deployableDeps, deploy)
for pattern, addr := range deployAddrs {
deployed[pattern] = addr
res.Addrs[pattern] = addr
@ -205,7 +208,7 @@ func LinkAndDeploy(auth *bind.TransactOpts, backend bind.ContractBackend, deploy
if err != nil {
return res, err
}
contractTx, contractAddr, err := deployContract(backend, auth, contractParams.Input, linkedContract)
contractAddr, contractTx, err := deployContract(contractParams.Input, linkedContract, deploy)
if err != nil {
return res, err
}

View file

@ -96,6 +96,13 @@ func testSetup() (*bind.TransactOpts, *backends.SimulatedBackend, error) {
return opts, &bindBackend, nil
}
func makeTestDeployer(auth *bind.TransactOpts, backend bind.ContractBackend) func(input, deployer []byte) (common.Address, *types.Transaction, error) {
return func(input, deployer []byte) (common.Address, *types.Transaction, error) {
addr, tx, _, err := bind.DeployContractRaw(auth, deployer, backend, input)
return addr, tx, err
}
}
// test that deploying a contract with library dependencies works,
// verifying by calling method on the deployed contract.
func TestDeploymentLibraries(t *testing.T) {
@ -124,7 +131,8 @@ func TestDeploymentLibraries(t *testing.T) {
Libraries: nested_libraries.C1LibraryDeps,
Overrides: nil,
}
res, err := LinkAndDeploy(opts, bindBackend, deploymentParams)
res, err := LinkAndDeploy(deploymentParams, makeTestDeployer(opts, bindBackend))
if err != nil {
t.Fatalf("err: %+v\n", err)
}
@ -181,7 +189,7 @@ func TestDeploymentWithOverrides(t *testing.T) {
Libraries: nested_libraries.C1LibraryDeps,
}
res, err := LinkAndDeploy(opts, bindBackend, deploymentParams)
res, err := LinkAndDeploy(deploymentParams, makeTestDeployer(opts, bindBackend))
if err != nil {
t.Fatalf("err: %+v\n", err)
}
@ -217,7 +225,7 @@ func TestDeploymentWithOverrides(t *testing.T) {
Libraries: nil,
Overrides: overrides,
}
res, err = LinkAndDeploy(opts, bindBackend, deploymentParams)
res, err = LinkAndDeploy(deploymentParams, makeTestDeployer(opts, bindBackend))
if err != nil {
t.Fatalf("err: %+v\n", err)
}
@ -280,7 +288,7 @@ func TestEvents(t *testing.T) {
},
}
res, err := LinkAndDeploy(txAuth, backend, deploymentParams)
res, err := LinkAndDeploy(deploymentParams, makeTestDeployer(txAuth, backend))
if err != nil {
t.Fatalf("error deploying contract for testing: %v", err)
}