From c88e3874bba2ba9e72befc96b2b8e788c5dfd289 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 10 Feb 2025 17:26:47 -0800 Subject: [PATCH] accounts/abi: attempt to improve documentation throughout the PR. Make the method docs in generated bindings consistent. --- accounts/abi/abigen/bindv2.go | 39 ++++++++++++++------- accounts/abi/abigen/source2.go.tpl | 8 ++--- accounts/abi/abigen/template.go | 4 +-- accounts/abi/bind/v2/base.go | 8 +++-- accounts/abi/bind/v2/dep_tree.go | 56 +++++++++++++++++------------- 5 files changed, 69 insertions(+), 46 deletions(-) diff --git a/accounts/abi/abigen/bindv2.go b/accounts/abi/abigen/bindv2.go index d730b4fb16..b5583b44c9 100644 --- a/accounts/abi/abigen/bindv2.go +++ b/accounts/abi/abigen/bindv2.go @@ -31,8 +31,9 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" ) -// underlyingBindType returns the underlying Go type represented by the given -// type, panicking if it is not a pointer type. +// underlyingBindType returns a string representation of the Go type +// that corresponds to the given ABI type, panicking if it is not a +// pointer. func underlyingBindType(typ abi.Type) string { goType := typ.GetType() if goType.Kind() != reflect.Pointer { @@ -41,11 +42,12 @@ func underlyingBindType(typ abi.Type) string { return goType.Elem().String() } -// isPointerType returns true if the +// isPointerType returns true if the underlying type is a pointer. func isPointerType(typ abi.Type) bool { return typ.GetType().Kind() == reflect.Pointer } +// OLD: // binder is used during the conversion of an ABI definition into Go bindings // (as part of the execution of BindV2). In contrast to contractBinder, binder // contains binding-generation-state that is shared between contracts: @@ -63,6 +65,18 @@ func isPointerType(typ abi.Type) bool { // instantiated to produce a set of tmplContractV2 and tmplStruct objects from // the provided ABI definition. These are used as part of the input to rendering // the binding template. + +// NEW: +// binder is used to translate an ABI definition into a set of data-structures +// that will be used to render the template and produce Go bindings. This can +// be thought of as the "backend" that sanitizes the ABI definition to a format +// that can be directly rendered with minimal complexity in the template. +// +// The input data to the template rendering consists of: +// - the set of all contracts requested for binding, each containing +// methods/events/errors to emit pack/unpack methods for. +// - the set of structures defined by the contracts, and created +// as part of the binding process. type binder struct { // contracts is the map of each individual contract requested binding. // It is keyed by the contract name provided in the ABI definition. @@ -79,7 +93,7 @@ type binder struct { aliases map[string]string } -// BindStructType register the type to be emitted as a struct in the +// BindStructType registers the type to be emitted as a struct in the // bindings. func (b *binder) BindStructType(typ abi.Type) { bindStructType(typ, b.structs) @@ -87,8 +101,7 @@ func (b *binder) BindStructType(typ abi.Type) { // contractBinder holds state for binding of a single contract. It is a type // registry for compiling maps of identifiers that will be emitted in generated -// bindings. It also sanitizes/converts information contained in the ABI -// definition into a data-format that is amenable for rendering the templates. +// bindings. type contractBinder struct { binder *binder @@ -114,8 +127,8 @@ func newContractBinder(binder *binder) *contractBinder { } } -// registerIdentifier applies alias renaming, name normalization (conversion to -// camel case), and registers the normalized name in the specified identifier map. +// registerIdentifier applies alias renaming, name normalization (conversion +// from snake to camel-case), and registers the normalized name in the specified identifier map. // It returns an error if the normalized name already exists in the map. func (cb *contractBinder) registerIdentifier(identifiers map[string]bool, original string) (normalized string, err error) { normalized = abi.ToCamelCase(alias(cb.binder.aliases, original)) @@ -138,7 +151,7 @@ func (cb *contractBinder) registerIdentifier(identifiers map[string]bool, origin // bindMethod registers a method to be emitted in the bindings. The name, inputs // and outputs are normalized. If any inputs are struct-type their structs are // registered to be emitted in the bindings. Any methods that return more than -// one output have their result coalesced into a struct. +// one output have their results gathered into a struct. func (cb *contractBinder) bindMethod(original abi.Method) error { normalized := original normalizedName, err := cb.registerIdentifier(cb.callIdentifiers, original.Name) @@ -164,7 +177,7 @@ func (cb *contractBinder) bindMethod(original abi.Method) error { } isStructured := structured(original.Outputs) - // If the call returns multiple values, coalesced them into a struct + // If the call returns multiple values, gather them into a struct if len(normalized.Outputs) > 1 { isStructured = true } @@ -202,8 +215,8 @@ func normalizeArgs(args abi.Arguments) abi.Arguments { return args } -// normalizeErrorOrEventFields normalizes errors/events for emitting through bindings: -// Any anonymous fields are given generated names. +// normalizeErrorOrEventFields normalizes errors/events for emitting through +// bindings: Any anonymous fields are given generated names. func (cb *contractBinder) normalizeErrorOrEventFields(originalInputs abi.Arguments) abi.Arguments { normalizedArguments := normalizeArgs(originalInputs) for _, input := range normalizedArguments { @@ -246,6 +259,8 @@ func (cb *contractBinder) bindError(original abi.Error) error { return nil } +// parseLibraryDeps extracts references to library dependencies from the unlinked +// hex string deployment bytecode. func parseLibraryDeps(unlinkedCode string) (res []string) { reMatchSpecificPattern, err := regexp.Compile(`__\$([a-f0-9]+)\$__`) if err != nil { diff --git a/accounts/abi/abigen/source2.go.tpl b/accounts/abi/abigen/source2.go.tpl index ad6ee39f18..c809b4c217 100644 --- a/accounts/abi/abigen/source2.go.tpl +++ b/accounts/abi/abigen/source2.go.tpl @@ -89,8 +89,8 @@ var ( {{ end }} {{range .Calls}} - // {{.Normalized.Name}} is the Go binding used to pack the parameters required for calling - // the contract method 0x{{printf "%x" .Original.ID}}. + // Pack{{.Normalized.Name}} is the Go binding used to pack the parameters required for calling + // the contract method with ID 0x{{printf "%x" .Original.ID}}. // // Solidity: {{.Original.String}} func ({{ decapitalise $contract.Type}} *{{$contract.Type}}) Pack{{.Normalized.Name}}({{range .Normalized.Inputs}} {{.Name}} {{bindtype .Type $structs}}, {{end}}) []byte { @@ -152,7 +152,7 @@ var ( {{end}} {{range .Events}} - // {{$contract.Type}}{{.Normalized.Name}} represents a {{.Normalized.Name}} event raised by the {{$contract.Type}} contract. + // {{$contract.Type}}{{.Normalized.Name}} represents a {{.Original.Name}} event raised by the {{$contract.Type}} contract. type {{$contract.Type}}{{.Normalized.Name}} struct { {{- range .Normalized.Inputs}} {{ capitalise .Name}} @@ -211,7 +211,7 @@ var ( {{ end }} {{range .Errors}} - // {{$contract.Type}}{{.Normalized.Name}} represents a {{.Normalized.Name}} error raised by the {{$contract.Type}} contract. + // {{$contract.Type}}{{.Normalized.Name}} represents a {{.Original.Name}} error raised by the {{$contract.Type}} contract. type {{$contract.Type}}{{.Normalized.Name}} struct { {{range .Normalized.Inputs}} {{capitalise .Name}} {{if .Indexed}}{{bindtopictype .Type $structs}}{{else}}{{bindtype .Type $structs}}{{end}}; {{end}} } diff --git a/accounts/abi/abigen/template.go b/accounts/abi/abigen/template.go index 9b918e3fd6..cbb21037a6 100644 --- a/accounts/abi/abigen/template.go +++ b/accounts/abi/abigen/template.go @@ -80,8 +80,8 @@ func newTmplContractV2(typ string, abiStr string, bytecode string, constructor a } type tmplDataV2 struct { - Package string // Name of the package to place the generated file in - Contracts map[string]*tmplContractV2 // List of contracts to generate into this file + Package string // Name of the package to use for the generated bindings + Contracts map[string]*tmplContractV2 // Contracts that will be emitted in the bindings (keyed by contract name) Libraries map[string]string // Map of the contract's name to link pattern Structs map[string]*tmplStruct // Contract struct type definitions } diff --git a/accounts/abi/bind/v2/base.go b/accounts/abi/bind/v2/base.go index 16e7101e7f..a9b667e697 100644 --- a/accounts/abi/bind/v2/base.go +++ b/accounts/abi/bind/v2/base.go @@ -89,7 +89,7 @@ type WatchOpts struct { // MetaData collects all metadata for a bound contract. type MetaData struct { - Bin string // runtime bytecode (as a hex string) + Bin string // deployer bytecode (as a hex string) ABI string // the raw ABI definition (JSON) Deps []*MetaData // library dependencies of the contract @@ -110,7 +110,8 @@ type MetaData struct { parsedABI *abi.ABI } -// ParseABI returns the parsed ABI specification. +// ParseABI returns the parsed ABI specification, or an error if the string +// representation of the ABI set in the MetaData instance could not be parsed. func (m *MetaData) ParseABI() (*abi.ABI, error) { m.mu.Lock() defer m.mu.Unlock() @@ -262,7 +263,8 @@ func (c *BoundContract) RawTransact(opts *TransactOpts, calldata []byte) (*types return c.transact(opts, &c.address, calldata) } -// RawTransact initiates a contract-creation transaction with the given raw calldata as the input. +// RawCreationTransact creates and submits a contract-creation transaction with +// the given calldata as the input. func (c *BoundContract) RawCreationTransact(opts *TransactOpts, calldata []byte) (*types.Transaction, error) { return c.transact(opts, nil, calldata) } diff --git a/accounts/abi/bind/v2/dep_tree.go b/accounts/abi/bind/v2/dep_tree.go index e07a82f3cd..72a6468951 100644 --- a/accounts/abi/bind/v2/dep_tree.go +++ b/accounts/abi/bind/v2/dep_tree.go @@ -26,23 +26,21 @@ import ( "github.com/ethereum/go-ethereum/core/types" ) -// DeploymentParams represents parameters needed to deploy a set of contracts. -// It takes an optional override list to specify contracts/libraries that have -// already been deployed on-chain. +// DeploymentParams contains parameters needed to deploy one or more contracts via LinkAndDeploy type DeploymentParams struct { + // list of all contracts targeted for the deployment Contracts []*MetaData - // Map of solidity library pattern to constructor input. + // optional map of ABI-encoded constructor inputs keyed by the MetaData.ID. Inputs map[string][]byte - // Overrides is an optional map of pattern to deployment address. - // Contracts/libraries that refer to dependencies in the override - // set are linked to the provided address (an already-deployed contract). + // optional map of override addresses for specifying already-deployed + // contracts. It is keyed by the MetaData.ID. Overrides map[string]common.Address } // validate determines whether the contracts specified in the DeploymentParams -// instance have provided deployer bytecode. +// instance have embedded deployer code in their provided MetaData instances. func (d *DeploymentParams) validate() error { for _, meta := range d.Contracts { if meta.Bin == "" { @@ -52,14 +50,13 @@ func (d *DeploymentParams) validate() error { 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. +// DeploymentResult contains information about the result of a pending +// deployment made by LinkAndDeploy. type DeploymentResult struct { - // Map of contract MetaData Id to deploy transaction + // Map of contract MetaData.ID to pending deployment transaction Txs map[string]*types.Transaction - // Map of contract MetaData Id to deployed contract address + // Map of contract MetaData.ID to the address where it will be deployed Addresses map[string]common.Address } @@ -94,11 +91,12 @@ func newDepTreeDeployer(deployParams *DeploymentParams, deployFn DeployFn) *depT } } -// linkAndDeploy recursively deploys a contract and its dependencies: starting by -// linking/deploying its dependencies. The deployment result (deploy addresses/txs -// or an error) is stored in the depTreeDeployer object. +// linkAndDeploy deploys a contract and it's dependencies. Because libraries +// can in-turn have their own library dependencies, linkAndDeploy performs +// deployment recursively (deepest-dependency first). The address of the +// pending contract deployment for the top-level contract is returned. func (d *depTreeDeployer) linkAndDeploy(metadata *MetaData) (common.Address, error) { - // Don't deploy already deployed contracts + // Don't re-deploy aliased or previously-deployed contracts if addr, ok := d.deployedAddrs[metadata.ID]; ok { return addr, nil } @@ -113,7 +111,7 @@ func (d *depTreeDeployer) linkAndDeploy(metadata *MetaData) (common.Address, err // Link their deployed addresses into the bytecode to produce deployerCode = strings.ReplaceAll(deployerCode, "__$"+dep.ID+"$__", strings.ToLower(addr.String()[2:])) } - // Finally, deploy the contract. + // Finally, deploy the top-level contract. code, err := hex.DecodeString(deployerCode[2:]) if err != nil { panic(fmt.Sprintf("error decoding contract deployer hex %s:\n%v", deployerCode[2:], err)) @@ -127,9 +125,10 @@ func (d *depTreeDeployer) linkAndDeploy(metadata *MetaData) (common.Address, err return addr, nil } -// result returns a result for this deployment, or an error if it failed. +// result returns a DeploymentResult instance referencing contracts deployed +// and not including any overrides specified for this deployment. func (d *depTreeDeployer) result() *DeploymentResult { - // remove the override addresses from the resulting deployed addresses + // filter the override addresses from the deployed address set. for pattern := range d.deployedAddrs { if _, ok := d.deployerTxs[pattern]; !ok { delete(d.deployedAddrs, pattern) @@ -141,12 +140,19 @@ 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. +// LinkAndDeploy performs the contract deployment specified by params using the +// provided DeployFn to create, sign and submit transactions. // -// In the case where multiple contracts share a common dependency: the shared -// dependency will only be deployed once. +// Contracts can depend on libraries, which in-turn can have their own library +// dependencies. Therefore, LinkAndDeploy performs the deployment recursively, +// starting with libraries (and contracts) that don't have dependencies, and +// progressing through the contracts that depend upon them. +// +// If an error is encountered, the returned DeploymentResult only contains +// entries for the contracts whose deployment submission succeeded. +// +// LinkAndDeploy performs creation and submission of creation transactions, +// but does not ensure that the contracts are included in the chain. func LinkAndDeploy(params *DeploymentParams, deploy DeployFn) (*DeploymentResult, error) { if err := params.validate(); err != nil { return nil, err