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")