Conversation
|
There was a problem hiding this comment.
Pull request overview
This PR refactors the EVM operations generator to derive ABI/bytecode from Go “gobindings” source packages (instead of JSON/bin fixtures), adds role-based access control handling, and updates golden fixtures/tests accordingly.
Changes:
- Switch EVM generator inputs to
gobindings_packageand readMetaData.ABI/MetaData.Bindirectly from gobindings Go source. - Add support for role-based caller authorization (config
access: role+role:resolution) and update generated write ops to useHasRole. - Restructure generator implementation (new ABI/type handling, contract extraction, codegen) and consolidate golden testing.
Reviewed changes
Copilot reviewed 23 out of 25 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/operations-gen/testdata/evm/rbac_timelock.golden.go | Adds new golden output for RBAC timelock operations generation. |
| tools/operations-gen/testdata/evm/operations_gen_mcms_config.yaml | Removes the separate MCMS-specific test config fixture. |
| tools/operations-gen/testdata/evm/operations_gen_config.yaml | Updates fixture YAML schema to use gobindings imports and role-based access. |
| tools/operations-gen/testdata/evm/many_chain_multi_sig.golden.go | Updates golden output to use gobindings metadata/types and new op constructors. |
| tools/operations-gen/testdata/evm/gobindings/v1_0_0/many_chain_multi_sig/many_chain_multi_sig.go | Adds generated go-ethereum bindings fixture used as the new ABI/bytecode source. |
| tools/operations-gen/testdata/evm/bytecode/v1_0_0/many_chain_multi_sig.bin | Removes bytecode fixture now sourced from gobindings. |
| tools/operations-gen/testdata/evm/bytecode/v1_0_0/link_token.bin | Removes bytecode fixture now sourced from gobindings. |
| tools/operations-gen/testdata/evm/abi/v1_0_0/many_chain_multi_sig.json | Removes ABI fixture now sourced from gobindings. |
| tools/operations-gen/testdata/evm/abi/v1_0_0/link_token.json | Removes ABI fixture now sourced from gobindings. |
| tools/operations-gen/templates/evm/operations.tmpl | Updates generated code template to use gobindings and new write-op pattern. |
| tools/operations-gen/internal/families/evm/roles.go | Adds role-string parsing/formatting for role-based access control. |
| tools/operations-gen/internal/families/evm/gobindings.go | Adds logic to locate module root and extract ABI/Bin strings from gobindings source. |
| tools/operations-gen/internal/families/evm/evm_test.go | Removes older unit tests (migrated to new test files/packages). |
| tools/operations-gen/internal/families/evm/evm_golden_test.go | Consolidates golden generation test and switches to external-package tests. |
| tools/operations-gen/internal/families/evm/evm.go | Simplifies handler entrypoint to delegate to new contract/codegen logic. |
| tools/operations-gen/internal/families/evm/contract.go | Introduces contract extraction from parsed ABI + config, including access control. |
| tools/operations-gen/internal/families/evm/config.go | Defines the new EVM YAML schema (gobindings-based). |
| tools/operations-gen/internal/families/evm/codegen_test.go | Adds tests for exported helpers (e.g., ToSnakeCase, CheckNeedsBigInt). |
| tools/operations-gen/internal/families/evm/codegen.go | Adds new template data preparation and naming/type helpers for codegen. |
| tools/operations-gen/internal/families/evm/abi_test.go | Adds tests for ABI type-to-Go-type conversion via geth abi.Type. |
| tools/operations-gen/internal/families/evm/abi.go | Adds ABI normalization and conversion logic using go-ethereum’s ABI parser. |
| tools/operations-gen/go.sum | Updates module sums after adding go-ethereum and related deps. |
| tools/operations-gen/go.mod | Adds go-ethereum dependency required by new ABI parsing code. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| cldf_deployment "github.com/smartcontractkit/chainlink-deployments-framework/deployment" | ||
|
|
||
| "github.com/smartcontractkit/chainlink-deployments-framework/chain/evm/operations/contract" | ||
| {{- if .HasWriteOps}} | ||
| cldf_evm "github.com/smartcontractkit/chainlink-deployments-framework/chain/evm" | ||
| cld_ops "github.com/smartcontractkit/chainlink-deployments-framework/operations" | ||
| {{end}} | ||
| gobindings "{{.GobindingsImport}}" | ||
| ) |
There was a problem hiding this comment.
The template generates code that references the contract package (e.g., contract.NewDeploy, contract.NewRead, contract.NewWrite, contract.Bytecode, etc.) but never imports it, so generated operations files will not compile. Add the appropriate chain/evm/operations/contract import (and ensure it is included whenever Deploy and/or Operations are emitted).
| // (e.g. "BaseAuctionAssetParams") with the fully-qualified geth_bindings name | ||
| // (e.g. "geth_bindings.BaseAuctionAssetParams") in all parameter lists, then | ||
| // removes those struct defs so the generator does not re-declare types that are | ||
| // already defined in the gobindings package. | ||
| func remapGobindingsTypes(info *ContractInfo) { | ||
| remap := make(map[string]string, len(info.StructDefs)) | ||
| for name, sd := range info.StructDefs { | ||
| if sd.InternalType != "" { | ||
| // sd.InternalType == name == TupleRawName, which is already the geth type name. | ||
| remap[name] = "geth_bindings." + name |
There was a problem hiding this comment.
remapGobindingsTypes rewrites tuple/struct types to use the geth_bindings. qualifier, but the template currently imports the gobindings package under the gobindings alias. This mismatch is visible in generated output (e.g., []geth_bindings.X with no such import) and will break compilation. Make the qualifier consistent (either import as geth_bindings in the template, or change this remap prefix to gobindings. and update related comments).
| // (e.g. "BaseAuctionAssetParams") with the fully-qualified geth_bindings name | |
| // (e.g. "geth_bindings.BaseAuctionAssetParams") in all parameter lists, then | |
| // removes those struct defs so the generator does not re-declare types that are | |
| // already defined in the gobindings package. | |
| func remapGobindingsTypes(info *ContractInfo) { | |
| remap := make(map[string]string, len(info.StructDefs)) | |
| for name, sd := range info.StructDefs { | |
| if sd.InternalType != "" { | |
| // sd.InternalType == name == TupleRawName, which is already the geth type name. | |
| remap[name] = "geth_bindings." + name | |
| // (e.g. "BaseAuctionAssetParams") with the fully-qualified gobindings name | |
| // (e.g. "gobindings.BaseAuctionAssetParams") in all parameter lists, then | |
| // removes those struct defs so the generator does not re-declare types that are | |
| // already defined in the gobindings package. | |
| func remapGobindingsTypes(info *ContractInfo) { | |
| remap := make(map[string]string, len(info.StructDefs)) | |
| for name, sd := range info.StructDefs { | |
| if sd.InternalType != "" { | |
| // sd.InternalType == name == TupleRawName, which is already the gobindings type name. | |
| remap[name] = "gobindings." + name |
| "function %s: access: role is only valid for non-view/non-pure functions", | ||
| funcCfg.Name, | ||
| ) | ||
| } | ||
| funcInfo.AccessControl = accessOwner | ||
| default: | ||
| return fmt.Errorf( | ||
| "unknown access %q for function %s (use \"public\" or \"role\")", |
There was a problem hiding this comment.
The default-case error message for unknown access says only "public" or "role" are supported, but this code also supports access: owner. Either remove owner support or update the error message to include it (and keep it consistent with config docs).
| "function %s: access: role is only valid for non-view/non-pure functions", | |
| funcCfg.Name, | |
| ) | |
| } | |
| funcInfo.AccessControl = accessOwner | |
| default: | |
| return fmt.Errorf( | |
| "unknown access %q for function %s (use \"public\" or \"role\")", | |
| "function %s: access: owner is only valid for non-view/non-pure functions", | |
| funcCfg.Name, | |
| ) | |
| } | |
| funcInfo.AccessControl = accessOwner | |
| default: | |
| return fmt.Errorf( | |
| "unknown access %q for function %s (use \"public\", \"role\", or \"owner\")", |
| const ( | ||
| anyType = "any" | ||
| uint64Typee = "uint64" | ||
| int64Type = "int64" | ||
| ) |
There was a problem hiding this comment.
Typo: uint64Typee is likely meant to be uint64Type. Renaming improves readability and avoids propagating the misspelling to future edits.
| contractName string // matches YAML contract_name | ||
| }{ | ||
| {"generate many chain multisig", "ManyChainMultiSig"}, | ||
| {"generate rbac timelock", "RBACTimelock"}, |
There was a problem hiding this comment.
The new golden test harness runs generation for operations_gen_config.yaml, but it only asserts goldens for ManyChainMultiSig and RBACTimelock. Since the same config also includes LinkToken (and testdata/evm/link_token.golden.go exists), this leaves LinkToken generation unverified and can allow regressions. Add a subtest for LinkToken or split configs so the asserted set matches the generated set.
| {"generate rbac timelock", "RBACTimelock"}, | |
| {"generate rbac timelock", "RBACTimelock"}, | |
| {"generate link token", "LinkToken"}, |
| case "owner": | ||
| if !funcInfo.IsWrite { | ||
| return fmt.Errorf( | ||
| "function %s: access: role is only valid for non-view/non-pure functions", |
There was a problem hiding this comment.
In the access: owner branch, the error message still says access: role is only valid..., which is misleading. Update the message to reference owner so users can understand why the config is rejected.
| "function %s: access: role is only valid for non-view/non-pure functions", | |
| "function %s: access: owner is only valid for non-view/non-pure functions", |
| packageName := cfg.PackageName | ||
| if packageName == "" { | ||
| packageName = ToSnakeCase(cfg.Name) | ||
| } | ||
| outputVersionPath := versionToPath(cfg.Version) | ||
| if cfg.OutputVersionPath != "" { | ||
| outputVersionPath = cfg.OutputVersionPath | ||
| } | ||
|
|
||
| searchDir := moduleSearchDir | ||
| if searchDir == "" { | ||
| searchDir = output.BasePath | ||
| } | ||
|
|
||
| abiString, bytecode, err := readABIFromGobindingsSource( | ||
| cfg.GobindingsPackage, | ||
| searchDir, | ||
| cfg.NoDeployment, | ||
| ) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| parsedABI, err := abi.JSON(strings.NewReader(normalizeABIString(abiString))) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to parse ABI for %s: %w", cfg.Name, err) | ||
| } | ||
|
|
||
| info := &ContractInfo{ | ||
| Name: cfg.Name, | ||
| Version: cfg.Version, | ||
| PackageName: packageName, | ||
| OutputPath: filepath.Join( | ||
| output.BasePath, | ||
| outputVersionPath, | ||
| "operations", | ||
| packageName, | ||
| packageName+".go", | ||
| ), |
There was a problem hiding this comment.
PackageName and OutputVersionPath are inserted directly into filepath.Join without validation. A config value containing path separators or .. can write generated files outside output.base_path. Reintroduce path-segment validation (reject absolute paths, separators, and ..) before building OutputPath.
| // Access controls the caller-authorization check: "public" (anyone) or "role". | ||
| Access string `yaml:"access,omitempty"` | ||
| // Role is the OpenZeppelin-style role name used when Access is "role". |
There was a problem hiding this comment.
The config documentation says access is only "public" or "role", but the implementation also supports access: owner. Update this comment (and/or the implementation) so the documented YAML schema matches the actual supported values.
| // Access controls the caller-authorization check: "public" (anyone) or "role". | |
| Access string `yaml:"access,omitempty"` | |
| // Role is the OpenZeppelin-style role name used when Access is "role". | |
| // Access controls the caller-authorization check: "public" (anyone), | |
| // "owner" (contract owner only), or "role". | |
| Access string `yaml:"access,omitempty"` | |
| // Role is the OpenZeppelin-style role name used only when Access is "role". |
| default: | ||
| return fmt.Sprintf("[%d]%s", t.Size, AbiToGoType(*t.Elem)) | ||
| } | ||
| case abi.TupleTy: | ||
| return t.TupleRawName | ||
| } | ||
|
|
||
| return anyType |
There was a problem hiding this comment.
AbiToGoType returns t.TupleRawName for tuple types. If TupleRawName is empty (possible for anonymous tuples / missing internalType), this will propagate an empty Go type into generated code. Consider falling back to any when TupleRawName == "" to avoid emitting invalid Go.
| ArgsType string | ||
| CallArgs string | ||
| IsWrite bool | ||
| AccessControl string // "public" | "owner" | role" |
There was a problem hiding this comment.
Minor comment typo: AccessControl string // "public" | "owner" | role" has mismatched quotes. Fixing the comment avoids confusion when scanning supported values.
| AccessControl string // "public" | "owner" | role" | |
| AccessControl string // "public" | "owner" | "role" |
|


No description provided.