From d43e3756838430474e8313181a642b3b549676ed Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 3 Feb 2025 07:13:30 -0800 Subject: [PATCH] accounts/abi: rename contract MetaData field 'Pattern' to 'Id'. This reflects the fact that this field will not hold the solidity library pattern when it cannot be defined (when not using the --combined-json option). Modify LinkAndDeploy to return an error upfront if any of the provided metadatas in the DeploymentParams do not have deployer code embedded. Add comment clarifying semantics of link and deploy wrt contracts that share common dependency libraries --- accounts/abi/abigen/source2.go.tpl | 4 +++- accounts/abi/bind/v2/base.go | 18 ++++++++++------- accounts/abi/bind/v2/dep_tree.go | 29 ++++++++++++++++++++------- accounts/abi/bind/v2/dep_tree_test.go | 8 ++++---- accounts/abi/bind/v2/lib_test.go | 18 ++++++++--------- 5 files changed, 49 insertions(+), 28 deletions(-) diff --git a/accounts/abi/abigen/source2.go.tpl b/accounts/abi/abigen/source2.go.tpl index 51f306d328..6112144f0f 100644 --- a/accounts/abi/abigen/source2.go.tpl +++ b/accounts/abi/abigen/source2.go.tpl @@ -37,7 +37,9 @@ var ( var {{.Type}}MetaData = bind.MetaData{ ABI: "{{.InputABI}}", {{if (index $.Libraries .Type) -}} - Pattern: "{{index $.Libraries .Type}}", + Id: "{{index $.Libraries .Type}}", + {{ else -}} + Id: "{{.Type}}", {{end -}} {{if .InputBin -}} Bin: "0x{{.InputBin}}", diff --git a/accounts/abi/bind/v2/base.go b/accounts/abi/bind/v2/base.go index 1cf04e66f7..50dd808e9a 100644 --- a/accounts/abi/bind/v2/base.go +++ b/accounts/abi/bind/v2/base.go @@ -93,14 +93,18 @@ type MetaData struct { ABI string // the raw ABI definition (JSON) Deps []*MetaData // library dependencies of the contract - // Solidity library placeholder name. This is a unique identifier of a contract within - // a compilation unit. The Pattern is used to link contracts during deployment using - // [LinkAndDeploy]. + // For bindings that were compiled from combined-json Id is the Solidity library pattern: a 34 character prefix + // of the hex encoding of the keccak256 + // hash of the fully qualified 'library name', i.e. the path of the source file. // - // The library pattern is a 34 character prefix of the hex encoding of the keccak256 - // hash of the fully qualified 'library name', i.e. the path of the source file - // containing the library code. - Pattern string + // For contracts compiled from the ABI definition alone, this is the type name of the contract (as specified + // in the ABI definition or overridden via the --type flag). + // + // This is a unique identifier of a contract within a compilation unit. When used as part of a multi-contract + // deployment with library dependencies, the Id is used to link + // contracts during deployment using + // [LinkAndDeploy]. + Id string mu sync.Mutex parsedABI *abi.ABI diff --git a/accounts/abi/bind/v2/dep_tree.go b/accounts/abi/bind/v2/dep_tree.go index aa74c3d858..633ce63d74 100644 --- a/accounts/abi/bind/v2/dep_tree.go +++ b/accounts/abi/bind/v2/dep_tree.go @@ -25,14 +25,24 @@ type DeploymentParams struct { Overrides map[string]common.Address } +// validate determines whether the contracts specified in the DeploymentParams instance have provided deployer bytecode. +func (d *DeploymentParams) validate() error { + for _, meta := range d.Contracts { + if meta.Bin == "" { + return fmt.Errorf("cannot deploy contract %s: deployer code missing from metadata", meta.Id) + } + } + return nil +} + // DeploymentResult encapsulates information about the result of the deployment // of a set of contracts: the pending deployment transactions, and the addresses // where the contracts will be deployed at. type DeploymentResult struct { - // map of contract library pattern -> deploy transaction + // map of contract MetaData Id to deploy transaction Txs map[string]*types.Transaction - // map of contract library pattern -> deployed address + // map of contract MetaData Id to deployed contract address Addrs map[string]common.Address } @@ -71,7 +81,7 @@ func newDepTreeDeployer(deployParams *DeploymentParams, deployFn DeployFn) *depT // The deployment result (deploy addresses/txs or an error) is stored in the depTreeDeployer object. func (d *depTreeDeployer) linkAndDeploy(metadata *MetaData) (common.Address, error) { // Don't deploy already deployed contracts - if addr, ok := d.deployedAddrs[metadata.Pattern]; ok { + if addr, ok := d.deployedAddrs[metadata.Id]; ok { return addr, nil } // if this contract/library depends on other libraries deploy them (and their dependencies) first @@ -82,19 +92,19 @@ func (d *depTreeDeployer) linkAndDeploy(metadata *MetaData) (common.Address, err return common.Address{}, err } // link their deployed addresses into the bytecode to produce - deployerCode = strings.ReplaceAll(deployerCode, "__$"+dep.Pattern+"$__", strings.ToLower(addr.String()[2:])) + deployerCode = strings.ReplaceAll(deployerCode, "__$"+dep.Id+"$__", strings.ToLower(addr.String()[2:])) } // Finally, deploy the contract. code, err := hex.DecodeString(deployerCode[2:]) if err != nil { panic(fmt.Sprintf("error decoding contract deployer hex %s:\n%v", deployerCode[2:], err)) } - addr, tx, err := d.deployFn(d.inputs[metadata.Pattern], code) + addr, tx, err := d.deployFn(d.inputs[metadata.Id], code) if err != nil { return common.Address{}, err } - d.deployedAddrs[metadata.Pattern] = addr - d.deployerTxs[metadata.Pattern] = tx + d.deployedAddrs[metadata.Id] = addr + d.deployerTxs[metadata.Id] = tx return addr, nil } @@ -115,7 +125,12 @@ func (d *depTreeDeployer) result() *DeploymentResult { // 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. +// +// In the case where multiple contracts share a common dependency: the shared dependency will only be deployed once. func LinkAndDeploy(deployParams *DeploymentParams, deploy DeployFn) (res *DeploymentResult, err error) { + if err := deployParams.validate(); err != nil { + return nil, err + } deployer := newDepTreeDeployer(deployParams, deploy) for _, contract := range deployParams.Contracts { if _, err := deployer.linkAndDeploy(contract); err != nil { diff --git a/accounts/abi/bind/v2/dep_tree_test.go b/accounts/abi/bind/v2/dep_tree_test.go index efb0c57410..5cc887f891 100644 --- a/accounts/abi/bind/v2/dep_tree_test.go +++ b/accounts/abi/bind/v2/dep_tree_test.go @@ -50,7 +50,7 @@ func copyMetaData(m *MetaData) *MetaData { Bin: m.Bin, ABI: m.ABI, Deps: deps, - Pattern: m.Pattern, + Id: m.Id, mu: sync.Mutex{}, parsedABI: m.parsedABI, } @@ -200,12 +200,12 @@ func testLinkCase(tcInput linkTestCaseInput) error { overrides := make(map[string]common.Address) for pattern, bin := range tc.contractCodes { - contracts[pattern] = &MetaData{Pattern: pattern, Bin: "0x" + bin} + contracts[pattern] = &MetaData{Id: pattern, Bin: "0x" + bin} } for pattern, bin := range tc.libCodes { contracts[pattern] = &MetaData{ - Bin: "0x" + bin, - Pattern: pattern, + Bin: "0x" + bin, + Id: pattern, } } diff --git a/accounts/abi/bind/v2/lib_test.go b/accounts/abi/bind/v2/lib_test.go index a88ab44c59..8dcddab93f 100644 --- a/accounts/abi/bind/v2/lib_test.go +++ b/accounts/abi/bind/v2/lib_test.go @@ -110,7 +110,7 @@ func TestDeploymentLibraries(t *testing.T) { constructorInput := c.PackConstructor(big.NewInt(42), big.NewInt(1)) deploymentParams := &bind.DeploymentParams{ Contracts: []*bind.MetaData{&nested_libraries.C1MetaData}, - Inputs: map[string][]byte{nested_libraries.C1MetaData.Pattern: constructorInput}, + Inputs: map[string][]byte{nested_libraries.C1MetaData.Id: constructorInput}, } res, err := bind.LinkAndDeploy(deploymentParams, makeTestDeployer(opts, bindBackend)) if err != nil { @@ -129,7 +129,7 @@ func TestDeploymentLibraries(t *testing.T) { } doInput := c.PackDo(big.NewInt(1)) - contractAddr := res.Addrs[nested_libraries.C1MetaData.Pattern] + contractAddr := res.Addrs[nested_libraries.C1MetaData.Id] callOpts := &bind.CallOpts{From: common.Address{}, Context: context.Background()} instance := c.Instance(bindBackend, contractAddr) internalCallCount, err := bind.Call(instance, callOpts, doInput, c.UnpackDo) @@ -177,7 +177,7 @@ func TestDeploymentWithOverrides(t *testing.T) { // deploy the contract deploymentParams = &bind.DeploymentParams{ Contracts: []*bind.MetaData{&nested_libraries.C1MetaData}, - Inputs: map[string][]byte{nested_libraries.C1MetaData.Pattern: constructorInput}, + Inputs: map[string][]byte{nested_libraries.C1MetaData.Id: constructorInput}, Overrides: overrides, } res, err = bind.LinkAndDeploy(deploymentParams, makeTestDeployer(opts, bindBackend)) @@ -198,7 +198,7 @@ func TestDeploymentWithOverrides(t *testing.T) { // call the deployed contract and make sure it returns the correct result doInput := c.PackDo(big.NewInt(1)) - instance := c.Instance(bindBackend, res.Addrs[nested_libraries.C1MetaData.Pattern]) + instance := c.Instance(bindBackend, res.Addrs[nested_libraries.C1MetaData.Id]) callOpts := new(bind.CallOpts) internalCallCount, err := bind.Call(instance, callOpts, doInput, c.UnpackDo) if err != nil { @@ -223,12 +223,12 @@ func TestEvents(t *testing.T) { } backend.Commit() - if _, err := bind.WaitDeployed(context.Background(), backend, res.Txs[events.CMetaData.Pattern].Hash()); err != nil { + if _, err := bind.WaitDeployed(context.Background(), backend, res.Txs[events.CMetaData.Id].Hash()); err != nil { t.Fatalf("WaitDeployed failed %v", err) } c := events.NewC() - instance := c.Instance(backend, res.Addrs[events.CMetaData.Pattern]) + instance := c.Instance(backend, res.Addrs[events.CMetaData.Id]) newCBasic1Ch := make(chan *events.CBasic1) newCBasic2Ch := make(chan *events.CBasic2) @@ -322,14 +322,14 @@ func TestErrors(t *testing.T) { } backend.Commit() - if _, err := bind.WaitDeployed(context.Background(), backend, res.Txs[solc_errors.CMetaData.Pattern].Hash()); err != nil { + if _, err := bind.WaitDeployed(context.Background(), backend, res.Txs[solc_errors.CMetaData.Id].Hash()); err != nil { t.Fatalf("WaitDeployed failed %v", err) } c := solc_errors.NewC() - instance := c.Instance(backend, res.Addrs[solc_errors.CMetaData.Pattern]) + instance := c.Instance(backend, res.Addrs[solc_errors.CMetaData.Id]) packedInput := c.PackFoo() - opts := &bind.CallOpts{From: res.Addrs[solc_errors.CMetaData.Pattern]} + opts := &bind.CallOpts{From: res.Addrs[solc_errors.CMetaData.Id]} _, err = bind.Call[struct{}](instance, opts, packedInput, nil) if err == nil { t.Fatalf("expected call to fail")