Skip to content

Commit 75ed314

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 75ed314

16 files changed

Lines changed: 458 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: supports zksync

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: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package evm
2+
3+
import (
4+
"fmt"
5+
6+
"gopkg.in/yaml.v3"
7+
)
8+
9+
// ZksyncBytecodeRef identifies zkSync VM deploy bytecode for a contract.
10+
// YAML accepts a shorthand symbol string or {package, symbol}.
11+
type ZksyncBytecodeRef struct {
12+
Package string
13+
Symbol string
14+
}
15+
16+
func (z *ZksyncBytecodeRef) UnmarshalYAML(value *yaml.Node) error {
17+
*z = ZksyncBytecodeRef{}
18+
if value == nil {
19+
return nil
20+
}
21+
22+
switch value.Kind {
23+
case yaml.ScalarNode:
24+
z.Symbol = value.Value
25+
26+
return nil
27+
case yaml.MappingNode:
28+
var raw struct {
29+
Package string `yaml:"package"`
30+
Symbol string `yaml:"symbol"`
31+
}
32+
if err := value.Decode(&raw); err != nil {
33+
return err
34+
}
35+
z.Package = raw.Package
36+
z.Symbol = raw.Symbol
37+
if z.Symbol == "" {
38+
return fmt.Errorf("zksync_bytecode mapping requires symbol")
39+
}
40+
41+
return nil
42+
default:
43+
return fmt.Errorf("zksync_bytecode must be a string or mapping, got %v", value.ShortTag())
44+
}
45+
}
46+
47+
func (z ZksyncBytecodeRef) IsZero() bool {
48+
return z.Symbol == ""
49+
}
50+
51+
func resolveZksyncBytecode(
52+
cfg EvmContractConfig,
53+
input EvmInputConfig,
54+
gobindingsPackage string,
55+
) (packagePath string, symbol string, err error) {
56+
if cfg.ZksyncBytecode.IsZero() {
57+
return "", "", nil
58+
}
59+
60+
symbol = cfg.ZksyncBytecode.Symbol
61+
pkgPath := cfg.ZksyncBytecode.Package
62+
if pkgPath == "" {
63+
pkgPath = input.ZksyncBindingsPackage
64+
}
65+
if pkgPath == "" {
66+
return gobindingsPackage, symbol, nil
67+
}
68+
69+
resolved, err := resolveGobindingsImportPath(pkgPath, cfg.ConfigDir)
70+
if err != nil {
71+
return "", "", fmt.Errorf("resolve zksync_bytecode package for contract %q: %w", cfg.Name, err)
72+
}
73+
74+
return resolved, symbol, nil
75+
}

0 commit comments

Comments
 (0)