Skip to content

Commit

Permalink
commit of wip-code: rework the contract deployment code to be cleaner
Browse files Browse the repository at this point in the history
  • Loading branch information
jwasinger committed Nov 24, 2024
1 parent cd86fca commit 0df73f3
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 53 deletions.
7 changes: 4 additions & 3 deletions accounts/abi/bind/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
20 changes: 4 additions & 16 deletions accounts/abi/bind/template2.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

119 changes: 86 additions & 33 deletions accounts/abi/bind/v2/lib.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package v2

import (
"encoding/hex"
"fmt"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
Expand All @@ -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)
}

// deps we are linking
wipDeps := make(map[string]string)
for id, metadata := range contracts {
wipDeps[id] = metadata.Bin
reMatchAnyPattern, err := regexp.Compile("__\\$.*\\$__")
if err != nil {
panic(err)
}
deployableDeps = make(map[string]string)

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) {
Expand Down
60 changes: 59 additions & 1 deletion accounts/abi/bind/v2/v2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 0df73f3

Please sign in to comment.