diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index f103910c17..bb7301e965 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -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 { diff --git a/accounts/abi/bind/template2.go b/accounts/abi/bind/template2.go index e63de89b95..9c85a466a7 100644 --- a/accounts/abi/bind/template2.go +++ b/accounts/abi/bind/template2.go @@ -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) { diff --git a/accounts/abi/bind/v2/lib.go b/accounts/abi/bind/v2/lib.go index eb049b77b6..f00df1b9e7 100644 --- a/accounts/abi/bind/v2/lib.go +++ b/accounts/abi/bind/v2/lib.go @@ -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) { diff --git a/accounts/abi/bind/v2/v2_test.go b/accounts/abi/bind/v2/v2_test.go index 61b5ac1844..9250093939 100644 --- a/accounts/abi/bind/v2/v2_test.go +++ b/accounts/abi/bind/v2/v2_test.go @@ -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