Skip to content

Commit fef2b6c

Browse files
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.
1 parent 7f1a31d commit fef2b6c

16 files changed

Lines changed: 495 additions & 40 deletions

File tree

.changeset/clear-beans-deny.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"operations-gen": minor
3+
---
4+
5+
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.

tools/operations-gen/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ contracts:
136136
| `version` | Yes | Config schema version |
137137
| `chain_family` | No | Target chain family. Only `"evm"` is supported. Defaults to `"evm"`. |
138138
| `input.gobindings_package` | No | Parent Go import path or relative filesystem path containing versioned abigen packages. Used to derive contract bindings as `<input.gobindings_package>/<version_path>/<package_name>`. |
139+
| `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. |
139140
| `output.base_path` | Yes | Root directory where generated files are written. Relative to the config file. |
140141

141142
### Contract fields
@@ -147,7 +148,8 @@ contracts:
147148
| `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. |
148149
| `package_name` | No | Override the generated Go package name. Defaults to `snake_case(contract_name)`. |
149150
| `version_path` | No | Override the directory path derived from the version. Defaults to `v{major}_{minor}_{patch}`. |
150-
| `omit_deploy` | No | Skip generation of the `Deploy` operation and bytecode constant. Defaults to `false`. |
151+
| `omit_deploy` | No | Skip generation of the `Deploy` operation and bytecode constant. Defaults to `false`. Cannot be combined with `zksync_bytecode`. |
152+
| `zksync_bytecode` | No | zkSync VM deploy bytecode symbol, or `{package, symbol}`. Package defaults to `input.zksync_bindings_package`, then the contract's `gobindings_package`. |
151153

152154
### Function access control
153155

tools/operations-gen/generate/templates/evm/operations.tmpl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ import (
2323
cld_ops "github.com/smartcontractkit/chainlink-deployments-framework/operations"
2424
{{- end}}
2525
gobindings "{{.GobindingsImport}}"
26+
{{- if and .ZksyncBytecodeSymbol (not .ZksyncBytecodeUseGobindings)}}
27+
zkbindings "{{.ZksyncBytecodeImport}}"
28+
{{- end}}
2629
)
2730

2831
var ContractType cldf_deployment.ContractType = "{{.ContractType}}"
@@ -61,6 +64,9 @@ var Deploy = contract.NewDeploy(contract.DeployParams[ConstructorArgs]{
6164
BytecodeByTypeAndVersion: map[string]contract.Bytecode{
6265
cldf_deployment.NewTypeAndVersion(ContractType, *Version).String(): {
6366
EVM: common.FromHex(gobindings.{{.ContractType}}MetaData.Bin),
67+
{{- if .ZksyncBytecodeSymbol}}
68+
ZkSyncVM: {{if .ZksyncBytecodeUseGobindings}}gobindings{{else}}zkbindings{{end}}.{{.ZksyncBytecodeSymbol}},
69+
{{- end}}
6470
},
6571
},
6672
})

tools/operations-gen/internal/families/evm/codegen.go

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,22 @@ import (
1313
// ---- Template data (EVM-specific) ----
1414

1515
type templateData struct {
16-
PackageName string
17-
PackageNameHyphen string
18-
ContractType string
19-
Version string
20-
GobindingsImport string
21-
NeedsBigInt bool
22-
HasWriteOps bool
23-
OmitDeploy bool
24-
Constructor *constructorData
25-
StructDefs []structDefData
26-
ArgStructs []argStructData
27-
Operations []OperationData
28-
ContractMethods []contractMethodData
16+
PackageName string
17+
PackageNameHyphen string
18+
ContractType string
19+
Version string
20+
GobindingsImport string
21+
ZksyncBytecodeSymbol string
22+
ZksyncBytecodeImport string
23+
ZksyncBytecodeUseGobindings bool
24+
NeedsBigInt bool
25+
HasWriteOps bool
26+
OmitDeploy bool
27+
Constructor *constructorData
28+
StructDefs []structDefData
29+
ArgStructs []argStructData
30+
Operations []OperationData
31+
ContractMethods []contractMethodData
2932
}
3033

3134
type constructorData struct {
@@ -84,13 +87,16 @@ func generateOperationsFile(info *ContractInfo, tmpl *template.Template) error {
8487

8588
func prepareTemplateData(info *ContractInfo) templateData {
8689
data := templateData{
87-
PackageName: info.PackageName,
88-
PackageNameHyphen: toKebabCase(info.PackageName),
89-
ContractType: info.Name,
90-
Version: info.Version,
91-
GobindingsImport: info.GobindingsPackage,
92-
NeedsBigInt: ChecksNeedsBigInt(info),
93-
OmitDeploy: info.OmitDeploy,
90+
PackageName: info.PackageName,
91+
PackageNameHyphen: toKebabCase(info.PackageName),
92+
ContractType: info.Name,
93+
Version: info.Version,
94+
GobindingsImport: info.GobindingsPackage,
95+
ZksyncBytecodeSymbol: info.ZksyncBytecodeSymbol,
96+
ZksyncBytecodeImport: info.ZksyncBytecodePackage,
97+
ZksyncBytecodeUseGobindings: info.ZksyncBytecodeSymbol != "" && info.ZksyncBytecodePackage == info.GobindingsPackage,
98+
NeedsBigInt: ChecksNeedsBigInt(info),
99+
OmitDeploy: info.OmitDeploy,
94100
}
95101

96102
if info.Constructor != nil {

tools/operations-gen/internal/families/evm/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ type EvmContractConfig struct {
88
PackageName string `yaml:"package_name,omitempty"` // Optional: override package name
99
OmitDeploy bool `yaml:"omit_deploy,omitempty"` // Optional: skip Deploy operation
1010
GobindingsPackage string `yaml:"gobindings_package"` // Optional: override the derived gobindings import path or relative filesystem path for this contract.
11+
ZksyncBytecode ZksyncBytecodeRef `yaml:"zksync_bytecode,omitempty"`
1112
Functions []EvmFunctionConfig `yaml:"functions"`
1213
ConfigDir string `yaml:"-"`
1314
}
@@ -18,6 +19,9 @@ type EvmInputConfig struct {
1819
// Contract packages default to:
1920
// <gobindings_package>/<version_path>/<package_name>
2021
GobindingsPackage string `yaml:"gobindings_package"`
22+
// ZksyncBindingsPackage is the default Go import path for zkSync VM deploy bytecode.
23+
// Used when a contract sets zksync_bytecode to a symbol only.
24+
ZksyncBindingsPackage string `yaml:"zksync_bindings_package,omitempty"`
2125
}
2226

2327
// EvmFunctionConfig selects a contract function and assigns its access control.

tools/operations-gen/internal/families/evm/contract.go

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,18 @@ const (
2525

2626
// ContractInfo holds all parsed information about a contract needed for code generation.
2727
type ContractInfo struct {
28-
Name string
29-
Version string
30-
PackageName string
31-
GobindingsPackage string
32-
OutputPath string
33-
OmitDeploy bool
34-
Constructor *FunctionInfo
35-
Functions map[string]*FunctionInfo
36-
FunctionOrder []string
37-
StructDefs map[string]*structDef
28+
Name string
29+
Version string
30+
PackageName string
31+
GobindingsPackage string
32+
ZksyncBytecodePackage string
33+
ZksyncBytecodeSymbol string
34+
OutputPath string
35+
OmitDeploy bool
36+
Constructor *FunctionInfo
37+
Functions map[string]*FunctionInfo
38+
FunctionOrder []string
39+
StructDefs map[string]*structDef
3840
}
3941

4042
type structDef struct {
@@ -98,20 +100,31 @@ func extractContractInfo(cfg EvmContractConfig, input EvmInputConfig, output Evm
98100
}
99101
cfg.GobindingsPackage = resolvedGobindingsPackage
100102

103+
if cfg.OmitDeploy && !cfg.ZksyncBytecode.IsZero() {
104+
return nil, fmt.Errorf("contract %q: zksync_bytecode cannot be set when omit_deploy is true", cfg.Name)
105+
}
106+
107+
zksyncPackage, zksyncSymbol, err := resolveZksyncBytecode(cfg, input, cfg.GobindingsPackage)
108+
if err != nil {
109+
return nil, err
110+
}
111+
101112
parsedAbi, err := ReadABI(cfg)
102113
if err != nil {
103114
return nil, err
104115
}
105116

106117
info := &ContractInfo{
107-
Name: cfg.Name,
108-
Version: cfg.Version,
109-
PackageName: packageName,
110-
GobindingsPackage: cfg.GobindingsPackage,
111-
OutputPath: core.ContractOutputPath(output.BasePath, versionPath, packageName),
112-
OmitDeploy: cfg.OmitDeploy,
113-
Functions: make(map[string]*FunctionInfo),
114-
StructDefs: make(map[string]*structDef),
118+
Name: cfg.Name,
119+
Version: cfg.Version,
120+
PackageName: packageName,
121+
GobindingsPackage: cfg.GobindingsPackage,
122+
ZksyncBytecodePackage: zksyncPackage,
123+
ZksyncBytecodeSymbol: zksyncSymbol,
124+
OutputPath: core.ContractOutputPath(output.BasePath, versionPath, packageName),
125+
OmitDeploy: cfg.OmitDeploy,
126+
Functions: make(map[string]*FunctionInfo),
127+
StructDefs: make(map[string]*structDef),
115128
}
116129

117130
extractConstructor(info, parsedAbi)

tools/operations-gen/internal/families/evm/evm_golden_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ func TestGenerateFeeQuoter(t *testing.T) {
3939
runGoldenGenerationTest(t, "operations_gen_fee_quoter.yaml", "fee_quoter.golden.go")
4040
}
4141

42+
// TestGenerateLinkTokenWithZksyncBindingsPackage verifies generation when
43+
// input.zksync_bindings_package and contract zksync_bytecode are both set.
44+
func TestGenerateLinkTokenWithZksyncBindingsPackage(t *testing.T) {
45+
t.Parallel()
46+
runGoldenGenerationTest(t, "operations_gen_link_token_zksync_config.yaml", "link_token_zksync.golden.go")
47+
}
48+
4249
func runGoldenGenerationTest(t *testing.T, configFileName string, goldenFileName string) {
4350
t.Helper()
4451

tools/operations-gen/internal/families/evm/evm_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,51 @@ contracts:
9898
require.Contains(t, string(got), `gobindings "`+wantGobindingsPackage+`"`)
9999
require.NotContains(t, string(got), `gobindings "./testdata/evm/gobindings`)
100100
}
101+
102+
func TestGenerateResolvesRelativeZksyncBindingsPackage(t *testing.T) {
103+
t.Parallel()
104+
105+
const wantZksyncPackage = "github.com/smartcontractkit/chainlink-deployments-framework/tools/operations-gen/testdata/evm/zksync_bindings"
106+
config := `version: "1.0.0"
107+
chain_family: evm
108+
109+
input:
110+
gobindings_package: "github.com/smartcontractkit/chainlink-deployments-framework/tools/operations-gen/testdata/evm/gobindings"
111+
zksync_bindings_package: "./testdata/evm/zksync_bindings"
112+
113+
output:
114+
base_path: "."
115+
116+
contracts:
117+
- contract_name: ManyChainMultiSig
118+
version: "1.0.0"
119+
package_name: many_chain_multi_sig
120+
zksync_bytecode: ManyChainMultiSigZkBytecode
121+
functions:
122+
- name: owner
123+
access: public
124+
`
125+
126+
var cfg core.Config
127+
require.NoError(t, yaml.Unmarshal([]byte(config), &cfg), "parsing config")
128+
129+
moduleDir, err := filepath.Abs(filepath.Join("..", "..", ".."))
130+
require.NoError(t, err)
131+
tmpDir := t.TempDir()
132+
outputBasePath, err := filepath.Rel(moduleDir, tmpDir)
133+
require.NoError(t, err)
134+
cfg.ConfigDir = moduleDir
135+
cfg.Output = mustYAMLNode(t, evm.EvmOutputConfig{BasePath: outputBasePath})
136+
137+
tmpl, err := generate.LoadTemplate("evm")
138+
require.NoError(t, err, "loadTemplate")
139+
140+
require.NoError(t, evm.Handler{}.Generate(cfg, tmpl), "Generate")
141+
142+
outputPath := core.ContractOutputPath(tmpDir, core.VersionToPath("1.0.0"), "many_chain_multi_sig")
143+
got, err := os.ReadFile(outputPath)
144+
require.NoError(t, err, "reading generated file %s", outputPath)
145+
146+
require.Contains(t, string(got), `zkbindings "`+wantZksyncPackage+`"`)
147+
require.Contains(t, string(got), "ZkSyncVM: zkbindings.ManyChainMultiSigZkBytecode")
148+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package evm
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
7+
"gopkg.in/yaml.v3"
8+
)
9+
10+
// ZksyncBytecodeRef identifies zkSync VM deploy bytecode for a contract.
11+
// YAML accepts a shorthand symbol string or {package, symbol}.
12+
type ZksyncBytecodeRef struct {
13+
Package string
14+
Symbol string
15+
}
16+
17+
func (z *ZksyncBytecodeRef) UnmarshalYAML(value *yaml.Node) error {
18+
*z = ZksyncBytecodeRef{}
19+
if value == nil {
20+
return nil
21+
}
22+
23+
switch value.Kind {
24+
case yaml.ScalarNode:
25+
if isYAMLNullScalar(value) {
26+
return nil
27+
}
28+
z.Symbol = value.Value
29+
30+
return nil
31+
case yaml.MappingNode:
32+
var raw struct {
33+
Package string `yaml:"package"`
34+
Symbol string `yaml:"symbol"`
35+
}
36+
if err := value.Decode(&raw); err != nil {
37+
return err
38+
}
39+
z.Package = raw.Package
40+
z.Symbol = raw.Symbol
41+
if z.Symbol == "" {
42+
return errors.New("zksync_bytecode mapping requires symbol")
43+
}
44+
45+
return nil
46+
case yaml.DocumentNode, yaml.SequenceNode, yaml.AliasNode:
47+
return fmt.Errorf("zksync_bytecode must be a string or mapping, got %v", value.ShortTag())
48+
}
49+
50+
return fmt.Errorf("zksync_bytecode must be a string or mapping, got %v", value.ShortTag())
51+
}
52+
53+
func isYAMLNullScalar(value *yaml.Node) bool {
54+
return value.Tag == "!!null" || value.Value == "" || value.Value == "~"
55+
}
56+
57+
func (z ZksyncBytecodeRef) IsZero() bool {
58+
return z.Symbol == ""
59+
}
60+
61+
func resolveZksyncBytecode(
62+
cfg EvmContractConfig,
63+
input EvmInputConfig,
64+
gobindingsPackage string,
65+
) (packagePath string, symbol string, err error) {
66+
if cfg.ZksyncBytecode.IsZero() {
67+
return "", "", nil
68+
}
69+
70+
symbol = cfg.ZksyncBytecode.Symbol
71+
pkgPath := cfg.ZksyncBytecode.Package
72+
if pkgPath == "" {
73+
pkgPath = input.ZksyncBindingsPackage
74+
}
75+
if pkgPath == "" {
76+
return gobindingsPackage, symbol, nil
77+
}
78+
79+
resolved, err := resolveGobindingsImportPath(pkgPath, cfg.ConfigDir)
80+
if err != nil {
81+
return "", "", fmt.Errorf("resolve zksync_bytecode package for contract %q: %w", cfg.Name, err)
82+
}
83+
84+
return resolved, symbol, nil
85+
}

0 commit comments

Comments
 (0)