commit of wip-code: rework the contract deployment code to be cleaner

This commit is contained in:
Jared Wasinger 2024-11-24 19:35:16 +07:00
parent cd86fca55c
commit 0df73f3fac
4 changed files with 152 additions and 52 deletions

View File

@ -151,9 +151,10 @@ func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend Co
return c.address, tx, c, nil
}
func DeployContractRaw(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend ContractBackend, packedParams []byte) (common.Address, *types.Transaction, *BoundContract, error) {
// Otherwise try to deploy the contract
c := NewBoundContract(common.Address{}, abi, backend, backend, backend)
func DeployContractRaw(opts *TransactOpts, bytecode []byte, backend ContractBackend, packedParams []byte) (common.Address, *types.Transaction, *BoundContract, error) {
// TODO: it's weird to instantiate a bound contract (implies existence of contract) in order to deploy a contract
// that doesn't yet exist
c := NewBoundContract(common.Address{}, abi.ABI{}, backend, backend, backend)
tx, err := c.transact(opts, nil, append(bytecode, packedParams...))
if err != nil {

View File

@ -39,8 +39,8 @@ var (
{{range $contract := .Contracts}}
var {{$contract.Type}}LibraryDeps = map[string]*bind.MetaData{
{{range $pattern, $name := .AllLibraries -}}
"{{$pattern}}": &{{$name}}MetaData,
{{range $name, $pattern := .AllLibraries -}}
"{{$pattern}}": {{$name}}MetaData,
{{ end}}
}
@ -58,18 +58,9 @@ var (
{{end}}
}
func (i *{{$contract.Type}}Instance) Address() common.Address {
return i.address
}
func (i *{{$contract.Type}}Instance) Backend() bind.ContractBackend {
return i.backend
}
// {{.Type}} is an auto generated Go binding around an Ethereum contract.
type {{.Type}} struct {
abi abi.ABI
deployCode []byte
}
// New{{.Type}} creates a new instance of {{.Type}}.
@ -78,13 +69,10 @@ var (
if err != nil {
return nil, err
}
code := common.Hex2Bytes({{.Type}}MetaData.Bin)
return &{{.Type}}{abi: *parsed, deployCode: code}, nil
return &{{.Type}}{abi: *parsed}, nil
}
func (_{{$contract.Type}} *{{$contract.Type}}) DeployCode() []byte {
return _{{$contract.Type}}.deployCode
}
// TODO: create custom exported types where unpack would generate a struct return.
// TODO: test constructor with inputs
func (_{{$contract.Type}} *{{$contract.Type}}) PackConstructor({{range .Constructor.Inputs}} {{.Name}} {{bindtype .Type $structs}} {{end}}) ([]byte, error) {

View File

@ -1,6 +1,7 @@
package v2
import (
"encoding/hex"
"fmt"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
@ -17,49 +18,101 @@ type ContractInstance struct {
Backend bind.ContractBackend
}
func DeployContracts(auth *bind.TransactOpts, backend bind.ContractBackend, constructorInput []byte, contracts map[string]*bind.MetaData) {
// match if the contract has dynamic libraries that need to be linked
hasDepsMatcher, err := regexp.Compile("__\\$.*\\$__")
func deployDeps(backend bind.ContractBackend, auth *bind.TransactOpts, constructorInputs map[string][]byte, contracts map[string]string) (deploymentTxs map[common.Address]*types.Transaction, deployAddrs map[common.Address]struct{}, err error) {
for pattern, contractBin := range contracts {
contractBinBytes, err := hex.DecodeString(contractBin[2:])
if err != nil {
return deploymentTxs, deployAddrs, fmt.Errorf("contract bytecode is not a hex string: %s", contractBin[2:])
}
var constructorInput []byte
if inp, ok := constructorInputs[pattern]; ok {
constructorInput = inp
} else {
constructorInput = make([]byte, 0) // TODO check if we can pass a nil byte slice here.
}
addr, tx, _, err := bind.DeployContractRaw(auth, contractBinBytes, backend, constructorInput)
if err != nil {
return deploymentTxs, deployAddrs, fmt.Errorf("failed to deploy contract: %v", err)
}
deploymentTxs[addr] = tx
deployAddrs[addr] = struct{}{}
}
return deploymentTxs, deployAddrs, nil
}
func linkDeps(deps *map[string]string, linked *map[string]common.Address) (deployableDeps 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)
// deps we are linking
wipDeps := make(map[string]string)
for id, metadata := range contracts {
wipDeps[id] = metadata.Bin
for pattern, dep := range *deps {
// attempt to replace references to every single linked dep
for _, match := range reMatchSpecificPattern.FindAllStringSubmatch(dep, -1) {
matchingPattern := match[1]
addr, ok := (*linked)[matchingPattern]
if !ok {
continue
}
(*deps)[pattern] = strings.ReplaceAll(dep, matchingPattern, addr.String())
}
// if we linked something into this dep, see if it can be deployed
if !reMatchAnyPattern.MatchString((*deps)[pattern]) {
deployableDeps[pattern] = (*deps)[pattern]
delete(*deps, pattern)
}
}
// nested iteration: find contracts without library dependencies first,
// deploy them, link them into any other contracts that depend on them.
// repeat this until there are no more contracts to link/deploy
for {
for id, contractBin := range wipDeps {
if !hasDepsMatcher.MatchString(contractBin) {
// this library/contract doesn't depend on any others
// it can be deployed as-is.
abi, err := contracts[id].GetAbi()
if err != nil {
panic(err)
}
addr, _, _, err := bind.DeployContractRaw(auth, *abi, []byte(contractBin), backend, constructorInput)
if err != nil {
panic(err)
}
delete(wipDeps, id)
return deployableDeps
}
// embed the address of the deployed contract into any
// libraries/contracts that depend on it.
for id, contractBin := range wipDeps {
contractBin = strings.ReplaceAll(contractBin, fmt.Sprintf("__$%s%__", id), fmt.Sprintf("__$%s$__", addr.String()))
wipDeps[id] = contractBin
}
}
func LinkAndDeployContractsWithOverride(auth *bind.TransactOpts, backend bind.ContractBackend, constructorInputs map[string][]byte, contracts, libs map[string]string, overrides map[string]common.Address) (allDeployTxs map[common.Address]*types.Transaction, allDeployAddrs map[common.Address]struct{}, err error) {
var depsToDeploy map[string]string // map of pattern -> unlinked binary for deps we will deploy
// initialize the set of already-deployed contracts with given override addresses
linked := make(map[string]common.Address)
for pattern, deployAddr := range overrides {
linked[pattern] = deployAddr
if _, ok := contracts[pattern]; ok {
delete(contracts, pattern)
}
if len(wipDeps) == 0 {
}
// link and deploy dynamic libraries
for {
deployableDeps := linkDeps(&depsToDeploy, &linked)
if len(deployableDeps) == 0 {
break
}
deployTxs, deployAddrs, err := deployDeps(backend, auth, constructorInputs, deployableDeps)
for addr, _ := range deployAddrs {
allDeployAddrs[addr] = struct{}{}
}
for addr, tx := range deployTxs {
allDeployTxs[addr] = tx
}
if err != nil {
return deployTxs, allDeployAddrs, err
}
}
// link and deploy the contracts
contractBins := make(map[string]string)
linkedContracts := linkDeps(&contractBins, &linked)
deployTxs, deployAddrs, err := deployDeps(backend, auth, constructorInputs, linkedContracts)
for addr, _ := range deployAddrs {
allDeployAddrs[addr] = struct{}{}
}
for addr, tx := range deployTxs {
allDeployTxs[addr] = tx
}
return allDeployTxs, allDeployAddrs, err
}
func FilterLogs[T any](instance *ContractInstance, opts *bind.FilterOpts, eventID common.Hash, unpack func(*types.Log) (*T, error), topics ...[]any) (*EventIterator[T], error) {

View File

@ -3,11 +3,15 @@ package v2
import (
"context"
"encoding/json"
"fmt"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/accounts/abi/bind/testdata/v2_generated_testcase"
"github.com/ethereum/go-ethereum/accounts/abi/bind/testdata/v2_testcase_library"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"io"
"os"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
@ -116,7 +120,61 @@ func TestV2(t *testing.T) {
}
func TestDeployment(t *testing.T) {
DeployContracts
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
backend := simulated.NewBackend(
types.GenesisAlloc{
testAddr: {Balance: big.NewInt(10000000000000000)},
},
func(nodeConf *node.Config, ethConf *ethconfig.Config) {
ethConf.Genesis.Difficulty = big.NewInt(0)
},
)
defer backend.Close()
_, err := JSON(strings.NewReader(v2_generated_testcase.V2GeneratedTestcaseMetaData.ABI))
if err != nil {
panic(err)
}
signer := types.LatestSigner(params.AllDevChainProtocolChanges)
opts := bind.TransactOpts{
From: testAddr,
Nonce: nil,
Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) {
signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey)
if err != nil {
t.Fatal(err)
}
signedTx, err := tx.WithSignature(signer, signature)
if err != nil {
t.Fatal(err)
}
return signedTx, nil
},
Context: context.Background(),
}
// we should just be able to use the backend directly, instead of using
// this deprecated interface. However, the simulated backend no longer
// implements backends.SimulatedBackend...
bindBackend := backends.SimulatedBackend{
Backend: backend,
Client: backend.Client(),
}
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stdout, log.LevelDebug, true)))
///LinkAndDeployContractsWithOverride(&opts, bindBackend, v2_test)
deployTxs, err := DeployContracts(&opts, bindBackend, []byte{}, v2_testcase_library.TestArrayLibraryDeps)
if err != nil {
t.Fatalf("err: %+v\n", err)
}
for _, tx := range deployTxs {
fmt.Println("waiting for deployment")
_, err = bind.WaitDeployed(context.Background(), &bindBackend, tx)
if err != nil {
t.Fatalf("error deploying bound contract: %+v", err)
}
}
}
/* test-cases that should be extracted from v1 tests