Skip to content

chore: operations generation improvements#949

Draft
RayXpub wants to merge 5 commits intomainfrom
chore/operations-generation-improvements
Draft

chore: operations generation improvements#949
RayXpub wants to merge 5 commits intomainfrom
chore/operations-generation-improvements

Conversation

@RayXpub
Copy link
Copy Markdown
Contributor

@RayXpub RayXpub commented Apr 22, 2026

No description provided.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 22, 2026

⚠️ No Changeset found

Latest commit: bb3f77a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_package and read MetaData.ABI / MetaData.Bin directly from gobindings Go source.
  • Add support for role-based caller authorization (config access: role + role: resolution) and update generated write ops to use HasRole.
  • 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.

Comment on lines 17 to 23
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}}"
)
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copilot uses AI. Check for mistakes.
Comment on lines +270 to +279
// (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
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Suggested change
// (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

Copilot uses AI. Check for mistakes.
Comment on lines +208 to +215
"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\")",
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Suggested change
"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\")",

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +16
const (
anyType = "any"
uint64Typee = "uint64"
int64Type = "int64"
)
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: uint64Typee is likely meant to be uint64Type. Renaming improves readability and avoids propagating the misspelling to future edits.

Copilot uses AI. Check for mistakes.
contractName string // matches YAML contract_name
}{
{"generate many chain multisig", "ManyChainMultiSig"},
{"generate rbac timelock", "RBACTimelock"},
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
{"generate rbac timelock", "RBACTimelock"},
{"generate rbac timelock", "RBACTimelock"},
{"generate link token", "LinkToken"},

Copilot uses AI. Check for mistakes.
case "owner":
if !funcInfo.IsWrite {
return fmt.Errorf(
"function %s: access: role is only valid for non-view/non-pure functions",
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
"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",

Copilot uses AI. Check for mistakes.
Comment on lines +90 to +128
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",
),
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +39
// 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".
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
// 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".

Copilot uses AI. Check for mistakes.
Comment on lines +96 to +103
default:
return fmt.Sprintf("[%d]%s", t.Size, AbiToGoType(*t.Elem))
}
case abi.TupleTy:
return t.TupleRawName
}

return anyType
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
ArgsType string
CallArgs string
IsWrite bool
AccessControl string // "public" | "owner" | role"
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor comment typo: AccessControl string // "public" | "owner" | role" has mismatched quotes. Fixing the comment avoids confusion when scanning supported values.

Suggested change
AccessControl string // "public" | "owner" | role"
AccessControl string // "public" | "owner" | "role"

Copilot uses AI. Check for mistakes.
@cl-sonarqube-production
Copy link
Copy Markdown

Quality Gate failed Quality Gate failed

Failed conditions
0.0% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants