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

This commit is contained in:
Jared Wasinger 2025-02-03 07:13:30 -08:00
parent ddb9390bae
commit d43e375683
5 changed files with 49 additions and 28 deletions

View file

@ -37,7 +37,9 @@ var (
var {{.Type}}MetaData = bind.MetaData{ var {{.Type}}MetaData = bind.MetaData{
ABI: "{{.InputABI}}", ABI: "{{.InputABI}}",
{{if (index $.Libraries .Type) -}} {{if (index $.Libraries .Type) -}}
Pattern: "{{index $.Libraries .Type}}", Id: "{{index $.Libraries .Type}}",
{{ else -}}
Id: "{{.Type}}",
{{end -}} {{end -}}
{{if .InputBin -}} {{if .InputBin -}}
Bin: "0x{{.InputBin}}", Bin: "0x{{.InputBin}}",

View file

@ -93,14 +93,18 @@ type MetaData struct {
ABI string // the raw ABI definition (JSON) ABI string // the raw ABI definition (JSON)
Deps []*MetaData // library dependencies of the contract Deps []*MetaData // library dependencies of the contract
// Solidity library placeholder name. This is a unique identifier of a contract within // For bindings that were compiled from combined-json Id is the Solidity library pattern: a 34 character prefix
// a compilation unit. The Pattern is used to link contracts during deployment using // of the hex encoding of the keccak256
// [LinkAndDeploy]. // 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 // For contracts compiled from the ABI definition alone, this is the type name of the contract (as specified
// hash of the fully qualified 'library name', i.e. the path of the source file // in the ABI definition or overridden via the --type flag).
// containing the library code. //
Pattern string // 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 mu sync.Mutex
parsedABI *abi.ABI parsedABI *abi.ABI

View file

@ -25,14 +25,24 @@ type DeploymentParams struct {
Overrides map[string]common.Address 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 // DeploymentResult encapsulates information about the result of the deployment
// of a set of contracts: the pending deployment transactions, and the addresses // of a set of contracts: the pending deployment transactions, and the addresses
// where the contracts will be deployed at. // where the contracts will be deployed at.
type DeploymentResult struct { type DeploymentResult struct {
// map of contract library pattern -> deploy transaction // map of contract MetaData Id to deploy transaction
Txs map[string]*types.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 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. // The deployment result (deploy addresses/txs or an error) is stored in the depTreeDeployer object.
func (d *depTreeDeployer) linkAndDeploy(metadata *MetaData) (common.Address, error) { func (d *depTreeDeployer) linkAndDeploy(metadata *MetaData) (common.Address, error) {
// Don't deploy already deployed contracts // 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 return addr, nil
} }
// if this contract/library depends on other libraries deploy them (and their dependencies) first // 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 return common.Address{}, err
} }
// link their deployed addresses into the bytecode to produce // 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. // Finally, deploy the contract.
code, err := hex.DecodeString(deployerCode[2:]) code, err := hex.DecodeString(deployerCode[2:])
if err != nil { if err != nil {
panic(fmt.Sprintf("error decoding contract deployer hex %s:\n%v", deployerCode[2:], err)) 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 { if err != nil {
return common.Address{}, err return common.Address{}, err
} }
d.deployedAddrs[metadata.Pattern] = addr d.deployedAddrs[metadata.Id] = addr
d.deployerTxs[metadata.Pattern] = tx d.deployerTxs[metadata.Id] = tx
return addr, nil return addr, nil
} }
@ -115,7 +125,12 @@ func (d *depTreeDeployer) result() *DeploymentResult {
// LinkAndDeploy deploys a specified set of contracts and their dependent // LinkAndDeploy deploys a specified set of contracts and their dependent
// libraries. If an error occurs, only contracts which were successfully // libraries. If an error occurs, only contracts which were successfully
// deployed are returned in the result. // 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) { func LinkAndDeploy(deployParams *DeploymentParams, deploy DeployFn) (res *DeploymentResult, err error) {
if err := deployParams.validate(); err != nil {
return nil, err
}
deployer := newDepTreeDeployer(deployParams, deploy) deployer := newDepTreeDeployer(deployParams, deploy)
for _, contract := range deployParams.Contracts { for _, contract := range deployParams.Contracts {
if _, err := deployer.linkAndDeploy(contract); err != nil { if _, err := deployer.linkAndDeploy(contract); err != nil {

View file

@ -50,7 +50,7 @@ func copyMetaData(m *MetaData) *MetaData {
Bin: m.Bin, Bin: m.Bin,
ABI: m.ABI, ABI: m.ABI,
Deps: deps, Deps: deps,
Pattern: m.Pattern, Id: m.Id,
mu: sync.Mutex{}, mu: sync.Mutex{},
parsedABI: m.parsedABI, parsedABI: m.parsedABI,
} }
@ -200,12 +200,12 @@ func testLinkCase(tcInput linkTestCaseInput) error {
overrides := make(map[string]common.Address) overrides := make(map[string]common.Address)
for pattern, bin := range tc.contractCodes { 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 { for pattern, bin := range tc.libCodes {
contracts[pattern] = &MetaData{ contracts[pattern] = &MetaData{
Bin: "0x" + bin, Bin: "0x" + bin,
Pattern: pattern, Id: pattern,
} }
} }

View file

@ -110,7 +110,7 @@ func TestDeploymentLibraries(t *testing.T) {
constructorInput := c.PackConstructor(big.NewInt(42), big.NewInt(1)) constructorInput := c.PackConstructor(big.NewInt(42), big.NewInt(1))
deploymentParams := &bind.DeploymentParams{ deploymentParams := &bind.DeploymentParams{
Contracts: []*bind.MetaData{&nested_libraries.C1MetaData}, 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)) res, err := bind.LinkAndDeploy(deploymentParams, makeTestDeployer(opts, bindBackend))
if err != nil { if err != nil {
@ -129,7 +129,7 @@ func TestDeploymentLibraries(t *testing.T) {
} }
doInput := c.PackDo(big.NewInt(1)) 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()} callOpts := &bind.CallOpts{From: common.Address{}, Context: context.Background()}
instance := c.Instance(bindBackend, contractAddr) instance := c.Instance(bindBackend, contractAddr)
internalCallCount, err := bind.Call(instance, callOpts, doInput, c.UnpackDo) internalCallCount, err := bind.Call(instance, callOpts, doInput, c.UnpackDo)
@ -177,7 +177,7 @@ func TestDeploymentWithOverrides(t *testing.T) {
// deploy the contract // deploy the contract
deploymentParams = &bind.DeploymentParams{ deploymentParams = &bind.DeploymentParams{
Contracts: []*bind.MetaData{&nested_libraries.C1MetaData}, 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, Overrides: overrides,
} }
res, err = bind.LinkAndDeploy(deploymentParams, makeTestDeployer(opts, bindBackend)) 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 // call the deployed contract and make sure it returns the correct result
doInput := c.PackDo(big.NewInt(1)) 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) callOpts := new(bind.CallOpts)
internalCallCount, err := bind.Call(instance, callOpts, doInput, c.UnpackDo) internalCallCount, err := bind.Call(instance, callOpts, doInput, c.UnpackDo)
if err != nil { if err != nil {
@ -223,12 +223,12 @@ func TestEvents(t *testing.T) {
} }
backend.Commit() 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) t.Fatalf("WaitDeployed failed %v", err)
} }
c := events.NewC() 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) newCBasic1Ch := make(chan *events.CBasic1)
newCBasic2Ch := make(chan *events.CBasic2) newCBasic2Ch := make(chan *events.CBasic2)
@ -322,14 +322,14 @@ func TestErrors(t *testing.T) {
} }
backend.Commit() 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) t.Fatalf("WaitDeployed failed %v", err)
} }
c := solc_errors.NewC() 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() 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) _, err = bind.Call[struct{}](instance, opts, packedInput, nil)
if err == nil { if err == nil {
t.Fatalf("expected call to fail") t.Fatalf("expected call to fail")