From 42d710ae8e6c8d3e9b5a6282fe79919c562fbde6 Mon Sep 17 00:00:00 2001 From: Graham Goh Date: Tue, 16 Jun 2026 16:35:32 +1000 Subject: [PATCH 1/2] feat(operations-gen): add zkSync bytecode support to EVM deploy generation Enable operations-gen to wire zkSync VM deploy bytecode into generated Deploy operations via zksync_bytecode and zksync_bindings_package config fields. --- .changeset/clear-beans-deny.md | 5 + tools/operations-gen/README.md | 4 +- .../generate/templates/evm/operations.tmpl | 6 + .../internal/families/evm/codegen.go | 46 +++--- .../internal/families/evm/config.go | 4 + .../internal/families/evm/contract.go | 49 +++--- .../internal/families/evm/evm_golden_test.go | 7 + .../internal/families/evm/evm_test.go | 48 ++++++ .../internal/families/evm/zksync_bytecode.go | 85 ++++++++++ .../families/evm/zksync_bytecode_test.go | 146 ++++++++++++++++++ .../v1_0_0/link_token/link_token.go | 3 + .../testdata/evm/link_token_zksync.golden.go | 99 ++++++++++++ .../evm/many_chain_multi_sig.golden.go | 4 +- ...erations_gen_link_token_zksync_config.yaml | 21 +++ .../evm/operations_gen_mcms_config.yaml | 2 + .../testdata/evm/zksync_bindings/bytecode.go | 6 + 16 files changed, 495 insertions(+), 40 deletions(-) create mode 100644 .changeset/clear-beans-deny.md create mode 100644 tools/operations-gen/internal/families/evm/zksync_bytecode.go create mode 100644 tools/operations-gen/internal/families/evm/zksync_bytecode_test.go create mode 100644 tools/operations-gen/testdata/evm/link_token_zksync.golden.go create mode 100644 tools/operations-gen/testdata/evm/operations_gen_link_token_zksync_config.yaml create mode 100644 tools/operations-gen/testdata/evm/zksync_bindings/bytecode.go diff --git a/.changeset/clear-beans-deny.md b/.changeset/clear-beans-deny.md new file mode 100644 index 000000000..376856bc7 --- /dev/null +++ b/.changeset/clear-beans-deny.md @@ -0,0 +1,5 @@ +--- +"operations-gen": minor +--- + +feat: Add zkSync VM bytecode support to operations-gen. Generated Deploy operations can now include `ZkSyncVM` bytecode via `zksync_bytecode` and `zksync_bindings_package` config fields. diff --git a/tools/operations-gen/README.md b/tools/operations-gen/README.md index eee63cac9..35629a4f4 100644 --- a/tools/operations-gen/README.md +++ b/tools/operations-gen/README.md @@ -136,6 +136,7 @@ contracts: | `version` | Yes | Config schema version | | `chain_family` | No | Target chain family. Only `"evm"` is supported. Defaults to `"evm"`. | | `input.gobindings_package` | No | Parent Go import path or relative filesystem path containing versioned abigen packages. Used to derive contract bindings as `//`. | +| `input.zksync_bindings_package` | No | Default Go import path or relative filesystem path for zkSync VM deploy bytecode. Used when a contract sets `zksync_bytecode` to a symbol only. | | `output.base_path` | Yes | Root directory where generated files are written. Relative to the config file. | ### Contract fields @@ -147,7 +148,8 @@ contracts: | `gobindings_package` | No | Optional full Go import path or relative filesystem path override for this contract's abigen-generated bindings package. Required only when `input.gobindings_package` is not set. | | `package_name` | No | Override the generated Go package name. Defaults to `snake_case(contract_name)`. | | `version_path` | No | Override the directory path derived from the version. Defaults to `v{major}_{minor}_{patch}`. | -| `omit_deploy` | No | Skip generation of the `Deploy` operation and bytecode constant. Defaults to `false`. | +| `omit_deploy` | No | Skip generation of the `Deploy` operation and bytecode constant. Defaults to `false`. Cannot be combined with `zksync_bytecode`. | +| `zksync_bytecode` | No | zkSync VM deploy bytecode symbol, or `{package, symbol}`. Package defaults to `input.zksync_bindings_package`, then the contract's `gobindings_package`. | ### Function access control diff --git a/tools/operations-gen/generate/templates/evm/operations.tmpl b/tools/operations-gen/generate/templates/evm/operations.tmpl index 595f5667a..170a69261 100644 --- a/tools/operations-gen/generate/templates/evm/operations.tmpl +++ b/tools/operations-gen/generate/templates/evm/operations.tmpl @@ -23,6 +23,9 @@ import ( cld_ops "github.com/smartcontractkit/chainlink-deployments-framework/operations" {{- end}} gobindings "{{.GobindingsImport}}" +{{- if and .ZkSyncBytecodeSymbol (not .ZkSyncBytecodeUseGobindings)}} + zkbindings "{{.ZkSyncBytecodeImport}}" +{{- end}} ) var ContractType cldf_deployment.ContractType = "{{.ContractType}}" @@ -61,6 +64,9 @@ var Deploy = contract.NewDeploy(contract.DeployParams[ConstructorArgs]{ BytecodeByTypeAndVersion: map[string]contract.Bytecode{ cldf_deployment.NewTypeAndVersion(ContractType, *Version).String(): { EVM: common.FromHex(gobindings.{{.ContractType}}MetaData.Bin), +{{- if .ZkSyncBytecodeSymbol}} + ZkSyncVM: {{if .ZkSyncBytecodeUseGobindings}}gobindings{{else}}zkbindings{{end}}.{{.ZkSyncBytecodeSymbol}}, +{{- end}} }, }, }) diff --git a/tools/operations-gen/internal/families/evm/codegen.go b/tools/operations-gen/internal/families/evm/codegen.go index d1bc3b3f2..0ac6798c0 100644 --- a/tools/operations-gen/internal/families/evm/codegen.go +++ b/tools/operations-gen/internal/families/evm/codegen.go @@ -13,19 +13,22 @@ import ( // ---- Template data (EVM-specific) ---- type templateData struct { - PackageName string - PackageNameHyphen string - ContractType string - Version string - GobindingsImport string - NeedsBigInt bool - HasWriteOps bool - OmitDeploy bool - Constructor *constructorData - StructDefs []structDefData - ArgStructs []argStructData - Operations []OperationData - ContractMethods []contractMethodData + PackageName string + PackageNameHyphen string + ContractType string + Version string + GobindingsImport string + ZkSyncBytecodeSymbol string + ZkSyncBytecodeImport string + ZkSyncBytecodeUseGobindings bool + NeedsBigInt bool + HasWriteOps bool + OmitDeploy bool + Constructor *constructorData + StructDefs []structDefData + ArgStructs []argStructData + Operations []OperationData + ContractMethods []contractMethodData } type constructorData struct { @@ -84,13 +87,16 @@ func generateOperationsFile(info *ContractInfo, tmpl *template.Template) error { func prepareTemplateData(info *ContractInfo) templateData { data := templateData{ - PackageName: info.PackageName, - PackageNameHyphen: toKebabCase(info.PackageName), - ContractType: info.Name, - Version: info.Version, - GobindingsImport: info.GobindingsPackage, - NeedsBigInt: ChecksNeedsBigInt(info), - OmitDeploy: info.OmitDeploy, + PackageName: info.PackageName, + PackageNameHyphen: toKebabCase(info.PackageName), + ContractType: info.Name, + Version: info.Version, + GobindingsImport: info.GobindingsPackage, + ZkSyncBytecodeSymbol: info.ZkSyncBytecodeSymbol, + ZkSyncBytecodeImport: info.ZkSyncBytecodePackage, + ZkSyncBytecodeUseGobindings: info.ZkSyncBytecodeSymbol != "" && info.ZkSyncBytecodePackage == info.GobindingsPackage, + NeedsBigInt: ChecksNeedsBigInt(info), + OmitDeploy: info.OmitDeploy, } if info.Constructor != nil { diff --git a/tools/operations-gen/internal/families/evm/config.go b/tools/operations-gen/internal/families/evm/config.go index a00c58022..0c79443bc 100644 --- a/tools/operations-gen/internal/families/evm/config.go +++ b/tools/operations-gen/internal/families/evm/config.go @@ -8,6 +8,7 @@ type EvmContractConfig struct { PackageName string `yaml:"package_name,omitempty"` // Optional: override package name OmitDeploy bool `yaml:"omit_deploy,omitempty"` // Optional: skip Deploy operation GobindingsPackage string `yaml:"gobindings_package"` // Optional: override the derived gobindings import path or relative filesystem path for this contract. + ZkSyncBytecode ZkSyncBytecodeRef `yaml:"zksync_bytecode,omitempty"` Functions []EvmFunctionConfig `yaml:"functions"` ConfigDir string `yaml:"-"` } @@ -18,6 +19,9 @@ type EvmInputConfig struct { // Contract packages default to: // // GobindingsPackage string `yaml:"gobindings_package"` + // ZkSyncBindingsPackage is the default Go import path for zkSync VM deploy bytecode. + // Used when a contract sets zksync_bytecode to a symbol only. + ZkSyncBindingsPackage string `yaml:"zksync_bindings_package,omitempty"` } // EvmFunctionConfig selects a contract function and assigns its access control. diff --git a/tools/operations-gen/internal/families/evm/contract.go b/tools/operations-gen/internal/families/evm/contract.go index 08f8997dd..84acc3a59 100644 --- a/tools/operations-gen/internal/families/evm/contract.go +++ b/tools/operations-gen/internal/families/evm/contract.go @@ -25,16 +25,18 @@ const ( // ContractInfo holds all parsed information about a contract needed for code generation. type ContractInfo struct { - Name string - Version string - PackageName string - GobindingsPackage string - OutputPath string - OmitDeploy bool - Constructor *FunctionInfo - Functions map[string]*FunctionInfo - FunctionOrder []string - StructDefs map[string]*structDef + Name string + Version string + PackageName string + GobindingsPackage string + ZkSyncBytecodePackage string + ZkSyncBytecodeSymbol string + OutputPath string + OmitDeploy bool + Constructor *FunctionInfo + Functions map[string]*FunctionInfo + FunctionOrder []string + StructDefs map[string]*structDef } type structDef struct { @@ -98,20 +100,31 @@ func extractContractInfo(cfg EvmContractConfig, input EvmInputConfig, output Evm } cfg.GobindingsPackage = resolvedGobindingsPackage + if cfg.OmitDeploy && !cfg.ZkSyncBytecode.IsZero() { + return nil, fmt.Errorf("contract %q: zksync_bytecode cannot be set when omit_deploy is true", cfg.Name) + } + + zkSyncPackage, zkSyncSymbol, err := resolveZkSyncBytecode(cfg, input, cfg.GobindingsPackage) + if err != nil { + return nil, err + } + parsedAbi, err := ReadABI(cfg) if err != nil { return nil, err } info := &ContractInfo{ - Name: cfg.Name, - Version: cfg.Version, - PackageName: packageName, - GobindingsPackage: cfg.GobindingsPackage, - OutputPath: core.ContractOutputPath(output.BasePath, versionPath, packageName), - OmitDeploy: cfg.OmitDeploy, - Functions: make(map[string]*FunctionInfo), - StructDefs: make(map[string]*structDef), + Name: cfg.Name, + Version: cfg.Version, + PackageName: packageName, + GobindingsPackage: cfg.GobindingsPackage, + ZkSyncBytecodePackage: zkSyncPackage, + ZkSyncBytecodeSymbol: zkSyncSymbol, + OutputPath: core.ContractOutputPath(output.BasePath, versionPath, packageName), + OmitDeploy: cfg.OmitDeploy, + Functions: make(map[string]*FunctionInfo), + StructDefs: make(map[string]*structDef), } extractConstructor(info, parsedAbi) diff --git a/tools/operations-gen/internal/families/evm/evm_golden_test.go b/tools/operations-gen/internal/families/evm/evm_golden_test.go index 6292a187c..008560d7f 100644 --- a/tools/operations-gen/internal/families/evm/evm_golden_test.go +++ b/tools/operations-gen/internal/families/evm/evm_golden_test.go @@ -39,6 +39,13 @@ func TestGenerateFeeQuoter(t *testing.T) { runGoldenGenerationTest(t, "operations_gen_fee_quoter.yaml", "fee_quoter.golden.go") } +// TestGenerateLinkTokenWithZkSyncBindingsPackage verifies generation when +// input.zksync_bindings_package and contract zksync_bytecode are both set. +func TestGenerateLinkTokenWithZkSyncBindingsPackage(t *testing.T) { + t.Parallel() + runGoldenGenerationTest(t, "operations_gen_link_token_zksync_config.yaml", "link_token_zksync.golden.go") +} + func runGoldenGenerationTest(t *testing.T, configFileName string, goldenFileName string) { t.Helper() diff --git a/tools/operations-gen/internal/families/evm/evm_test.go b/tools/operations-gen/internal/families/evm/evm_test.go index 99d8f7abb..55394a40c 100644 --- a/tools/operations-gen/internal/families/evm/evm_test.go +++ b/tools/operations-gen/internal/families/evm/evm_test.go @@ -98,3 +98,51 @@ contracts: require.Contains(t, string(got), `gobindings "`+wantGobindingsPackage+`"`) require.NotContains(t, string(got), `gobindings "./testdata/evm/gobindings`) } + +func TestGenerateResolvesRelativeZkSyncBindingsPackage(t *testing.T) { + t.Parallel() + + const wantZkSyncPackage = "github.com/smartcontractkit/chainlink-deployments-framework/tools/operations-gen/testdata/evm/zksync_bindings" + config := `version: "1.0.0" +chain_family: evm + +input: + gobindings_package: "github.com/smartcontractkit/chainlink-deployments-framework/tools/operations-gen/testdata/evm/gobindings" + zksync_bindings_package: "./testdata/evm/zksync_bindings" + +output: + base_path: "." + +contracts: + - contract_name: ManyChainMultiSig + version: "1.0.0" + package_name: many_chain_multi_sig + zksync_bytecode: ManyChainMultiSigZkBytecode + functions: + - name: owner + access: public +` + + var cfg core.Config + require.NoError(t, yaml.Unmarshal([]byte(config), &cfg), "parsing config") + + moduleDir, err := filepath.Abs(filepath.Join("..", "..", "..")) + require.NoError(t, err) + tmpDir := t.TempDir() + outputBasePath, err := filepath.Rel(moduleDir, tmpDir) + require.NoError(t, err) + cfg.ConfigDir = moduleDir + cfg.Output = mustYAMLNode(t, evm.EvmOutputConfig{BasePath: outputBasePath}) + + tmpl, err := generate.LoadTemplate("evm") + require.NoError(t, err, "loadTemplate") + + require.NoError(t, evm.Handler{}.Generate(cfg, tmpl), "Generate") + + outputPath := core.ContractOutputPath(tmpDir, core.VersionToPath("1.0.0"), "many_chain_multi_sig") + got, err := os.ReadFile(outputPath) + require.NoError(t, err, "reading generated file %s", outputPath) + + require.Contains(t, string(got), `zkbindings "`+wantZkSyncPackage+`"`) + require.Contains(t, string(got), "ZkSyncVM: zkbindings.ManyChainMultiSigZkBytecode") +} diff --git a/tools/operations-gen/internal/families/evm/zksync_bytecode.go b/tools/operations-gen/internal/families/evm/zksync_bytecode.go new file mode 100644 index 000000000..a72c573c8 --- /dev/null +++ b/tools/operations-gen/internal/families/evm/zksync_bytecode.go @@ -0,0 +1,85 @@ +package evm + +import ( + "errors" + "fmt" + + "gopkg.in/yaml.v3" +) + +// ZkSyncBytecodeRef identifies zkSync VM deploy bytecode for a contract. +// YAML accepts a shorthand symbol string or {package, symbol}. +type ZkSyncBytecodeRef struct { + Package string + Symbol string +} + +func (z *ZkSyncBytecodeRef) UnmarshalYAML(value *yaml.Node) error { + *z = ZkSyncBytecodeRef{} + if value == nil { + return nil + } + + switch value.Kind { + case yaml.ScalarNode: + if isYAMLNullScalar(value) { + return nil + } + z.Symbol = value.Value + + return nil + case yaml.MappingNode: + var raw struct { + Package string `yaml:"package"` + Symbol string `yaml:"symbol"` + } + if err := value.Decode(&raw); err != nil { + return err + } + z.Package = raw.Package + z.Symbol = raw.Symbol + if z.Symbol == "" { + return errors.New("zksync_bytecode mapping requires symbol") + } + + return nil + case yaml.DocumentNode, yaml.SequenceNode, yaml.AliasNode: + return fmt.Errorf("zksync_bytecode must be a string or mapping, got %v", value.ShortTag()) + } + + return fmt.Errorf("zksync_bytecode must be a string or mapping, got %v", value.ShortTag()) +} + +func isYAMLNullScalar(value *yaml.Node) bool { + return value.Tag == "!!null" || value.Value == "" || value.Value == "~" +} + +func (z ZkSyncBytecodeRef) IsZero() bool { + return z.Symbol == "" +} + +func resolveZkSyncBytecode( + cfg EvmContractConfig, + input EvmInputConfig, + gobindingsPackage string, +) (packagePath string, symbol string, err error) { + if cfg.ZkSyncBytecode.IsZero() { + return "", "", nil + } + + symbol = cfg.ZkSyncBytecode.Symbol + pkgPath := cfg.ZkSyncBytecode.Package + if pkgPath == "" { + pkgPath = input.ZkSyncBindingsPackage + } + if pkgPath == "" { + return gobindingsPackage, symbol, nil + } + + resolved, err := resolveGobindingsImportPath(pkgPath, cfg.ConfigDir) + if err != nil { + return "", "", fmt.Errorf("resolve zksync_bytecode package for contract %q: %w", cfg.Name, err) + } + + return resolved, symbol, nil +} diff --git a/tools/operations-gen/internal/families/evm/zksync_bytecode_test.go b/tools/operations-gen/internal/families/evm/zksync_bytecode_test.go new file mode 100644 index 000000000..9c5d32667 --- /dev/null +++ b/tools/operations-gen/internal/families/evm/zksync_bytecode_test.go @@ -0,0 +1,146 @@ +package evm + +import ( + "testing" + + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +func TestZkSyncBytecodeRefUnmarshalYAML(t *testing.T) { + t.Parallel() + + t.Run("shorthand symbol", func(t *testing.T) { + t.Parallel() + var ref ZkSyncBytecodeRef + require.NoError(t, yaml.Unmarshal([]byte(`CallProxyZkBytecode`), &ref)) + require.Equal(t, "CallProxyZkBytecode", ref.Symbol) + require.Empty(t, ref.Package) + }) + + t.Run("mapping", func(t *testing.T) { + t.Parallel() + var ref ZkSyncBytecodeRef + require.NoError(t, yaml.Unmarshal([]byte(` +package: github.com/example/zk +symbol: FooZkBytecode +`), &ref)) + require.Equal(t, "github.com/example/zk", ref.Package) + require.Equal(t, "FooZkBytecode", ref.Symbol) + }) + + t.Run("mapping symbol only", func(t *testing.T) { + t.Parallel() + var ref ZkSyncBytecodeRef + require.NoError(t, yaml.Unmarshal([]byte(` +symbol: FooZkBytecode +`), &ref)) + require.Empty(t, ref.Package) + require.Equal(t, "FooZkBytecode", ref.Symbol) + }) + + t.Run("mapping without symbol", func(t *testing.T) { + t.Parallel() + var ref ZkSyncBytecodeRef + err := yaml.Unmarshal([]byte(` +package: github.com/example/zk +`), &ref) + require.ErrorContains(t, err, "zksync_bytecode mapping requires symbol") + }) + + t.Run("scalar overwrites previous mapping", func(t *testing.T) { + t.Parallel() + var ref ZkSyncBytecodeRef + require.NoError(t, yaml.Unmarshal([]byte(` +package: github.com/example/zk +symbol: FooZkBytecode +`), &ref)) + require.NoError(t, yaml.Unmarshal([]byte(`BarZkBytecode`), &ref)) + require.Equal(t, "BarZkBytecode", ref.Symbol) + require.Empty(t, ref.Package) + }) + + t.Run("null is unset", func(t *testing.T) { + t.Parallel() + var cfg struct { + ZkSyncBytecode ZkSyncBytecodeRef `yaml:"zksync_bytecode"` + } + require.NoError(t, yaml.Unmarshal([]byte(`zksync_bytecode: null`), &cfg)) + require.True(t, cfg.ZkSyncBytecode.IsZero()) + }) + + t.Run("tilde is unset", func(t *testing.T) { + t.Parallel() + var cfg struct { + ZkSyncBytecode ZkSyncBytecodeRef `yaml:"zksync_bytecode"` + } + require.NoError(t, yaml.Unmarshal([]byte(`zksync_bytecode: ~`), &cfg)) + require.True(t, cfg.ZkSyncBytecode.IsZero()) + }) + + t.Run("empty string is unset", func(t *testing.T) { + t.Parallel() + var cfg struct { + ZkSyncBytecode ZkSyncBytecodeRef `yaml:"zksync_bytecode"` + } + require.NoError(t, yaml.Unmarshal([]byte(`zksync_bytecode: ""`), &cfg)) + require.True(t, cfg.ZkSyncBytecode.IsZero()) + }) +} + +func TestResolveZkSyncBytecodeUsesInputDefaultPackage(t *testing.T) { + t.Parallel() + + const zkPackage = "github.com/smartcontractkit/chainlink-deployments-framework/tools/operations-gen/testdata/evm/zksync_bindings" + cfg := EvmContractConfig{ + Name: "ManyChainMultiSig", + ZkSyncBytecode: mustZkSyncBytecodeRef(t, "ManyChainMultiSigZkBytecode"), + } + input := EvmInputConfig{ZkSyncBindingsPackage: zkPackage} + + pkg, symbol, err := resolveZkSyncBytecode(cfg, input, "github.com/example/evm") + require.NoError(t, err) + require.Equal(t, zkPackage, pkg) + require.Equal(t, "ManyChainMultiSigZkBytecode", symbol) +} + +func TestResolveZkSyncBytecodeFallsBackToGobindingsPackage(t *testing.T) { + t.Parallel() + + const gobindingsPackage = "github.com/example/gobindings/link_token" + cfg := EvmContractConfig{ + Name: "LinkToken", + ZkSyncBytecode: mustZkSyncBytecodeRef(t, "ZkBytecode"), + } + + pkg, symbol, err := resolveZkSyncBytecode(cfg, EvmInputConfig{}, gobindingsPackage) + require.NoError(t, err) + require.Equal(t, gobindingsPackage, pkg) + require.Equal(t, "ZkBytecode", symbol) +} + +func TestExtractContractInfoRejectsZkSyncBytecodeWithOmitDeploy(t *testing.T) { + t.Parallel() + + cfg := EvmContractConfig{ + Name: "LinkToken", + Version: "1.0.0", + OmitDeploy: true, + GobindingsPackage: "github.com/smartcontractkit/chainlink-deployments-framework/tools/operations-gen/testdata/evm/gobindings/v1_0_0/link_token", + ZkSyncBytecode: mustZkSyncBytecodeRef(t, "ZkBytecode"), + Functions: []EvmFunctionConfig{ + {Name: "transfer", Access: "public"}, + }, + } + + _, err := extractContractInfo(cfg, EvmInputConfig{}, EvmOutputConfig{BasePath: t.TempDir()}) + require.ErrorContains(t, err, "zksync_bytecode cannot be set when omit_deploy is true") +} + +func mustZkSyncBytecodeRef(t *testing.T, symbol string) ZkSyncBytecodeRef { + t.Helper() + var ref ZkSyncBytecodeRef + require.NoError(t, yaml.Unmarshal([]byte(symbol), &ref)) + + return ref +} diff --git a/tools/operations-gen/testdata/evm/gobindings/v1_0_0/link_token/link_token.go b/tools/operations-gen/testdata/evm/gobindings/v1_0_0/link_token/link_token.go index 95ef74617..ed3f583d8 100644 --- a/tools/operations-gen/testdata/evm/gobindings/v1_0_0/link_token/link_token.go +++ b/tools/operations-gen/testdata/evm/gobindings/v1_0_0/link_token/link_token.go @@ -37,6 +37,9 @@ var LinkTokenABI = LinkTokenMetaData.ABI var LinkTokenBin = LinkTokenMetaData.Bin +// ZkBytecode is zkSync VM deploy bytecode for operations-gen tests. +var ZkBytecode = []byte{0xca, 0xfe} + func DeployLinkToken(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *LinkToken, error) { parsed, err := LinkTokenMetaData.GetAbi() if err != nil { diff --git a/tools/operations-gen/testdata/evm/link_token_zksync.golden.go b/tools/operations-gen/testdata/evm/link_token_zksync.golden.go new file mode 100644 index 000000000..5b91cbb46 --- /dev/null +++ b/tools/operations-gen/testdata/evm/link_token_zksync.golden.go @@ -0,0 +1,99 @@ +// Code generated by operations-gen. DO NOT EDIT. + +package link_token + +import ( + "math/big" + + "github.com/Masterminds/semver/v3" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + + cldf_evm "github.com/smartcontractkit/chainlink-deployments-framework/chain/evm" + "github.com/smartcontractkit/chainlink-deployments-framework/chain/evm/operations2/contract" + cldf_deployment "github.com/smartcontractkit/chainlink-deployments-framework/deployment" + cld_ops "github.com/smartcontractkit/chainlink-deployments-framework/operations" + gobindings "github.com/smartcontractkit/chainlink-deployments-framework/tools/operations-gen/testdata/evm/gobindings/v1_0_0/link_token" + zkbindings "github.com/smartcontractkit/chainlink-deployments-framework/tools/operations-gen/testdata/evm/zksync_bindings" +) + +var ContractType cldf_deployment.ContractType = "LinkToken" +var Version = semver.MustParse("1.0.0") +var TypeAndVersion = cldf_deployment.NewTypeAndVersion(ContractType, *Version) + +type TransferArgs struct { + To common.Address `json:"to"` + Amount *big.Int `json:"amount"` +} + +type ApproveArgs struct { + Spender common.Address `json:"spender"` + Amount *big.Int `json:"amount"` +} + +type ConstructorArgs = struct{} + +var Deploy = contract.NewDeploy(contract.DeployParams[ConstructorArgs]{ + Name: "link-token:deploy", + Version: Version, + Description: "Deploys the LinkToken contract", + ContractMetadata: gobindings.LinkTokenMetaData, + BytecodeByTypeAndVersion: map[string]contract.Bytecode{ + cldf_deployment.NewTypeAndVersion(ContractType, *Version).String(): { + EVM: common.FromHex(gobindings.LinkTokenMetaData.Bin), + ZkSyncVM: zkbindings.LinkTokenZkBytecode, + }, + }, +}) + +func NewWriteTransfer(c gobindings.LinkTokenInterface) *cld_ops.Operation[contract.FunctionInput[TransferArgs], contract.WriteOutput, cldf_evm.Chain] { + return contract.NewWrite(contract.WriteParams[TransferArgs, gobindings.LinkTokenInterface]{ + Name: "link-token:transfer", + Version: Version, + Description: "Calls transfer on the contract", + ContractType: ContractType, + ContractABI: gobindings.LinkTokenMetaData.ABI, + Contract: c, + IsAllowedCaller: contract.AllCallersAllowed[gobindings.LinkTokenInterface, TransferArgs], + CallContract: func( + c gobindings.LinkTokenInterface, + opts *bind.TransactOpts, + args TransferArgs, + ) (*types.Transaction, error) { + return c.Transfer(opts, args.To, args.Amount) + }, + }) +} + +func NewReadBalanceOf(c gobindings.LinkTokenInterface) *cld_ops.Operation[contract.FunctionInput[common.Address], *big.Int, cldf_evm.Chain] { + return contract.NewRead(contract.ReadParams[common.Address, *big.Int, gobindings.LinkTokenInterface]{ + Name: "link-token:balance-of", + Version: Version, + Description: "Calls balanceOf on the contract", + ContractType: ContractType, + Contract: c, + CallContract: func(c gobindings.LinkTokenInterface, opts *bind.CallOpts, args common.Address) (*big.Int, error) { + return c.BalanceOf(opts, args) + }, + }) +} + +func NewWriteApprove(c gobindings.LinkTokenInterface) *cld_ops.Operation[contract.FunctionInput[ApproveArgs], contract.WriteOutput, cldf_evm.Chain] { + return contract.NewWrite(contract.WriteParams[ApproveArgs, gobindings.LinkTokenInterface]{ + Name: "link-token:approve", + Version: Version, + Description: "Calls approve on the contract", + ContractType: ContractType, + ContractABI: gobindings.LinkTokenMetaData.ABI, + Contract: c, + IsAllowedCaller: contract.AllCallersAllowed[gobindings.LinkTokenInterface, ApproveArgs], + CallContract: func( + c gobindings.LinkTokenInterface, + opts *bind.TransactOpts, + args ApproveArgs, + ) (*types.Transaction, error) { + return c.Approve(opts, args.Spender, args.Amount) + }, + }) +} diff --git a/tools/operations-gen/testdata/evm/many_chain_multi_sig.golden.go b/tools/operations-gen/testdata/evm/many_chain_multi_sig.golden.go index 08ff53cf4..c35e87f43 100644 --- a/tools/operations-gen/testdata/evm/many_chain_multi_sig.golden.go +++ b/tools/operations-gen/testdata/evm/many_chain_multi_sig.golden.go @@ -13,6 +13,7 @@ import ( cldf_deployment "github.com/smartcontractkit/chainlink-deployments-framework/deployment" cld_ops "github.com/smartcontractkit/chainlink-deployments-framework/operations" gobindings "github.com/smartcontractkit/chainlink-deployments-framework/tools/operations-gen/testdata/evm/gobindings/v1_0_0/many_chain_multi_sig" + zkbindings "github.com/smartcontractkit/chainlink-deployments-framework/tools/operations-gen/testdata/evm/zksync_bindings" ) var ContractType cldf_deployment.ContractType = "ManyChainMultiSig" @@ -44,7 +45,8 @@ var Deploy = contract.NewDeploy(contract.DeployParams[ConstructorArgs]{ ContractMetadata: gobindings.ManyChainMultiSigMetaData, BytecodeByTypeAndVersion: map[string]contract.Bytecode{ cldf_deployment.NewTypeAndVersion(ContractType, *Version).String(): { - EVM: common.FromHex(gobindings.ManyChainMultiSigMetaData.Bin), + EVM: common.FromHex(gobindings.ManyChainMultiSigMetaData.Bin), + ZkSyncVM: zkbindings.ManyChainMultiSigZkBytecode, }, }, }) diff --git a/tools/operations-gen/testdata/evm/operations_gen_link_token_zksync_config.yaml b/tools/operations-gen/testdata/evm/operations_gen_link_token_zksync_config.yaml new file mode 100644 index 000000000..6d4c79aa8 --- /dev/null +++ b/tools/operations-gen/testdata/evm/operations_gen_link_token_zksync_config.yaml @@ -0,0 +1,21 @@ +version: "1.0.0" +chain_family: evm + +input: + gobindings_package: "github.com/smartcontractkit/chainlink-deployments-framework/tools/operations-gen/testdata/evm/gobindings" + zksync_bindings_package: "github.com/smartcontractkit/chainlink-deployments-framework/tools/operations-gen/testdata/evm/zksync_bindings" + +output: + base_path: "." + +contracts: + - contract_name: LinkToken + version: "1.0.0" + zksync_bytecode: LinkTokenZkBytecode + functions: + - name: transfer + access: public + - name: balanceOf + access: public + - name: approve + access: public diff --git a/tools/operations-gen/testdata/evm/operations_gen_mcms_config.yaml b/tools/operations-gen/testdata/evm/operations_gen_mcms_config.yaml index 65c7e40de..3efcbc35f 100644 --- a/tools/operations-gen/testdata/evm/operations_gen_mcms_config.yaml +++ b/tools/operations-gen/testdata/evm/operations_gen_mcms_config.yaml @@ -3,6 +3,7 @@ chain_family: evm input: gobindings_package: "github.com/smartcontractkit/chainlink-deployments-framework/tools/operations-gen/testdata/evm/gobindings" + zksync_bindings_package: "github.com/smartcontractkit/chainlink-deployments-framework/tools/operations-gen/testdata/evm/zksync_bindings" output: base_path: "." @@ -11,6 +12,7 @@ contracts: - contract_name: ManyChainMultiSig version: "1.0.0" package_name: many_chain_multi_sig + zksync_bytecode: ManyChainMultiSigZkBytecode functions: - name: owner access: public diff --git a/tools/operations-gen/testdata/evm/zksync_bindings/bytecode.go b/tools/operations-gen/testdata/evm/zksync_bindings/bytecode.go new file mode 100644 index 000000000..f0e9f6f2c --- /dev/null +++ b/tools/operations-gen/testdata/evm/zksync_bindings/bytecode.go @@ -0,0 +1,6 @@ +// Package zksync_bindings holds zkSync VM bytecode fixtures for operations-gen tests. +package zksync_bindings + +var LinkTokenZkBytecode = []byte{0xca, 0xfe} + +var ManyChainMultiSigZkBytecode = []byte{0xde, 0xad, 0xbe, 0xef} From 9348b7f6a363e8f2156d634f0ac0de70e80445c6 Mon Sep 17 00:00:00 2001 From: Graham Goh Date: Wed, 17 Jun 2026 00:41:24 +1000 Subject: [PATCH 2/2] incorporate feedback --- .../internal/families/evm/codegen.go | 22 ++++---- .../internal/families/evm/contract.go | 53 +++++++++++-------- 2 files changed, 43 insertions(+), 32 deletions(-) diff --git a/tools/operations-gen/internal/families/evm/codegen.go b/tools/operations-gen/internal/families/evm/codegen.go index 0ac6798c0..54b4d690f 100644 --- a/tools/operations-gen/internal/families/evm/codegen.go +++ b/tools/operations-gen/internal/families/evm/codegen.go @@ -87,16 +87,18 @@ func generateOperationsFile(info *ContractInfo, tmpl *template.Template) error { func prepareTemplateData(info *ContractInfo) templateData { data := templateData{ - PackageName: info.PackageName, - PackageNameHyphen: toKebabCase(info.PackageName), - ContractType: info.Name, - Version: info.Version, - GobindingsImport: info.GobindingsPackage, - ZkSyncBytecodeSymbol: info.ZkSyncBytecodeSymbol, - ZkSyncBytecodeImport: info.ZkSyncBytecodePackage, - ZkSyncBytecodeUseGobindings: info.ZkSyncBytecodeSymbol != "" && info.ZkSyncBytecodePackage == info.GobindingsPackage, - NeedsBigInt: ChecksNeedsBigInt(info), - OmitDeploy: info.OmitDeploy, + PackageName: info.PackageName, + PackageNameHyphen: toKebabCase(info.PackageName), + ContractType: info.Name, + Version: info.Version, + GobindingsImport: info.GobindingsPackage, + NeedsBigInt: ChecksNeedsBigInt(info), + OmitDeploy: info.OmitDeploy, + } + if info.ZkSync != nil { + data.ZkSyncBytecodeSymbol = info.ZkSync.BytecodeSymbol + data.ZkSyncBytecodeImport = info.ZkSync.BytecodePackage + data.ZkSyncBytecodeUseGobindings = info.ZkSync.BytecodePackage == info.GobindingsPackage } if info.Constructor != nil { diff --git a/tools/operations-gen/internal/families/evm/contract.go b/tools/operations-gen/internal/families/evm/contract.go index 84acc3a59..93b955787 100644 --- a/tools/operations-gen/internal/families/evm/contract.go +++ b/tools/operations-gen/internal/families/evm/contract.go @@ -25,18 +25,23 @@ const ( // ContractInfo holds all parsed information about a contract needed for code generation. type ContractInfo struct { - Name string - Version string - PackageName string - GobindingsPackage string - ZkSyncBytecodePackage string - ZkSyncBytecodeSymbol string - OutputPath string - OmitDeploy bool - Constructor *FunctionInfo - Functions map[string]*FunctionInfo - FunctionOrder []string - StructDefs map[string]*structDef + Name string + Version string + PackageName string + GobindingsPackage string + ZkSync *ZkSyncContractInfo + OutputPath string + OmitDeploy bool + Constructor *FunctionInfo + Functions map[string]*FunctionInfo + FunctionOrder []string + StructDefs map[string]*structDef +} + +// ZkSyncContractInfo holds resolved zkSync VM deploy bytecode for code generation. +type ZkSyncContractInfo struct { + BytecodePackage string + BytecodeSymbol string } type structDef struct { @@ -115,16 +120,20 @@ func extractContractInfo(cfg EvmContractConfig, input EvmInputConfig, output Evm } info := &ContractInfo{ - Name: cfg.Name, - Version: cfg.Version, - PackageName: packageName, - GobindingsPackage: cfg.GobindingsPackage, - ZkSyncBytecodePackage: zkSyncPackage, - ZkSyncBytecodeSymbol: zkSyncSymbol, - OutputPath: core.ContractOutputPath(output.BasePath, versionPath, packageName), - OmitDeploy: cfg.OmitDeploy, - Functions: make(map[string]*FunctionInfo), - StructDefs: make(map[string]*structDef), + Name: cfg.Name, + Version: cfg.Version, + PackageName: packageName, + GobindingsPackage: cfg.GobindingsPackage, + OutputPath: core.ContractOutputPath(output.BasePath, versionPath, packageName), + OmitDeploy: cfg.OmitDeploy, + Functions: make(map[string]*FunctionInfo), + StructDefs: make(map[string]*structDef), + } + if zkSyncSymbol != "" { + info.ZkSync = &ZkSyncContractInfo{ + BytecodePackage: zkSyncPackage, + BytecodeSymbol: zkSyncSymbol, + } } extractConstructor(info, parsedAbi)