Skip to content

Commit 830158e

Browse files
ilanolkiesCopilot
andauthored
Add zksync compilation scripts (#3)
* Add zksync compilation scripts * Amend variable order * Add docs * Simple generation * Add wrapper See smartcontractkit/chainlink#17047 * Generate necessary contracts * Remove simple tempalte * CI * Remove magic number * Update gethwrappers/generation/generate/zksync/vars.go Co-authored-by: Copilot <[email protected]> * Make forge-zksync phony + detect version as abigen Resolves https://github.com/smartcontractkit/chainlink-evm/pull/3/files#r2032856565 * Detect version * Revert .github/workflows/solidity-wrappers.yml * Comment zksync-wrappers * Remove FOUNDRY_PROJECT_SUFFIX variable usage * Fix FOUNDRY_PROFILE * Remove variable * Use bytecode instead of dir This enables using bytcode from other kind of artifacts * Refactor forge json decode * Update install_forge_zksync --------- Co-authored-by: Copilot <[email protected]>
1 parent 2211e07 commit 830158e

File tree

19 files changed

+659
-0
lines changed

19 files changed

+659
-0
lines changed

.github/workflows/solidity-wrappers.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@
6464
# run: make wrappers-all
6565
# working-directory: ./contracts
6666
#
67+
# - name: Run zksync compile and generate wrappers
68+
# run: make wrappers-zksync
69+
# working-directory: ./contracts
70+
#
6771
# - name: Assume role capable of dispatching action
6872
# uses: smartcontractkit/.github/actions/setup-github-token@ef78fa97bf3c77de6563db1175422703e9e6674f # [email protected]
6973
# id: get-gh-token

.github/workflows/solidity.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ jobs:
121121
working-directory: ./chainlink/contracts
122122
run: make wrappers-all
123123

124+
- name: Run zksync compile and generate wrappers
125+
working-directory: ./chainlink/contracts
126+
run: make wrappers-zksync
127+
124128
- name: Check if Go solidity wrappers are updated
125129
if: ${{ needs.changes.outputs.changes == 'true' }}
126130
working-directory: chainlink

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ tmp/
44
.pnp
55
.pnp.js
66
tools/bin/abigen
7+
tools/bin/forge_zksync
78

89
/chainlink
910
core/chainlink

contracts/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ artifacts
22
cache
33
node_modules
44
solc
5+
zkout
56
abi
67
coverage
78
coverage.json

contracts/FOUNDRY_GUIDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ Check out the official [Foundry best practices section](https://book.getfoundry.
219219
- Use `make snapshot` to generate the correct snapshot for the selected Foundry profile.
220220
- Use `make snapshot-diff` to see the diff between the local snapshot and your latest changes.
221221
- use `make wrappers` to generate the gethwrappers for the selected Foundry profile.
222+
- use `make wrappers-zksync` to generate the gethwrappers for ZK Sync deployments.
222223
- Use `vm.recordLogs();` to record all logs emitted
223224
- Use `vm.getRecordedLogs()` to get the logs emitted.
224225
- This way you can assert that a log was *not* emitted.

contracts/GNUmakefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ mockery: $(mockery) ## Install mockery.
4545
foundry: ## Install foundry.
4646
foundryup --install v1.0.0
4747

48+
.PHONY: forge-zksync
49+
forge-zksync:
50+
../tools/bin/install_forge_zksync
51+
4852
.PHONY: foundry-refresh
4953
foundry-refresh: foundry
5054
git submodule deinit -f .
@@ -82,6 +86,10 @@ wrappers-all: pnpmdep mockery abigen ## Recompiles solidity contracts and their
8286
# go_generate contains a call to compile all contracts before generating wrappers
8387
go generate ../gethwrappers/go_generate.go
8488

89+
.PHONY: wrappers-zksync
90+
wrappers-zksync: pnpmdep forge-zksync
91+
go generate ../gethwrappers/zksync/go_generate.go
92+
8593
help:
8694
@echo ""
8795
@echo " .__ .__ .__ .__ __"

contracts/scripts/zksync_compile_all

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env bash
2+
3+
set -e
4+
5+
echo " ┌──────────────────────────────────────────────┐"
6+
echo " │ Compiling ZKSync contracts... │"
7+
echo " └──────────────────────────────────────────────┘"
8+
9+
CONTRACTS_DIR="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; cd ../ && pwd -P )"
10+
11+
compileContract() {
12+
local contract
13+
contract=$(basename "$1")
14+
echo "Compiling" "$contract"
15+
16+
export FOUNDRY_PROFILE="$2"
17+
18+
${CONTRACTS_DIR}/../tools/bin/forge_zksync build $CONTRACTS_DIR/src/v0.8/"$1.sol" --zksync \
19+
--root $CONTRACTS_DIR \
20+
$3
21+
}
22+
23+
compileContract shared/token/ERC677/LinkToken shared "--use 0.8.19"
24+
compileContract shared/token/ERC677/BurnMintERC677 shared "--use 0.8.19"
25+
compileContract vendor/multicall/ebd8b64/src/Multicall3 shared "--use 0.8.19"
26+
compileContract keystone/CapabilitiesRegistry keystone
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package zksyncwrapper
2+
3+
import (
4+
"context"
5+
"crypto/rand"
6+
"fmt"
7+
8+
"github.com/ethereum/go-ethereum/accounts/abi/bind"
9+
"github.com/ethereum/go-ethereum/common"
10+
"github.com/zksync-sdk/zksync2-go/accounts"
11+
"github.com/zksync-sdk/zksync2-go/clients"
12+
"github.com/zksync-sdk/zksync2-go/types"
13+
)
14+
15+
// this file is used as a template. see wrap_zk_bytecode.go before editing
16+
func DeployPlaceholderContractNameZk(deployOpts *accounts.TransactOpts, client *clients.Client, wallet *accounts.Wallet, backend bind.ContractBackend, args ...interface{}) (common.Address, *types.Receipt, *PlaceholderContractName, error) {
17+
var calldata []byte
18+
if len(args) > 0 {
19+
abi, err := PlaceholderContractNameMetaData.GetAbi()
20+
if err != nil {
21+
return common.Address{}, nil, nil, err
22+
}
23+
calldata, err = abi.Pack("", args...)
24+
if err != nil {
25+
return common.Address{}, nil, nil, err
26+
}
27+
}
28+
29+
salt := make([]byte, 32)
30+
n, err := rand.Read(salt)
31+
if err != nil {
32+
return common.Address{}, nil, nil, err
33+
}
34+
if n != len(salt) {
35+
return common.Address{}, nil, nil, fmt.Errorf("failed to read random bytes: expected %d, got %d", len(salt), n)
36+
}
37+
38+
txHash, err := wallet.Deploy(deployOpts, accounts.Create2Transaction{
39+
Bytecode: ZkBytecode,
40+
Calldata: calldata,
41+
Salt: salt,
42+
})
43+
if err != nil {
44+
return common.Address{}, nil, nil, err
45+
}
46+
47+
receipt, err := client.WaitMined(context.Background(), txHash)
48+
if err != nil {
49+
return common.Address{}, nil, nil, err
50+
}
51+
52+
address := receipt.ContractAddress
53+
contract, err := NewPlaceholderContractName(address, backend)
54+
if err != nil {
55+
return common.Address{}, nil, nil, err
56+
}
57+
58+
return address, receipt, contract, nil
59+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package zksyncwrapper
2+
3+
import (
4+
"github.com/ethereum/go-ethereum/accounts/abi"
5+
"github.com/ethereum/go-ethereum/accounts/abi/bind"
6+
"github.com/ethereum/go-ethereum/common"
7+
)
8+
9+
// these are mocks for the placeholders used in the template
10+
type PlaceholderContractName struct{}
11+
12+
type IPlaceholderContractNameMetaData struct {
13+
GetAbi func() (*abi.ABI, error)
14+
}
15+
16+
var PlaceholderContractNameMetaData = IPlaceholderContractNameMetaData{
17+
GetAbi: func() (*abi.ABI, error) {
18+
return nil, nil
19+
},
20+
}
21+
22+
var ZkBytecode = []byte{}
23+
24+
func NewPlaceholderContractName(address common.Address, backend bind.ContractBackend) (*PlaceholderContractName, error) {
25+
return nil, nil
26+
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package zksyncwrapper
2+
3+
import (
4+
"bytes"
5+
_ "embed"
6+
"encoding/json"
7+
"fmt"
8+
"go/ast"
9+
"go/format"
10+
"go/token"
11+
"os"
12+
"strings"
13+
)
14+
15+
func ReadBytecodeFromForgeJson(srcFile string) string {
16+
jsonData, err := os.ReadFile(srcFile)
17+
if err != nil {
18+
panic(err)
19+
}
20+
21+
var bytecodeData struct {
22+
Bytecode struct {
23+
Object string `json:"object"`
24+
} `json:"bytecode"`
25+
}
26+
if err := json.Unmarshal(jsonData, &bytecodeData); err != nil {
27+
panic(err)
28+
}
29+
30+
return bytecodeData.Bytecode.Object
31+
}
32+
33+
//go:embed template.go
34+
var zksyncDeployTemplate string
35+
36+
func WrapZksyncDeploy(bytecode, className, pkgName, outPath string) {
37+
fmt.Printf("Generating zk bytecode binding for %s\n", pkgName)
38+
39+
fileNode := &ast.File{
40+
Name: ast.NewIdent(pkgName),
41+
Decls: []ast.Decl{
42+
declareImports(),
43+
declareDeployFunction(className),
44+
declareBytecodeVar(bytecode)}}
45+
46+
writeFile(fileNode, outPath)
47+
}
48+
49+
const comment = `// Code generated - DO NOT EDIT.
50+
// This file is a generated binding and any manual changes will be lost.
51+
`
52+
53+
var importValues = []string{
54+
`"context"`,
55+
`"crypto/rand"`,
56+
`"fmt"`,
57+
`"github.com/ethereum/go-ethereum/accounts/abi/bind"`,
58+
`"github.com/ethereum/go-ethereum/common"`,
59+
`"github.com/zksync-sdk/zksync2-go/accounts"`,
60+
`"github.com/zksync-sdk/zksync2-go/clients"`,
61+
`"github.com/zksync-sdk/zksync2-go/types"`,
62+
}
63+
64+
func declareImports() ast.Decl {
65+
66+
specs := make([]ast.Spec, len(importValues))
67+
for i, value := range importValues {
68+
specs[i] = &ast.ImportSpec{
69+
Path: &ast.BasicLit{
70+
Kind: token.STRING,
71+
Value: value}}
72+
}
73+
74+
return &ast.GenDecl{
75+
Tok: token.IMPORT,
76+
Specs: specs}
77+
}
78+
79+
func declareDeployFunction(contractName string) ast.Decl {
80+
template := zksyncDeployTemplate
81+
82+
sep := "\n"
83+
lines := strings.Split(template, sep)
84+
from := 0
85+
to := 0
86+
// get the func body as string
87+
for !strings.Contains(lines[to], "return address, receipt, contract, nil") {
88+
if strings.Contains(lines[to], "DeployPlaceholderContractNameZk") {
89+
from = to
90+
}
91+
to++
92+
}
93+
template = strings.Join(lines[from+1:to+1], sep)
94+
template = template[1:] // remove the first space
95+
template = strings.Replace(template, "PlaceholderContractName", contractName, 2)
96+
97+
return &ast.FuncDecl{
98+
Name: ast.NewIdent("Deploy" + contractName + "Zk"),
99+
Type: &ast.FuncType{
100+
Params: &ast.FieldList{
101+
List: []*ast.Field{{
102+
Names: []*ast.Ident{ast.NewIdent("deployOpts")},
103+
Type: &ast.Ident{Name: "*accounts.TransactOpts"}}, {
104+
Names: []*ast.Ident{ast.NewIdent("client")},
105+
Type: &ast.Ident{Name: "*clients.Client"}}, {
106+
Names: []*ast.Ident{ast.NewIdent("wallet")},
107+
Type: &ast.Ident{Name: "*accounts.Wallet"}}, {
108+
Names: []*ast.Ident{ast.NewIdent("backend")},
109+
Type: &ast.Ident{Name: "bind.ContractBackend"}}, {
110+
Names: []*ast.Ident{ast.NewIdent("args")},
111+
Type: &ast.Ellipsis{Elt: &ast.Ident{Name: "interface{}"}}}}},
112+
Results: &ast.FieldList{
113+
List: []*ast.Field{
114+
{Type: &ast.Ident{Name: "common.Address"}},
115+
{Type: &ast.Ident{Name: "*types.Receipt"}},
116+
{Type: &ast.StarExpr{X: &ast.Ident{Name: contractName}}},
117+
{Type: &ast.Ident{Name: "error"}}}}},
118+
Body: &ast.BlockStmt{
119+
List: []ast.Stmt{
120+
&ast.ExprStmt{
121+
X: &ast.BasicLit{
122+
Kind: token.STRING,
123+
Value: template}}}}}
124+
}
125+
126+
func declareBytecodeVar(bytecode string) ast.Decl {
127+
return &ast.GenDecl{
128+
Tok: token.VAR,
129+
Specs: []ast.Spec{
130+
&ast.ValueSpec{
131+
Names: []*ast.Ident{ast.NewIdent("ZkBytecode")},
132+
Values: []ast.Expr{
133+
&ast.CallExpr{
134+
Fun: &ast.SelectorExpr{
135+
X: ast.NewIdent("common"),
136+
Sel: ast.NewIdent("Hex2Bytes")},
137+
Args: []ast.Expr{
138+
&ast.BasicLit{
139+
Kind: token.STRING,
140+
Value: fmt.Sprintf(`"%s"`, bytecode)}}}}}}}
141+
}
142+
143+
func writeFile(fileNode *ast.File, dstFile string) {
144+
var buf bytes.Buffer
145+
fset := token.NewFileSet()
146+
if err := format.Node(&buf, fset, fileNode); err != nil {
147+
panic(err)
148+
}
149+
150+
bs := buf.Bytes()
151+
bs = append([]byte(comment), bs...)
152+
153+
if err := os.WriteFile(dstFile, bs, 0600); err != nil {
154+
panic(err)
155+
}
156+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
8+
"github.com/smartcontractkit/chainlink-evm/gethwrappers"
9+
zksyncwrapper "github.com/smartcontractkit/chainlink-evm/gethwrappers/generation/generate/zksync"
10+
)
11+
12+
func main() {
13+
project := os.Args[1]
14+
contractName := os.Args[2]
15+
packageName := os.Args[3]
16+
17+
fmt.Println("Generating", packageName, "contract wrapper")
18+
19+
cwd, err := os.Getwd() // gethwrappers/zksync directory
20+
if err != nil {
21+
gethwrappers.Exit("could not get working directory", err)
22+
}
23+
24+
srcFile := filepath.Join(cwd, "..", "..", "contracts", "zkout", contractName+".sol", contractName+".json")
25+
bytecode := zksyncwrapper.ReadBytecodeFromForgeJson(srcFile)
26+
27+
outPath := filepath.Join(cwd, "..", project, "generated", packageName, packageName+"_zksync.go")
28+
29+
zksyncwrapper.WrapZksyncDeploy(bytecode, contractName, packageName, outPath)
30+
}

0 commit comments

Comments
 (0)