Compare commits

..

35 Commits

Author SHA1 Message Date
colin
2b2cc62efe fix(coordinator): add metric roller_proofs_generated_failed_time (#419) 2023-04-12 18:27:55 +08:00
colin
807b7c7f33 refactor(coordinator): adjust logs for Loki query (#417)
Co-authored-by: Lawliet-Chan <1576710154@qq.com>
Co-authored-by: Péter Garamvölgyi <peter@scroll.io>
2023-04-11 16:38:52 +08:00
HAOYUatHZ
454f032c9f doc(test): add testing doc (#412) 2023-04-11 11:05:33 +08:00
maskpp
d1c4fa716d fix(test): Clean the exited container by --rm after container stopped. (#416) 2023-04-10 19:18:59 +08:00
Lawliet-Chan
de1e40d19c fix(libzkp): load_params and seed in zk (#415) 2023-04-10 15:41:16 +08:00
Ahmed Castro
76b5a6c751 Small typo fix on documentation comments (#411)
Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com>
2023-04-09 15:34:27 +08:00
colin
bad77eac2f feat(coordinator): prover monitoring (#392)
Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com>
2023-04-07 09:06:58 +08:00
Péter Garamvölgyi
5d761ad812 Make sure attempts can be deserialized from db on startup (#410) 2023-04-05 19:00:54 +02:00
Nazarii Denha
4042bea6db retry proving timeout batch (#313)
Co-authored-by: Péter Garamvölgyi <peter@scroll.io>
Co-authored-by: colin <102356659+colinlyguo@users.noreply.github.com>
2023-04-05 16:42:06 +02:00
maskpp
de7c38a903 feat(test): let integration-test log verbosity be configurable (#409) 2023-04-04 16:20:12 +08:00
Péter Garamvölgyi
41e2d960d8 Fix already executed revert message (#408) 2023-04-03 21:26:30 +08:00
HAOYUatHZ
170bc08207 build(docker): auto docker push when pushing git tags (#406) 2023-04-03 16:52:51 +08:00
maskpp
d3fc4e1606 feat(pending limit): Let sender's pending limit be configurable. (#398)
Co-authored-by: Péter Garamvölgyi <peter@scroll.io>
Co-authored-by: ChuhanJin <60994121+ChuhanJin@users.noreply.github.com>
Co-authored-by: vincent <419436363@qq.com>
Co-authored-by: colinlyguo <651734127@qq.com>
2023-04-03 14:24:47 +08:00
HAOYUatHZ
77749477db build(docker): only build docker images when push github tags (#404) 2023-04-01 11:54:56 +08:00
HAOYUatHZ
1a5df6f4d7 fix(build): move docker build from jenkins to github to avoid unknown errors (#403) 2023-03-31 15:55:55 +08:00
maskpp
826280253a fix(test): fix bug in testBatchProposerProposeBatch (#399)
Co-authored-by: colinlyguo <651734127@qq.com>
2023-03-31 13:58:46 +08:00
ChuhanJin
d376c903af feat(bridge): separate bridge into subcomponents (#397)
Co-authored-by: vincent <419436363@qq.com>
Co-authored-by: colinlyguo <651734127@qq.com>
2023-03-31 11:04:24 +08:00
Max Wolff
179c6ee978 add failed relay status to db (#384)
Co-authored-by: Péter Garamvölgyi <peter@scroll.io>
2023-03-27 11:52:07 +02:00
HAOYUatHZ
165c9092da feat(bridge): fetch block transaction data instead of trace (#393)
Co-authored-by: colinlyguo <651734127@qq.com>
2023-03-25 11:50:02 +08:00
HAOYUatHZ
54c28fa512 build: update version (#387) 2023-03-24 09:25:10 +08:00
maskpp
3c7c41e1bb fix(cmd test): add more log to handle error and remove serial execution test (#391) 2023-03-23 16:43:41 +08:00
Péter Garamvölgyi
2962fa4b0e batch proposer: only sleep if we failed to create batch (#388) 2023-03-22 22:16:31 +08:00
colin
5b7ee9e55c fix(batch proposer): propose up to propose batch limit (#383)
Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com>
2023-03-22 20:39:25 +08:00
maskpp
0b8a737090 fix(integration test): fix bug in integration test (#386)
Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com>
2023-03-22 18:18:13 +08:00
Lawliet-Chan
ceb406b68b feat(roller): add dump proof (#289)
Co-authored-by: xinran chen <lawliet@xinran-m1x.local>
Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com>
2023-03-22 13:46:09 +08:00
HAOYUatHZ
1a29797ee1 fix(CI): temporarily disable integration test (#385) 2023-03-22 12:42:52 +08:00
Xi Lin
19f74075a1 fix(contracts): add missing payable in L2 ERC721/1155 gateway (#382) 2023-03-22 12:20:12 +08:00
HAOYUatHZ
c752e3473d feat: upgade l2geth to alpha-v1.10 (#379) 2023-03-21 15:53:52 +08:00
Haichen Shen
cb6a609366 fix(contract): mutated function -> mutation function (#377)
Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com>
2023-03-20 21:20:19 +08:00
maskpp
87cc80e6e3 feat(jenkins): Remove duplicate module tests (#374)
Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com>
2023-03-20 19:59:27 +08:00
Xi Lin
77f1fa7ca7 style(contracts): reformat contract codes with prettier (#376) 2023-03-20 13:43:55 +08:00
Nazarii Denha
c2445176ec feat(verifier): add worker pool for verifying proofs (#357)
Co-authored-by: maskpp <maskpp266@gmail.com>
Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com>
Co-authored-by: Péter Garamvölgyi <peter@scroll.io>
Co-authored-by: colinlyguo <651734127@qq.com>
2023-03-18 21:39:14 +08:00
Xi Lin
3a1cb6a34b feat(contracts): add refund address in sendMessage (#371)
Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com>
2023-03-18 16:03:38 +08:00
ChuhanJin
0a404fe10f Build(contracts): add coverage report in /contracts (#373)
Co-authored-by: vincent <419436363@qq.com>
Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com>
2023-03-17 18:20:07 +08:00
Xi Lin
73b6bd176e feat(bridge): add min gas limit for message relay (#365)
Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com>
Co-authored-by: maskpp <maskpp266@gmail.com>
Co-authored-by: HAOYUatHZ <haoyu@protonmail.com>
2023-03-16 18:19:13 +08:00
217 changed files with 17539 additions and 16574 deletions

View File

@@ -66,3 +66,11 @@ jobs:
if [ -n "$(git status --porcelain)" ]; then
exit 1
fi
# docker-build:
# runs-on: ubuntu-latest
# steps:
# - name: Checkout code
# uses: actions/checkout@v2
# - name: Set up Docker Buildx
# uses: docker/setup-buildx-action@v2
# - run: make docker

View File

@@ -2,26 +2,10 @@ name: Contracts
on:
push:
branches:
- master
- main
- prod
- release/*
- staging
- develop
- alpha
paths:
- 'contracts/**'
- '.github/workflows/contracts.yaml'
pull_request:
branches:
- master
- main
- prod
- release/*
- staging
- develop
- alpha
paths:
- 'contracts/**'
- '.github/workflows/contracts.yaml'
@@ -44,6 +28,9 @@ jobs:
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly
- name: Setup LCOV
uses: hrishikesh-kadam/setup-lcov@v1
- name: Install Node.js 14
uses: actions/setup-node@v2
@@ -80,6 +67,22 @@ jobs:
- name: Run foundry tests
run: forge test -vvv
- name: Run foundry coverage
run : forge coverage --report lcov
- name : Prune coverage
run : lcov --remove ./lcov.info -o ./lcov.info.pruned 'src/mocks/*' 'src/test/*' 'scripts/*' 'node_modules/*' 'lib/*'
- name: Report code coverage
uses: zgosalvez/github-actions-report-lcov@v3
with:
coverage-files: contracts/lcov.info.pruned
minimum-coverage: 0
artifact-name: code-coverage-report
github-token: ${{ secrets.GITHUB_TOKEN }}
working-directory: contracts
update-comment: true
hardhat:
runs-on: ubuntu-latest

View File

@@ -62,3 +62,18 @@ jobs:
if [ -n "$(git status --porcelain)" ]; then
exit 1
fi
# docker-build:
# runs-on: ubuntu-latest
# steps:
# - name: Checkout code
# uses: actions/checkout@v2
# - name: Set up Docker Buildx
# uses: docker/setup-buildx-action@v2
# - name: Build and push
# uses: docker/build-push-action@v2
# with:
# context: .
# file: ./build/dockerfiles/coordinator.Dockerfile
# push: false
# # cache-from: type=gha,scope=${{ github.workflow }}
# # cache-to: type=gha,scope=${{ github.workflow }}

65
.github/workflows/docker.yaml vendored Normal file
View File

@@ -0,0 +1,65 @@
name: Docker
on:
push:
tags:
- v**
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push coordinator docker
uses: docker/build-push-action@v2
with:
context: .
file: ./build/dockerfiles/coordinator.Dockerfile
push: true
tags: scrolltech/coordinator:${{github.ref_name}}
# cache-from: type=gha,scope=${{ github.workflow }}
# cache-to: type=gha,scope=${{ github.workflow }}
- name: Build and push event_watcher docker
uses: docker/build-push-action@v2
with:
context: .
file: ./build/dockerfiles/event_watcher.Dockerfile
push: true
tags: scrolltech/event-watcher:${{github.ref_name}}
# cache-from: type=gha,scope=${{ github.workflow }}
# cache-to: type=gha,scope=${{ github.workflow }}
- name: Build and push gas_oracle docker
uses: docker/build-push-action@v2
with:
context: .
file: ./build/dockerfiles/gas_oracle.Dockerfile
push: true
tags: scrolltech/gas-oracle:${{github.ref_name}}
# cache-from: type=gha,scope=${{ github.workflow }}
# cache-to: type=gha,scope=${{ github.workflow }}
- name: Build and push msg_relayer docker
uses: docker/build-push-action@v2
with:
context: .
file: ./build/dockerfiles/msg_relayer.Dockerfile
push: true
tags: scrolltech/msg-relayer:${{github.ref_name}}
# cache-from: type=gha,scope=${{ github.workflow }}
# cache-to: type=gha,scope=${{ github.workflow }}
- name: Build and push rollup_relayer docker
uses: docker/build-push-action@v2
with:
context: .
file: ./build/dockerfiles/rollup_relayer.Dockerfile
push: true
tags: scrolltech/rollup-relayer:${{github.ref_name}}
# cache-from: type=gha,scope=${{ github.workflow }}
# cache-to: type=gha,scope=${{ github.workflow }}

47
Jenkinsfile vendored
View File

@@ -28,7 +28,7 @@ pipeline {
}
stage('Check Bridge Compilation') {
steps {
sh 'make -C bridge bridge'
sh 'make -C bridge bridge_bins'
}
}
stage('Check Coordinator Compilation') {
@@ -42,16 +42,6 @@ pipeline {
sh 'make -C database db_cli'
}
}
stage('Check Bridge Docker Build') {
steps {
sh 'make -C bridge docker'
}
}
stage('Check Coordinator Docker Build') {
steps {
sh 'make -C coordinator docker'
}
}
stage('Check Database Docker Build') {
steps {
sh 'make -C database docker'
@@ -61,44 +51,29 @@ pipeline {
}
stage('Parallel Test') {
parallel{
stage('Test bridge package') {
stage('Race test common package') {
steps {
sh 'go test -v -race -coverprofile=coverage.bridge.txt -covermode=atomic -p 1 scroll-tech/bridge/...'
}
}
stage('Test common package') {
steps {
sh 'go test -v -race -coverprofile=coverage.common.txt -covermode=atomic -p 1 scroll-tech/common/...'
}
}
stage('Test coordinator package') {
steps {
sh 'go test -v -race -coverprofile=coverage.coordinator.txt -covermode=atomic -p 1 scroll-tech/coordinator/...'
}
}
stage('Test database package') {
steps {
sh 'go test -v -race -coverprofile=coverage.db.txt -covermode=atomic -p 1 scroll-tech/database/...'
}
}
stage('Integration test') {
steps {
sh 'go test -v -race -tags="mock_prover mock_verifier" -coverprofile=coverage.integration.txt -covermode=atomic -p 1 scroll-tech/integration-test/...'
sh 'go test -v -race -coverprofile=coverage.common.txt -covermode=atomic scroll-tech/common/...'
}
}
stage('Race test bridge package') {
steps {
sh "cd bridge && go test -v -race -coverprofile=coverage.txt -covermode=atomic \$(go list ./... | grep -v 'database\\|common\\|l1\\|l2\\|coordinator')"
sh 'go test -v -race -coverprofile=coverage.bridge.txt -covermode=atomic scroll-tech/bridge/...'
}
}
stage('Race test coordinator package') {
steps {
sh "cd coordinator && go test -v -race -coverprofile=coverage.txt -covermode=atomic \$(go list ./... | grep -v 'database\\|common\\|l1\\|l2\\|coordinator')"
sh 'go test -v -race -coverprofile=coverage.coordinator.txt -covermode=atomic scroll-tech/coordinator/...'
}
}
stage('Race test database package') {
steps {
sh "cd database && go test -v -race -coverprofile=coverage.txt -covermode=atomic \$(go list ./... | grep -v 'database\\|common\\|l1\\|l2\\|coordinator')"
sh 'go test -v -race -coverprofile=coverage.db.txt -covermode=atomic scroll-tech/database/...'
}
}
stage('Integration test') {
steps {
sh 'go test -v -tags="mock_prover mock_verifier" -p 1 scroll-tech/integration-test/...'
}
}
}

View File

@@ -16,11 +16,11 @@ lint: ## The code's format and security checks.
update: ## update dependencies
go work sync
cd $(PWD)/bridge/ && go get -u github.com/scroll-tech/go-ethereum@staging && go mod tidy
cd $(PWD)/common/ && go get -u github.com/scroll-tech/go-ethereum@staging && go mod tidy
cd $(PWD)/coordinator/ && go get -u github.com/scroll-tech/go-ethereum@staging && go mod tidy
cd $(PWD)/database/ && go get -u github.com/scroll-tech/go-ethereum@staging && go mod tidy
cd $(PWD)/roller/ && go get -u github.com/scroll-tech/go-ethereum@staging && go mod tidy
cd $(PWD)/bridge/ && go get -u github.com/scroll-tech/go-ethereum@scroll && go mod tidy
cd $(PWD)/common/ && go get -u github.com/scroll-tech/go-ethereum@scroll && go mod tidy
cd $(PWD)/coordinator/ && go get -u github.com/scroll-tech/go-ethereum@scroll && go mod tidy
cd $(PWD)/database/ && go get -u github.com/scroll-tech/go-ethereum@scroll && go mod tidy
cd $(PWD)/roller/ && go get -u github.com/scroll-tech/go-ethereum@scroll && go mod tidy
goimports -local $(PWD)/bridge/ -w .
goimports -local $(PWD)/common/ -w .
goimports -local $(PWD)/coordinator/ -w .

View File

@@ -1,3 +1,5 @@
# Scroll Monorepo
[![Contracts](https://github.com/scroll-tech/scroll/actions/workflows/contracts.yaml/badge.svg)](https://github.com/scroll-tech/scroll/actions/workflows/contracts.yaml) [![Bridge](https://github.com/scroll-tech/scroll/actions/workflows/bridge.yml/badge.svg)](https://github.com/scroll-tech/scroll/actions/workflows/bridge.yml) [![Coordinator](https://github.com/scroll-tech/scroll/actions/workflows/coordinator.yml/badge.svg)](https://github.com/scroll-tech/scroll/actions/workflows/coordinator.yml) [![Database](https://github.com/scroll-tech/scroll/actions/workflows/database.yml/badge.svg)](https://github.com/scroll-tech/scroll/actions/workflows/database.yml) [![Common](https://github.com/scroll-tech/scroll/actions/workflows/common.yml/badge.svg)](https://github.com/scroll-tech/scroll/actions/workflows/common.yml) [![Roller](https://github.com/scroll-tech/scroll/actions/workflows/roller.yml/badge.svg)](https://github.com/scroll-tech/scroll/actions/workflows/roller.yml)
For a more comprehensive doc, see [`docs/`](./docs).

View File

@@ -8,8 +8,23 @@ mock_abi:
go run github.com/scroll-tech/go-ethereum/cmd/abigen --sol mock_bridge/MockBridgeL1.sol --pkg mock_bridge --out mock_bridge/MockBridgeL1.go
go run github.com/scroll-tech/go-ethereum/cmd/abigen --sol mock_bridge/MockBridgeL2.sol --pkg mock_bridge --out mock_bridge/MockBridgeL2.go
bridge: ## Builds the Bridge instance.
go build -o $(PWD)/build/bin/bridge ./cmd
bridge_bins: ## Builds the Bridge bins.
go build -o $(PWD)/build/bin/event_watcher ./cmd/event_watcher/
go build -o $(PWD)/build/bin/gas_oracle ./cmd/gas_oracle/
go build -o $(PWD)/build/bin/message_relayer ./cmd/msg_relayer/
go build -o $(PWD)/build/bin/rollup_relayer ./cmd/rollup_relayer/
event_watcher: ## Builds the event_watcher bin
go build -o $(PWD)/build/bin/event_watcher ./cmd/event_watcher/
gas_oracle: ## Builds the gas_oracle bin
go build -o $(PWD)/build/bin/gas_oracle ./cmd/gas_oracle/
message_relayer: ## Builds the message_relayer bin
go build -o $(PWD)/build/bin/message_relayer ./cmd/msg_relayer/
rollup_relayer: ## Builds the rollup_relayer bin
go build -o $(PWD)/build/bin/rollup_relayer ./cmd/rollup_relayer/
test:
go test -v -race -coverprofile=coverage.txt -covermode=atomic -p 1 $(PWD)/...
@@ -20,8 +35,14 @@ lint: ## Lint the files - used for CI
clean: ## Empty out the bin folder
@rm -rf build/bin
docker:
DOCKER_BUILDKIT=1 docker build -t scrolltech/${IMAGE_NAME}:${IMAGE_VERSION} ${REPO_ROOT_DIR}/ -f ${REPO_ROOT_DIR}/build/dockerfiles/bridge.Dockerfile
docker_push:
docker push scrolltech/${IMAGE_NAME}:${IMAGE_VERSION}
docker docker push scrolltech/gas-oracle:${IMAGE_VERSION}
docker docker push scrolltech/event-watcher:${IMAGE_VERSION}
docker docker push scrolltech/rollup-relayer:${IMAGE_VERSION}
docker docker push scrolltech/msg-relayer:${IMAGE_VERSION}
docker:
DOCKER_BUILDKIT=1 docker build -t scrolltech/gas-oracle:${IMAGE_VERSION} ${REPO_ROOT_DIR}/ -f ${REPO_ROOT_DIR}/build/dockerfiles/gas_oracle.Dockerfile
DOCKER_BUILDKIT=1 docker build -t scrolltech/event-watcher:${IMAGE_VERSION} ${REPO_ROOT_DIR}/ -f ${REPO_ROOT_DIR}/build/dockerfiles/event_watcher.Dockerfile
DOCKER_BUILDKIT=1 docker build -t scrolltech/rollup-relayer:${IMAGE_VERSION} ${REPO_ROOT_DIR}/ -f ${REPO_ROOT_DIR}/build/dockerfiles/rollup_relayer.Dockerfile
DOCKER_BUILDKIT=1 docker build -t scrolltech/msg-relayer:${IMAGE_VERSION} ${REPO_ROOT_DIR}/ -f ${REPO_ROOT_DIR}/build/dockerfiles/msg_relayer.Dockerfile

View File

@@ -1,130 +0,0 @@
package app
import (
"context"
"fmt"
"os"
"os/signal"
"github.com/scroll-tech/go-ethereum/log"
"github.com/urfave/cli/v2"
"scroll-tech/database"
"scroll-tech/common/metrics"
"scroll-tech/common/utils"
"scroll-tech/common/version"
"scroll-tech/bridge/config"
"scroll-tech/bridge/l1"
"scroll-tech/bridge/l2"
)
var (
app *cli.App
)
func init() {
// Set up Bridge app info.
app = cli.NewApp()
app.Action = action
app.Name = "bridge"
app.Usage = "The Scroll Bridge"
app.Version = version.Version
app.Flags = append(app.Flags, utils.CommonFlags...)
app.Flags = append(app.Flags, apiFlags...)
app.Before = func(ctx *cli.Context) error {
return utils.LogSetup(ctx)
}
// Register `bridge-test` app for integration-test.
utils.RegisterSimulation(app, "bridge-test")
}
func action(ctx *cli.Context) error {
// Load config file.
cfgFile := ctx.String(utils.ConfigFileFlag.Name)
cfg, err := config.NewConfig(cfgFile)
if err != nil {
log.Crit("failed to load config file", "config file", cfgFile, "error", err)
}
// Start metrics server.
metrics.Serve(context.Background(), ctx)
// Init db connection.
var ormFactory database.OrmFactory
if ormFactory, err = database.NewOrmFactory(cfg.DBConfig); err != nil {
log.Crit("failed to init db connection", "err", err)
}
var (
l1Backend *l1.Backend
l2Backend *l2.Backend
)
// @todo change nil to actual client after https://scroll-tech/bridge/pull/40 merged
l1Backend, err = l1.New(ctx.Context, cfg.L1Config, ormFactory)
if err != nil {
return err
}
l2Backend, err = l2.New(ctx.Context, cfg.L2Config, ormFactory)
if err != nil {
return err
}
defer func() {
l1Backend.Stop()
l2Backend.Stop()
err = ormFactory.Close()
if err != nil {
log.Error("can not close ormFactory", "error", err)
}
}()
// Start all modules.
if err = l1Backend.Start(); err != nil {
log.Crit("couldn't start l1 backend", "error", err)
}
if err = l2Backend.Start(); err != nil {
log.Crit("couldn't start l2 backend", "error", err)
}
// Register api and start rpc service.
if ctx.Bool(httpEnabledFlag.Name) {
handler, addr, err := utils.StartHTTPEndpoint(
fmt.Sprintf(
"%s:%d",
ctx.String(httpListenAddrFlag.Name),
ctx.Int(httpPortFlag.Name)),
l2Backend.APIs())
if err != nil {
log.Crit("Could not start RPC api", "error", err)
}
defer func() {
_ = handler.Shutdown(ctx.Context)
log.Info("HTTP endpoint closed", "url", fmt.Sprintf("http://%v/", addr))
}()
log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", addr))
}
log.Info("Start bridge successfully")
// Catch CTRL-C to ensure a graceful shutdown.
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
// Wait until the interrupt signal is received from an OS signal.
<-interrupt
return nil
}
// Run run bridge cmd instance.
func Run() {
// Run the bridge.
if err := app.Run(os.Args); err != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

View File

@@ -1,19 +0,0 @@
package app
import (
"fmt"
"testing"
"time"
"scroll-tech/common/cmd"
"scroll-tech/common/version"
)
func TestRunBridge(t *testing.T) {
bridge := cmd.NewCmd("bridge-test", "--version")
defer bridge.WaitExit()
// wait result
bridge.ExpectWithTimeout(t, true, time.Second*3, fmt.Sprintf("bridge version %s", version.Version))
bridge.RunApp(nil)
}

View File

@@ -1,31 +0,0 @@
package app
import (
"github.com/urfave/cli/v2"
)
var (
apiFlags = []cli.Flag{
&httpEnabledFlag,
&httpListenAddrFlag,
&httpPortFlag,
}
// httpEnabledFlag enable rpc server.
httpEnabledFlag = cli.BoolFlag{
Name: "http",
Usage: "Enable the HTTP-RPC server",
Value: false,
}
// httpListenAddrFlag set the http address.
httpListenAddrFlag = cli.StringFlag{
Name: "http.addr",
Usage: "HTTP-RPC server listening interface",
Value: "localhost",
}
// httpPortFlag set http.port.
httpPortFlag = cli.IntFlag{
Name: "http.port",
Usage: "HTTP-RPC server listening port",
Value: 8290,
}
)

View File

@@ -0,0 +1,114 @@
package app
import (
"context"
"fmt"
"os"
"os/signal"
"time"
"github.com/scroll-tech/go-ethereum/ethclient"
"github.com/scroll-tech/go-ethereum/log"
"github.com/urfave/cli/v2"
"scroll-tech/database"
"scroll-tech/common/metrics"
"scroll-tech/common/version"
"scroll-tech/bridge/config"
"scroll-tech/bridge/watcher"
cutils "scroll-tech/common/utils"
)
var (
app *cli.App
)
func init() {
// Set up event-watcher app info.
app = cli.NewApp()
app.Action = action
app.Name = "event-watcher"
app.Usage = "The Scroll Event Watcher"
app.Version = version.Version
app.Flags = append(app.Flags, cutils.CommonFlags...)
app.Commands = []*cli.Command{}
app.Before = func(ctx *cli.Context) error {
return cutils.LogSetup(ctx)
}
// Register `event-watcher-test` app for integration-test.
cutils.RegisterSimulation(app, "event-watcher-test")
}
func action(ctx *cli.Context) error {
// Load config file.
cfgFile := ctx.String(cutils.ConfigFileFlag.Name)
cfg, err := config.NewConfig(cfgFile)
if err != nil {
log.Crit("failed to load config file", "config file", cfgFile, "error", err)
}
subCtx, cancel := context.WithCancel(ctx.Context)
// Init db connection
var ormFactory database.OrmFactory
if ormFactory, err = database.NewOrmFactory(cfg.DBConfig); err != nil {
log.Crit("failed to init db connection", "err", err)
}
defer func() {
cancel()
err = ormFactory.Close()
if err != nil {
log.Error("can not close ormFactory", "error", err)
}
}()
// Start metrics server.
metrics.Serve(subCtx, ctx)
l1client, err := ethclient.Dial(cfg.L1Config.Endpoint)
if err != nil {
log.Error("failed to connect l1 geth", "config file", cfgFile, "error", err)
return err
}
l2client, err := ethclient.Dial(cfg.L2Config.Endpoint)
if err != nil {
log.Error("failed to connect l2 geth", "config file", cfgFile, "error", err)
return err
}
l1watcher := watcher.NewL1WatcherClient(ctx.Context, l1client, cfg.L1Config.StartHeight, cfg.L1Config.Confirmations, cfg.L1Config.L1MessengerAddress, cfg.L1Config.L1MessageQueueAddress, cfg.L1Config.ScrollChainContractAddress, ormFactory)
l2watcher := watcher.NewL2WatcherClient(ctx.Context, l2client, cfg.L2Config.Confirmations, cfg.L2Config.L2MessengerAddress, cfg.L2Config.L2MessageQueueAddress, cfg.L2Config.WithdrawTrieRootSlot, ormFactory)
go cutils.Loop(subCtx, 10*time.Second, func() {
if loopErr := l1watcher.FetchContractEvent(); loopErr != nil {
log.Error("Failed to fetch bridge contract", "err", loopErr)
}
})
// Start l2 watcher process
go cutils.Loop(subCtx, 2*time.Second, l2watcher.FetchContractEvent)
// Finish start all l2 functions
log.Info("Start event-watcher successfully")
// Catch CTRL-C to ensure a graceful shutdown.
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
// Wait until the interrupt signal is received from an OS signal.
<-interrupt
return nil
}
// Run event watcher cmd instance.
func Run() {
if err := app.Run(os.Args); err != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

View File

@@ -0,0 +1,7 @@
package main
import "scroll-tech/bridge/cmd/event_watcher/app"
func main() {
app.Run()
}

View File

@@ -0,0 +1,136 @@
package app
import (
"context"
"fmt"
"os"
"os/signal"
"time"
"github.com/scroll-tech/go-ethereum/ethclient"
"github.com/scroll-tech/go-ethereum/log"
"github.com/urfave/cli/v2"
"scroll-tech/database"
"scroll-tech/common/metrics"
"scroll-tech/common/version"
"scroll-tech/bridge/config"
"scroll-tech/bridge/relayer"
"scroll-tech/bridge/utils"
"scroll-tech/bridge/watcher"
cutils "scroll-tech/common/utils"
)
var (
app *cli.App
)
func init() {
// Set up gas-oracle app info.
app = cli.NewApp()
app.Action = action
app.Name = "gas-oracle"
app.Usage = "The Scroll Gas Oracle"
app.Description = "Scroll Gas Oracle."
app.Version = version.Version
app.Flags = append(app.Flags, cutils.CommonFlags...)
app.Commands = []*cli.Command{}
app.Before = func(ctx *cli.Context) error {
return cutils.LogSetup(ctx)
}
// Register `gas-oracle-test` app for integration-test.
cutils.RegisterSimulation(app, "gas-oracle-test")
}
func action(ctx *cli.Context) error {
// Load config file.
cfgFile := ctx.String(cutils.ConfigFileFlag.Name)
cfg, err := config.NewConfig(cfgFile)
if err != nil {
log.Crit("failed to load config file", "config file", cfgFile, "error", err)
}
subCtx, cancel := context.WithCancel(ctx.Context)
// Init db connection
var ormFactory database.OrmFactory
if ormFactory, err = database.NewOrmFactory(cfg.DBConfig); err != nil {
log.Crit("failed to init db connection", "err", err)
}
defer func() {
cancel()
err = ormFactory.Close()
if err != nil {
log.Error("can not close ormFactory", "error", err)
}
}()
// Start metrics server.
metrics.Serve(subCtx, ctx)
l1client, err := ethclient.Dial(cfg.L1Config.Endpoint)
if err != nil {
log.Error("failed to connect l1 geth", "config file", cfgFile, "error", err)
return err
}
// Init l2geth connection
l2client, err := ethclient.Dial(cfg.L2Config.Endpoint)
if err != nil {
log.Error("failed to connect l2 geth", "config file", cfgFile, "error", err)
return err
}
l1watcher := watcher.NewL1WatcherClient(ctx.Context, l1client, cfg.L1Config.StartHeight, cfg.L1Config.Confirmations, cfg.L1Config.L1MessengerAddress, cfg.L1Config.L1MessageQueueAddress, cfg.L1Config.ScrollChainContractAddress, ormFactory)
l1relayer, err := relayer.NewLayer1Relayer(ctx.Context, ormFactory, cfg.L1Config.RelayerConfig)
if err != nil {
log.Error("failed to create new l1 relayer", "config file", cfgFile, "error", err)
return err
}
l2relayer, err := relayer.NewLayer2Relayer(ctx.Context, l2client, ormFactory, cfg.L2Config.RelayerConfig)
if err != nil {
log.Error("failed to create new l2 relayer", "config file", cfgFile, "error", err)
return err
}
// Start l1 watcher process
go cutils.LoopWithContext(subCtx, 10*time.Second, func(ctx context.Context) {
number, loopErr := utils.GetLatestConfirmedBlockNumber(ctx, l1client, cfg.L1Config.Confirmations)
if loopErr != nil {
log.Error("failed to get block number", "err", loopErr)
return
}
if loopErr = l1watcher.FetchBlockHeader(number); loopErr != nil {
log.Error("Failed to fetch L1 block header", "lastest", number, "err", loopErr)
}
})
// Start l1relayer process
go cutils.Loop(subCtx, 10*time.Second, l1relayer.ProcessGasPriceOracle)
go cutils.Loop(subCtx, 2*time.Second, l2relayer.ProcessGasPriceOracle)
// Finish start all message relayer functions
log.Info("Start gas-oracle successfully")
// Catch CTRL-C to ensure a graceful shutdown.
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
// Wait until the interrupt signal is received from an OS signal.
<-interrupt
return nil
}
// Run message_relayer cmd instance.
func Run() {
if err := app.Run(os.Args); err != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

View File

@@ -0,0 +1,7 @@
package main
import "scroll-tech/bridge/cmd/gas_oracle/app"
func main() {
app.Run()
}

View File

@@ -1,7 +0,0 @@
package main
import "scroll-tech/bridge/cmd/app"
func main() {
app.Run()
}

View File

@@ -0,0 +1,118 @@
package app
import (
"context"
"fmt"
"os"
"os/signal"
"time"
"github.com/scroll-tech/go-ethereum/ethclient"
"github.com/scroll-tech/go-ethereum/log"
"github.com/urfave/cli/v2"
"scroll-tech/database"
"scroll-tech/common/metrics"
"scroll-tech/common/version"
"scroll-tech/bridge/config"
"scroll-tech/bridge/relayer"
cutils "scroll-tech/common/utils"
)
var (
app *cli.App
)
func init() {
// Set up message-relayer app info.
app = cli.NewApp()
app.Action = action
app.Name = "message-relayer"
app.Usage = "The Scroll Message Relayer"
app.Description = "Message Relayer contains two main service: 1) relay l1 message to l2. 2) relay l2 message to l1."
app.Version = version.Version
app.Flags = append(app.Flags, cutils.CommonFlags...)
app.Commands = []*cli.Command{}
app.Before = func(ctx *cli.Context) error {
return cutils.LogSetup(ctx)
}
// Register `message-relayer-test` app for integration-test.
cutils.RegisterSimulation(app, "message-relayer-test")
}
func action(ctx *cli.Context) error {
// Load config file.
cfgFile := ctx.String(cutils.ConfigFileFlag.Name)
cfg, err := config.NewConfig(cfgFile)
if err != nil {
log.Crit("failed to load config file", "config file", cfgFile, "error", err)
}
subCtx, cancel := context.WithCancel(ctx.Context)
// Init db connection
var ormFactory database.OrmFactory
if ormFactory, err = database.NewOrmFactory(cfg.DBConfig); err != nil {
log.Crit("failed to init db connection", "err", err)
}
defer func() {
cancel()
err = ormFactory.Close()
if err != nil {
log.Error("can not close ormFactory", "error", err)
}
}()
// Start metrics server.
metrics.Serve(subCtx, ctx)
// Init l2geth connection
l2client, err := ethclient.Dial(cfg.L2Config.Endpoint)
if err != nil {
log.Error("failed to connect l2 geth", "config file", cfgFile, "error", err)
return err
}
l1relayer, err := relayer.NewLayer1Relayer(ctx.Context, ormFactory, cfg.L1Config.RelayerConfig)
if err != nil {
log.Error("failed to create new l1 relayer", "config file", cfgFile, "error", err)
return err
}
l2relayer, err := relayer.NewLayer2Relayer(ctx.Context, l2client, ormFactory, cfg.L2Config.RelayerConfig)
if err != nil {
log.Error("failed to create new l2 relayer", "config file", cfgFile, "error", err)
return err
}
// Start l1relayer process
go cutils.Loop(subCtx, 10*time.Second, l1relayer.ProcessSavedEvents)
// Start l2relayer process
go cutils.Loop(subCtx, 2*time.Second, l2relayer.ProcessSavedEvents)
// Finish start all message relayer functions
log.Info("Start message-relayer successfully")
// Catch CTRL-C to ensure a graceful shutdown.
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
// Wait until the interrupt signal is received from an OS signal.
<-interrupt
return nil
}
// Run message_relayer cmd instance.
func Run() {
if err := app.Run(os.Args); err != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

View File

@@ -0,0 +1,7 @@
package main
import "scroll-tech/bridge/cmd/msg_relayer/app"
func main() {
app.Run()
}

View File

@@ -0,0 +1,133 @@
package app
import (
"context"
"fmt"
"os"
"os/signal"
"time"
"github.com/scroll-tech/go-ethereum/ethclient"
"github.com/scroll-tech/go-ethereum/log"
"github.com/urfave/cli/v2"
"scroll-tech/database"
"scroll-tech/common/metrics"
"scroll-tech/common/version"
"scroll-tech/bridge/config"
"scroll-tech/bridge/relayer"
"scroll-tech/bridge/utils"
"scroll-tech/bridge/watcher"
cutils "scroll-tech/common/utils"
)
var (
app *cli.App
)
func init() {
// Set up rollup-relayer app info.
app = cli.NewApp()
app.Action = action
app.Name = "rollup-relayer"
app.Usage = "The Scroll Rollup Relayer"
app.Version = version.Version
app.Flags = append(app.Flags, cutils.CommonFlags...)
app.Commands = []*cli.Command{}
app.Before = func(ctx *cli.Context) error {
return cutils.LogSetup(ctx)
}
// Register `rollup-relayer-test` app for integration-test.
cutils.RegisterSimulation(app, "rollup-relayer-test")
}
func action(ctx *cli.Context) error {
// Load config file.
cfgFile := ctx.String(cutils.ConfigFileFlag.Name)
cfg, err := config.NewConfig(cfgFile)
if err != nil {
log.Crit("failed to load config file", "config file", cfgFile, "error", err)
}
subCtx, cancel := context.WithCancel(ctx.Context)
// init db connection
var ormFactory database.OrmFactory
if ormFactory, err = database.NewOrmFactory(cfg.DBConfig); err != nil {
log.Crit("failed to init db connection", "err", err)
}
defer func() {
cancel()
err = ormFactory.Close()
if err != nil {
log.Error("can not close ormFactory", "error", err)
}
}()
// Start metrics server.
metrics.Serve(subCtx, ctx)
// Init l2geth connection
l2client, err := ethclient.Dial(cfg.L2Config.Endpoint)
if err != nil {
log.Error("failed to connect l2 geth", "config file", cfgFile, "error", err)
return err
}
l2relayer, err := relayer.NewLayer2Relayer(ctx.Context, l2client, ormFactory, cfg.L2Config.RelayerConfig)
if err != nil {
log.Error("failed to create l2 relayer", "config file", cfgFile, "error", err)
return err
}
batchProposer := watcher.NewBatchProposer(subCtx, cfg.L2Config.BatchProposerConfig, l2relayer, ormFactory)
if err != nil {
log.Error("failed to create batchProposer", "config file", cfgFile, "error", err)
return err
}
l2watcher := watcher.NewL2WatcherClient(subCtx, l2client, cfg.L2Config.Confirmations, cfg.L2Config.L2MessengerAddress, cfg.L2Config.L2MessageQueueAddress, cfg.L2Config.WithdrawTrieRootSlot, ormFactory)
// Watcher loop to fetch missing blocks
go cutils.LoopWithContext(subCtx, 2*time.Second, func(ctx context.Context) {
number, loopErr := utils.GetLatestConfirmedBlockNumber(ctx, l2client, cfg.L2Config.Confirmations)
if loopErr != nil {
log.Error("failed to get block number", "err", loopErr)
return
}
l2watcher.TryFetchRunningMissingBlocks(ctx, number)
})
// Batch proposer loop
go cutils.Loop(subCtx, 2*time.Second, func() {
batchProposer.TryProposeBatch()
batchProposer.TryCommitBatches()
})
go cutils.Loop(subCtx, 2*time.Second, l2relayer.ProcessCommittedBatches)
// Finish start all rollup relayer functions.
log.Info("Start rollup-relayer successfully")
// Catch CTRL-C to ensure a graceful shutdown.
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
// Wait until the interrupt signal is received from an OS signal.
<-interrupt
return nil
}
// Run rollup relayer cmd instance.
func Run() {
if err := app.Run(os.Args); err != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

View File

@@ -0,0 +1,7 @@
package main
import "scroll-tech/bridge/cmd/rollup_relayer/app"
func main() {
app.Run()
}

View File

@@ -19,7 +19,8 @@
"escalate_multiple_den": 10,
"max_gas_price": 10000000000,
"tx_type": "LegacyTx",
"min_balance": 100000000000000000000
"min_balance": 100000000000000000000,
"pending_limit": 10
},
"gas_oracle_config": {
"min_gas_price": 0,
@@ -53,7 +54,8 @@
"escalate_multiple_den": 10,
"max_gas_price": 10000000000,
"tx_type": "LegacyTx",
"min_balance": 100000000000000000000
"min_balance": 100000000000000000000,
"pending_limit": 10
},
"gas_oracle_config": {
"min_gas_price": 0,

View File

@@ -18,6 +18,8 @@ type L2Config struct {
L2MessengerAddress common.Address `json:"l2_messenger_address"`
// The L2MessageQueue contract address deployed on layer 2 chain.
L2MessageQueueAddress common.Address `json:"l2_message_queue_address"`
// The WithdrawTrieRootSlot in L2MessageQueue contract.
WithdrawTrieRootSlot common.Hash `json:"withdraw_trie_root_slot,omitempty"`
// The relayer config
RelayerConfig *RelayerConfig `json:"relayer_config"`
// The batch_proposer config
@@ -40,6 +42,8 @@ type BatchProposerConfig struct {
BatchBlocksLimit uint64 `json:"batch_blocks_limit"`
// Commit tx calldata size limit in bytes, target to cap the gas use of commit tx at 2M gas
CommitTxCalldataSizeLimit uint64 `json:"commit_tx_calldata_size_limit"`
// Commit tx calldata min size limit in bytes
CommitTxCalldataMinSize uint64 `json:"commit_tx_calldata_min_size,omitempty"`
// The public input hash config
PublicInputConfig *types.PublicInputHashConfig `json:"public_input_config"`
}

View File

@@ -33,6 +33,8 @@ type SenderConfig struct {
MinBalance *big.Int `json:"min_balance,omitempty"`
// The interval (in seconds) to check balance and top up sender's accounts
CheckBalanceTime uint64 `json:"check_balance_time"`
// The sender's pending count limit.
PendingLimit int `json:"pending_limit,omitempty"`
}
// RelayerConfig loads relayer configuration items.
@@ -51,6 +53,8 @@ type RelayerConfig struct {
GasOracleConfig *GasOracleConfig `json:"gas_oracle_config"`
// The interval in which we send finalize batch transactions.
FinalizeBatchIntervalSec uint64 `json:"finalize_batch_interval_sec"`
// MessageRelayMinGasLimit to avoid OutOfGas error
MessageRelayMinGasLimit uint64 `json:"message_relay_min_gas_limit,omitempty"`
// The private key of the relayer
MessageSenderPrivateKeys []*ecdsa.PrivateKey `json:"-"`
GasOracleSenderPrivateKeys []*ecdsa.PrivateKey `json:"-"`

View File

@@ -4,7 +4,8 @@ go 1.18
require (
github.com/orcaman/concurrent-map v1.0.0
github.com/scroll-tech/go-ethereum v1.10.14-0.20230306131930-03b4de32b78b
github.com/orcaman/concurrent-map/v2 v2.0.1
github.com/scroll-tech/go-ethereum v1.10.14-0.20230321020420-127af384ed04
github.com/stretchr/testify v1.8.2
github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa
golang.org/x/sync v0.1.0
@@ -28,7 +29,7 @@ require (
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
github.com/rjeczalik/notify v0.9.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/scroll-tech/zktrie v0.5.0 // indirect
github.com/scroll-tech/zktrie v0.5.2 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/tklauser/numcpus v0.4.0 // indirect
@@ -36,6 +37,7 @@ require (
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/sys v0.6.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -48,6 +48,11 @@ github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7Bd
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
@@ -61,6 +66,8 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
github.com/orcaman/concurrent-map/v2 v2.0.1 h1:jOJ5Pg2w1oeB6PeDurIYf6k9PQ+aTITr/6lP/L/zp6c=
github.com/orcaman/concurrent-map/v2 v2.0.1/go.mod h1:9Eq3TG2oBe5FirmYWQfYO5iH1q0Jv47PLaNK++uCdOM=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -69,13 +76,14 @@ github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6O
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rjeczalik/notify v0.9.1 h1:CLCKso/QK1snAlnhNR/CNvNiFU2saUtjV0bx3EwNeCE=
github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/scroll-tech/go-ethereum v1.10.14-0.20230306131930-03b4de32b78b h1:shNTzAnD2oDcDCrM4aaVCTzQNVfYxF1An08R2H2DLAg=
github.com/scroll-tech/go-ethereum v1.10.14-0.20230306131930-03b4de32b78b/go.mod h1:f9ygxrxL7WRCTzuloV+t/UlcxMq3AL+gcNU60liiNNU=
github.com/scroll-tech/zktrie v0.5.0 h1:dABDR6lMZq6Hs+fWQSiHbX8s3AOX6hY+5nkhSYm5rmU=
github.com/scroll-tech/zktrie v0.5.0/go.mod h1:XvNo7vAk8yxNyTjBDj5WIiFzYW4bx/gJ78+NK6Zn6Uk=
github.com/scroll-tech/go-ethereum v1.10.14-0.20230321020420-127af384ed04 h1:PpI31kaBVm6+7sZtyK03Ex0QIg3P821Ktae0FHFh7IM=
github.com/scroll-tech/go-ethereum v1.10.14-0.20230321020420-127af384ed04/go.mod h1:jH8c08L9K8Hieaf0r/ur2P/cpesn4dFhmLm2Mmoi8kI=
github.com/scroll-tech/zktrie v0.5.2 h1:U34jPXMLGOlRHfdvYp5VVgOcC0RuPeJmcS3bWotCWiY=
github.com/scroll-tech/zktrie v0.5.2/go.mod h1:XvNo7vAk8yxNyTjBDj5WIiFzYW4bx/gJ78+NK6Zn6Uk=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA=
@@ -113,8 +121,9 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=

View File

@@ -1,55 +0,0 @@
package l1
import (
"context"
"github.com/scroll-tech/go-ethereum/ethclient"
"scroll-tech/database"
"scroll-tech/bridge/config"
)
// Backend manage the resources and services of L1 backend.
// The backend should monitor events in layer 1 and relay transactions to layer 2
type Backend struct {
cfg *config.L1Config
watcher *Watcher
relayer *Layer1Relayer
orm database.OrmFactory
}
// New returns a new instance of Backend.
func New(ctx context.Context, cfg *config.L1Config, orm database.OrmFactory) (*Backend, error) {
client, err := ethclient.Dial(cfg.Endpoint)
if err != nil {
return nil, err
}
relayer, err := NewLayer1Relayer(ctx, orm, cfg.RelayerConfig)
if err != nil {
return nil, err
}
watcher := NewWatcher(ctx, client, cfg.StartHeight, cfg.Confirmations, cfg.L1MessengerAddress, cfg.L1MessageQueueAddress, cfg.ScrollChainContractAddress, orm)
return &Backend{
cfg: cfg,
watcher: watcher,
relayer: relayer,
orm: orm,
}, nil
}
// Start Backend module.
func (l1 *Backend) Start() error {
l1.watcher.Start()
l1.relayer.Start()
return nil
}
// Stop Backend module.
func (l1 *Backend) Stop() {
l1.watcher.Stop()
l1.relayer.Stop()
}

View File

@@ -1,46 +0,0 @@
package l1
import (
"testing"
"github.com/stretchr/testify/assert"
"scroll-tech/common/docker"
"scroll-tech/bridge/config"
)
var (
// config
cfg *config.Config
// docker consider handler.
base *docker.App
)
func TestMain(m *testing.M) {
base = docker.NewDockerApp()
m.Run()
base.Free()
}
func setupEnv(t *testing.T) {
// Load config.
var err error
cfg, err = config.NewConfig("../config.json")
assert.NoError(t, err)
base.RunImages(t)
cfg.L2Config.RelayerConfig.SenderConfig.Endpoint = base.L1GethEndpoint()
cfg.L1Config.RelayerConfig.SenderConfig.Endpoint = base.L2GethEndpoint()
cfg.DBConfig.DSN = base.DBEndpoint()
}
func TestL1(t *testing.T) {
setupEnv(t)
t.Run("testCreateNewL1Relayer", testCreateNewL1Relayer)
t.Run("testStartWatcher", testStartWatcher)
}

View File

@@ -1,76 +0,0 @@
package l2
import (
"context"
"github.com/scroll-tech/go-ethereum/ethclient"
"github.com/scroll-tech/go-ethereum/rpc"
"scroll-tech/database"
"scroll-tech/bridge/config"
)
// Backend manage the resources and services of L2 backend.
// The backend should monitor events in layer 2 and relay transactions to layer 1
type Backend struct {
cfg *config.L2Config
watcher *WatcherClient
relayer *Layer2Relayer
batchProposer *BatchProposer
orm database.OrmFactory
}
// New returns a new instance of Backend.
func New(ctx context.Context, cfg *config.L2Config, orm database.OrmFactory) (*Backend, error) {
client, err := ethclient.Dial(cfg.Endpoint)
if err != nil {
return nil, err
}
// Note: initialize watcher before relayer to keep DB consistent.
// Otherwise, there will be a race condition between watcher.initializeGenesis and relayer.ProcessPendingBatches.
watcher := NewL2WatcherClient(ctx, client, cfg.Confirmations, cfg.L2MessengerAddress, cfg.L2MessageQueueAddress, orm)
relayer, err := NewLayer2Relayer(ctx, client, orm, cfg.RelayerConfig)
if err != nil {
return nil, err
}
batchProposer := NewBatchProposer(ctx, cfg.BatchProposerConfig, relayer, orm)
return &Backend{
cfg: cfg,
watcher: watcher,
relayer: relayer,
batchProposer: batchProposer,
orm: orm,
}, nil
}
// Start Backend module.
func (l2 *Backend) Start() error {
l2.watcher.Start()
l2.relayer.Start()
l2.batchProposer.Start()
return nil
}
// Stop Backend module.
func (l2 *Backend) Stop() {
l2.batchProposer.Stop()
l2.relayer.Stop()
l2.watcher.Stop()
}
// APIs collect API modules.
func (l2 *Backend) APIs() []rpc.API {
return []rpc.API{
{
Namespace: "l2",
Version: "1.0",
Service: WatcherAPI(l2.watcher),
Public: true,
},
}
}

View File

@@ -1,5 +0,0 @@
package l2
// WatcherAPI watcher api service
type WatcherAPI interface {
}

View File

@@ -1,10 +1,9 @@
package l1
package relayer
import (
"context"
"errors"
"math/big"
"time"
// not sure if this will make problems when relay with l1geth
@@ -15,7 +14,6 @@ import (
geth_metrics "github.com/scroll-tech/go-ethereum/metrics"
"scroll-tech/common/types"
"scroll-tech/common/utils"
"scroll-tech/database"
@@ -31,12 +29,6 @@ var (
bridgeL1MsgsRelayedConfirmedTotalCounter = geth_metrics.NewRegisteredCounter("bridge/l1/msgs/relayed/confirmed/total", metrics.ScrollRegistry)
)
const (
gasPriceDiffPrecision = 1000000
defaultGasPriceDiff = 50000 // 5%
)
// Layer1Relayer is responsible for
// 1. fetch pending L1Message from db
// 2. relay pending message to layer 2 node
@@ -51,18 +43,16 @@ type Layer1Relayer struct {
// channel used to communicate with transaction sender
messageSender *sender.Sender
messageCh <-chan *sender.Confirmation
l2MessengerABI *abi.ABI
gasOracleSender *sender.Sender
gasOracleCh <-chan *sender.Confirmation
l1GasOracleABI *abi.ABI
minGasLimitForMessageRelay uint64
lastGasPrice uint64
minGasPrice uint64
gasPriceDiff uint64
stopCh chan struct{}
}
// NewLayer1Relayer will return a new instance of Layer1RelayerClient
@@ -92,24 +82,31 @@ func NewLayer1Relayer(ctx context.Context, db database.OrmFactory, cfg *config.R
gasPriceDiff = defaultGasPriceDiff
}
return &Layer1Relayer{
minGasLimitForMessageRelay := uint64(defaultL1MessageRelayMinGasLimit)
if cfg.MessageRelayMinGasLimit != 0 {
minGasLimitForMessageRelay = cfg.MessageRelayMinGasLimit
}
l1Relayer := &Layer1Relayer{
ctx: ctx,
db: db,
messageSender: messageSender,
messageCh: messageSender.ConfirmChan(),
l2MessengerABI: bridge_abi.L2ScrollMessengerABI,
gasOracleSender: gasOracleSender,
gasOracleCh: gasOracleSender.ConfirmChan(),
l1GasOracleABI: bridge_abi.L1GasPriceOracleABI,
minGasLimitForMessageRelay: minGasLimitForMessageRelay,
minGasPrice: minGasPrice,
gasPriceDiff: gasPriceDiff,
cfg: cfg,
stopCh: make(chan struct{}),
}, nil
cfg: cfg,
}
go l1Relayer.handleConfirmLoop(ctx)
return l1Relayer, nil
}
// ProcessSavedEvents relays saved un-processed cross-domain transactions to desired blockchain
@@ -127,7 +124,7 @@ func (r *Layer1Relayer) ProcessSavedEvents() {
for _, msg := range msgs {
if err = r.processSavedEvent(msg); err != nil {
if !errors.Is(err, sender.ErrNoAvailableAccount) {
if !errors.Is(err, sender.ErrNoAvailableAccount) && !errors.Is(err, sender.ErrFullPending) {
log.Error("failed to process event", "msg.msgHash", msg.MsgHash, "err", err)
}
return
@@ -138,11 +135,11 @@ func (r *Layer1Relayer) ProcessSavedEvents() {
func (r *Layer1Relayer) processSavedEvent(msg *types.L1Message) error {
calldata := common.Hex2Bytes(msg.Calldata)
hash, err := r.messageSender.SendTransaction(msg.MsgHash, &r.cfg.MessengerContractAddress, big.NewInt(0), calldata)
hash, err := r.messageSender.SendTransaction(msg.MsgHash, &r.cfg.MessengerContractAddress, big.NewInt(0), calldata, r.minGasLimitForMessageRelay)
if err != nil && err.Error() == "execution reverted: Message expired" {
return r.db.UpdateLayer1Status(r.ctx, msg.MsgHash, types.MsgExpired)
}
if err != nil && err.Error() == "execution reverted: Message successfully executed" {
if err != nil && err.Error() == "execution reverted: Message was already successfully executed" {
return r.db.UpdateLayer1Status(r.ctx, msg.MsgHash, types.MsgConfirmed)
}
if err != nil {
@@ -190,9 +187,9 @@ func (r *Layer1Relayer) ProcessGasPriceOracle() {
return
}
hash, err := r.gasOracleSender.SendTransaction(block.Hash, &r.cfg.GasPriceOracleContractAddress, big.NewInt(0), data)
hash, err := r.gasOracleSender.SendTransaction(block.Hash, &r.cfg.GasPriceOracleContractAddress, big.NewInt(0), data, 0)
if err != nil {
if !errors.Is(err, sender.ErrNoAvailableAccount) {
if !errors.Is(err, sender.ErrNoAvailableAccount) && !errors.Is(err, sender.ErrFullPending) {
log.Error("Failed to send setL1BaseFee tx to layer2 ", "block.Hash", block.Hash, "block.Height", block.Number, "err", err)
}
return
@@ -209,57 +206,43 @@ func (r *Layer1Relayer) ProcessGasPriceOracle() {
}
}
// Start the relayer process
func (r *Layer1Relayer) Start() {
go func() {
ctx, cancel := context.WithCancel(r.ctx)
go utils.Loop(ctx, 2*time.Second, r.ProcessSavedEvents)
go utils.Loop(ctx, 2*time.Second, r.ProcessGasPriceOracle)
go func(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
case cfm := <-r.messageCh:
bridgeL1MsgsRelayedConfirmedTotalCounter.Inc(1)
if !cfm.IsSuccessful {
log.Warn("transaction confirmed but failed in layer2", "confirmation", cfm)
} else {
// @todo handle db error
err := r.db.UpdateLayer1StatusAndLayer2Hash(r.ctx, cfm.ID, types.MsgConfirmed, cfm.TxHash.String())
if err != nil {
log.Warn("UpdateLayer1StatusAndLayer2Hash failed", "err", err)
}
log.Info("transaction confirmed in layer2", "confirmation", cfm)
}
case cfm := <-r.gasOracleCh:
if !cfm.IsSuccessful {
// @discuss: maybe make it pending again?
err := r.db.UpdateL1GasOracleStatusAndOracleTxHash(r.ctx, cfm.ID, types.GasOracleFailed, cfm.TxHash.String())
if err != nil {
log.Warn("UpdateL1GasOracleStatusAndOracleTxHash failed", "err", err)
}
log.Warn("transaction confirmed but failed in layer2", "confirmation", cfm)
} else {
// @todo handle db error
err := r.db.UpdateL1GasOracleStatusAndOracleTxHash(r.ctx, cfm.ID, types.GasOracleImported, cfm.TxHash.String())
if err != nil {
log.Warn("UpdateGasOracleStatusAndOracleTxHash failed", "err", err)
}
log.Info("transaction confirmed in layer2", "confirmation", cfm)
}
func (r *Layer1Relayer) handleConfirmLoop(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
case cfm := <-r.messageSender.ConfirmChan():
bridgeL1MsgsRelayedConfirmedTotalCounter.Inc(1)
if !cfm.IsSuccessful {
err := r.db.UpdateLayer1StatusAndLayer2Hash(r.ctx, cfm.ID, types.MsgRelayFailed, cfm.TxHash.String())
if err != nil {
log.Warn("UpdateLayer1StatusAndLayer2Hash failed", "err", err)
}
log.Warn("transaction confirmed but failed in layer2", "confirmation", cfm)
} else {
// @todo handle db error
err := r.db.UpdateLayer1StatusAndLayer2Hash(r.ctx, cfm.ID, types.MsgConfirmed, cfm.TxHash.String())
if err != nil {
log.Warn("UpdateLayer1StatusAndLayer2Hash failed", "err", err)
}
log.Info("transaction confirmed in layer2", "confirmation", cfm)
}
}(ctx)
<-r.stopCh
cancel()
}()
}
// Stop the relayer module, for a graceful shutdown.
func (r *Layer1Relayer) Stop() {
close(r.stopCh)
case cfm := <-r.gasOracleSender.ConfirmChan():
if !cfm.IsSuccessful {
// @discuss: maybe make it pending again?
err := r.db.UpdateL1GasOracleStatusAndOracleTxHash(r.ctx, cfm.ID, types.GasOracleFailed, cfm.TxHash.String())
if err != nil {
log.Warn("UpdateL1GasOracleStatusAndOracleTxHash failed", "err", err)
}
log.Warn("transaction confirmed but failed in layer2", "confirmation", cfm)
} else {
// @todo handle db error
err := r.db.UpdateL1GasOracleStatusAndOracleTxHash(r.ctx, cfm.ID, types.GasOracleImported, cfm.TxHash.String())
if err != nil {
log.Warn("UpdateGasOracleStatusAndOracleTxHash failed", "err", err)
}
log.Info("transaction confirmed in layer2", "confirmation", cfm)
}
}
}
}

View File

@@ -1,4 +1,4 @@
package l1
package relayer_test
import (
"context"
@@ -8,6 +8,8 @@ import (
"scroll-tech/database/migrate"
"scroll-tech/bridge/relayer"
"scroll-tech/database"
)
@@ -19,9 +21,7 @@ func testCreateNewL1Relayer(t *testing.T) {
assert.NoError(t, migrate.ResetDB(db.GetDB().DB))
defer db.Close()
relayer, err := NewLayer1Relayer(context.Background(), db, cfg.L2Config.RelayerConfig)
relayer, err := relayer.NewLayer1Relayer(context.Background(), db, cfg.L2Config.RelayerConfig)
assert.NoError(t, err)
defer relayer.Stop()
relayer.Start()
assert.NotNil(t, relayer)
}

View File

@@ -1,4 +1,4 @@
package l2
package relayer
import (
"context"
@@ -7,7 +7,6 @@ import (
"math/big"
"runtime"
"sync"
"time"
"github.com/scroll-tech/go-ethereum/accounts/abi"
"github.com/scroll-tech/go-ethereum/common"
@@ -22,8 +21,6 @@ import (
"scroll-tech/common/types"
"scroll-tech/database"
cutil "scroll-tech/common/utils"
bridge_abi "scroll-tech/bridge/abi"
"scroll-tech/bridge/config"
"scroll-tech/bridge/sender"
@@ -40,12 +37,6 @@ var (
bridgeL2BatchesSkippedTotalCounter = geth_metrics.NewRegisteredCounter("bridge/l2/batches/skipped/total", metrics.ScrollRegistry)
)
const (
gasPriceDiffPrecision = 1000000
defaultGasPriceDiff = 50000 // 5%
)
// Layer2Relayer is responsible for
// 1. Committing and finalizing L2 blocks on L1
// 2. Relaying messages from L2 to L1
@@ -61,17 +52,16 @@ type Layer2Relayer struct {
cfg *config.RelayerConfig
messageSender *sender.Sender
messageCh <-chan *sender.Confirmation
l1MessengerABI *abi.ABI
rollupSender *sender.Sender
rollupCh <-chan *sender.Confirmation
l1RollupABI *abi.ABI
gasOracleSender *sender.Sender
gasOracleCh <-chan *sender.Confirmation
l2GasOracleABI *abi.ABI
minGasLimitForMessageRelay uint64
lastGasPrice uint64
minGasPrice uint64
gasPriceDiff uint64
@@ -87,8 +77,6 @@ type Layer2Relayer struct {
// A list of processing batch finalization.
// key(string): confirmation ID, value(string): batch hash.
processingFinalization sync.Map
stopCh chan struct{}
}
// NewLayer2Relayer will return a new instance of Layer2RelayerClient
@@ -122,24 +110,28 @@ func NewLayer2Relayer(ctx context.Context, l2Client *ethclient.Client, db databa
gasPriceDiff = defaultGasPriceDiff
}
return &Layer2Relayer{
minGasLimitForMessageRelay := uint64(defaultL2MessageRelayMinGasLimit)
if cfg.MessageRelayMinGasLimit != 0 {
minGasLimitForMessageRelay = cfg.MessageRelayMinGasLimit
}
layer2Relayer := &Layer2Relayer{
ctx: ctx,
db: db,
l2Client: l2Client,
messageSender: messageSender,
messageCh: messageSender.ConfirmChan(),
l1MessengerABI: bridge_abi.L1ScrollMessengerABI,
rollupSender: rollupSender,
rollupCh: rollupSender.ConfirmChan(),
l1RollupABI: bridge_abi.ScrollChainABI,
gasOracleSender: gasOracleSender,
gasOracleCh: gasOracleSender.ConfirmChan(),
l2GasOracleABI: bridge_abi.L2GasPriceOracleABI,
minGasLimitForMessageRelay: minGasLimitForMessageRelay,
minGasPrice: minGasPrice,
gasPriceDiff: gasPriceDiff,
@@ -147,8 +139,9 @@ func NewLayer2Relayer(ctx context.Context, l2Client *ethclient.Client, db databa
processingMessage: sync.Map{},
processingBatchesCommitment: sync.Map{},
processingFinalization: sync.Map{},
stopCh: make(chan struct{}),
}, nil
}
go layer2Relayer.handleConfirmLoop(ctx)
return layer2Relayer, nil
}
const processMsgLimit = 100
@@ -187,7 +180,7 @@ func (r *Layer2Relayer) ProcessSavedEvents() {
})
}
if err := g.Wait(); err != nil {
if !errors.Is(err, sender.ErrNoAvailableAccount) {
if !errors.Is(err, sender.ErrNoAvailableAccount) && !errors.Is(err, sender.ErrFullPending) {
log.Error("failed to process l2 saved event", "err", err)
}
return
@@ -232,15 +225,15 @@ func (r *Layer2Relayer) processSavedEvent(msg *types.L2Message) error {
return err
}
hash, err := r.messageSender.SendTransaction(msg.MsgHash, &r.cfg.MessengerContractAddress, big.NewInt(0), data)
hash, err := r.messageSender.SendTransaction(msg.MsgHash, &r.cfg.MessengerContractAddress, big.NewInt(0), data, r.minGasLimitForMessageRelay)
if err != nil && err.Error() == "execution reverted: Message expired" {
return r.db.UpdateLayer2Status(r.ctx, msg.MsgHash, types.MsgExpired)
}
if err != nil && err.Error() == "execution reverted: Message successfully executed" {
if err != nil && err.Error() == "execution reverted: Message was already successfully executed" {
return r.db.UpdateLayer2Status(r.ctx, msg.MsgHash, types.MsgConfirmed)
}
if err != nil {
if !errors.Is(err, sender.ErrNoAvailableAccount) {
if !errors.Is(err, sender.ErrNoAvailableAccount) && !errors.Is(err, sender.ErrFullPending) {
log.Error("Failed to send relayMessageWithProof tx to layer1 ", "msg.height", msg.Height, "msg.MsgHash", msg.MsgHash, "err", err)
}
return err
@@ -284,9 +277,9 @@ func (r *Layer2Relayer) ProcessGasPriceOracle() {
return
}
hash, err := r.gasOracleSender.SendTransaction(batch.Hash, &r.cfg.GasPriceOracleContractAddress, big.NewInt(0), data)
hash, err := r.gasOracleSender.SendTransaction(batch.Hash, &r.cfg.GasPriceOracleContractAddress, big.NewInt(0), data, 0)
if err != nil {
if !errors.Is(err, sender.ErrNoAvailableAccount) {
if !errors.Is(err, sender.ErrNoAvailableAccount) && !errors.Is(err, sender.ErrFullPending) {
log.Error("Failed to send setL2BaseFee tx to layer2 ", "batch.Hash", batch.Hash, "err", err)
}
return
@@ -330,9 +323,9 @@ func (r *Layer2Relayer) SendCommitTx(batchData []*types.BatchData) error {
bytes = append(bytes, batch.Hash().Bytes()...)
}
txID := crypto.Keccak256Hash(bytes).String()
txHash, err := r.rollupSender.SendTransaction(txID, &r.cfg.RollupContractAddress, big.NewInt(0), calldata)
txHash, err := r.rollupSender.SendTransaction(txID, &r.cfg.RollupContractAddress, big.NewInt(0), calldata, 0)
if err != nil {
if !errors.Is(err, sender.ErrNoAvailableAccount) {
if !errors.Is(err, sender.ErrNoAvailableAccount) && !errors.Is(err, sender.ErrFullPending) {
log.Error("Failed to send commitBatches tx to layer1 ", "err", err)
}
return err
@@ -479,10 +472,10 @@ func (r *Layer2Relayer) ProcessCommittedBatches() {
txID := hash + "-finalize"
// add suffix `-finalize` to avoid duplication with commit tx in unit tests
txHash, err := r.rollupSender.SendTransaction(txID, &r.cfg.RollupContractAddress, big.NewInt(0), data)
txHash, err := r.rollupSender.SendTransaction(txID, &r.cfg.RollupContractAddress, big.NewInt(0), data, 0)
finalizeTxHash := &txHash
if err != nil {
if !errors.Is(err, sender.ErrNoAvailableAccount) {
if !errors.Is(err, sender.ErrNoAvailableAccount) && !errors.Is(err, sender.ErrFullPending) {
log.Error("finalizeBatchWithProof in layer1 failed", "hash", hash, "err", err)
}
return
@@ -505,65 +498,20 @@ func (r *Layer2Relayer) ProcessCommittedBatches() {
}
}
// Start the relayer process
func (r *Layer2Relayer) Start() {
go func() {
ctx, cancel := context.WithCancel(r.ctx)
go cutil.Loop(ctx, time.Second, r.ProcessSavedEvents)
go cutil.Loop(ctx, time.Second, r.ProcessCommittedBatches)
go cutil.Loop(ctx, time.Second, r.ProcessGasPriceOracle)
go func(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
case confirmation := <-r.messageCh:
r.handleConfirmation(confirmation)
case confirmation := <-r.rollupCh:
r.handleConfirmation(confirmation)
case cfm := <-r.gasOracleCh:
if !cfm.IsSuccessful {
// @discuss: maybe make it pending again?
err := r.db.UpdateL2GasOracleStatusAndOracleTxHash(r.ctx, cfm.ID, types.GasOracleFailed, cfm.TxHash.String())
if err != nil {
log.Warn("UpdateL2GasOracleStatusAndOracleTxHash failed", "err", err)
}
log.Warn("transaction confirmed but failed in layer1", "confirmation", cfm)
} else {
// @todo handle db error
err := r.db.UpdateL2GasOracleStatusAndOracleTxHash(r.ctx, cfm.ID, types.GasOracleImported, cfm.TxHash.String())
if err != nil {
log.Warn("UpdateL2GasOracleStatusAndOracleTxHash failed", "err", err)
}
log.Info("transaction confirmed in layer1", "confirmation", cfm)
}
}
}
}(ctx)
<-r.stopCh
cancel()
}()
}
// Stop the relayer module, for a graceful shutdown.
func (r *Layer2Relayer) Stop() {
close(r.stopCh)
}
func (r *Layer2Relayer) handleConfirmation(confirmation *sender.Confirmation) {
if !confirmation.IsSuccessful {
log.Warn("transaction confirmed but failed in layer1", "confirmation", confirmation)
return
}
transactionType := "Unknown"
// check whether it is message relay transaction
if msgHash, ok := r.processingMessage.Load(confirmation.ID); ok {
transactionType = "MessageRelay"
var status types.MsgStatus
if confirmation.IsSuccessful {
status = types.MsgConfirmed
} else {
status = types.MsgRelayFailed
log.Warn("transaction confirmed but failed in layer1", "confirmation", confirmation)
}
// @todo handle db error
err := r.db.UpdateLayer2StatusAndLayer1Hash(r.ctx, msgHash.(string), types.MsgConfirmed, confirmation.TxHash.String())
err := r.db.UpdateLayer2StatusAndLayer1Hash(r.ctx, msgHash.(string), status, confirmation.TxHash.String())
if err != nil {
log.Warn("UpdateLayer2StatusAndLayer1Hash failed", "msgHash", msgHash.(string), "err", err)
}
@@ -575,9 +523,16 @@ func (r *Layer2Relayer) handleConfirmation(confirmation *sender.Confirmation) {
if batchBatches, ok := r.processingBatchesCommitment.Load(confirmation.ID); ok {
transactionType = "BatchesCommitment"
batchHashes := batchBatches.([]string)
var status types.RollupStatus
if confirmation.IsSuccessful {
status = types.RollupCommitted
} else {
status = types.RollupCommitFailed
log.Warn("transaction confirmed but failed in layer1", "confirmation", confirmation)
}
for _, batchHash := range batchHashes {
// @todo handle db error
err := r.db.UpdateCommitTxHashAndRollupStatus(r.ctx, batchHash, confirmation.TxHash.String(), types.RollupCommitted)
err := r.db.UpdateCommitTxHashAndRollupStatus(r.ctx, batchHash, confirmation.TxHash.String(), status)
if err != nil {
log.Warn("UpdateCommitTxHashAndRollupStatus failed", "batch_hash", batchHash, "err", err)
}
@@ -589,8 +544,15 @@ func (r *Layer2Relayer) handleConfirmation(confirmation *sender.Confirmation) {
// check whether it is proof finalization transaction
if batchHash, ok := r.processingFinalization.Load(confirmation.ID); ok {
transactionType = "ProofFinalization"
var status types.RollupStatus
if confirmation.IsSuccessful {
status = types.RollupFinalized
} else {
status = types.RollupFinalizeFailed
log.Warn("transaction confirmed but failed in layer1", "confirmation", confirmation)
}
// @todo handle db error
err := r.db.UpdateFinalizeTxHashAndRollupStatus(r.ctx, batchHash.(string), confirmation.TxHash.String(), types.RollupFinalized)
err := r.db.UpdateFinalizeTxHashAndRollupStatus(r.ctx, batchHash.(string), confirmation.TxHash.String(), status)
if err != nil {
log.Warn("UpdateFinalizeTxHashAndRollupStatus failed", "batch_hash", batchHash.(string), "err", err)
}
@@ -599,3 +561,32 @@ func (r *Layer2Relayer) handleConfirmation(confirmation *sender.Confirmation) {
}
log.Info("transaction confirmed in layer1", "type", transactionType, "confirmation", confirmation)
}
func (r *Layer2Relayer) handleConfirmLoop(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
case confirmation := <-r.messageSender.ConfirmChan():
r.handleConfirmation(confirmation)
case confirmation := <-r.rollupSender.ConfirmChan():
r.handleConfirmation(confirmation)
case cfm := <-r.gasOracleSender.ConfirmChan():
if !cfm.IsSuccessful {
// @discuss: maybe make it pending again?
err := r.db.UpdateL2GasOracleStatusAndOracleTxHash(r.ctx, cfm.ID, types.GasOracleFailed, cfm.TxHash.String())
if err != nil {
log.Warn("UpdateL2GasOracleStatusAndOracleTxHash failed", "err", err)
}
log.Warn("transaction confirmed but failed in layer1", "confirmation", cfm)
} else {
// @todo handle db error
err := r.db.UpdateL2GasOracleStatusAndOracleTxHash(r.ctx, cfm.ID, types.GasOracleImported, cfm.TxHash.String())
if err != nil {
log.Warn("UpdateL2GasOracleStatusAndOracleTxHash failed", "err", err)
}
log.Info("transaction confirmed in layer1", "confirmation", cfm)
}
}
}
}

View File

@@ -1,4 +1,4 @@
package l2
package relayer_test
import (
"context"
@@ -14,6 +14,8 @@ import (
"scroll-tech/common/types"
"scroll-tech/bridge/relayer"
"scroll-tech/database"
"scroll-tech/database/migrate"
)
@@ -39,11 +41,9 @@ func testCreateNewRelayer(t *testing.T) {
assert.NoError(t, migrate.ResetDB(db.GetDB().DB))
defer db.Close()
relayer, err := NewLayer2Relayer(context.Background(), l2Cli, db, cfg.L2Config.RelayerConfig)
relayer, err := relayer.NewLayer2Relayer(context.Background(), l2Cli, db, cfg.L2Config.RelayerConfig)
assert.NoError(t, err)
defer relayer.Stop()
relayer.Start()
assert.NotNil(t, relayer)
}
func testL2RelayerProcessSaveEvents(t *testing.T) {
@@ -54,27 +54,36 @@ func testL2RelayerProcessSaveEvents(t *testing.T) {
defer db.Close()
l2Cfg := cfg.L2Config
relayer, err := NewLayer2Relayer(context.Background(), l2Cli, db, l2Cfg.RelayerConfig)
relayer, err := relayer.NewLayer2Relayer(context.Background(), l2Cli, db, l2Cfg.RelayerConfig)
assert.NoError(t, err)
defer relayer.Stop()
err = db.SaveL2Messages(context.Background(), templateL2Message)
assert.NoError(t, err)
traces := []*geth_types.BlockTrace{
traces := []*types.WrappedBlock{
{
Header: &geth_types.Header{
Number: big.NewInt(int64(templateL2Message[0].Height)),
},
Transactions: nil,
WithdrawTrieRoot: common.Hash{},
},
{
Header: &geth_types.Header{
Number: big.NewInt(int64(templateL2Message[0].Height + 1)),
},
Transactions: nil,
WithdrawTrieRoot: common.Hash{},
},
}
assert.NoError(t, db.InsertL2BlockTraces(traces))
assert.NoError(t, db.InsertWrappedBlocks(traces))
parentBatch1 := &types.BlockBatch{
Index: 0,
Hash: common.Hash{}.String(),
StateRoot: common.Hash{}.String(),
}
batchData1 := types.NewBatchData(parentBatch1, []*types.WrappedBlock{wrappedBlock1}, nil)
dbTx, err := db.Beginx()
assert.NoError(t, err)
assert.NoError(t, db.NewBatchInDBTx(dbTx, batchData1))
@@ -100,10 +109,15 @@ func testL2RelayerProcessCommittedBatches(t *testing.T) {
defer db.Close()
l2Cfg := cfg.L2Config
relayer, err := NewLayer2Relayer(context.Background(), l2Cli, db, l2Cfg.RelayerConfig)
relayer, err := relayer.NewLayer2Relayer(context.Background(), l2Cli, db, l2Cfg.RelayerConfig)
assert.NoError(t, err)
defer relayer.Stop()
parentBatch1 := &types.BlockBatch{
Index: 0,
Hash: common.Hash{}.String(),
StateRoot: common.Hash{}.String(),
}
batchData1 := types.NewBatchData(parentBatch1, []*types.WrappedBlock{wrappedBlock1}, nil)
dbTx, err := db.Beginx()
assert.NoError(t, err)
assert.NoError(t, db.NewBatchInDBTx(dbTx, batchData1))
@@ -136,9 +150,8 @@ func testL2RelayerSkipBatches(t *testing.T) {
defer db.Close()
l2Cfg := cfg.L2Config
relayer, err := NewLayer2Relayer(context.Background(), l2Cli, db, l2Cfg.RelayerConfig)
relayer, err := relayer.NewLayer2Relayer(context.Background(), l2Cli, db, l2Cfg.RelayerConfig)
assert.NoError(t, err)
defer relayer.Stop()
createBatch := func(rollupStatus types.RollupStatus, provingStatus types.ProvingStatus, index uint64) string {
dbTx, err := db.Beginx()
@@ -198,13 +211,13 @@ func genBatchData(t *testing.T, index uint64) *types.BatchData {
templateBlockTrace, err := os.ReadFile("../../common/testdata/blockTrace_02.json")
assert.NoError(t, err)
// unmarshal blockTrace
blockTrace := &geth_types.BlockTrace{}
err = json.Unmarshal(templateBlockTrace, blockTrace)
wrappedBlock := &types.WrappedBlock{}
err = json.Unmarshal(templateBlockTrace, wrappedBlock)
assert.NoError(t, err)
blockTrace.Header.ParentHash = common.HexToHash("0x" + strconv.FormatUint(index+1, 16))
wrappedBlock.Header.ParentHash = common.HexToHash("0x" + strconv.FormatUint(index+1, 16))
parentBatch := &types.BlockBatch{
Index: index,
Hash: "0x0000000000000000000000000000000000000000",
}
return types.NewBatchData(parentBatch, []*geth_types.BlockTrace{blockTrace}, nil)
return types.NewBatchData(parentBatch, []*types.WrappedBlock{wrappedBlock}, nil)
}

11
bridge/relayer/params.go Normal file
View File

@@ -0,0 +1,11 @@
package relayer
const (
gasPriceDiffPrecision = 1000000
defaultGasPriceDiff = 50000 // 5%
defaultL1MessageRelayMinGasLimit = 130000 // should be enough for both ERC20 and ETH relay
defaultL2MessageRelayMinGasLimit = 200000
)

View File

@@ -0,0 +1,108 @@
package relayer_test
import (
"encoding/json"
"os"
"testing"
"github.com/scroll-tech/go-ethereum/ethclient"
"github.com/scroll-tech/go-ethereum/log"
"github.com/stretchr/testify/assert"
"scroll-tech/common/docker"
"scroll-tech/common/types"
"scroll-tech/bridge/config"
)
var (
// config
cfg *config.Config
base *docker.App
// l2geth client
l2Cli *ethclient.Client
// block trace
wrappedBlock1 *types.WrappedBlock
wrappedBlock2 *types.WrappedBlock
// batch data
batchData1 *types.BatchData
batchData2 *types.BatchData
)
func setupEnv(t *testing.T) (err error) {
// Load config.
cfg, err = config.NewConfig("../config.json")
assert.NoError(t, err)
base.RunImages(t)
cfg.L2Config.RelayerConfig.SenderConfig.Endpoint = base.L1GethEndpoint()
cfg.L1Config.RelayerConfig.SenderConfig.Endpoint = base.L2GethEndpoint()
cfg.DBConfig.DSN = base.DBEndpoint()
// Create l2geth client.
l2Cli, err = base.L2Client()
assert.NoError(t, err)
templateBlockTrace1, err := os.ReadFile("../../common/testdata/blockTrace_02.json")
if err != nil {
return err
}
// unmarshal blockTrace
wrappedBlock1 = &types.WrappedBlock{}
if err = json.Unmarshal(templateBlockTrace1, wrappedBlock1); err != nil {
return err
}
parentBatch1 := &types.BlockBatch{
Index: 0,
Hash: "0x0cc6b102c2924402c14b2e3a19baccc316252bfdc44d9ec62e942d34e39ec729",
StateRoot: "0x2579122e8f9ec1e862e7d415cef2fb495d7698a8e5f0dddc5651ba4236336e7d",
}
batchData1 = types.NewBatchData(parentBatch1, []*types.WrappedBlock{wrappedBlock1}, nil)
templateBlockTrace2, err := os.ReadFile("../../common/testdata/blockTrace_03.json")
if err != nil {
return err
}
// unmarshal blockTrace
wrappedBlock2 = &types.WrappedBlock{}
if err = json.Unmarshal(templateBlockTrace2, wrappedBlock2); err != nil {
return err
}
parentBatch2 := &types.BlockBatch{
Index: batchData1.Batch.BatchIndex,
Hash: batchData1.Hash().Hex(),
StateRoot: batchData1.Batch.NewStateRoot.String(),
}
batchData2 = types.NewBatchData(parentBatch2, []*types.WrappedBlock{wrappedBlock2}, nil)
log.Info("batchHash", "batchhash1", batchData1.Hash().Hex(), "batchhash2", batchData2.Hash().Hex())
return err
}
func TestMain(m *testing.M) {
base = docker.NewDockerApp()
m.Run()
base.Free()
}
func TestFunctions(t *testing.T) {
if err := setupEnv(t); err != nil {
t.Fatal(err)
}
// Run l1 relayer test cases.
t.Run("TestCreateNewL1Relayer", testCreateNewL1Relayer)
// Run l2 relayer test cases.
t.Run("TestCreateNewRelayer", testCreateNewRelayer)
t.Run("TestL2RelayerProcessSaveEvents", testL2RelayerProcessSaveEvents)
t.Run("TestL2RelayerProcessCommittedBatches", testL2RelayerProcessCommittedBatches)
t.Run("TestL2RelayerSkipBatches", testL2RelayerSkipBatches)
}

View File

@@ -9,12 +9,12 @@ import (
"github.com/scroll-tech/go-ethereum/common"
)
func (s *Sender) estimateLegacyGas(auth *bind.TransactOpts, contract *common.Address, value *big.Int, input []byte) (*FeeData, error) {
func (s *Sender) estimateLegacyGas(auth *bind.TransactOpts, contract *common.Address, value *big.Int, input []byte, minGasLimit uint64) (*FeeData, error) {
gasPrice, err := s.client.SuggestGasPrice(s.ctx)
if err != nil {
return nil, err
}
gasLimit, err := s.estimateGasLimit(auth, contract, input, gasPrice, nil, nil, value)
gasLimit, err := s.estimateGasLimit(auth, contract, input, gasPrice, nil, nil, value, minGasLimit)
if err != nil {
return nil, err
}
@@ -24,7 +24,7 @@ func (s *Sender) estimateLegacyGas(auth *bind.TransactOpts, contract *common.Add
}, nil
}
func (s *Sender) estimateDynamicGas(auth *bind.TransactOpts, contract *common.Address, value *big.Int, input []byte) (*FeeData, error) {
func (s *Sender) estimateDynamicGas(auth *bind.TransactOpts, contract *common.Address, value *big.Int, input []byte, minGasLimit uint64) (*FeeData, error) {
gasTipCap, err := s.client.SuggestGasTipCap(s.ctx)
if err != nil {
return nil, err
@@ -38,7 +38,7 @@ func (s *Sender) estimateDynamicGas(auth *bind.TransactOpts, contract *common.Ad
gasTipCap,
new(big.Int).Mul(baseFee, big.NewInt(2)),
)
gasLimit, err := s.estimateGasLimit(auth, contract, input, nil, gasTipCap, gasFeeCap, value)
gasLimit, err := s.estimateGasLimit(auth, contract, input, nil, gasTipCap, gasFeeCap, value, minGasLimit)
if err != nil {
return nil, err
}
@@ -49,7 +49,7 @@ func (s *Sender) estimateDynamicGas(auth *bind.TransactOpts, contract *common.Ad
}, nil
}
func (s *Sender) estimateGasLimit(opts *bind.TransactOpts, contract *common.Address, input []byte, gasPrice, gasTipCap, gasFeeCap, value *big.Int) (uint64, error) {
func (s *Sender) estimateGasLimit(opts *bind.TransactOpts, contract *common.Address, input []byte, gasPrice, gasTipCap, gasFeeCap, value *big.Int, minGasLimit uint64) (uint64, error) {
msg := ethereum.CallMsg{
From: opts.From,
To: contract,
@@ -63,6 +63,10 @@ func (s *Sender) estimateGasLimit(opts *bind.TransactOpts, contract *common.Addr
if err != nil {
return 0, err
}
if minGasLimit > gasLimit {
gasLimit = minGasLimit
}
gasLimit = gasLimit * 15 / 10 // 50% extra gas to void out of gas error
return gasLimit, nil

View File

@@ -6,21 +6,19 @@ import (
"errors"
"fmt"
"math/big"
"reflect"
"strings"
"sync"
"sync/atomic"
"time"
cmapV2 "github.com/orcaman/concurrent-map/v2"
"github.com/scroll-tech/go-ethereum/accounts/abi/bind"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/ethclient"
"github.com/scroll-tech/go-ethereum/log"
"scroll-tech/bridge/utils"
"scroll-tech/bridge/config"
"scroll-tech/bridge/utils"
)
const (
@@ -37,6 +35,12 @@ const (
var (
// ErrNoAvailableAccount indicates no available account error in the account pool.
ErrNoAvailableAccount = errors.New("sender has no available account to send transaction")
// ErrFullPending sender's pending pool is full.
ErrFullPending = errors.New("sender's pending pool is full")
)
var (
defaultPendingLimit = 10
)
// Confirmation struct used to indicate transaction confirmation details
@@ -74,9 +78,9 @@ type Sender struct {
// account fields.
auths *accountPool
blockNumber uint64 // Current block number on chain.
baseFeePerGas uint64 // Current base fee per gas on chain
pendingTxs sync.Map // Mapping from nonce to pending transaction
blockNumber uint64 // Current block number on chain.
baseFeePerGas uint64 // Current base fee per gas on chain
pendingTxs cmapV2.ConcurrentMap[string, *PendingTransaction] // Mapping from nonce to pending transaction
confirmCh chan *Confirmation
stopCh chan struct{}
@@ -116,6 +120,11 @@ func NewSender(ctx context.Context, config *config.SenderConfig, privs []*ecdsa.
}
}
// initialize pending limit with a default value
if config.PendingLimit == 0 {
config.PendingLimit = defaultPendingLimit
}
sender := &Sender{
ctx: ctx,
config: config,
@@ -125,7 +134,7 @@ func NewSender(ctx context.Context, config *config.SenderConfig, privs []*ecdsa.
confirmCh: make(chan *Confirmation, 128),
blockNumber: header.Number.Uint64(),
baseFeePerGas: baseFeePerGas,
pendingTxs: sync.Map{},
pendingTxs: cmapV2.New[*PendingTransaction](),
stopCh: make(chan struct{}),
}
@@ -134,6 +143,21 @@ func NewSender(ctx context.Context, config *config.SenderConfig, privs []*ecdsa.
return sender, nil
}
// PendingCount returns the current number of pending txs.
func (s *Sender) PendingCount() int {
return s.pendingTxs.Count()
}
// PendingLimit returns the maximum number of pending txs the sender can handle.
func (s *Sender) PendingLimit() int {
return s.config.PendingLimit
}
// IsFull returns true if the sender's pending tx pool is full.
func (s *Sender) IsFull() bool {
return s.pendingTxs.Count() >= s.config.PendingLimit
}
// Stop stop the sender module.
func (s *Sender) Stop() {
close(s.stopCh)
@@ -150,30 +174,33 @@ func (s *Sender) NumberOfAccounts() int {
return len(s.auths.accounts)
}
func (s *Sender) getFeeData(auth *bind.TransactOpts, target *common.Address, value *big.Int, data []byte) (*FeeData, error) {
func (s *Sender) getFeeData(auth *bind.TransactOpts, target *common.Address, value *big.Int, data []byte, minGasLimit uint64) (*FeeData, error) {
if s.config.TxType == DynamicFeeTxType {
return s.estimateDynamicGas(auth, target, value, data)
return s.estimateDynamicGas(auth, target, value, data, minGasLimit)
}
return s.estimateLegacyGas(auth, target, value, data)
return s.estimateLegacyGas(auth, target, value, data, minGasLimit)
}
// SendTransaction send a signed L2tL1 transaction.
func (s *Sender) SendTransaction(ID string, target *common.Address, value *big.Int, data []byte) (hash common.Hash, err error) {
func (s *Sender) SendTransaction(ID string, target *common.Address, value *big.Int, data []byte, minGasLimit uint64) (hash common.Hash, err error) {
if s.IsFull() {
return common.Hash{}, ErrFullPending
}
// We occupy the ID, in case some other threads call with the same ID in the same time
if _, loaded := s.pendingTxs.LoadOrStore(ID, nil); loaded {
if ok := s.pendingTxs.SetIfAbsent(ID, nil); !ok {
return common.Hash{}, fmt.Errorf("has the repeat tx ID, ID: %s", ID)
}
// get
auth := s.auths.getAccount()
if auth == nil {
s.pendingTxs.Delete(ID) // release the ID on failure
s.pendingTxs.Remove(ID) // release the ID on failure
return common.Hash{}, ErrNoAvailableAccount
}
defer s.auths.releaseAccount(auth)
defer func() {
if err != nil {
s.pendingTxs.Delete(ID) // release the ID on failure
s.pendingTxs.Remove(ID) // release the ID on failure
}
}()
@@ -182,7 +209,7 @@ func (s *Sender) SendTransaction(ID string, target *common.Address, value *big.I
tx *types.Transaction
)
// estimate gas fee
if feeData, err = s.getFeeData(auth, target, value, data); err != nil {
if feeData, err = s.getFeeData(auth, target, value, data, minGasLimit); err != nil {
return
}
if tx, err = s.createAndSendTx(auth, feeData, target, value, data, nil); err == nil {
@@ -194,7 +221,7 @@ func (s *Sender) SendTransaction(ID string, target *common.Address, value *big.I
submitAt: atomic.LoadUint64(&s.blockNumber),
feeData: feeData,
}
s.pendingTxs.Store(ID, pending)
s.pendingTxs.Set(ID, pending)
return tx.Hash(), nil
}
@@ -335,17 +362,17 @@ func (s *Sender) checkPendingTransaction(header *types.Header, confirmed uint64)
}
}
s.pendingTxs.Range(func(key, value interface{}) bool {
for item := range s.pendingTxs.IterBuffered() {
key, pending := item.Key, item.Val
// ignore empty id, since we use empty id to occupy pending task
if value == nil || reflect.ValueOf(value).IsNil() {
return true
if pending == nil {
continue
}
pending := value.(*PendingTransaction)
receipt, err := s.client.TransactionReceipt(s.ctx, pending.tx.Hash())
if (err == nil) && (receipt != nil) {
if receipt.BlockNumber.Uint64() <= confirmed {
s.pendingTxs.Delete(key)
s.pendingTxs.Remove(key)
// send confirm message
s.confirmCh <- &Confirmation{
ID: pending.id,
@@ -376,7 +403,7 @@ func (s *Sender) checkPendingTransaction(header *types.Header, confirmed uint64)
// We need to stop the program and manually handle the situation.
if strings.Contains(err.Error(), "nonce") {
// This key can be deleted
s.pendingTxs.Delete(key)
s.pendingTxs.Remove(key)
// Try get receipt by the latest replaced tx hash
receipt, err := s.client.TransactionReceipt(s.ctx, pending.tx.Hash())
if (err == nil) && (receipt != nil) {
@@ -398,8 +425,7 @@ func (s *Sender) checkPendingTransaction(header *types.Header, confirmed uint64)
pending.submitAt = number
}
}
return true
})
}
}
// Loop is the main event loop

View File

@@ -14,6 +14,7 @@ import (
cmap "github.com/orcaman/concurrent-map"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/crypto"
"github.com/scroll-tech/go-ethereum/ethclient"
"github.com/scroll-tech/go-ethereum/rpc"
"github.com/stretchr/testify/assert"
@@ -56,11 +57,55 @@ func TestSender(t *testing.T) {
// Setup
setupEnv(t)
t.Run("test pending limit", func(t *testing.T) { testPendLimit(t) })
t.Run("test min gas limit", func(t *testing.T) { testMinGasLimit(t) })
t.Run("test 1 account sender", func(t *testing.T) { testBatchSender(t, 1) })
t.Run("test 3 account sender", func(t *testing.T) { testBatchSender(t, 3) })
t.Run("test 8 account sender", func(t *testing.T) { testBatchSender(t, 8) })
}
func testPendLimit(t *testing.T) {
senderCfg := cfg.L1Config.RelayerConfig.SenderConfig
senderCfg.Confirmations = rpc.LatestBlockNumber
senderCfg.PendingLimit = 2
newSender, err := sender.NewSender(context.Background(), senderCfg, privateKeys)
assert.NoError(t, err)
defer newSender.Stop()
for i := 0; i < newSender.PendingLimit(); i++ {
_, err = newSender.SendTransaction(strconv.Itoa(i), &common.Address{}, big.NewInt(1), nil, 0)
assert.NoError(t, err)
}
assert.True(t, newSender.PendingCount() <= newSender.PendingLimit())
}
func testMinGasLimit(t *testing.T) {
senderCfg := cfg.L1Config.RelayerConfig.SenderConfig
senderCfg.Confirmations = rpc.LatestBlockNumber
newSender, err := sender.NewSender(context.Background(), senderCfg, privateKeys)
assert.NoError(t, err)
defer newSender.Stop()
client, err := ethclient.Dial(senderCfg.Endpoint)
assert.NoError(t, err)
// MinGasLimit = 0
txHash0, err := newSender.SendTransaction("0", &common.Address{}, big.NewInt(1), nil, 0)
assert.NoError(t, err)
tx0, _, err := client.TransactionByHash(context.Background(), txHash0)
assert.NoError(t, err)
assert.Greater(t, tx0.Gas(), uint64(0))
// MinGasLimit = 100000
txHash1, err := newSender.SendTransaction("1", &common.Address{}, big.NewInt(1), nil, 100000)
assert.NoError(t, err)
tx1, _, err := client.TransactionByHash(context.Background(), txHash1)
assert.NoError(t, err)
assert.Equal(t, tx1.Gas(), uint64(150000))
}
func testBatchSender(t *testing.T, batchSize int) {
for len(privateKeys) < batchSize {
priv, err := crypto.GenerateKey()
@@ -72,6 +117,7 @@ func testBatchSender(t *testing.T, batchSize int) {
senderCfg := cfg.L1Config.RelayerConfig.SenderConfig
senderCfg.Confirmations = rpc.LatestBlockNumber
senderCfg.PendingLimit = batchSize * TXBatch
newSender, err := sender.NewSender(context.Background(), senderCfg, privateKeys)
if err != nil {
t.Fatal(err)
@@ -90,8 +136,8 @@ func testBatchSender(t *testing.T, batchSize int) {
for i := 0; i < TXBatch; i++ {
toAddr := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
id := strconv.Itoa(i + index*1000)
_, err := newSender.SendTransaction(id, &toAddr, big.NewInt(1), nil)
if errors.Is(err, sender.ErrNoAvailableAccount) {
_, err := newSender.SendTransaction(id, &toAddr, big.NewInt(1), nil, 0)
if errors.Is(err, sender.ErrNoAvailableAccount) || errors.Is(err, sender.ErrFullPending) {
<-time.After(time.Second)
continue
}

View File

@@ -11,8 +11,8 @@ import (
"scroll-tech/common/types"
"scroll-tech/bridge/l1"
"scroll-tech/bridge/l2"
"scroll-tech/bridge/relayer"
"scroll-tech/bridge/watcher"
"scroll-tech/database"
"scroll-tech/database/migrate"
@@ -30,14 +30,13 @@ func testImportL1GasPrice(t *testing.T) {
l1Cfg := cfg.L1Config
// Create L1Relayer
l1Relayer, err := l1.NewLayer1Relayer(context.Background(), db, l1Cfg.RelayerConfig)
l1Relayer, err := relayer.NewLayer1Relayer(context.Background(), db, l1Cfg.RelayerConfig)
assert.NoError(t, err)
defer l1Relayer.Stop()
// Create L1Watcher
startHeight, err := l1Client.BlockNumber(context.Background())
assert.NoError(t, err)
l1Watcher := l1.NewWatcher(context.Background(), l1Client, startHeight-1, 0, l1Cfg.L1MessengerAddress, l1Cfg.L1MessageQueueAddress, l1Cfg.ScrollChainContractAddress, db)
l1Watcher := watcher.NewL1WatcherClient(context.Background(), l1Client, startHeight-1, 0, l1Cfg.L1MessengerAddress, l1Cfg.L1MessageQueueAddress, l1Cfg.ScrollChainContractAddress, db)
// fetch new blocks
number, err := l1Client.BlockNumber(context.Background())
@@ -81,12 +80,11 @@ func testImportL2GasPrice(t *testing.T) {
l2Cfg := cfg.L2Config
// Create L2Relayer
l2Relayer, err := l2.NewLayer2Relayer(context.Background(), l2Client, db, l2Cfg.RelayerConfig)
l2Relayer, err := relayer.NewLayer2Relayer(context.Background(), l2Client, db, l2Cfg.RelayerConfig)
assert.NoError(t, err)
defer l2Relayer.Stop()
// add fake blocks
traces := []*geth_types.BlockTrace{
traces := []*types.WrappedBlock{
{
Header: &geth_types.Header{
Number: big.NewInt(1),
@@ -94,16 +92,17 @@ func testImportL2GasPrice(t *testing.T) {
Difficulty: big.NewInt(0),
BaseFee: big.NewInt(0),
},
StorageTrace: &geth_types.StorageTrace{},
Transactions: nil,
WithdrawTrieRoot: common.Hash{},
},
}
assert.NoError(t, db.InsertL2BlockTraces(traces))
assert.NoError(t, db.InsertWrappedBlocks(traces))
parentBatch := &types.BlockBatch{
Index: 0,
Hash: "0x0000000000000000000000000000000000000000",
}
batchData := types.NewBatchData(parentBatch, []*geth_types.BlockTrace{
batchData := types.NewBatchData(parentBatch, []*types.WrappedBlock{
traces[0],
}, cfg.L2Config.BatchProposerConfig.PublicInputConfig)

View File

@@ -13,8 +13,8 @@ import (
"scroll-tech/common/types"
"scroll-tech/bridge/l1"
"scroll-tech/bridge/l2"
"scroll-tech/bridge/relayer"
"scroll-tech/bridge/watcher"
"scroll-tech/database"
"scroll-tech/database/migrate"
@@ -33,16 +33,14 @@ func testRelayL1MessageSucceed(t *testing.T) {
l2Cfg := cfg.L2Config
// Create L1Relayer
l1Relayer, err := l1.NewLayer1Relayer(context.Background(), db, l1Cfg.RelayerConfig)
l1Relayer, err := relayer.NewLayer1Relayer(context.Background(), db, l1Cfg.RelayerConfig)
assert.NoError(t, err)
defer l1Relayer.Stop()
// Create L1Watcher
confirmations := rpc.LatestBlockNumber
l1Watcher := l1.NewWatcher(context.Background(), l1Client, 0, confirmations, l1Cfg.L1MessengerAddress, l1Cfg.L1MessageQueueAddress, l1Cfg.ScrollChainContractAddress, db)
l1Watcher := watcher.NewL1WatcherClient(context.Background(), l1Client, 0, confirmations, l1Cfg.L1MessengerAddress, l1Cfg.L1MessageQueueAddress, l1Cfg.ScrollChainContractAddress, db)
// Create L2Watcher
l2Watcher := l2.NewL2WatcherClient(context.Background(), l2Client, confirmations, l2Cfg.L2MessengerAddress, l2Cfg.L2MessageQueueAddress, db)
l2Watcher := watcher.NewL2WatcherClient(context.Background(), l2Client, confirmations, l2Cfg.L2MessengerAddress, l2Cfg.L2MessageQueueAddress, l2Cfg.WithdrawTrieRootSlot, db)
// send message through l1 messenger contract
nonce, err := l1MessengerInstance.MessageNonce(&bind.CallOpts{})
@@ -56,7 +54,7 @@ func testRelayL1MessageSucceed(t *testing.T) {
}
// l1 watch process events
l1Watcher.FetchContractEvent(sendReceipt.BlockNumber.Uint64())
l1Watcher.FetchContractEvent()
// check db status
msg, err := db.GetL1MessageByQueueIndex(nonce.Uint64())
@@ -79,7 +77,7 @@ func testRelayL1MessageSucceed(t *testing.T) {
assert.Equal(t, len(relayTxReceipt.Logs), 1)
// fetch message relayed events
l2Watcher.FetchContractEvent(relayTxReceipt.BlockNumber.Uint64())
l2Watcher.FetchContractEvent()
msg, err = db.GetL1MessageByQueueIndex(nonce.Uint64())
assert.NoError(t, err)
assert.Equal(t, msg.Status, types.MsgConfirmed)

View File

@@ -13,8 +13,8 @@ import (
"scroll-tech/common/types"
"scroll-tech/bridge/l1"
"scroll-tech/bridge/l2"
"scroll-tech/bridge/relayer"
"scroll-tech/bridge/watcher"
"scroll-tech/database"
"scroll-tech/database/migrate"
@@ -33,15 +33,15 @@ func testRelayL2MessageSucceed(t *testing.T) {
// Create L2Watcher
confirmations := rpc.LatestBlockNumber
l2Watcher := l2.NewL2WatcherClient(context.Background(), l2Client, confirmations, l2Cfg.L2MessengerAddress, l2Cfg.L2MessageQueueAddress, db)
l2Watcher := watcher.NewL2WatcherClient(context.Background(), l2Client, confirmations, l2Cfg.L2MessengerAddress, l2Cfg.L2MessageQueueAddress, l2Cfg.WithdrawTrieRootSlot, db)
// Create L2Relayer
l2Relayer, err := l2.NewLayer2Relayer(context.Background(), l2Client, db, l2Cfg.RelayerConfig)
l2Relayer, err := relayer.NewLayer2Relayer(context.Background(), l2Client, db, l2Cfg.RelayerConfig)
assert.NoError(t, err)
// Create L1Watcher
l1Cfg := cfg.L1Config
l1Watcher := l1.NewWatcher(context.Background(), l1Client, 0, confirmations, l1Cfg.L1MessengerAddress, l1Cfg.L1MessageQueueAddress, l1Cfg.ScrollChainContractAddress, db)
l1Watcher := watcher.NewL1WatcherClient(context.Background(), l1Client, 0, confirmations, l1Cfg.L1MessengerAddress, l1Cfg.L1MessageQueueAddress, l1Cfg.ScrollChainContractAddress, db)
// send message through l2 messenger contract
nonce, err := l2MessengerInstance.MessageNonce(&bind.CallOpts{})
@@ -55,7 +55,7 @@ func testRelayL2MessageSucceed(t *testing.T) {
}
// l2 watch process events
l2Watcher.FetchContractEvent(sendReceipt.BlockNumber.Uint64())
l2Watcher.FetchContractEvent()
// check db status
msg, err := db.GetL2MessageByNonce(nonce.Uint64())
@@ -65,7 +65,7 @@ func testRelayL2MessageSucceed(t *testing.T) {
assert.Equal(t, msg.Target, l1Auth.From.String())
// add fake blocks
traces := []*geth_types.BlockTrace{
traces := []*types.WrappedBlock{
{
Header: &geth_types.Header{
Number: sendReceipt.BlockNumber,
@@ -73,16 +73,17 @@ func testRelayL2MessageSucceed(t *testing.T) {
Difficulty: big.NewInt(0),
BaseFee: big.NewInt(0),
},
StorageTrace: &geth_types.StorageTrace{},
Transactions: nil,
WithdrawTrieRoot: common.Hash{},
},
}
assert.NoError(t, db.InsertL2BlockTraces(traces))
assert.NoError(t, db.InsertWrappedBlocks(traces))
parentBatch := &types.BlockBatch{
Index: 0,
Hash: "0x0000000000000000000000000000000000000000",
}
batchData := types.NewBatchData(parentBatch, []*geth_types.BlockTrace{
batchData := types.NewBatchData(parentBatch, []*types.WrappedBlock{
traces[0],
}, cfg.L2Config.BatchProposerConfig.PublicInputConfig)
batchHash := batchData.Hash().String()
@@ -122,7 +123,7 @@ func testRelayL2MessageSucceed(t *testing.T) {
assert.Equal(t, len(commitTxReceipt.Logs), 1)
// fetch CommitBatch rollup events
err = l1Watcher.FetchContractEvent(commitTxReceipt.BlockNumber.Uint64())
err = l1Watcher.FetchContractEvent()
assert.NoError(t, err)
status, err = db.GetRollupStatus(batchHash)
assert.NoError(t, err)
@@ -143,7 +144,7 @@ func testRelayL2MessageSucceed(t *testing.T) {
assert.Equal(t, len(finalizeTxReceipt.Logs), 1)
// fetch FinalizeBatch events
err = l1Watcher.FetchContractEvent(finalizeTxReceipt.BlockNumber.Uint64())
err = l1Watcher.FetchContractEvent()
assert.NoError(t, err)
status, err = db.GetRollupStatus(batchHash)
assert.NoError(t, err)
@@ -164,7 +165,7 @@ func testRelayL2MessageSucceed(t *testing.T) {
assert.Equal(t, len(relayTxReceipt.Logs), 1)
// fetch message relayed events
err = l1Watcher.FetchContractEvent(relayTxReceipt.BlockNumber.Uint64())
err = l1Watcher.FetchContractEvent()
assert.NoError(t, err)
msg, err = db.GetL2MessageByNonce(nonce.Uint64())
assert.NoError(t, err)

View File

@@ -12,8 +12,8 @@ import (
"scroll-tech/common/types"
"scroll-tech/bridge/l1"
"scroll-tech/bridge/l2"
"scroll-tech/bridge/relayer"
"scroll-tech/bridge/watcher"
"scroll-tech/database"
"scroll-tech/database/migrate"
@@ -30,16 +30,15 @@ func testCommitBatchAndFinalizeBatch(t *testing.T) {
// Create L2Relayer
l2Cfg := cfg.L2Config
l2Relayer, err := l2.NewLayer2Relayer(context.Background(), l2Client, db, l2Cfg.RelayerConfig)
l2Relayer, err := relayer.NewLayer2Relayer(context.Background(), l2Client, db, l2Cfg.RelayerConfig)
assert.NoError(t, err)
defer l2Relayer.Stop()
// Create L1Watcher
l1Cfg := cfg.L1Config
l1Watcher := l1.NewWatcher(context.Background(), l1Client, 0, l1Cfg.Confirmations, l1Cfg.L1MessengerAddress, l1Cfg.L1MessageQueueAddress, l1Cfg.ScrollChainContractAddress, db)
l1Watcher := watcher.NewL1WatcherClient(context.Background(), l1Client, 0, l1Cfg.Confirmations, l1Cfg.L1MessengerAddress, l1Cfg.L1MessageQueueAddress, l1Cfg.ScrollChainContractAddress, db)
// add some blocks to db
var traces []*geth_types.BlockTrace
var wrappedBlocks []*types.WrappedBlock
var parentHash common.Hash
for i := 1; i <= 10; i++ {
header := geth_types.Header{
@@ -48,21 +47,22 @@ func testCommitBatchAndFinalizeBatch(t *testing.T) {
Difficulty: big.NewInt(0),
BaseFee: big.NewInt(0),
}
traces = append(traces, &geth_types.BlockTrace{
Header: &header,
StorageTrace: &geth_types.StorageTrace{},
wrappedBlocks = append(wrappedBlocks, &types.WrappedBlock{
Header: &header,
Transactions: nil,
WithdrawTrieRoot: common.Hash{},
})
parentHash = header.Hash()
}
assert.NoError(t, db.InsertL2BlockTraces(traces))
assert.NoError(t, db.InsertWrappedBlocks(wrappedBlocks))
parentBatch := &types.BlockBatch{
Index: 0,
Hash: "0x0000000000000000000000000000000000000000",
}
batchData := types.NewBatchData(parentBatch, []*geth_types.BlockTrace{
traces[0],
traces[1],
batchData := types.NewBatchData(parentBatch, []*types.WrappedBlock{
wrappedBlocks[0],
wrappedBlocks[1],
}, cfg.L2Config.BatchProposerConfig.PublicInputConfig)
batchHash := batchData.Hash().String()
@@ -95,7 +95,7 @@ func testCommitBatchAndFinalizeBatch(t *testing.T) {
assert.Equal(t, len(commitTxReceipt.Logs), 1)
// fetch rollup events
err = l1Watcher.FetchContractEvent(commitTxReceipt.BlockNumber.Uint64())
err = l1Watcher.FetchContractEvent()
assert.NoError(t, err)
status, err = db.GetRollupStatus(batchHash)
assert.NoError(t, err)
@@ -125,7 +125,7 @@ func testCommitBatchAndFinalizeBatch(t *testing.T) {
assert.Equal(t, len(finalizeTxReceipt.Logs), 1)
// fetch rollup events
err = l1Watcher.FetchContractEvent(finalizeTxReceipt.BlockNumber.Uint64())
err = l1Watcher.FetchContractEvent()
assert.NoError(t, err)
status, err = db.GetRollupStatus(batchHash)
assert.NoError(t, err)

View File

@@ -1,4 +1,4 @@
package l2
package utils
import (
"fmt"

View File

@@ -1,35 +1,33 @@
package l2
package watcher
import (
"context"
"fmt"
"math"
"reflect"
"sync"
"time"
geth_types "github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/log"
geth_metrics "github.com/scroll-tech/go-ethereum/metrics"
"scroll-tech/common/metrics"
"scroll-tech/common/types"
"scroll-tech/common/utils"
"scroll-tech/database"
bridgeabi "scroll-tech/bridge/abi"
"scroll-tech/bridge/config"
"scroll-tech/bridge/relayer"
)
var (
bridgeL2BatchesGasOverThresholdTotalCounter = geth_metrics.NewRegisteredCounter("bridge/l2/batches/gas/over/threshold/total", metrics.ScrollRegistry)
bridgeL2BatchesTxsOverThresholdTotalCounter = geth_metrics.NewRegisteredCounter("bridge/l2/batches/txs/over/threshold/total", metrics.ScrollRegistry)
bridgeL2BatchesCommitTotalCounter = geth_metrics.NewRegisteredCounter("bridge/l2/batches/commit/total", metrics.ScrollRegistry)
bridgeL2BatchesBlocksCreatedTotalCounter = geth_metrics.NewRegisteredCounter("bridge/l2/batches/blocks/created/total", metrics.ScrollRegistry)
bridgeL2BatchesCommitsSentTotalCounter = geth_metrics.NewRegisteredCounter("bridge/l2/batches/commits/sent/total", metrics.ScrollRegistry)
bridgeL2BatchesCreatedRateMeter = geth_metrics.NewRegisteredMeter("bridge/l2/batches/blocks/created/rate", metrics.ScrollRegistry)
bridgeL2BatchesTxsCreatedRateMeter = geth_metrics.NewRegisteredMeter("bridge/l2/batches/txs/created/rate", metrics.ScrollRegistry)
bridgeL2BatchesGasCreatedRateMeter = geth_metrics.NewRegisteredMeter("bridge/l2/batches/gas/created/rate", metrics.ScrollRegistry)
bridgeL2BatchesTxsCreatedPerBatchGauge = geth_metrics.NewRegisteredGauge("bridge/l2/batches/txs/created/per/batch", metrics.ScrollRegistry)
bridgeL2BatchesGasCreatedPerBatchGauge = geth_metrics.NewRegisteredGauge("bridge/l2/batches/gas/created/per/batch", metrics.ScrollRegistry)
)
// AddBatchInfoToDB inserts the batch information to the BlockBatch table and updates the batch_hash
@@ -80,18 +78,17 @@ type BatchProposer struct {
batchCommitTimeSec uint64
commitCalldataSizeLimit uint64
batchDataBufferSizeLimit uint64
commitCalldataMinSize uint64
proofGenerationFreq uint64
batchDataBuffer []*types.BatchData
relayer *Layer2Relayer
relayer *relayer.Layer2Relayer
piCfg *types.PublicInputHashConfig
stopCh chan struct{}
}
// NewBatchProposer will return a new instance of BatchProposer.
func NewBatchProposer(ctx context.Context, cfg *config.BatchProposerConfig, relayer *Layer2Relayer, orm database.OrmFactory) *BatchProposer {
func NewBatchProposer(ctx context.Context, cfg *config.BatchProposerConfig, relayer *relayer.Layer2Relayer, orm database.OrmFactory) *BatchProposer {
p := &BatchProposer{
mutex: sync.Mutex{},
ctx: ctx,
@@ -102,46 +99,22 @@ func NewBatchProposer(ctx context.Context, cfg *config.BatchProposerConfig, rela
batchBlocksLimit: cfg.BatchBlocksLimit,
batchCommitTimeSec: cfg.BatchCommitTimeSec,
commitCalldataSizeLimit: cfg.CommitTxCalldataSizeLimit,
commitCalldataMinSize: cfg.CommitTxCalldataMinSize,
batchDataBufferSizeLimit: 100*cfg.CommitTxCalldataSizeLimit + 1*1024*1024, // @todo: determine the value.
proofGenerationFreq: cfg.ProofGenerationFreq,
piCfg: cfg.PublicInputConfig,
relayer: relayer,
stopCh: make(chan struct{}),
}
// for graceful restart.
p.recoverBatchDataBuffer()
// try to commit the leftover pending batches
p.tryCommitBatches()
p.TryCommitBatches()
return p
}
// Start the Listening process
func (p *BatchProposer) Start() {
go func() {
if reflect.ValueOf(p.orm).IsNil() {
panic("must run BatchProposer with DB")
}
ctx, cancel := context.WithCancel(p.ctx)
go utils.Loop(ctx, 2*time.Second, func() {
p.tryProposeBatch()
p.tryCommitBatches()
})
<-p.stopCh
cancel()
}()
}
// Stop the Watcher module, for a graceful shutdown.
func (p *BatchProposer) Stop() {
p.stopCh <- struct{}{}
}
func (p *BatchProposer) recoverBatchDataBuffer() {
// batches are sorted by batch index in increasing order
batchHashes, err := p.orm.GetPendingBatches(math.MaxInt32)
@@ -213,11 +186,12 @@ func (p *BatchProposer) recoverBatchDataBuffer() {
}
}
func (p *BatchProposer) tryProposeBatch() {
// TryProposeBatch will try to propose a batch.
func (p *BatchProposer) TryProposeBatch() {
p.mutex.Lock()
defer p.mutex.Unlock()
if p.getBatchDataBufferSize() < p.batchDataBufferSizeLimit {
for p.getBatchDataBufferSize() < p.batchDataBufferSizeLimit {
blocks, err := p.orm.GetUnbatchedL2Blocks(
map[string]interface{}{},
fmt.Sprintf("order by number ASC LIMIT %d", p.batchBlocksLimit),
@@ -227,11 +201,23 @@ func (p *BatchProposer) tryProposeBatch() {
return
}
p.proposeBatch(blocks)
batchCreated := p.proposeBatch(blocks)
// while size of batchDataBuffer < commitCalldataMinSize,
// proposer keeps fetching and porposing batches.
if p.getBatchDataBufferSize() >= p.commitCalldataMinSize {
return
}
if !batchCreated {
// wait for watcher to insert l2 traces.
time.Sleep(time.Second)
}
}
}
func (p *BatchProposer) tryCommitBatches() {
// TryCommitBatches will try to commit the pending batches.
func (p *BatchProposer) TryCommitBatches() {
p.mutex.Lock()
defer p.mutex.Unlock()
@@ -271,14 +257,14 @@ func (p *BatchProposer) tryCommitBatches() {
log.Error("SendCommitTx failed", "error", err)
} else {
// pop the processed batches from the buffer
bridgeL2BatchesCommitTotalCounter.Inc(1)
bridgeL2BatchesCommitsSentTotalCounter.Inc(1)
p.batchDataBuffer = p.batchDataBuffer[index:]
}
}
func (p *BatchProposer) proposeBatch(blocks []*types.BlockInfo) {
func (p *BatchProposer) proposeBatch(blocks []*types.BlockInfo) bool {
if len(blocks) == 0 {
return
return false
}
if blocks[0].GasUsed > p.batchGasThreshold {
@@ -287,11 +273,11 @@ func (p *BatchProposer) proposeBatch(blocks []*types.BlockInfo) {
if err := p.createBatchForBlocks(blocks[:1]); err != nil {
log.Error("failed to create batch", "number", blocks[0].Number, "err", err)
} else {
bridgeL2BatchesTxsCreatedRateMeter.Mark(int64(blocks[0].TxNum))
bridgeL2BatchesGasCreatedRateMeter.Mark(int64(blocks[0].GasUsed))
bridgeL2BatchesCreatedRateMeter.Mark(1)
bridgeL2BatchesTxsCreatedPerBatchGauge.Update(int64(blocks[0].TxNum))
bridgeL2BatchesGasCreatedPerBatchGauge.Update(int64(blocks[0].GasUsed))
bridgeL2BatchesBlocksCreatedTotalCounter.Inc(1)
}
return
return true
}
if blocks[0].TxNum > p.batchTxNumThreshold {
@@ -300,11 +286,11 @@ func (p *BatchProposer) proposeBatch(blocks []*types.BlockInfo) {
if err := p.createBatchForBlocks(blocks[:1]); err != nil {
log.Error("failed to create batch", "number", blocks[0].Number, "err", err)
} else {
bridgeL2BatchesTxsCreatedRateMeter.Mark(int64(blocks[0].TxNum))
bridgeL2BatchesGasCreatedRateMeter.Mark(int64(blocks[0].GasUsed))
bridgeL2BatchesCreatedRateMeter.Mark(1)
bridgeL2BatchesTxsCreatedPerBatchGauge.Update(int64(blocks[0].TxNum))
bridgeL2BatchesGasCreatedPerBatchGauge.Update(int64(blocks[0].GasUsed))
bridgeL2BatchesBlocksCreatedTotalCounter.Inc(1)
}
return
return true
}
var gasUsed, txNum uint64
@@ -324,16 +310,18 @@ func (p *BatchProposer) proposeBatch(blocks []*types.BlockInfo) {
// if it's not old enough we will skip proposing the batch,
// otherwise we will still propose a batch
if !reachThreshold && blocks[0].BlockTimestamp+p.batchTimeSec > uint64(time.Now().Unix()) {
return
return false
}
if err := p.createBatchForBlocks(blocks); err != nil {
log.Error("failed to create batch", "from", blocks[0].Number, "to", blocks[len(blocks)-1].Number, "err", err)
} else {
bridgeL2BatchesTxsCreatedRateMeter.Mark(int64(txNum))
bridgeL2BatchesGasCreatedRateMeter.Mark(int64(gasUsed))
bridgeL2BatchesCreatedRateMeter.Mark(int64(len(blocks)))
bridgeL2BatchesTxsCreatedPerBatchGauge.Update(int64(txNum))
bridgeL2BatchesGasCreatedPerBatchGauge.Update(int64(gasUsed))
bridgeL2BatchesBlocksCreatedTotalCounter.Inc(int64(len(blocks)))
}
return true
}
func (p *BatchProposer) createBatchForBlocks(blocks []*types.BlockInfo) error {
@@ -359,16 +347,16 @@ func (p *BatchProposer) createBatchForBlocks(blocks []*types.BlockInfo) error {
}
func (p *BatchProposer) generateBatchData(parentBatch *types.BlockBatch, blocks []*types.BlockInfo) (*types.BatchData, error) {
var traces []*geth_types.BlockTrace
var wrappedBlocks []*types.WrappedBlock
for _, block := range blocks {
trs, err := p.orm.GetL2BlockTraces(map[string]interface{}{"hash": block.Hash})
trs, err := p.orm.GetL2WrappedBlocks(map[string]interface{}{"hash": block.Hash})
if err != nil || len(trs) != 1 {
log.Error("Failed to GetBlockTraces", "hash", block.Hash, "err", err)
return nil, err
}
traces = append(traces, trs[0])
wrappedBlocks = append(wrappedBlocks, trs[0])
}
return types.NewBatchData(parentBatch, traces, p.piCfg), nil
return types.NewBatchData(parentBatch, wrappedBlocks, p.piCfg), nil
}
func (p *BatchProposer) getBatchDataBufferSize() (size uint64) {

View File

@@ -1,4 +1,4 @@
package l2
package watcher_test
import (
"context"
@@ -6,13 +6,15 @@ import (
"math"
"testing"
geth_types "github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/common"
"github.com/stretchr/testify/assert"
"scroll-tech/database"
"scroll-tech/database/migrate"
"scroll-tech/bridge/config"
"scroll-tech/bridge/relayer"
"scroll-tech/bridge/watcher"
"scroll-tech/common/types"
)
@@ -22,34 +24,49 @@ func testBatchProposerProposeBatch(t *testing.T) {
db, err := database.NewOrmFactory(cfg.DBConfig)
assert.NoError(t, err)
assert.NoError(t, migrate.ResetDB(db.GetDB().DB))
defer db.Close()
ctx := context.Background()
subCtx, cancel := context.WithCancel(ctx)
defer func() {
cancel()
db.Close()
}()
// Insert traces into db.
assert.NoError(t, db.InsertL2BlockTraces([]*geth_types.BlockTrace{blockTrace1}))
assert.NoError(t, db.InsertWrappedBlocks([]*types.WrappedBlock{wrappedBlock1}))
l2cfg := cfg.L2Config
wc := NewL2WatcherClient(context.Background(), l2Cli, l2cfg.Confirmations, l2cfg.L2MessengerAddress, l2cfg.L2MessageQueueAddress, db)
wc.Start()
defer wc.Stop()
wc := watcher.NewL2WatcherClient(context.Background(), l2Cli, l2cfg.Confirmations, l2cfg.L2MessengerAddress, l2cfg.L2MessageQueueAddress, l2cfg.WithdrawTrieRootSlot, db)
loopToFetchEvent(subCtx, wc)
relayer, err := NewLayer2Relayer(context.Background(), l2Cli, db, cfg.L2Config.RelayerConfig)
batch, err := db.GetLatestBatch()
assert.NoError(t, err)
proposer := NewBatchProposer(context.Background(), &config.BatchProposerConfig{
// Create a new batch.
batchData := types.NewBatchData(&types.BlockBatch{
Index: 0,
Hash: batch.Hash,
StateRoot: batch.StateRoot,
}, []*types.WrappedBlock{wrappedBlock1}, nil)
relayer, err := relayer.NewLayer2Relayer(context.Background(), l2Cli, db, cfg.L2Config.RelayerConfig)
assert.NoError(t, err)
proposer := watcher.NewBatchProposer(context.Background(), &config.BatchProposerConfig{
ProofGenerationFreq: 1,
BatchGasThreshold: 3000000,
BatchTxNumThreshold: 135,
BatchTimeSec: 1,
BatchBlocksLimit: 100,
}, relayer, db)
proposer.tryProposeBatch()
proposer.TryProposeBatch()
infos, err := db.GetUnbatchedL2Blocks(map[string]interface{}{},
fmt.Sprintf("order by number ASC LIMIT %d", 100))
assert.NoError(t, err)
assert.Equal(t, 0, len(infos))
exist, err := db.BatchRecordExist(batchData1.Hash().Hex())
exist, err := db.BatchRecordExist(batchData.Hash().Hex())
assert.NoError(t, err)
assert.Equal(t, true, exist)
}
@@ -61,13 +78,26 @@ func testBatchProposerGracefulRestart(t *testing.T) {
assert.NoError(t, migrate.ResetDB(db.GetDB().DB))
defer db.Close()
relayer, err := NewLayer2Relayer(context.Background(), l2Cli, db, cfg.L2Config.RelayerConfig)
relayer, err := relayer.NewLayer2Relayer(context.Background(), l2Cli, db, cfg.L2Config.RelayerConfig)
assert.NoError(t, err)
// Insert traces into db.
assert.NoError(t, db.InsertL2BlockTraces([]*geth_types.BlockTrace{blockTrace2}))
assert.NoError(t, db.InsertWrappedBlocks([]*types.WrappedBlock{wrappedBlock2}))
// Insert block batch into db.
batchData1 := types.NewBatchData(&types.BlockBatch{
Index: 0,
Hash: common.Hash{}.String(),
StateRoot: common.Hash{}.String(),
}, []*types.WrappedBlock{wrappedBlock1}, nil)
parentBatch2 := &types.BlockBatch{
Index: batchData1.Batch.BatchIndex,
Hash: batchData1.Hash().Hex(),
StateRoot: batchData1.Batch.NewStateRoot.String(),
}
batchData2 := types.NewBatchData(parentBatch2, []*types.WrappedBlock{wrappedBlock2}, nil)
dbTx, err := db.Beginx()
assert.NoError(t, err)
assert.NoError(t, db.NewBatchInDBTx(dbTx, batchData1))
@@ -85,7 +115,7 @@ func testBatchProposerGracefulRestart(t *testing.T) {
assert.Equal(t, 1, len(batchHashes))
assert.Equal(t, batchData2.Hash().Hex(), batchHashes[0])
// test p.recoverBatchDataBuffer().
_ = NewBatchProposer(context.Background(), &config.BatchProposerConfig{
_ = watcher.NewBatchProposer(context.Background(), &config.BatchProposerConfig{
ProofGenerationFreq: 1,
BatchGasThreshold: 3000000,
BatchTxNumThreshold: 135,

11
bridge/watcher/common.go Normal file
View File

@@ -0,0 +1,11 @@
package watcher
import "github.com/scroll-tech/go-ethereum/common"
const contractEventsBlocksFetchLimit = int64(10)
type relayedMessage struct {
msgHash common.Hash
txHash common.Hash
isSuccessful bool
}

View File

@@ -1,9 +1,8 @@
package l1
package watcher
import (
"context"
"math/big"
"time"
geth "github.com/scroll-tech/go-ethereum"
"github.com/scroll-tech/go-ethereum/accounts/abi"
@@ -19,8 +18,6 @@ import (
"scroll-tech/common/types"
"scroll-tech/database"
cutil "scroll-tech/common/utils"
bridge_abi "scroll-tech/bridge/abi"
"scroll-tech/bridge/utils"
)
@@ -33,20 +30,14 @@ var (
bridgeL1MsgsRollupEventsTotalCounter = geth_metrics.NewRegisteredCounter("bridge/l1/msgs/rollup/events/total", metrics.ScrollRegistry)
)
type relayedMessage struct {
msgHash common.Hash
txHash common.Hash
isSuccessful bool
}
type rollupEvent struct {
batchHash common.Hash
txHash common.Hash
status types.RollupStatus
}
// Watcher will listen for smart contract events from Eth L1.
type Watcher struct {
// L1WatcherClient will listen for smart contract events from Eth L1.
type L1WatcherClient struct {
ctx context.Context
client *ethclient.Client
db database.OrmFactory
@@ -67,13 +58,10 @@ type Watcher struct {
processedMsgHeight uint64
// The height of the block that the watcher has retrieved header rlp
processedBlockHeight uint64
stopCh chan bool
}
// NewWatcher returns a new instance of Watcher. The instance will be not fully prepared,
// and still needs to be finalized and ran by calling `watcher.Start`.
func NewWatcher(ctx context.Context, client *ethclient.Client, startHeight uint64, confirmations rpc.BlockNumber, messengerAddress, messageQueueAddress, scrollChainAddress common.Address, db database.OrmFactory) *Watcher {
// NewL1WatcherClient returns a new instance of L1WatcherClient.
func NewL1WatcherClient(ctx context.Context, client *ethclient.Client, startHeight uint64, confirmations rpc.BlockNumber, messengerAddress, messageQueueAddress, scrollChainAddress common.Address, db database.OrmFactory) *L1WatcherClient {
savedHeight, err := db.GetLayer1LatestWatchedHeight()
if err != nil {
log.Warn("Failed to fetch height from db", "err", err)
@@ -92,9 +80,7 @@ func NewWatcher(ctx context.Context, client *ethclient.Client, startHeight uint6
savedL1BlockHeight = startHeight
}
stopCh := make(chan bool)
return &Watcher{
return &L1WatcherClient{
ctx: ctx,
client: client,
db: db,
@@ -111,51 +97,11 @@ func NewWatcher(ctx context.Context, client *ethclient.Client, startHeight uint6
processedMsgHeight: uint64(savedHeight),
processedBlockHeight: savedL1BlockHeight,
stopCh: stopCh,
}
}
// Start the Watcher module.
func (w *Watcher) Start() {
go func() {
ctx, cancel := context.WithCancel(w.ctx)
go cutil.LoopWithContext(ctx, 2*time.Second, func(subCtx context.Context) {
number, err := utils.GetLatestConfirmedBlockNumber(subCtx, w.client, w.confirmations)
if err != nil {
log.Error("failed to get block number", "err", err)
} else {
if err := w.FetchBlockHeader(number); err != nil {
log.Error("Failed to fetch L1 block header", "lastest", number, "err", err)
}
}
})
go cutil.LoopWithContext(ctx, 2*time.Second, func(subCtx context.Context) {
number, err := utils.GetLatestConfirmedBlockNumber(subCtx, w.client, w.confirmations)
if err != nil {
log.Error("failed to get block number", "err", err)
} else {
if err := w.FetchContractEvent(number); err != nil {
log.Error("Failed to fetch bridge contract", "err", err)
}
}
})
<-w.stopCh
cancel()
}()
}
// Stop the Watcher module, for a graceful shutdown.
func (w *Watcher) Stop() {
w.stopCh <- true
}
const contractEventsBlocksFetchLimit = int64(10)
// FetchBlockHeader pull latest L1 blocks and save in DB
func (w *Watcher) FetchBlockHeader(blockHeight uint64) error {
func (w *L1WatcherClient) FetchBlockHeader(blockHeight uint64) error {
fromBlock := int64(w.processedBlockHeight) + 1
toBlock := int64(blockHeight)
if toBlock < fromBlock {
@@ -201,10 +147,15 @@ func (w *Watcher) FetchBlockHeader(blockHeight uint64) error {
}
// FetchContractEvent pull latest event logs from given contract address and save in DB
func (w *Watcher) FetchContractEvent(blockHeight uint64) error {
func (w *L1WatcherClient) FetchContractEvent() error {
defer func() {
log.Info("l1 watcher fetchContractEvent", "w.processedMsgHeight", w.processedMsgHeight)
}()
blockHeight, err := utils.GetLatestConfirmedBlockNumber(w.ctx, w.client, w.confirmations)
if err != nil {
log.Error("failed to get block number", "err", err)
return err
}
fromBlock := int64(w.processedMsgHeight) + 1
toBlock := int64(blockHeight)
@@ -317,7 +268,7 @@ func (w *Watcher) FetchContractEvent(blockHeight uint64) error {
return nil
}
func (w *Watcher) parseBridgeEventLogs(logs []geth_types.Log) ([]*types.L1Message, []relayedMessage, []rollupEvent, error) {
func (w *L1WatcherClient) parseBridgeEventLogs(logs []geth_types.Log) ([]*types.L1Message, []relayedMessage, []rollupEvent, error) {
// Need use contract abi to parse event Log
// Can only be tested after we have our contracts set up

View File

@@ -1,4 +1,4 @@
package l1
package watcher_test
import (
"context"
@@ -9,6 +9,8 @@ import (
"scroll-tech/database"
"scroll-tech/database/migrate"
"scroll-tech/bridge/watcher"
)
func testStartWatcher(t *testing.T) {
@@ -23,7 +25,6 @@ func testStartWatcher(t *testing.T) {
l1Cfg := cfg.L1Config
watcher := NewWatcher(context.Background(), client, l1Cfg.StartHeight, l1Cfg.Confirmations, l1Cfg.L1MessengerAddress, l1Cfg.L1MessageQueueAddress, l1Cfg.RelayerConfig.RollupContractAddress, db)
watcher.Start()
defer watcher.Stop()
watcher := watcher.NewL1WatcherClient(context.Background(), client, l1Cfg.StartHeight, l1Cfg.Confirmations, l1Cfg.L1MessengerAddress, l1Cfg.L1MessageQueueAddress, l1Cfg.RelayerConfig.RollupContractAddress, db)
assert.NoError(t, watcher.FetchContractEvent())
}

View File

@@ -1,16 +1,15 @@
package l2
package watcher
import (
"context"
"errors"
"fmt"
"math/big"
"reflect"
"time"
geth "github.com/scroll-tech/go-ethereum"
"github.com/scroll-tech/go-ethereum/accounts/abi"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/common/hexutil"
geth_types "github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/ethclient"
"github.com/scroll-tech/go-ethereum/event"
@@ -20,7 +19,6 @@ import (
"scroll-tech/common/metrics"
"scroll-tech/common/types"
cutil "scroll-tech/common/utils"
"scroll-tech/database"
bridge_abi "scroll-tech/bridge/abi"
@@ -30,22 +28,16 @@ import (
// Metrics
var (
bridgeL2MsgsSyncHeightGauge = geth_metrics.NewRegisteredGauge("bridge/l2/msgs/sync/height", metrics.ScrollRegistry)
bridgeL2TracesFetchedHeightGauge = geth_metrics.NewRegisteredGauge("bridge/l2/traces/fetched/height", metrics.ScrollRegistry)
bridgeL2TracesFetchedGapGauge = geth_metrics.NewRegisteredGauge("bridge/l2/traces/fetched/gap", metrics.ScrollRegistry)
bridgeL2BlocksFetchedHeightGauge = geth_metrics.NewRegisteredGauge("bridge/l2/blocks/fetched/height", metrics.ScrollRegistry)
bridgeL2BlocksFetchedGapGauge = geth_metrics.NewRegisteredGauge("bridge/l2/blocks/fetched/gap", metrics.ScrollRegistry)
bridgeL2MsgsSentEventsTotalCounter = geth_metrics.NewRegisteredCounter("bridge/l2/msgs/sent/events/total", metrics.ScrollRegistry)
bridgeL2MsgsAppendEventsTotalCounter = geth_metrics.NewRegisteredCounter("bridge/l2/msgs/append/events/total", metrics.ScrollRegistry)
bridgeL2MsgsRelayedEventsTotalCounter = geth_metrics.NewRegisteredCounter("bridge/l2/msgs/relayed/events/total", metrics.ScrollRegistry)
)
type relayedMessage struct {
msgHash common.Hash
txHash common.Hash
isSuccessful bool
}
// WatcherClient provide APIs which support others to subscribe to various event from l2geth
type WatcherClient struct {
// L2WatcherClient provide APIs which support others to subscribe to various event from l2geth
type L2WatcherClient struct {
ctx context.Context
event.Feed
@@ -58,25 +50,25 @@ type WatcherClient struct {
messengerAddress common.Address
messengerABI *abi.ABI
messageQueueAddress common.Address
messageQueueABI *abi.ABI
messageQueueAddress common.Address
messageQueueABI *abi.ABI
withdrawTrieRootSlot common.Hash
// The height of the block that the watcher has retrieved event logs
processedMsgHeight uint64
stopped uint64
stopCh chan struct{}
}
// NewL2WatcherClient take a l2geth instance to generate a l2watcherclient instance
func NewL2WatcherClient(ctx context.Context, client *ethclient.Client, confirmations rpc.BlockNumber, messengerAddress, messageQueueAddress common.Address, orm database.OrmFactory) *WatcherClient {
func NewL2WatcherClient(ctx context.Context, client *ethclient.Client, confirmations rpc.BlockNumber, messengerAddress, messageQueueAddress common.Address, withdrawTrieRootSlot common.Hash, orm database.OrmFactory) *L2WatcherClient {
savedHeight, err := orm.GetLayer2LatestWatchedHeight()
if err != nil {
log.Warn("fetch height from db failed", "err", err)
savedHeight = 0
}
w := WatcherClient{
w := L2WatcherClient{
ctx: ctx,
Client: client,
orm: orm,
@@ -86,10 +78,10 @@ func NewL2WatcherClient(ctx context.Context, client *ethclient.Client, confirmat
messengerAddress: messengerAddress,
messengerABI: bridge_abi.L2ScrollMessengerABI,
messageQueueAddress: messageQueueAddress,
messageQueueABI: bridge_abi.L2MessageQueueABI,
messageQueueAddress: messageQueueAddress,
messageQueueABI: bridge_abi.L2MessageQueueABI,
withdrawTrieRootSlot: withdrawTrieRootSlot,
stopCh: make(chan struct{}),
stopped: 0,
}
@@ -101,7 +93,7 @@ func NewL2WatcherClient(ctx context.Context, client *ethclient.Client, confirmat
return &w
}
func (w *WatcherClient) initializeGenesis() error {
func (w *L2WatcherClient) initializeGenesis() error {
if count, err := w.orm.GetBatchCount(); err != nil {
return fmt.Errorf("failed to get batch count: %v", err)
} else if count > 0 {
@@ -116,15 +108,7 @@ func (w *WatcherClient) initializeGenesis() error {
log.Info("retrieved L2 genesis header", "hash", genesis.Hash().String())
blockTrace := &geth_types.BlockTrace{
Coinbase: nil,
Header: genesis,
Transactions: []*geth_types.TransactionData{},
StorageTrace: nil,
ExecutionResults: []*geth_types.ExecutionResult{},
MPTWitness: nil,
}
blockTrace := &types.WrappedBlock{Header: genesis, Transactions: nil, WithdrawTrieRoot: common.Hash{}}
batchData := types.NewGenesisBatchData(blockTrace)
if err = AddBatchInfoToDB(w.orm, batchData); err != nil {
@@ -147,52 +131,16 @@ func (w *WatcherClient) initializeGenesis() error {
return nil
}
// Start the Listening process
func (w *WatcherClient) Start() {
go func() {
if reflect.ValueOf(w.orm).IsNil() {
panic("must run L2 watcher with DB")
}
ctx, cancel := context.WithCancel(w.ctx)
go cutil.LoopWithContext(ctx, 2*time.Second, func(subCtx context.Context) {
number, err := utils.GetLatestConfirmedBlockNumber(subCtx, w.Client, w.confirmations)
if err != nil {
log.Error("failed to get block number", "err", err)
} else {
w.tryFetchRunningMissingBlocks(ctx, number)
}
})
go cutil.LoopWithContext(ctx, 2*time.Second, func(subCtx context.Context) {
number, err := utils.GetLatestConfirmedBlockNumber(subCtx, w.Client, w.confirmations)
if err != nil {
log.Error("failed to get block number", "err", err)
} else {
w.FetchContractEvent(number)
}
})
<-w.stopCh
cancel()
}()
}
// Stop the Watcher module, for a graceful shutdown.
func (w *WatcherClient) Stop() {
w.stopCh <- struct{}{}
}
const blockTracesFetchLimit = uint64(10)
// try fetch missing blocks if inconsistent
func (w *WatcherClient) tryFetchRunningMissingBlocks(ctx context.Context, blockHeight uint64) {
// TryFetchRunningMissingBlocks try fetch missing blocks if inconsistent
func (w *L2WatcherClient) TryFetchRunningMissingBlocks(ctx context.Context, blockHeight uint64) {
// Get newest block in DB. must have blocks at that time.
// Don't use "block_trace" table "trace" column's BlockTrace.Number,
// because it might be empty if the corresponding rollup_result is finalized/finalization_skipped
heightInDB, err := w.orm.GetL2BlockTracesLatestHeight()
heightInDB, err := w.orm.GetL2BlocksLatestHeight()
if err != nil {
log.Error("failed to GetL2BlockTracesLatestHeight", "err", err)
log.Error("failed to GetL2BlocksLatestHeight", "err", err)
return
}
@@ -214,24 +162,60 @@ func (w *WatcherClient) tryFetchRunningMissingBlocks(ctx context.Context, blockH
log.Error("fail to getAndStoreBlockTraces", "from", from, "to", to, "err", err)
return
}
bridgeL2TracesFetchedHeightGauge.Update(int64(to))
bridgeL2TracesFetchedGapGauge.Update(int64(blockHeight - to))
bridgeL2BlocksFetchedHeightGauge.Update(int64(to))
bridgeL2BlocksFetchedGapGauge.Update(int64(blockHeight - to))
}
}
func (w *WatcherClient) getAndStoreBlockTraces(ctx context.Context, from, to uint64) error {
var traces []*geth_types.BlockTrace
for number := from; number <= to; number++ {
log.Debug("retrieving block trace", "height", number)
trace, err2 := w.GetBlockTraceByNumber(ctx, big.NewInt(int64(number)))
if err2 != nil {
return fmt.Errorf("failed to GetBlockResultByHash: %v. number: %v", err2, number)
func txsToTxsData(txs geth_types.Transactions) []*geth_types.TransactionData {
txsData := make([]*geth_types.TransactionData, len(txs))
for i, tx := range txs {
v, r, s := tx.RawSignatureValues()
txsData[i] = &geth_types.TransactionData{
Type: tx.Type(),
TxHash: tx.Hash().String(),
Nonce: tx.Nonce(),
ChainId: (*hexutil.Big)(tx.ChainId()),
Gas: tx.Gas(),
GasPrice: (*hexutil.Big)(tx.GasPrice()),
To: tx.To(),
Value: (*hexutil.Big)(tx.Value()),
Data: hexutil.Encode(tx.Data()),
IsCreate: tx.To() == nil,
V: (*hexutil.Big)(v),
R: (*hexutil.Big)(r),
S: (*hexutil.Big)(s),
}
log.Info("retrieved block trace", "height", trace.Header.Number, "hash", trace.Header.Hash().String())
traces = append(traces, trace)
}
if len(traces) > 0 {
if err := w.orm.InsertL2BlockTraces(traces); err != nil {
return txsData
}
func (w *L2WatcherClient) getAndStoreBlockTraces(ctx context.Context, from, to uint64) error {
var blocks []*types.WrappedBlock
for number := from; number <= to; number++ {
log.Debug("retrieving block", "height", number)
block, err2 := w.BlockByNumber(ctx, big.NewInt(int64(number)))
if err2 != nil {
return fmt.Errorf("failed to GetBlockByNumber: %v. number: %v", err2, number)
}
log.Info("retrieved block", "height", block.Header().Number, "hash", block.Header().Hash().String())
withdrawTrieRoot, err3 := w.StorageAt(ctx, w.messageQueueAddress, w.withdrawTrieRootSlot, big.NewInt(int64(number)))
if err3 != nil {
return fmt.Errorf("failed to get withdrawTrieRoot: %v. number: %v", err3, number)
}
blocks = append(blocks, &types.WrappedBlock{
Header: block.Header(),
Transactions: txsToTxsData(block.Transactions()),
WithdrawTrieRoot: common.BytesToHash(withdrawTrieRoot),
})
}
if len(blocks) > 0 {
if err := w.orm.InsertWrappedBlocks(blocks); err != nil {
return fmt.Errorf("failed to batch insert BlockTraces: %v", err)
}
}
@@ -239,14 +223,18 @@ func (w *WatcherClient) getAndStoreBlockTraces(ctx context.Context, from, to uin
return nil
}
const contractEventsBlocksFetchLimit = int64(10)
// FetchContractEvent pull latest event logs from given contract address and save in DB
func (w *WatcherClient) FetchContractEvent(blockHeight uint64) {
func (w *L2WatcherClient) FetchContractEvent() {
defer func() {
log.Info("l2 watcher fetchContractEvent", "w.processedMsgHeight", w.processedMsgHeight)
}()
blockHeight, err := utils.GetLatestConfirmedBlockNumber(w.ctx, w.Client, w.confirmations)
if err != nil {
log.Error("failed to get block number", "err", err)
return
}
fromBlock := int64(w.processedMsgHeight) + 1
toBlock := int64(blockHeight)
@@ -322,7 +310,7 @@ func (w *WatcherClient) FetchContractEvent(blockHeight uint64) {
}
}
func (w *WatcherClient) parseBridgeEventLogs(logs []geth_types.Log) ([]*types.L2Message, []relayedMessage, error) {
func (w *L2WatcherClient) parseBridgeEventLogs(logs []geth_types.Log) ([]*types.L2Message, []relayedMessage, error) {
// Need use contract abi to parse event Log
// Can only be tested after we have our contracts set up

View File

@@ -1,4 +1,4 @@
package l2
package watcher_test
import (
"context"
@@ -19,7 +19,9 @@ import (
"scroll-tech/bridge/mock_bridge"
"scroll-tech/bridge/sender"
"scroll-tech/bridge/watcher"
cutils "scroll-tech/common/utils"
"scroll-tech/database"
"scroll-tech/database/migrate"
)
@@ -29,12 +31,16 @@ func testCreateNewWatcherAndStop(t *testing.T) {
l2db, err := database.NewOrmFactory(cfg.DBConfig)
assert.NoError(t, err)
assert.NoError(t, migrate.ResetDB(l2db.GetDB().DB))
defer l2db.Close()
ctx := context.Background()
subCtx, cancel := context.WithCancel(ctx)
defer func() {
cancel()
l2db.Close()
}()
l2cfg := cfg.L2Config
rc := NewL2WatcherClient(context.Background(), l2Cli, l2cfg.Confirmations, l2cfg.L2MessengerAddress, l2cfg.L2MessageQueueAddress, l2db)
rc.Start()
defer rc.Stop()
rc := watcher.NewL2WatcherClient(context.Background(), l2Cli, l2cfg.Confirmations, l2cfg.L2MessengerAddress, l2cfg.L2MessageQueueAddress, l2cfg.WithdrawTrieRootSlot, l2db)
loopToFetchEvent(subCtx, rc)
l1cfg := cfg.L1Config
l1cfg.RelayerConfig.SenderConfig.Confirmations = rpc.LatestBlockNumber
@@ -45,7 +51,7 @@ func testCreateNewWatcherAndStop(t *testing.T) {
numTransactions := 3
toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
for i := 0; i < numTransactions; i++ {
_, err = newSender.SendTransaction(strconv.Itoa(1000+i), &toAddress, big.NewInt(1000000000), nil)
_, err = newSender.SendTransaction(strconv.Itoa(1000+i), &toAddress, big.NewInt(1000000000), nil, 0)
assert.NoError(t, err)
<-newSender.ConfirmChan()
}
@@ -60,12 +66,17 @@ func testMonitorBridgeContract(t *testing.T) {
db, err := database.NewOrmFactory(cfg.DBConfig)
assert.NoError(t, err)
assert.NoError(t, migrate.ResetDB(db.GetDB().DB))
defer db.Close()
ctx := context.Background()
subCtx, cancel := context.WithCancel(ctx)
defer func() {
cancel()
db.Close()
}()
l2cfg := cfg.L2Config
wc := NewL2WatcherClient(context.Background(), l2Cli, l2cfg.Confirmations, l2cfg.L2MessengerAddress, l2cfg.L2MessageQueueAddress, db)
wc.Start()
defer wc.Stop()
wc := watcher.NewL2WatcherClient(context.Background(), l2Cli, l2cfg.Confirmations, l2cfg.L2MessengerAddress, l2cfg.L2MessageQueueAddress, l2cfg.WithdrawTrieRootSlot, db)
loopToFetchEvent(subCtx, wc)
previousHeight, err := l2Cli.BlockNumber(context.Background())
assert.NoError(t, err)
@@ -79,9 +90,7 @@ func testMonitorBridgeContract(t *testing.T) {
assert.NoError(t, err)
rc := prepareWatcherClient(l2Cli, db, address)
rc.Start()
defer rc.Stop()
loopToFetchEvent(subCtx, rc)
// Call mock_bridge instance sendMessage to trigger emit events
toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
message := []byte("testbridgecontract")
@@ -128,7 +137,13 @@ func testFetchMultipleSentMessageInOneBlock(t *testing.T) {
db, err := database.NewOrmFactory(cfg.DBConfig)
assert.NoError(t, err)
assert.NoError(t, migrate.ResetDB(db.GetDB().DB))
defer db.Close()
ctx := context.Background()
subCtx, cancel := context.WithCancel(ctx)
defer func() {
cancel()
db.Close()
}()
previousHeight, err := l2Cli.BlockNumber(context.Background()) // shallow the global previousHeight
assert.NoError(t, err)
@@ -141,8 +156,7 @@ func testFetchMultipleSentMessageInOneBlock(t *testing.T) {
assert.NoError(t, err)
rc := prepareWatcherClient(l2Cli, db, address)
rc.Start()
defer rc.Stop()
loopToFetchEvent(subCtx, rc)
// Call mock_bridge instance sendMessage to trigger emit events multiple times
numTransactions := 4
@@ -195,9 +209,9 @@ func testFetchMultipleSentMessageInOneBlock(t *testing.T) {
assert.Equal(t, 5, len(msgs))
}
func prepareWatcherClient(l2Cli *ethclient.Client, db database.OrmFactory, contractAddr common.Address) *WatcherClient {
func prepareWatcherClient(l2Cli *ethclient.Client, db database.OrmFactory, contractAddr common.Address) *watcher.L2WatcherClient {
confirmations := rpc.LatestBlockNumber
return NewL2WatcherClient(context.Background(), l2Cli, confirmations, contractAddr, contractAddr, db)
return watcher.NewL2WatcherClient(context.Background(), l2Cli, confirmations, contractAddr, contractAddr, common.Hash{}, db)
}
func prepareAuth(t *testing.T, l2Cli *ethclient.Client, privateKey *ecdsa.PrivateKey) *bind.TransactOpts {
@@ -209,3 +223,7 @@ func prepareAuth(t *testing.T, l2Cli *ethclient.Client, privateKey *ecdsa.Privat
assert.NoError(t, err)
return auth
}
func loopToFetchEvent(subCtx context.Context, watcher *watcher.L2WatcherClient) {
go cutils.Loop(subCtx, 2*time.Second, watcher.FetchContractEvent)
}

View File

@@ -1,12 +1,10 @@
package l2
package watcher_test
import (
"encoding/json"
"fmt"
"os"
"testing"
geth_types "github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/ethclient"
"github.com/stretchr/testify/assert"
@@ -26,12 +24,8 @@ var (
l2Cli *ethclient.Client
// block trace
blockTrace1 *geth_types.BlockTrace
blockTrace2 *geth_types.BlockTrace
// batch data
batchData1 *types.BatchData
batchData2 *types.BatchData
wrappedBlock1 *types.WrappedBlock
wrappedBlock2 *types.WrappedBlock
)
func setupEnv(t *testing.T) (err error) {
@@ -54,35 +48,20 @@ func setupEnv(t *testing.T) (err error) {
return err
}
// unmarshal blockTrace
blockTrace1 = &geth_types.BlockTrace{}
if err = json.Unmarshal(templateBlockTrace1, blockTrace1); err != nil {
wrappedBlock1 = &types.WrappedBlock{}
if err = json.Unmarshal(templateBlockTrace1, wrappedBlock1); err != nil {
return err
}
parentBatch1 := &types.BlockBatch{
Index: 1,
Hash: "0x0000000000000000000000000000000000000000",
}
batchData1 = types.NewBatchData(parentBatch1, []*geth_types.BlockTrace{blockTrace1}, nil)
templateBlockTrace2, err := os.ReadFile("../../common/testdata/blockTrace_03.json")
if err != nil {
return err
}
// unmarshal blockTrace
blockTrace2 = &geth_types.BlockTrace{}
if err = json.Unmarshal(templateBlockTrace2, blockTrace2); err != nil {
wrappedBlock2 = &types.WrappedBlock{}
if err = json.Unmarshal(templateBlockTrace2, wrappedBlock2); err != nil {
return err
}
parentBatch2 := &types.BlockBatch{
Index: batchData1.Batch.BatchIndex,
Hash: batchData1.Hash().Hex(),
}
batchData2 = types.NewBatchData(parentBatch2, []*geth_types.BlockTrace{blockTrace2}, nil)
fmt.Printf("batchhash1 = %x\n", batchData1.Hash())
fmt.Printf("batchhash2 = %x\n", batchData2.Hash())
return err
}
@@ -98,18 +77,13 @@ func TestFunction(t *testing.T) {
if err := setupEnv(t); err != nil {
t.Fatal(err)
}
// Run l1 watcher test cases.
t.Run("TestStartWatcher", testStartWatcher)
// Run l2 watcher test cases.
t.Run("TestCreateNewWatcherAndStop", testCreateNewWatcherAndStop)
t.Run("TestMonitorBridgeContract", testMonitorBridgeContract)
t.Run("TestFetchMultipleSentMessageInOneBlock", testFetchMultipleSentMessageInOneBlock)
// Run l2 relayer test cases.
t.Run("TestCreateNewRelayer", testCreateNewRelayer)
t.Run("TestL2RelayerProcessSaveEvents", testL2RelayerProcessSaveEvents)
t.Run("TestL2RelayerProcessCommittedBatches", testL2RelayerProcessCommittedBatches)
t.Run("TestL2RelayerSkipBatches", testL2RelayerSkipBatches)
// Run batch proposer test cases.
t.Run("TestBatchProposerProposeBatch", testBatchProposerProposeBatch)
t.Run("TestBatchProposerGracefulRestart", testBatchProposerGracefulRestart)

View File

@@ -0,0 +1,26 @@
# Download Go dependencies
FROM scrolltech/go-alpine-builder:1.18 as base
WORKDIR /src
COPY go.work* ./
COPY ./bridge/go.* ./bridge/
COPY ./common/go.* ./common/
COPY ./coordinator/go.* ./coordinator/
COPY ./database/go.* ./database/
COPY ./roller/go.* ./roller/
COPY ./tests/integration-test/go.* ./tests/integration-test/
RUN go mod download -x
# Build event_watcher
FROM base as builder
RUN --mount=target=. \
--mount=type=cache,target=/root/.cache/go-build \
cd /src/bridge/cmd/event_watcher/ && go build -v -p 4 -o /bin/event_watcher
# Pull event_watcher into a second stage deploy alpine container
FROM alpine:latest
COPY --from=builder /bin/event_watcher /bin/
ENTRYPOINT ["event_watcher"]

View File

@@ -2,4 +2,4 @@ assets/
docs/
l2geth/
rpc-gateway/
*target/*
*target/*

View File

@@ -11,16 +11,16 @@ COPY ./roller/go.* ./roller/
COPY ./tests/integration-test/go.* ./tests/integration-test/
RUN go mod download -x
# Build bridge
# Build gas_oracle
FROM base as builder
RUN --mount=target=. \
--mount=type=cache,target=/root/.cache/go-build \
cd /src/bridge/cmd && go build -v -p 4 -o /bin/bridge
cd /src/bridge/cmd/gas_oracle/ && go build -v -p 4 -o /bin/gas_oracle
# Pull bridge into a second stage deploy alpine container
# Pull gas_oracle into a second stage deploy alpine container
FROM alpine:latest
COPY --from=builder /bin/bridge /bin/
COPY --from=builder /bin/gas_oracle /bin/
ENTRYPOINT ["bridge"]
ENTRYPOINT ["gas_oracle"]

View File

@@ -0,0 +1,5 @@
assets/
docs/
l2geth/
rpc-gateway/
*target/*

View File

@@ -0,0 +1,26 @@
# Download Go dependencies
FROM scrolltech/go-alpine-builder:1.18 as base
WORKDIR /src
COPY go.work* ./
COPY ./bridge/go.* ./bridge/
COPY ./common/go.* ./common/
COPY ./coordinator/go.* ./coordinator/
COPY ./database/go.* ./database/
COPY ./roller/go.* ./roller/
COPY ./tests/integration-test/go.* ./tests/integration-test/
RUN go mod download -x
# Build msg_relayer
FROM base as builder
RUN --mount=target=. \
--mount=type=cache,target=/root/.cache/go-build \
cd /src/bridge/cmd/msg_relayer/ && go build -v -p 4 -o /bin/msg_relayer
# Pull msg_relayer into a second stage deploy alpine container
FROM alpine:latest
COPY --from=builder /bin/msg_relayer /bin/
ENTRYPOINT ["msg_relayer"]

View File

@@ -0,0 +1,5 @@
assets/
docs/
l2geth/
rpc-gateway/
*target/*

View File

@@ -0,0 +1,26 @@
# Download Go dependencies
FROM scrolltech/go-alpine-builder:1.18 as base
WORKDIR /src
COPY go.work* ./
COPY ./bridge/go.* ./bridge/
COPY ./common/go.* ./common/
COPY ./coordinator/go.* ./coordinator/
COPY ./database/go.* ./database/
COPY ./roller/go.* ./roller/
COPY ./tests/integration-test/go.* ./tests/integration-test/
RUN go mod download -x
# Build rollup_relayer
FROM base as builder
RUN --mount=target=. \
--mount=type=cache,target=/root/.cache/go-build \
cd /src/bridge/cmd/rollup_relayer/ && go build -v -p 4 -o /bin/rollup_relayer
# Pull rollup_relayer into a second stage deploy alpine container
FROM alpine:latest
COPY --from=builder /bin/rollup_relayer /bin/
ENTRYPOINT ["rollup_relayer"]

View File

@@ -0,0 +1,5 @@
assets/
docs/
l2geth/
rpc-gateway/
*target/*

View File

@@ -4,11 +4,11 @@ ${GOROOT}/bin/bin/gocover-cobertura < coverage.bridge.txt > coverage.bridge.xml
${GOROOT}/bin/bin/gocover-cobertura < coverage.db.txt > coverage.db.xml
${GOROOT}/bin/bin/gocover-cobertura < coverage.common.txt > coverage.common.xml
${GOROOT}/bin/bin/gocover-cobertura < coverage.coordinator.txt > coverage.coordinator.xml
${GOROOT}/bin/bin/gocover-cobertura < coverage.integration.txt > coverage.integration.xml
#${GOROOT}/bin/bin/gocover-cobertura < coverage.integration.txt > coverage.integration.xml
npx cobertura-merge -o cobertura.xml \
package1=coverage.bridge.xml \
package2=coverage.db.xml \
package3=coverage.common.xml \
package4=coverage.coordinator.xml \
package5=coverage.integration.xml
package4=coverage.coordinator.xml
# package5=coverage.integration.xml

View File

@@ -31,8 +31,10 @@ type Cmd struct {
checkFuncs cmap.ConcurrentMap //map[string]checkFunc
//stdout bytes.Buffer
Err error
// open log flag.
openLog bool
// error channel
ErrChan chan error
}
// NewCmd create Cmd instance.
@@ -41,6 +43,7 @@ func NewCmd(name string, args ...string) *Cmd {
checkFuncs: cmap.New(),
name: name,
args: args,
ErrChan: make(chan error, 10),
}
}
@@ -58,12 +61,12 @@ func (c *Cmd) runCmd() {
cmd := exec.Command(c.args[0], c.args[1:]...) //nolint:gosec
cmd.Stdout = c
cmd.Stderr = c
_ = cmd.Run()
c.ErrChan <- cmd.Run()
}
// RunCmd parallel running when parallel is true.
func (c *Cmd) RunCmd(parallel bool) {
fmt.Println("cmd: ", c.args)
fmt.Println("cmd:", c.args)
if parallel {
go c.runCmd()
} else {
@@ -71,12 +74,17 @@ func (c *Cmd) RunCmd(parallel bool) {
}
}
// OpenLog open cmd log by this api.
func (c *Cmd) OpenLog(open bool) {
c.openLog = open
}
func (c *Cmd) Write(data []byte) (int, error) {
out := string(data)
if verbose {
fmt.Printf("%s: %v", c.name, out)
if verbose || c.openLog {
fmt.Printf("%s:\n\t%v", c.name, out)
} else if strings.Contains(out, "error") || strings.Contains(out, "warning") {
fmt.Printf("%s: %v", c.name, out)
fmt.Printf("%s:\n\t%v", c.name, out)
}
go c.checkFuncs.IterCb(func(_ string, value interface{}) {
check := value.(checkFunc)

View File

@@ -38,22 +38,33 @@ func (c *Cmd) RunApp(waitResult func() bool) {
// WaitExit wait util process exit.
func (c *Cmd) WaitExit() {
// Wait all the check funcs are finished or test status is failed.
for !(c.Err != nil || c.checkFuncs.IsEmpty()) {
<-time.After(time.Millisecond * 500)
// Wait all the check functions are finished, interrupt loop when appear error.
var err error
for err == nil && !c.checkFuncs.IsEmpty() {
select {
case err = <-c.ErrChan:
if err != nil {
fmt.Printf("%s appear error durning running, err: %v\n", c.name, err)
}
default:
<-time.After(time.Millisecond * 500)
}
}
// Send interrupt signal.
c.mu.Lock()
_ = c.cmd.Process.Signal(os.Interrupt)
_, _ = c.cmd.Process.Wait()
// should use `_ = c.cmd.Process.Wait()` here, but we have some bugs in coordinator's graceful exit,
// so we use `Kill` as a temp workaround. And since `WaitExit` is only used in integration tests, so
// it won't really affect our functionalities.
_ = c.cmd.Process.Kill()
c.mu.Unlock()
}
// Interrupt send interrupt signal.
func (c *Cmd) Interrupt() {
c.mu.Lock()
c.Err = c.cmd.Process.Signal(os.Interrupt)
c.ErrChan <- c.cmd.Process.Signal(os.Interrupt)
c.mu.Unlock()
}

View File

@@ -80,7 +80,7 @@ func (b *App) RunDBApp(t *testing.T, option, keyword string) {
app.RunApp(nil)
}
// Free clear all running images
// Free clear all running images, double check and recycle docker container.
func (b *App) Free() {
if b.l1gethImg != nil {
_ = b.l1gethImg.Stop()
@@ -184,7 +184,7 @@ func newTestL1Docker(t *testing.T) ImgInstance {
assert.NoError(t, imgL1geth.Start())
// try 3 times to get chainID until is ok.
utils.TryTimes(3, func() bool {
utils.TryTimes(10, func() bool {
client, _ := ethclient.Dial(imgL1geth.Endpoint())
if client != nil {
if _, err := client.ChainID(context.Background()); err == nil {
@@ -203,7 +203,7 @@ func newTestL2Docker(t *testing.T) ImgInstance {
assert.NoError(t, imgL2geth.Start())
// try 3 times to get chainID until is ok.
utils.TryTimes(3, func() bool {
utils.TryTimes(10, func() bool {
client, _ := ethclient.Dial(imgL2geth.Endpoint())
if client != nil {
if _, err := client.ChainID(context.Background()); err == nil {
@@ -222,7 +222,7 @@ func newTestDBDocker(t *testing.T, driverName string) ImgInstance {
assert.NoError(t, imgDB.Start())
// try 5 times until the db is ready.
utils.TryTimes(5, func() bool {
utils.TryTimes(10, func() bool {
db, _ := sqlx.Open(driverName, imgDB.Endpoint())
if db != nil {
return db.Ping() == nil

View File

@@ -45,7 +45,6 @@ func (i *ImgDB) Start() error {
if id != "" {
return fmt.Errorf("container already exist, name: %s", i.name)
}
i.cmd.RunCmd(true)
i.running = i.isOk()
if !i.running {
_ = i.Stop()
@@ -83,7 +82,7 @@ func (i *ImgDB) Endpoint() string {
}
func (i *ImgDB) prepare() []string {
cmd := []string{"docker", "run", "--name", i.name, "-p", fmt.Sprintf("%d:5432", i.port)}
cmd := []string{"docker", "run", "--rm", "--name", i.name, "-p", fmt.Sprintf("%d:5432", i.port)}
envs := []string{
"-e", "POSTGRES_PASSWORD=" + i.password,
"-e", fmt.Sprintf("POSTGRES_DB=%s", i.dbName),
@@ -106,15 +105,21 @@ func (i *ImgDB) isOk() bool {
}
})
defer i.cmd.UnRegistFunc(keyword)
// Start cmd in parallel.
i.cmd.RunCmd(true)
select {
case <-okCh:
utils.TryTimes(3, func() bool {
utils.TryTimes(20, func() bool {
i.id = GetContainerID(i.name)
return i.id != ""
})
return i.id != ""
case err := <-i.cmd.ErrChan:
if err != nil {
fmt.Printf("failed to start %s, err: %v\n", i.name, err)
}
case <-time.After(time.Second * 20):
return false
}
return i.id != ""
}

View File

@@ -48,7 +48,6 @@ func (i *ImgGeth) Start() error {
if id != "" {
return fmt.Errorf("container already exist, name: %s", i.name)
}
i.cmd.RunCmd(true)
i.running = i.isOk()
if !i.running {
_ = i.Stop()
@@ -85,17 +84,23 @@ func (i *ImgGeth) isOk() bool {
}
})
defer i.cmd.UnRegistFunc(keyword)
// Start cmd in parallel.
i.cmd.RunCmd(true)
select {
case <-okCh:
utils.TryTimes(3, func() bool {
utils.TryTimes(20, func() bool {
i.id = GetContainerID(i.name)
return i.id != ""
})
return i.id != ""
case err := <-i.cmd.ErrChan:
if err != nil {
fmt.Printf("failed to start %s, err: %v\n", i.name, err)
}
case <-time.After(time.Second * 10):
return false
}
return i.id != ""
}
// Stop the docker container.
@@ -120,7 +125,7 @@ func (i *ImgGeth) Stop() error {
}
func (i *ImgGeth) prepare() []string {
cmds := []string{"docker", "run", "--name", i.name}
cmds := []string{"docker", "run", "--rm", "--name", i.name}
var ports []string
if i.httpPort != 0 {
ports = append(ports, []string{"-p", strconv.Itoa(i.httpPort) + ":8545"}...)

View File

@@ -8,9 +8,9 @@ require (
github.com/lib/pq v1.10.6
github.com/mattn/go-colorable v0.1.13
github.com/mattn/go-isatty v0.0.16
github.com/modern-go/reflect2 v1.0.1
github.com/modern-go/reflect2 v1.0.2
github.com/orcaman/concurrent-map v1.0.0
github.com/scroll-tech/go-ethereum v1.10.14-0.20230306131930-03b4de32b78b
github.com/scroll-tech/go-ethereum v1.10.14-0.20230321020420-127af384ed04
github.com/stretchr/testify v1.8.2
github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa
gotest.tools v2.2.0+incompatible
@@ -56,7 +56,6 @@ require (
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/pointerstructure v1.2.0 // indirect
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
@@ -69,7 +68,7 @@ require (
github.com/rjeczalik/notify v0.9.1 // indirect
github.com/rs/cors v1.7.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/scroll-tech/zktrie v0.5.0 // indirect
github.com/scroll-tech/zktrie v0.5.2 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/status-im/keycard-go v0.2.0 // indirect
@@ -87,6 +86,7 @@ require (
golang.org/x/text v0.8.0 // indirect
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
golang.org/x/tools v0.6.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
gopkg.in/urfave/cli.v1 v1.20.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect

View File

@@ -234,6 +234,7 @@ github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH6
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
@@ -276,10 +277,10 @@ github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjU
github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4=
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI=
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
@@ -342,10 +343,10 @@ github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/scroll-tech/go-ethereum v1.10.14-0.20230306131930-03b4de32b78b h1:shNTzAnD2oDcDCrM4aaVCTzQNVfYxF1An08R2H2DLAg=
github.com/scroll-tech/go-ethereum v1.10.14-0.20230306131930-03b4de32b78b/go.mod h1:f9ygxrxL7WRCTzuloV+t/UlcxMq3AL+gcNU60liiNNU=
github.com/scroll-tech/zktrie v0.5.0 h1:dABDR6lMZq6Hs+fWQSiHbX8s3AOX6hY+5nkhSYm5rmU=
github.com/scroll-tech/zktrie v0.5.0/go.mod h1:XvNo7vAk8yxNyTjBDj5WIiFzYW4bx/gJ78+NK6Zn6Uk=
github.com/scroll-tech/go-ethereum v1.10.14-0.20230321020420-127af384ed04 h1:PpI31kaBVm6+7sZtyK03Ex0QIg3P821Ktae0FHFh7IM=
github.com/scroll-tech/go-ethereum v1.10.14-0.20230321020420-127af384ed04/go.mod h1:jH8c08L9K8Hieaf0r/ur2P/cpesn4dFhmLm2Mmoi8kI=
github.com/scroll-tech/zktrie v0.5.2 h1:U34jPXMLGOlRHfdvYp5VVgOcC0RuPeJmcS3bWotCWiY=
github.com/scroll-tech/zktrie v0.5.2/go.mod h1:XvNo7vAk8yxNyTjBDj5WIiFzYW4bx/gJ78+NK6Zn6Uk=
github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
@@ -620,8 +621,9 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=

View File

@@ -3,7 +3,7 @@ use libc::c_char;
use std::cell::OnceCell;
use types::eth::BlockTrace;
use zkevm::circuit::AGG_DEGREE;
use zkevm::utils::{load_or_create_params, load_or_create_seed};
use zkevm::utils::{load_params, load_seed};
use zkevm::{circuit::DEGREE, prover::Prover};
static mut PROVER: OnceCell<Prover> = OnceCell::new();
@@ -15,9 +15,9 @@ pub unsafe extern "C" fn init_prover(params_path: *const c_char, seed_path: *con
let params_path = c_char_to_str(params_path);
let seed_path = c_char_to_str(seed_path);
let params = load_or_create_params(params_path, *DEGREE).unwrap();
let agg_params = load_or_create_params(params_path, *AGG_DEGREE).unwrap();
let seed = load_or_create_seed(seed_path).unwrap();
let params = load_params(params_path, *DEGREE).unwrap();
let agg_params = load_params(params_path, *AGG_DEGREE).unwrap();
let seed = load_seed(seed_path).unwrap();
let p = Prover::from_params_and_seed(params, agg_params, seed);
PROVER.set(p).unwrap();
}

View File

@@ -4,7 +4,7 @@ use std::fs::File;
use std::io::Read;
use zkevm::circuit::{AGG_DEGREE, DEGREE};
use zkevm::prover::AggCircuitProof;
use zkevm::utils::load_or_create_params;
use zkevm::utils::load_params;
use zkevm::verifier::Verifier;
static mut VERIFIER: Option<&Verifier> = None;
@@ -20,8 +20,8 @@ pub unsafe extern "C" fn init_verifier(params_path: *const c_char, agg_vk_path:
let mut agg_vk = vec![];
f.read_to_end(&mut agg_vk).unwrap();
let params = load_or_create_params(params_path, *DEGREE).unwrap();
let agg_params = load_or_create_params(params_path, *AGG_DEGREE).unwrap();
let params = load_params(params_path, *DEGREE).unwrap();
let agg_params = load_params(params_path, *AGG_DEGREE).unwrap();
let v = Box::new(Verifier::from_params(params, agg_params, Some(agg_vk)));
VERIFIER = Some(Box::leak(v))

View File

@@ -114,41 +114,41 @@ func (b *BatchData) Hash() *common.Hash {
// NewBatchData creates a BatchData given the parent batch information and the traces of the blocks
// included in this batch
func NewBatchData(parentBatch *BlockBatch, blockTraces []*types.BlockTrace, piCfg *PublicInputHashConfig) *BatchData {
func NewBatchData(parentBatch *BlockBatch, blocks []*WrappedBlock, piCfg *PublicInputHashConfig) *BatchData {
batchData := new(BatchData)
batch := &batchData.Batch
// set BatchIndex, ParentBatchHash
batch.BatchIndex = parentBatch.Index + 1
batch.ParentBatchHash = common.HexToHash(parentBatch.Hash)
batch.Blocks = make([]abi.IScrollChainBlockContext, len(blockTraces))
batch.Blocks = make([]abi.IScrollChainBlockContext, len(blocks))
var batchTxDataBuf bytes.Buffer
batchTxDataWriter := bufio.NewWriter(&batchTxDataBuf)
for i, trace := range blockTraces {
batchData.TotalTxNum += uint64(len(trace.Transactions))
batchData.TotalL2Gas += trace.Header.GasUsed
for i, block := range blocks {
batchData.TotalTxNum += uint64(len(block.Transactions))
batchData.TotalL2Gas += block.Header.GasUsed
// set baseFee to 0 when it's nil in the block header
baseFee := trace.Header.BaseFee
baseFee := block.Header.BaseFee
if baseFee == nil {
baseFee = big.NewInt(0)
}
batch.Blocks[i] = abi.IScrollChainBlockContext{
BlockHash: trace.Header.Hash(),
ParentHash: trace.Header.ParentHash,
BlockNumber: trace.Header.Number.Uint64(),
Timestamp: trace.Header.Time,
BlockHash: block.Header.Hash(),
ParentHash: block.Header.ParentHash,
BlockNumber: block.Header.Number.Uint64(),
Timestamp: block.Header.Time,
BaseFee: baseFee,
GasLimit: trace.Header.GasLimit,
NumTransactions: uint16(len(trace.Transactions)),
GasLimit: block.Header.GasLimit,
NumTransactions: uint16(len(block.Transactions)),
NumL1Messages: 0, // TODO: currently use 0, will re-enable after we use l2geth to include L1 messages
}
// fill in RLP-encoded transactions
for _, txData := range trace.Transactions {
for _, txData := range block.Transactions {
data, _ := hexutil.Decode(txData.Data)
// right now we only support legacy tx
tx := types.NewTx(&types.LegacyTx{
@@ -170,15 +170,14 @@ func NewBatchData(parentBatch *BlockBatch, blockTraces []*types.BlockTrace, piCf
batchData.TxHashes = append(batchData.TxHashes, tx.Hash())
}
// set PrevStateRoot from the first block
if i == 0 {
batch.PrevStateRoot = trace.StorageTrace.RootBefore
batch.PrevStateRoot = common.HexToHash(parentBatch.StateRoot)
}
// set NewStateRoot & WithdrawTrieRoot from the last block
if i == len(blockTraces)-1 {
batch.NewStateRoot = trace.Header.Root
batch.WithdrawTrieRoot = trace.WithdrawTrieRoot
if i == len(blocks)-1 {
batch.NewStateRoot = block.Header.Root
batch.WithdrawTrieRoot = block.WithdrawTrieRoot
}
}
@@ -193,7 +192,7 @@ func NewBatchData(parentBatch *BlockBatch, blockTraces []*types.BlockTrace, piCf
}
// NewGenesisBatchData generates the batch that contains the genesis block.
func NewGenesisBatchData(genesisBlockTrace *types.BlockTrace) *BatchData {
func NewGenesisBatchData(genesisBlockTrace *WrappedBlock) *BatchData {
header := genesisBlockTrace.Header
if header.Number.Uint64() != 0 {
panic("invalid genesis block trace: block number is not 0")

View File

@@ -75,15 +75,7 @@ func TestNewGenesisBatch(t *testing.T) {
"wrong genesis block header",
)
blockTrace := &geth_types.BlockTrace{
Coinbase: nil,
Header: genesisBlock,
Transactions: []*geth_types.TransactionData{},
StorageTrace: nil,
ExecutionResults: []*geth_types.ExecutionResult{},
MPTWitness: nil,
}
blockTrace := &WrappedBlock{genesisBlock, nil, common.Hash{}}
batchData := NewGenesisBatchData(blockTrace)
t.Log(batchData.Batch.Blocks[0])
batchData.piCfg = &PublicInputHashConfig{

14
common/types/block.go Normal file
View File

@@ -0,0 +1,14 @@
package types
import (
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/core/types"
)
// WrappedBlock contains the block's Header, Transactions and WithdrawTrieRoot hash.
type WrappedBlock struct {
Header *types.Header `json:"header"`
// Transactions is only used for recover types.Transactions, the from of types.TransactionData field is missing.
Transactions []*types.TransactionData `json:"transactions"`
WithdrawTrieRoot common.Hash `json:"withdraw_trie_root,omitempty"`
}

View File

@@ -82,6 +82,9 @@ const (
// MsgExpired represents the from_layer message status is expired
MsgExpired
// MsgRelayFailed represents the from_layer message status is relay failed
MsgRelayFailed
)
// L1Message is structure of stored layer1 bridge message
@@ -159,6 +162,7 @@ type SessionInfo struct {
ID string `json:"id"`
Rollers map[string]*RollerStatus `json:"rollers"`
StartTimestamp int64 `json:"start_timestamp"`
Attempts uint8 `json:"attempts,omitempty"`
}
// ProvingStatus block_batch proving_status (unassigned, assigned, proved, verified, submitted)
@@ -200,7 +204,7 @@ func (ps ProvingStatus) String() string {
}
}
// RollupStatus block_batch rollup_status (pending, committing, committed, finalizing, finalized)
// RollupStatus block_batch rollup_status (pending, committing, committed, commit_failed, finalizing, finalized, finalize_skipped, finalize_failed)
type RollupStatus int
const (
@@ -218,6 +222,10 @@ const (
RollupFinalized
// RollupFinalizationSkipped : batch finalization is skipped
RollupFinalizationSkipped
// RollupCommitFailed : rollup commit transaction confirmed but failed
RollupCommitFailed
// RollupFinalizeFailed : rollup finalize transaction is confirmed but failed
RollupFinalizeFailed
)
// BlockBatch is structure of stored block_batch

View File

@@ -0,0 +1,51 @@
package workerpool
import (
"sync"
)
// WorkerPool is responsible for creating workers and managing verify proof task between them
type WorkerPool struct {
maxWorker int
taskQueueChan chan func()
wg sync.WaitGroup
}
// NewWorkerPool creates new worker pool with given amount of workers
func NewWorkerPool(maxWorker int) *WorkerPool {
return &WorkerPool{
maxWorker: maxWorker,
taskQueueChan: nil,
wg: sync.WaitGroup{},
}
}
// Run runs WorkerPool
func (vwp *WorkerPool) Run() {
vwp.taskQueueChan = make(chan func())
for i := 0; i < vwp.maxWorker; i++ {
go func() {
for task := range vwp.taskQueueChan {
if task != nil {
task()
vwp.wg.Done()
} else {
return
}
}
}()
}
}
// Stop stop WorkerPool
func (vwp *WorkerPool) Stop() {
vwp.wg.Wait()
// close task queue channel, so that all goruotines listening from it stop
close(vwp.taskQueueChan)
}
// AddTask adds a task to WorkerPool
func (vwp *WorkerPool) AddTask(task func()) {
vwp.wg.Add(1)
vwp.taskQueueChan <- task
}

View File

@@ -0,0 +1,57 @@
package workerpool_test
import (
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"scroll-tech/common/utils/workerpool"
)
func TestWorkerPool(t *testing.T) {
as := assert.New(t)
vwp := workerpool.NewWorkerPool(2)
vwp.Run()
var cnt int32 = 3
task := func() {
time.Sleep(500 * time.Millisecond)
atomic.AddInt32(&cnt, -1)
}
go vwp.AddTask(task)
go vwp.AddTask(task)
go vwp.AddTask(task)
time.Sleep(600 * time.Millisecond)
as.Equal(int32(1), atomic.LoadInt32(&cnt))
vwp.Stop()
as.Equal(int32(0), atomic.LoadInt32(&cnt))
}
func TestWorkerPoolStopAndStart(t *testing.T) {
as := assert.New(t)
vwp := workerpool.NewWorkerPool(1)
var cnt int32 = 3
task := func() {
time.Sleep(500 * time.Millisecond)
atomic.AddInt32(&cnt, -1)
}
vwp.Run()
vwp.AddTask(task)
vwp.AddTask(task)
vwp.Stop()
as.Equal(int32(1), atomic.LoadInt32(&cnt))
vwp.Run()
vwp.AddTask(task)
vwp.Stop()
as.Equal(int32(0), atomic.LoadInt32(&cnt))
}

View File

@@ -5,7 +5,7 @@ import (
"runtime/debug"
)
var tag = "alpha-v2.0"
var tag = "v3.0.10"
var commit = func() string {
if info, ok := debug.ReadBuildInfo(); ok {

View File

@@ -3,3 +3,6 @@ artifacts
cache
coverage*
gasReporterOutput.json
src/libraries/verifier/ZkTrieVerifier.sol
src/libraries/verifier/PatriciaMerkleTrieVerifier.sol
src/L2/predeploys/L1BlockContainer.sol

View File

@@ -2,5 +2,17 @@
"printWidth": 120,
"singleQuote": false,
"tabWidth": 2,
"bracketSpacing": true
"bracketSpacing": true,
"overrides": [
{
"files": "src/**/*.sol",
"options": {
"printWidth": 120,
"tabWidth": 4,
"useTabs": false,
"singleQuote": false,
"bracketSpacing": false
}
}
]
}

View File

@@ -163,17 +163,6 @@ function owner() external view returns (address)
|---|---|---|
| _0 | address | undefined |
### pause
```solidity
function pause() external nonpayable
```
Pause the contract
*This function can only called by contract owner.*
### paused
```solidity
@@ -264,6 +253,26 @@ The address of Rollup contract.
### sendMessage
```solidity
function sendMessage(address _to, uint256 _value, bytes _message, uint256 _gasLimit, address _refundAddress) external payable
```
Send cross chain message from L1 to L2 or L2 to L1.
#### Parameters
| Name | Type | Description |
|---|---|---|
| _to | address | undefined |
| _value | uint256 | undefined |
| _message | bytes | undefined |
| _gasLimit | uint256 | undefined |
| _refundAddress | address | undefined |
### sendMessage
```solidity
function sendMessage(address _to, uint256 _value, bytes _message, uint256 _gasLimit) external payable
```
@@ -281,6 +290,22 @@ Send cross chain message from L1 to L2 or L2 to L1.
| _message | bytes | undefined |
| _gasLimit | uint256 | undefined |
### setPause
```solidity
function setPause(bool _status) external nonpayable
```
Pause the contract
*This function can only called by contract owner.*
#### Parameters
| Name | Type | Description |
|---|---|---|
| _status | bool | The pause status to update. |
### transferOwnership
```solidity

View File

@@ -212,17 +212,6 @@ function owner() external view returns (address)
|---|---|---|
| _0 | address | undefined |
### pause
```solidity
function pause() external nonpayable
```
Pause the contract
*This function can only called by contract owner.*
### paused
```solidity
@@ -294,6 +283,26 @@ function retryMessageWithProof(address _from, address _to, uint256 _value, uint2
### sendMessage
```solidity
function sendMessage(address _to, uint256 _value, bytes _message, uint256 _gasLimit, address _refundAddress) external payable
```
Send cross chain message from L1 to L2 or L2 to L1.
#### Parameters
| Name | Type | Description |
|---|---|---|
| _to | address | undefined |
| _value | uint256 | undefined |
| _message | bytes | undefined |
| _gasLimit | uint256 | undefined |
| _refundAddress | address | undefined |
### sendMessage
```solidity
function sendMessage(address _to, uint256 _value, bytes _message, uint256 _gasLimit) external payable
```
@@ -311,6 +320,22 @@ Send cross chain message from L1 to L2 or L2 to L1.
| _message | bytes | undefined |
| _gasLimit | uint256 | undefined |
### setPause
```solidity
function setPause(bool _status) external nonpayable
```
Pause the contract
*This function can only called by contract owner.*
#### Parameters
| Name | Type | Description |
|---|---|---|
| _status | bool | The pause status to update. |
### transferOwnership
```solidity

View File

@@ -2,5 +2,5 @@
pragma solidity ^0.8.0;
import { ProxyAdmin } from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";

View File

@@ -2,55 +2,55 @@
pragma solidity ^0.8.0;
import { IScrollMessenger } from "../libraries/IScrollMessenger.sol";
import {IScrollMessenger} from "../libraries/IScrollMessenger.sol";
interface IL1ScrollMessenger is IScrollMessenger {
/***********
* Structs *
***********/
/***********
* Structs *
***********/
struct L2MessageProof {
// The hash of the batch where the message belongs to.
bytes32 batchHash;
// Concatenation of merkle proof for withdraw merkle trie.
bytes merkleProof;
}
struct L2MessageProof {
// The hash of the batch where the message belongs to.
bytes32 batchHash;
// Concatenation of merkle proof for withdraw merkle trie.
bytes merkleProof;
}
/****************************
* Public Mutated Functions *
****************************/
/*****************************
* Public Mutating Functions *
*****************************/
/// @notice Relay a L2 => L1 message with message proof.
/// @param from The address of the sender of the message.
/// @param to The address of the recipient of the message.
/// @param value The msg.value passed to the message call.
/// @param nonce The nonce of the message to avoid replay attack.
/// @param message The content of the message.
/// @param proof The proof used to verify the correctness of the transaction.
function relayMessageWithProof(
address from,
address to,
uint256 value,
uint256 nonce,
bytes memory message,
L2MessageProof memory proof
) external;
/// @notice Relay a L2 => L1 message with message proof.
/// @param from The address of the sender of the message.
/// @param to The address of the recipient of the message.
/// @param value The msg.value passed to the message call.
/// @param nonce The nonce of the message to avoid replay attack.
/// @param message The content of the message.
/// @param proof The proof used to verify the correctness of the transaction.
function relayMessageWithProof(
address from,
address to,
uint256 value,
uint256 nonce,
bytes memory message,
L2MessageProof memory proof
) external;
/// @notice Replay an exsisting message.
/// @param from The address of the sender of the message.
/// @param to The address of the recipient of the message.
/// @param value The msg.value passed to the message call.
/// @param queueIndex The queue index for the message to replay.
/// @param message The content of the message.
/// @param oldGasLimit Original gas limit used to send the message.
/// @param newGasLimit New gas limit to be used for this message.
function replayMessage(
address from,
address to,
uint256 value,
uint256 queueIndex,
bytes memory message,
uint32 oldGasLimit,
uint32 newGasLimit
) external;
/// @notice Replay an exsisting message.
/// @param from The address of the sender of the message.
/// @param to The address of the recipient of the message.
/// @param value The msg.value passed to the message call.
/// @param queueIndex The queue index for the message to replay.
/// @param message The content of the message.
/// @param oldGasLimit Original gas limit used to send the message.
/// @param newGasLimit New gas limit to be used for this message.
function replayMessage(
address from,
address to,
uint256 value,
uint256 queueIndex,
bytes memory message,
uint32 oldGasLimit,
uint32 newGasLimit
) external;
}

View File

@@ -2,15 +2,15 @@
pragma solidity ^0.8.0;
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import { IScrollChain } from "./rollup/IScrollChain.sol";
import { IL1MessageQueue } from "./rollup/IL1MessageQueue.sol";
import { IL1ScrollMessenger } from "./IL1ScrollMessenger.sol";
import { ScrollConstants } from "../libraries/constants/ScrollConstants.sol";
import { IScrollMessenger } from "../libraries/IScrollMessenger.sol";
import { ScrollMessengerBase } from "../libraries/ScrollMessengerBase.sol";
import { WithdrawTrieVerifier } from "../libraries/verifier/WithdrawTrieVerifier.sol";
import {IScrollChain} from "./rollup/IScrollChain.sol";
import {IL1MessageQueue} from "./rollup/IL1MessageQueue.sol";
import {IL1ScrollMessenger} from "./IL1ScrollMessenger.sol";
import {ScrollConstants} from "../libraries/constants/ScrollConstants.sol";
import {IScrollMessenger} from "../libraries/IScrollMessenger.sol";
import {ScrollMessengerBase} from "../libraries/ScrollMessengerBase.sol";
import {WithdrawTrieVerifier} from "../libraries/verifier/WithdrawTrieVerifier.sol";
// solhint-disable avoid-low-level-calls
@@ -25,178 +25,228 @@ import { WithdrawTrieVerifier } from "../libraries/verifier/WithdrawTrieVerifier
/// @dev All deposited Ether (including `WETH` deposited throng `L1WETHGateway`) will locked in
/// this contract.
contract L1ScrollMessenger is PausableUpgradeable, ScrollMessengerBase, IL1ScrollMessenger {
/*************
* Variables *
*************/
/*************
* Variables *
*************/
/// @notice Mapping from relay id to relay status.
mapping(bytes32 => bool) public isL1MessageRelayed;
/// @notice Mapping from relay id to relay status.
mapping(bytes32 => bool) public isL1MessageRelayed;
/// @notice Mapping from L1 message hash to sent status.
mapping(bytes32 => bool) public isL1MessageSent;
/// @notice Mapping from L1 message hash to sent status.
mapping(bytes32 => bool) public isL1MessageSent;
/// @notice Mapping from L2 message hash to a boolean value indicating if the message has been successfully executed.
mapping(bytes32 => bool) public isL2MessageExecuted;
/// @notice Mapping from L2 message hash to a boolean value indicating if the message has been successfully executed.
mapping(bytes32 => bool) public isL2MessageExecuted;
/// @notice The address of Rollup contract.
address public rollup;
/// @notice The address of Rollup contract.
address public rollup;
/// @notice The address of L1MessageQueue contract.
address public messageQueue;
/// @notice The address of L1MessageQueue contract.
address public messageQueue;
/***************
* Constructor *
***************/
// @note move to ScrollMessengerBase in next big refactor
/// @dev The status of for non-reentrant check.
uint256 private _lock_status;
/// @notice Initialize the storage of L1ScrollMessenger.
/// @param _counterpart The address of L2ScrollMessenger contract in L2.
/// @param _feeVault The address of fee vault, which will be used to collect relayer fee.
/// @param _rollup The address of ScrollChain contract.
/// @param _messageQueue The address of L1MessageQueue contract.
function initialize(
address _counterpart,
address _feeVault,
address _rollup,
address _messageQueue
) public initializer {
PausableUpgradeable.__Pausable_init();
ScrollMessengerBase._initialize(_counterpart, _feeVault);
/**********************
* Function Modifiers *
**********************/
rollup = _rollup;
messageQueue = _messageQueue;
modifier nonReentrant() {
// On the first call to nonReentrant, _notEntered will be true
require(_lock_status != _ENTERED, "ReentrancyGuard: reentrant call");
// initialize to a nonzero value
xDomainMessageSender = ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER;
}
// Any calls to nonReentrant after this point will fail
_lock_status = _ENTERED;
/****************************
* Public Mutated Functions *
****************************/
_;
/// @inheritdoc IScrollMessenger
function sendMessage(
address _to,
uint256 _value,
bytes memory _message,
uint256 _gasLimit
) external payable override whenNotPaused {
address _messageQueue = messageQueue; // gas saving
address _counterpart = counterpart; // gas saving
// compute the actual cross domain message calldata.
uint256 _messageNonce = IL1MessageQueue(_messageQueue).nextCrossDomainMessageIndex();
bytes memory _xDomainCalldata = _encodeXDomainCalldata(msg.sender, _to, _value, _messageNonce, _message);
// compute and deduct the messaging fee to fee vault.
uint256 _fee = IL1MessageQueue(_messageQueue).estimateCrossDomainMessageFee(
address(this),
_counterpart,
_xDomainCalldata,
_gasLimit
);
require(msg.value >= _fee + _value, "Insufficient msg.value");
if (_fee > 0) {
(bool _success, ) = feeVault.call{ value: _fee }("");
require(_success, "Failed to deduct the fee");
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_lock_status = _NOT_ENTERED;
}
// append message to L1MessageQueue
IL1MessageQueue(_messageQueue).appendCrossDomainMessage(_counterpart, _gasLimit, _xDomainCalldata);
/***************
* Constructor *
***************/
// record the message hash for future use.
bytes32 _xDomainCalldataHash = keccak256(_xDomainCalldata);
/// @notice Initialize the storage of L1ScrollMessenger.
/// @param _counterpart The address of L2ScrollMessenger contract in L2.
/// @param _feeVault The address of fee vault, which will be used to collect relayer fee.
/// @param _rollup The address of ScrollChain contract.
/// @param _messageQueue The address of L1MessageQueue contract.
function initialize(
address _counterpart,
address _feeVault,
address _rollup,
address _messageQueue
) public initializer {
PausableUpgradeable.__Pausable_init();
ScrollMessengerBase._initialize(_counterpart, _feeVault);
// normally this won't happen, since each message has different nonce, but just in case.
require(!isL1MessageSent[_xDomainCalldataHash], "Duplicated message");
isL1MessageSent[_xDomainCalldataHash] = true;
rollup = _rollup;
messageQueue = _messageQueue;
emit SentMessage(msg.sender, _to, _value, _messageNonce, _gasLimit, _message);
// refund fee to tx.origin
unchecked {
uint256 _refund = msg.value - _fee - _value;
if (_refund > 0) {
(bool _success, ) = tx.origin.call{ value: _refund }("");
require(_success, "Failed to refund the fee");
}
// initialize to a nonzero value
xDomainMessageSender = ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER;
}
}
/// @inheritdoc IL1ScrollMessenger
function relayMessageWithProof(
address _from,
address _to,
uint256 _value,
uint256 _nonce,
bytes memory _message,
L2MessageProof memory _proof
) external override whenNotPaused onlyWhitelistedSender(msg.sender) {
require(xDomainMessageSender == ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER, "Message is already in execution");
/*****************************
* Public Mutating Functions *
*****************************/
bytes32 _xDomainCalldataHash = keccak256(_encodeXDomainCalldata(_from, _to, _value, _nonce, _message));
require(!isL2MessageExecuted[_xDomainCalldataHash], "Message was already successfully executed");
/// @inheritdoc IScrollMessenger
function sendMessage(
address _to,
uint256 _value,
bytes memory _message,
uint256 _gasLimit
) external payable override whenNotPaused {
_sendMessage(_to, _value, _message, _gasLimit, tx.origin);
}
{
address _rollup = rollup;
require(IScrollChain(_rollup).isBatchFinalized(_proof.batchHash), "Batch is not finalized");
// @note skip verify for now
/*
/// @inheritdoc IScrollMessenger
function sendMessage(
address _to,
uint256 _value,
bytes calldata _message,
uint256 _gasLimit,
address _refundAddress
) external payable override whenNotPaused {
_sendMessage(_to, _value, _message, _gasLimit, _refundAddress);
}
/// @inheritdoc IL1ScrollMessenger
function relayMessageWithProof(
address _from,
address _to,
uint256 _value,
uint256 _nonce,
bytes memory _message,
L2MessageProof memory _proof
) external override whenNotPaused onlyWhitelistedSender(msg.sender) {
require(
xDomainMessageSender == ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER,
"Message is already in execution"
);
bytes32 _xDomainCalldataHash = keccak256(_encodeXDomainCalldata(_from, _to, _value, _nonce, _message));
require(!isL2MessageExecuted[_xDomainCalldataHash], "Message was already successfully executed");
{
address _rollup = rollup;
require(IScrollChain(_rollup).isBatchFinalized(_proof.batchHash), "Batch is not finalized");
// @note skip verify for now
/*
bytes32 _messageRoot = IScrollChain(_rollup).getL2MessageRoot(_proof.batchHash);
require(
WithdrawTrieVerifier.verifyMerkleProof(_messageRoot, _xDomainCalldataHash, _nonce, _proof.merkleProof),
"Invalid proof"
);
*/
}
// @todo check more `_to` address to avoid attack.
require(_to != messageQueue, "Forbid to call message queue");
require(_to != address(this), "Forbid to call self");
// @note This usually will never happen, just in case.
require(_from != xDomainMessageSender, "Invalid message sender");
xDomainMessageSender = _from;
(bool success, ) = _to.call{value: _value}(_message);
// reset value to refund gas.
xDomainMessageSender = ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER;
if (success) {
isL2MessageExecuted[_xDomainCalldataHash] = true;
emit RelayedMessage(_xDomainCalldataHash);
} else {
emit FailedRelayedMessage(_xDomainCalldataHash);
}
bytes32 _relayId = keccak256(abi.encodePacked(_xDomainCalldataHash, msg.sender, block.number));
isL1MessageRelayed[_relayId] = true;
}
// @todo check more `_to` address to avoid attack.
require(_to != messageQueue, "Forbid to call message queue");
require(_to != address(this), "Forbid to call self");
// @note This usually will never happen, just in case.
require(_from != xDomainMessageSender, "Invalid message sender");
xDomainMessageSender = _from;
(bool success, ) = _to.call{ value: _value }(_message);
// reset value to refund gas.
xDomainMessageSender = ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER;
if (success) {
isL2MessageExecuted[_xDomainCalldataHash] = true;
emit RelayedMessage(_xDomainCalldataHash);
} else {
emit FailedRelayedMessage(_xDomainCalldataHash);
/// @inheritdoc IL1ScrollMessenger
function replayMessage(
address _from,
address _to,
uint256 _value,
uint256 _queueIndex,
bytes memory _message,
uint32 _oldGasLimit,
uint32 _newGasLimit
) external override whenNotPaused {
// @todo
}
bytes32 _relayId = keccak256(abi.encodePacked(_xDomainCalldataHash, msg.sender, block.number));
isL1MessageRelayed[_relayId] = true;
}
/************************
* Restricted Functions *
************************/
/// @inheritdoc IL1ScrollMessenger
function replayMessage(
address _from,
address _to,
uint256 _value,
uint256 _queueIndex,
bytes memory _message,
uint32 _oldGasLimit,
uint32 _newGasLimit
) external override whenNotPaused {
// @todo
}
/************************
* Restricted Functions *
************************/
/// @notice Pause the contract
/// @dev This function can only called by contract owner.
/// @param _status The pause status to update.
function setPause(bool _status) external onlyOwner {
if (_status) {
_pause();
} else {
_unpause();
/// @notice Pause the contract
/// @dev This function can only called by contract owner.
/// @param _status The pause status to update.
function setPause(bool _status) external onlyOwner {
if (_status) {
_pause();
} else {
_unpause();
}
}
/**********************
* Internal Functions *
**********************/
function _sendMessage(
address _to,
uint256 _value,
bytes memory _message,
uint256 _gasLimit,
address _refundAddress
) internal nonReentrant {
address _messageQueue = messageQueue; // gas saving
address _counterpart = counterpart; // gas saving
// compute the actual cross domain message calldata.
uint256 _messageNonce = IL1MessageQueue(_messageQueue).nextCrossDomainMessageIndex();
bytes memory _xDomainCalldata = _encodeXDomainCalldata(msg.sender, _to, _value, _messageNonce, _message);
// compute and deduct the messaging fee to fee vault.
uint256 _fee = IL1MessageQueue(_messageQueue).estimateCrossDomainMessageFee(
address(this),
_counterpart,
_xDomainCalldata,
_gasLimit
);
require(msg.value >= _fee + _value, "Insufficient msg.value");
if (_fee > 0) {
(bool _success, ) = feeVault.call{value: _fee}("");
require(_success, "Failed to deduct the fee");
}
// append message to L1MessageQueue
IL1MessageQueue(_messageQueue).appendCrossDomainMessage(_counterpart, _gasLimit, _xDomainCalldata);
// record the message hash for future use.
bytes32 _xDomainCalldataHash = keccak256(_xDomainCalldata);
// normally this won't happen, since each message has different nonce, but just in case.
require(!isL1MessageSent[_xDomainCalldataHash], "Duplicated message");
isL1MessageSent[_xDomainCalldataHash] = true;
emit SentMessage(msg.sender, _to, _value, _messageNonce, _gasLimit, _message);
// refund fee to tx.origin
unchecked {
uint256 _refund = msg.value - _fee - _value;
if (_refund > 0) {
(bool _success, ) = _refundAddress.call{value: _refund}("");
require(_success, "Failed to refund the fee");
}
}
}
}
}

View File

@@ -4,163 +4,163 @@ pragma solidity ^0.8.0;
/// @title The interface for the ERC1155 cross chain gateway in layer 1.
interface IL1ERC1155Gateway {
/**********
* Events *
**********/
/**********
* Events *
**********/
/// @notice Emitted when the ERC1155 NFT is transfered to recipient in layer 1.
/// @param _l1Token The address of ERC1155 NFT in layer 1.
/// @param _l2Token The address of ERC1155 NFT in layer 2.
/// @param _from The address of sender in layer 2.
/// @param _to The address of recipient in layer 1.
/// @param _tokenId The token id of the ERC1155 NFT to withdraw from layer 2.
/// @param _amount The number of token to withdraw from layer 2.
event FinalizeWithdrawERC1155(
address indexed _l1Token,
address indexed _l2Token,
address indexed _from,
address _to,
uint256 _tokenId,
uint256 _amount
);
/// @notice Emitted when the ERC1155 NFT is transfered to recipient in layer 1.
/// @param _l1Token The address of ERC1155 NFT in layer 1.
/// @param _l2Token The address of ERC1155 NFT in layer 2.
/// @param _from The address of sender in layer 2.
/// @param _to The address of recipient in layer 1.
/// @param _tokenId The token id of the ERC1155 NFT to withdraw from layer 2.
/// @param _amount The number of token to withdraw from layer 2.
event FinalizeWithdrawERC1155(
address indexed _l1Token,
address indexed _l2Token,
address indexed _from,
address _to,
uint256 _tokenId,
uint256 _amount
);
/// @notice Emitted when the ERC1155 NFT is batch transfered to recipient in layer 1.
/// @param _l1Token The address of ERC1155 NFT in layer 1.
/// @param _l2Token The address of ERC1155 NFT in layer 2.
/// @param _from The address of sender in layer 2.
/// @param _to The address of recipient in layer 1.
/// @param _tokenIds The list of token ids of the ERC1155 NFT to withdraw from layer 2.
/// @param _amounts The list of corresponding number of token to withdraw from layer 2.
event FinalizeBatchWithdrawERC1155(
address indexed _l1Token,
address indexed _l2Token,
address indexed _from,
address _to,
uint256[] _tokenIds,
uint256[] _amounts
);
/// @notice Emitted when the ERC1155 NFT is batch transfered to recipient in layer 1.
/// @param _l1Token The address of ERC1155 NFT in layer 1.
/// @param _l2Token The address of ERC1155 NFT in layer 2.
/// @param _from The address of sender in layer 2.
/// @param _to The address of recipient in layer 1.
/// @param _tokenIds The list of token ids of the ERC1155 NFT to withdraw from layer 2.
/// @param _amounts The list of corresponding number of token to withdraw from layer 2.
event FinalizeBatchWithdrawERC1155(
address indexed _l1Token,
address indexed _l2Token,
address indexed _from,
address _to,
uint256[] _tokenIds,
uint256[] _amounts
);
/// @notice Emitted when the ERC1155 NFT is deposited to gateway in layer 1.
/// @param _l1Token The address of ERC1155 NFT in layer 1.
/// @param _l2Token The address of ERC1155 NFT in layer 2.
/// @param _from The address of sender in layer 1.
/// @param _to The address of recipient in layer 2.
/// @param _tokenId The token id of the ERC1155 NFT to deposit in layer 1.
/// @param _amount The number of token to deposit in layer 1.
event DepositERC1155(
address indexed _l1Token,
address indexed _l2Token,
address indexed _from,
address _to,
uint256 _tokenId,
uint256 _amount
);
/// @notice Emitted when the ERC1155 NFT is deposited to gateway in layer 1.
/// @param _l1Token The address of ERC1155 NFT in layer 1.
/// @param _l2Token The address of ERC1155 NFT in layer 2.
/// @param _from The address of sender in layer 1.
/// @param _to The address of recipient in layer 2.
/// @param _tokenId The token id of the ERC1155 NFT to deposit in layer 1.
/// @param _amount The number of token to deposit in layer 1.
event DepositERC1155(
address indexed _l1Token,
address indexed _l2Token,
address indexed _from,
address _to,
uint256 _tokenId,
uint256 _amount
);
/// @notice Emitted when the ERC1155 NFT is batch deposited to gateway in layer 1.
/// @param _l1Token The address of ERC1155 NFT in layer 1.
/// @param _l2Token The address of ERC1155 NFT in layer 2.
/// @param _from The address of sender in layer 1.
/// @param _to The address of recipient in layer 2.
/// @param _tokenIds The list of token ids of the ERC1155 NFT to deposit in layer 1.
/// @param _amounts The list of corresponding number of token to deposit in layer 1.
event BatchDepositERC1155(
address indexed _l1Token,
address indexed _l2Token,
address indexed _from,
address _to,
uint256[] _tokenIds,
uint256[] _amounts
);
/// @notice Emitted when the ERC1155 NFT is batch deposited to gateway in layer 1.
/// @param _l1Token The address of ERC1155 NFT in layer 1.
/// @param _l2Token The address of ERC1155 NFT in layer 2.
/// @param _from The address of sender in layer 1.
/// @param _to The address of recipient in layer 2.
/// @param _tokenIds The list of token ids of the ERC1155 NFT to deposit in layer 1.
/// @param _amounts The list of corresponding number of token to deposit in layer 1.
event BatchDepositERC1155(
address indexed _l1Token,
address indexed _l2Token,
address indexed _from,
address _to,
uint256[] _tokenIds,
uint256[] _amounts
);
/*************************
* Public View Functions *
*************************/
/*************************
* Public View Functions *
*************************/
/// @notice Deposit some ERC1155 NFT to caller's account on layer 2.
/// @param _token The address of ERC1155 NFT in layer 1.
/// @param _tokenId The token id to deposit.
/// @param _amount The amount of token to deposit.
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
function depositERC1155(
address _token,
uint256 _tokenId,
uint256 _amount,
uint256 _gasLimit
) external payable;
/// @notice Deposit some ERC1155 NFT to caller's account on layer 2.
/// @param _token The address of ERC1155 NFT in layer 1.
/// @param _tokenId The token id to deposit.
/// @param _amount The amount of token to deposit.
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
function depositERC1155(
address _token,
uint256 _tokenId,
uint256 _amount,
uint256 _gasLimit
) external payable;
/// @notice Deposit some ERC1155 NFT to a recipient's account on layer 2.
/// @param _token The address of ERC1155 NFT in layer 1.
/// @param _to The address of recipient in layer 2.
/// @param _tokenId The token id to deposit.
/// @param _amount The amount of token to deposit.
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
function depositERC1155(
address _token,
address _to,
uint256 _tokenId,
uint256 _amount,
uint256 _gasLimit
) external payable;
/// @notice Deposit some ERC1155 NFT to a recipient's account on layer 2.
/// @param _token The address of ERC1155 NFT in layer 1.
/// @param _to The address of recipient in layer 2.
/// @param _tokenId The token id to deposit.
/// @param _amount The amount of token to deposit.
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
function depositERC1155(
address _token,
address _to,
uint256 _tokenId,
uint256 _amount,
uint256 _gasLimit
) external payable;
/// @notice Deposit a list of some ERC1155 NFT to caller's account on layer 2.
/// @param _token The address of ERC1155 NFT in layer 1.
/// @param _tokenIds The list of token ids to deposit.
/// @param _amounts The list of corresponding number of token to deposit.
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
function batchDepositERC1155(
address _token,
uint256[] calldata _tokenIds,
uint256[] calldata _amounts,
uint256 _gasLimit
) external payable;
/// @notice Deposit a list of some ERC1155 NFT to caller's account on layer 2.
/// @param _token The address of ERC1155 NFT in layer 1.
/// @param _tokenIds The list of token ids to deposit.
/// @param _amounts The list of corresponding number of token to deposit.
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
function batchDepositERC1155(
address _token,
uint256[] calldata _tokenIds,
uint256[] calldata _amounts,
uint256 _gasLimit
) external payable;
/// @notice Deposit a list of some ERC1155 NFT to a recipient's account on layer 2.
/// @param _token The address of ERC1155 NFT in layer 1.
/// @param _to The address of recipient in layer 2.
/// @param _tokenIds The list of token ids to deposit.
/// @param _amounts The list of corresponding number of token to deposit.
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
function batchDepositERC1155(
address _token,
address _to,
uint256[] calldata _tokenIds,
uint256[] calldata _amounts,
uint256 _gasLimit
) external payable;
/// @notice Deposit a list of some ERC1155 NFT to a recipient's account on layer 2.
/// @param _token The address of ERC1155 NFT in layer 1.
/// @param _to The address of recipient in layer 2.
/// @param _tokenIds The list of token ids to deposit.
/// @param _amounts The list of corresponding number of token to deposit.
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
function batchDepositERC1155(
address _token,
address _to,
uint256[] calldata _tokenIds,
uint256[] calldata _amounts,
uint256 _gasLimit
) external payable;
/// @notice Complete ERC1155 withdraw from layer 2 to layer 1 and send fund to recipient's account in layer 1.
/// The function should only be called by L1ScrollMessenger.
/// The function should also only be called by L2ERC1155Gateway in layer 2.
/// @param _l1Token The address of corresponding layer 1 token.
/// @param _l2Token The address of corresponding layer 2 token.
/// @param _from The address of account who withdraw the token in layer 2.
/// @param _to The address of recipient in layer 1 to receive the token.
/// @param _tokenId The token id to withdraw.
/// @param _amount The amount of token to withdraw.
function finalizeWithdrawERC1155(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256 _tokenId,
uint256 _amount
) external;
/// @notice Complete ERC1155 withdraw from layer 2 to layer 1 and send fund to recipient's account in layer 1.
/// The function should only be called by L1ScrollMessenger.
/// The function should also only be called by L2ERC1155Gateway in layer 2.
/// @param _l1Token The address of corresponding layer 1 token.
/// @param _l2Token The address of corresponding layer 2 token.
/// @param _from The address of account who withdraw the token in layer 2.
/// @param _to The address of recipient in layer 1 to receive the token.
/// @param _tokenId The token id to withdraw.
/// @param _amount The amount of token to withdraw.
function finalizeWithdrawERC1155(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256 _tokenId,
uint256 _amount
) external;
/// @notice Complete ERC1155 batch withdraw from layer 2 to layer 1 and send fund to recipient's account in layer 1.
/// The function should only be called by L1ScrollMessenger.
/// The function should also only be called by L2ERC1155Gateway in layer 2.
/// @param _l1Token The address of corresponding layer 1 token.
/// @param _l2Token The address of corresponding layer 2 token.
/// @param _from The address of account who withdraw the token in layer 2.
/// @param _to The address of recipient in layer 1 to receive the token.
/// @param _tokenIds The list of token ids to withdraw.
/// @param _amounts The list of corresponding number of token to withdraw.
function finalizeBatchWithdrawERC1155(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256[] calldata _tokenIds,
uint256[] calldata _amounts
) external;
/// @notice Complete ERC1155 batch withdraw from layer 2 to layer 1 and send fund to recipient's account in layer 1.
/// The function should only be called by L1ScrollMessenger.
/// The function should also only be called by L2ERC1155Gateway in layer 2.
/// @param _l1Token The address of corresponding layer 1 token.
/// @param _l2Token The address of corresponding layer 2 token.
/// @param _from The address of account who withdraw the token in layer 2.
/// @param _to The address of recipient in layer 1 to receive the token.
/// @param _tokenIds The list of token ids to withdraw.
/// @param _amounts The list of corresponding number of token to withdraw.
function finalizeBatchWithdrawERC1155(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256[] calldata _tokenIds,
uint256[] calldata _amounts
) external;
}

View File

@@ -3,109 +3,109 @@
pragma solidity ^0.8.0;
interface IL1ERC20Gateway {
/**********
* Events *
**********/
/**********
* Events *
**********/
/// @notice Emitted when ERC20 token is withdrawn from L2 to L1 and transfer to recipient.
/// @param l1Token The address of the token in L1.
/// @param l2Token The address of the token in L2.
/// @param from The address of sender in L2.
/// @param to The address of recipient in L1.
/// @param amount The amount of token withdrawn from L2 to L1.
/// @param data The optional calldata passed to recipient in L1.
event FinalizeWithdrawERC20(
address indexed l1Token,
address indexed l2Token,
address indexed from,
address to,
uint256 amount,
bytes data
);
/// @notice Emitted when ERC20 token is withdrawn from L2 to L1 and transfer to recipient.
/// @param l1Token The address of the token in L1.
/// @param l2Token The address of the token in L2.
/// @param from The address of sender in L2.
/// @param to The address of recipient in L1.
/// @param amount The amount of token withdrawn from L2 to L1.
/// @param data The optional calldata passed to recipient in L1.
event FinalizeWithdrawERC20(
address indexed l1Token,
address indexed l2Token,
address indexed from,
address to,
uint256 amount,
bytes data
);
/// @notice Emitted when someone deposit ERC20 token from L1 to L2.
/// @param l1Token The address of the token in L1.
/// @param l2Token The address of the token in L2.
/// @param from The address of sender in L1.
/// @param to The address of recipient in L2.
/// @param amount The amount of token will be deposited from L1 to L2.
/// @param data The optional calldata passed to recipient in L2.
event DepositERC20(
address indexed l1Token,
address indexed l2Token,
address indexed from,
address to,
uint256 amount,
bytes data
);
/// @notice Emitted when someone deposit ERC20 token from L1 to L2.
/// @param l1Token The address of the token in L1.
/// @param l2Token The address of the token in L2.
/// @param from The address of sender in L1.
/// @param to The address of recipient in L2.
/// @param amount The amount of token will be deposited from L1 to L2.
/// @param data The optional calldata passed to recipient in L2.
event DepositERC20(
address indexed l1Token,
address indexed l2Token,
address indexed from,
address to,
uint256 amount,
bytes data
);
/*************************
* Public View Functions *
*************************/
/*************************
* Public View Functions *
*************************/
/// @notice Return the corresponding l2 token address given l1 token address.
/// @param _l1Token The address of l1 token.
function getL2ERC20Address(address _l1Token) external view returns (address);
/// @notice Return the corresponding l2 token address given l1 token address.
/// @param _l1Token The address of l1 token.
function getL2ERC20Address(address _l1Token) external view returns (address);
/****************************
* Public Mutated Functions *
****************************/
/*****************************
* Public Mutating Functions *
*****************************/
/// @notice Deposit some token to a caller's account on L2.
/// @dev Make this function payable to send relayer fee in Ether.
/// @param _token The address of token in L1.
/// @param _amount The amount of token to transfer.
/// @param _gasLimit Gas limit required to complete the deposit on L2.
function depositERC20(
address _token,
uint256 _amount,
uint256 _gasLimit
) external payable;
/// @notice Deposit some token to a caller's account on L2.
/// @dev Make this function payable to send relayer fee in Ether.
/// @param _token The address of token in L1.
/// @param _amount The amount of token to transfer.
/// @param _gasLimit Gas limit required to complete the deposit on L2.
function depositERC20(
address _token,
uint256 _amount,
uint256 _gasLimit
) external payable;
/// @notice Deposit some token to a recipient's account on L2.
/// @dev Make this function payable to send relayer fee in Ether.
/// @param _token The address of token in L1.
/// @param _to The address of recipient's account on L2.
/// @param _amount The amount of token to transfer.
/// @param _gasLimit Gas limit required to complete the deposit on L2.
function depositERC20(
address _token,
address _to,
uint256 _amount,
uint256 _gasLimit
) external payable;
/// @notice Deposit some token to a recipient's account on L2.
/// @dev Make this function payable to send relayer fee in Ether.
/// @param _token The address of token in L1.
/// @param _to The address of recipient's account on L2.
/// @param _amount The amount of token to transfer.
/// @param _gasLimit Gas limit required to complete the deposit on L2.
function depositERC20(
address _token,
address _to,
uint256 _amount,
uint256 _gasLimit
) external payable;
/// @notice Deposit some token to a recipient's account on L2 and call.
/// @dev Make this function payable to send relayer fee in Ether.
/// @param _token The address of token in L1.
/// @param _to The address of recipient's account on L2.
/// @param _amount The amount of token to transfer.
/// @param _data Optional data to forward to recipient's account.
/// @param _gasLimit Gas limit required to complete the deposit on L2.
function depositERC20AndCall(
address _token,
address _to,
uint256 _amount,
bytes memory _data,
uint256 _gasLimit
) external payable;
/// @notice Deposit some token to a recipient's account on L2 and call.
/// @dev Make this function payable to send relayer fee in Ether.
/// @param _token The address of token in L1.
/// @param _to The address of recipient's account on L2.
/// @param _amount The amount of token to transfer.
/// @param _data Optional data to forward to recipient's account.
/// @param _gasLimit Gas limit required to complete the deposit on L2.
function depositERC20AndCall(
address _token,
address _to,
uint256 _amount,
bytes memory _data,
uint256 _gasLimit
) external payable;
/// @notice Complete ERC20 withdraw from L2 to L1 and send fund to recipient's account in L1.
/// @dev Make this function payable to handle WETH deposit/withdraw.
/// The function should only be called by L1ScrollMessenger.
/// The function should also only be called by L2ERC20Gateway in L2.
/// @param _l1Token The address of corresponding L1 token.
/// @param _l2Token The address of corresponding L2 token.
/// @param _from The address of account who withdraw the token in L2.
/// @param _to The address of recipient in L1 to receive the token.
/// @param _amount The amount of the token to withdraw.
/// @param _data Optional data to forward to recipient's account.
function finalizeWithdrawERC20(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256 _amount,
bytes calldata _data
) external payable;
/// @notice Complete ERC20 withdraw from L2 to L1 and send fund to recipient's account in L1.
/// @dev Make this function payable to handle WETH deposit/withdraw.
/// The function should only be called by L1ScrollMessenger.
/// The function should also only be called by L2ERC20Gateway in L2.
/// @param _l1Token The address of corresponding L1 token.
/// @param _l2Token The address of corresponding L2 token.
/// @param _from The address of account who withdraw the token in L2.
/// @param _to The address of recipient in L1 to receive the token.
/// @param _amount The amount of the token to withdraw.
/// @param _data Optional data to forward to recipient's account.
function finalizeWithdrawERC20(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256 _amount,
bytes calldata _data
) external payable;
}

View File

@@ -4,145 +4,145 @@ pragma solidity ^0.8.0;
/// @title The interface for the ERC721 cross chain gateway in layer 1.
interface IL1ERC721Gateway {
/**********
* Events *
**********/
/**********
* Events *
**********/
/// @notice Emitted when the ERC721 NFT is transfered to recipient in layer 1.
/// @param _l1Token The address of ERC721 NFT in layer 1.
/// @param _l2Token The address of ERC721 NFT in layer 2.
/// @param _from The address of sender in layer 2.
/// @param _to The address of recipient in layer 1.
/// @param _tokenId The token id of the ERC721 NFT to withdraw from layer 2.
event FinalizeWithdrawERC721(
address indexed _l1Token,
address indexed _l2Token,
address indexed _from,
address _to,
uint256 _tokenId
);
/// @notice Emitted when the ERC721 NFT is transfered to recipient in layer 1.
/// @param _l1Token The address of ERC721 NFT in layer 1.
/// @param _l2Token The address of ERC721 NFT in layer 2.
/// @param _from The address of sender in layer 2.
/// @param _to The address of recipient in layer 1.
/// @param _tokenId The token id of the ERC721 NFT to withdraw from layer 2.
event FinalizeWithdrawERC721(
address indexed _l1Token,
address indexed _l2Token,
address indexed _from,
address _to,
uint256 _tokenId
);
/// @notice Emitted when the ERC721 NFT is batch transfered to recipient in layer 1.
/// @param _l1Token The address of ERC721 NFT in layer 1.
/// @param _l2Token The address of ERC721 NFT in layer 2.
/// @param _from The address of sender in layer 2.
/// @param _to The address of recipient in layer 1.
/// @param _tokenIds The list of token ids of the ERC721 NFT to withdraw from layer 2.
event FinalizeBatchWithdrawERC721(
address indexed _l1Token,
address indexed _l2Token,
address indexed _from,
address _to,
uint256[] _tokenIds
);
/// @notice Emitted when the ERC721 NFT is batch transfered to recipient in layer 1.
/// @param _l1Token The address of ERC721 NFT in layer 1.
/// @param _l2Token The address of ERC721 NFT in layer 2.
/// @param _from The address of sender in layer 2.
/// @param _to The address of recipient in layer 1.
/// @param _tokenIds The list of token ids of the ERC721 NFT to withdraw from layer 2.
event FinalizeBatchWithdrawERC721(
address indexed _l1Token,
address indexed _l2Token,
address indexed _from,
address _to,
uint256[] _tokenIds
);
/// @notice Emitted when the ERC721 NFT is deposited to gateway in layer 1.
/// @param _l1Token The address of ERC721 NFT in layer 1.
/// @param _l2Token The address of ERC721 NFT in layer 2.
/// @param _from The address of sender in layer 1.
/// @param _to The address of recipient in layer 2.
/// @param _tokenId The token id of the ERC721 NFT to deposit in layer 1.
event DepositERC721(
address indexed _l1Token,
address indexed _l2Token,
address indexed _from,
address _to,
uint256 _tokenId
);
/// @notice Emitted when the ERC721 NFT is deposited to gateway in layer 1.
/// @param _l1Token The address of ERC721 NFT in layer 1.
/// @param _l2Token The address of ERC721 NFT in layer 2.
/// @param _from The address of sender in layer 1.
/// @param _to The address of recipient in layer 2.
/// @param _tokenId The token id of the ERC721 NFT to deposit in layer 1.
event DepositERC721(
address indexed _l1Token,
address indexed _l2Token,
address indexed _from,
address _to,
uint256 _tokenId
);
/// @notice Emitted when the ERC721 NFT is batch deposited to gateway in layer 1.
/// @param _l1Token The address of ERC721 NFT in layer 1.
/// @param _l2Token The address of ERC721 NFT in layer 2.
/// @param _from The address of sender in layer 1.
/// @param _to The address of recipient in layer 2.
/// @param _tokenIds The list of token ids of the ERC721 NFT to deposit in layer 1.
event BatchDepositERC721(
address indexed _l1Token,
address indexed _l2Token,
address indexed _from,
address _to,
uint256[] _tokenIds
);
/// @notice Emitted when the ERC721 NFT is batch deposited to gateway in layer 1.
/// @param _l1Token The address of ERC721 NFT in layer 1.
/// @param _l2Token The address of ERC721 NFT in layer 2.
/// @param _from The address of sender in layer 1.
/// @param _to The address of recipient in layer 2.
/// @param _tokenIds The list of token ids of the ERC721 NFT to deposit in layer 1.
event BatchDepositERC721(
address indexed _l1Token,
address indexed _l2Token,
address indexed _from,
address _to,
uint256[] _tokenIds
);
/****************************
* Public Mutated Functions *
****************************/
/*****************************
* Public Mutating Functions *
*****************************/
/// @notice Deposit some ERC721 NFT to caller's account on layer 2.
/// @param _token The address of ERC721 NFT in layer 1.
/// @param _tokenId The token id to deposit.
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
function depositERC721(
address _token,
uint256 _tokenId,
uint256 _gasLimit
) external payable;
/// @notice Deposit some ERC721 NFT to caller's account on layer 2.
/// @param _token The address of ERC721 NFT in layer 1.
/// @param _tokenId The token id to deposit.
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
function depositERC721(
address _token,
uint256 _tokenId,
uint256 _gasLimit
) external payable;
/// @notice Deposit some ERC721 NFT to a recipient's account on layer 2.
/// @param _token The address of ERC721 NFT in layer 1.
/// @param _to The address of recipient in layer 2.
/// @param _tokenId The token id to deposit.
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
function depositERC721(
address _token,
address _to,
uint256 _tokenId,
uint256 _gasLimit
) external payable;
/// @notice Deposit some ERC721 NFT to a recipient's account on layer 2.
/// @param _token The address of ERC721 NFT in layer 1.
/// @param _to The address of recipient in layer 2.
/// @param _tokenId The token id to deposit.
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
function depositERC721(
address _token,
address _to,
uint256 _tokenId,
uint256 _gasLimit
) external payable;
/// @notice Deposit a list of some ERC721 NFT to caller's account on layer 2.
/// @param _token The address of ERC721 NFT in layer 1.
/// @param _tokenIds The list of token ids to deposit.
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
function batchDepositERC721(
address _token,
uint256[] calldata _tokenIds,
uint256 _gasLimit
) external payable;
/// @notice Deposit a list of some ERC721 NFT to caller's account on layer 2.
/// @param _token The address of ERC721 NFT in layer 1.
/// @param _tokenIds The list of token ids to deposit.
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
function batchDepositERC721(
address _token,
uint256[] calldata _tokenIds,
uint256 _gasLimit
) external payable;
/// @notice Deposit a list of some ERC721 NFT to a recipient's account on layer 2.
/// @param _token The address of ERC721 NFT in layer 1.
/// @param _to The address of recipient in layer 2.
/// @param _tokenIds The list of token ids to deposit.
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
function batchDepositERC721(
address _token,
address _to,
uint256[] calldata _tokenIds,
uint256 _gasLimit
) external payable;
/// @notice Deposit a list of some ERC721 NFT to a recipient's account on layer 2.
/// @param _token The address of ERC721 NFT in layer 1.
/// @param _to The address of recipient in layer 2.
/// @param _tokenIds The list of token ids to deposit.
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
function batchDepositERC721(
address _token,
address _to,
uint256[] calldata _tokenIds,
uint256 _gasLimit
) external payable;
/// @notice Complete ERC721 withdraw from layer 2 to layer 1 and send NFT to recipient's account in layer 1.
/// @dev Requirements:
/// - The function should only be called by L1ScrollMessenger.
/// - The function should also only be called by L2ERC721Gateway in layer 2.
/// @param _l1Token The address of corresponding layer 1 token.
/// @param _l2Token The address of corresponding layer 2 token.
/// @param _from The address of account who withdraw the token in layer 2.
/// @param _to The address of recipient in layer 1 to receive the token.
/// @param _tokenId The token id to withdraw.
function finalizeWithdrawERC721(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256 _tokenId
) external;
/// @notice Complete ERC721 withdraw from layer 2 to layer 1 and send NFT to recipient's account in layer 1.
/// @dev Requirements:
/// - The function should only be called by L1ScrollMessenger.
/// - The function should also only be called by L2ERC721Gateway in layer 2.
/// @param _l1Token The address of corresponding layer 1 token.
/// @param _l2Token The address of corresponding layer 2 token.
/// @param _from The address of account who withdraw the token in layer 2.
/// @param _to The address of recipient in layer 1 to receive the token.
/// @param _tokenId The token id to withdraw.
function finalizeWithdrawERC721(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256 _tokenId
) external;
/// @notice Complete ERC721 batch withdraw from layer 2 to layer 1 and send NFT to recipient's account in layer 1.
/// @dev Requirements:
/// - The function should only be called by L1ScrollMessenger.
/// - The function should also only be called by L2ERC721Gateway in layer 2.
/// @param _l1Token The address of corresponding layer 1 token.
/// @param _l2Token The address of corresponding layer 2 token.
/// @param _from The address of account who withdraw the token in layer 2.
/// @param _to The address of recipient in layer 1 to receive the token.
/// @param _tokenIds The list of token ids to withdraw.
function finalizeBatchWithdrawERC721(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256[] calldata _tokenIds
) external;
/// @notice Complete ERC721 batch withdraw from layer 2 to layer 1 and send NFT to recipient's account in layer 1.
/// @dev Requirements:
/// - The function should only be called by L1ScrollMessenger.
/// - The function should also only be called by L2ERC721Gateway in layer 2.
/// @param _l1Token The address of corresponding layer 1 token.
/// @param _l2Token The address of corresponding layer 2 token.
/// @param _from The address of account who withdraw the token in layer 2.
/// @param _to The address of recipient in layer 1 to receive the token.
/// @param _tokenIds The list of token ids to withdraw.
function finalizeBatchWithdrawERC721(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256[] calldata _tokenIds
) external;
}

View File

@@ -3,66 +3,66 @@
pragma solidity ^0.8.0;
interface IL1ETHGateway {
/**********
* Events *
**********/
/**********
* Events *
**********/
/// @notice Emitted when ETH is withdrawn from L2 to L1 and transfer to recipient.
/// @param from The address of sender in L2.
/// @param to The address of recipient in L1.
/// @param amount The amount of ETH withdrawn from L2 to L1.
/// @param data The optional calldata passed to recipient in L1.
event FinalizeWithdrawETH(address indexed from, address indexed to, uint256 amount, bytes data);
/// @notice Emitted when ETH is withdrawn from L2 to L1 and transfer to recipient.
/// @param from The address of sender in L2.
/// @param to The address of recipient in L1.
/// @param amount The amount of ETH withdrawn from L2 to L1.
/// @param data The optional calldata passed to recipient in L1.
event FinalizeWithdrawETH(address indexed from, address indexed to, uint256 amount, bytes data);
/// @notice Emitted when someone deposit ETH from L1 to L2.
/// @param from The address of sender in L1.
/// @param to The address of recipient in L2.
/// @param amount The amount of ETH will be deposited from L1 to L2.
/// @param data The optional calldata passed to recipient in L2.
event DepositETH(address indexed from, address indexed to, uint256 amount, bytes data);
/// @notice Emitted when someone deposit ETH from L1 to L2.
/// @param from The address of sender in L1.
/// @param to The address of recipient in L2.
/// @param amount The amount of ETH will be deposited from L1 to L2.
/// @param data The optional calldata passed to recipient in L2.
event DepositETH(address indexed from, address indexed to, uint256 amount, bytes data);
/****************************
* Public Mutated Functions *
****************************/
/*****************************
* Public Mutating Functions *
*****************************/
/// @notice Deposit ETH to caller's account in L2.
/// @param amount The amount of ETH to be deposited.
/// @param gasLimit Gas limit required to complete the deposit on L2.
function depositETH(uint256 amount, uint256 gasLimit) external payable;
/// @notice Deposit ETH to caller's account in L2.
/// @param amount The amount of ETH to be deposited.
/// @param gasLimit Gas limit required to complete the deposit on L2.
function depositETH(uint256 amount, uint256 gasLimit) external payable;
/// @notice Deposit ETH to some recipient's account in L2.
/// @param to The address of recipient's account on L2.
/// @param amount The amount of ETH to be deposited.
/// @param gasLimit Gas limit required to complete the deposit on L2.
function depositETH(
address to,
uint256 amount,
uint256 gasLimit
) external payable;
/// @notice Deposit ETH to some recipient's account in L2.
/// @param to The address of recipient's account on L2.
/// @param amount The amount of ETH to be deposited.
/// @param gasLimit Gas limit required to complete the deposit on L2.
function depositETH(
address to,
uint256 amount,
uint256 gasLimit
) external payable;
/// @notice Deposit ETH to some recipient's account in L2 and call the target contract.
/// @param to The address of recipient's account on L2.
/// @param amount The amount of ETH to be deposited.
/// @param data Optional data to forward to recipient's account.
/// @param gasLimit Gas limit required to complete the deposit on L2.
function depositETHAndCall(
address to,
uint256 amount,
bytes calldata data,
uint256 gasLimit
) external payable;
/// @notice Deposit ETH to some recipient's account in L2 and call the target contract.
/// @param to The address of recipient's account on L2.
/// @param amount The amount of ETH to be deposited.
/// @param data Optional data to forward to recipient's account.
/// @param gasLimit Gas limit required to complete the deposit on L2.
function depositETHAndCall(
address to,
uint256 amount,
bytes calldata data,
uint256 gasLimit
) external payable;
/// @notice Complete ETH withdraw from L2 to L1 and send fund to recipient's account in L1.
/// @dev This function should only be called by L1ScrollMessenger.
/// This function should also only be called by L1ETHGateway in L2.
/// @param from The address of account who withdraw ETH in L2.
/// @param to The address of recipient in L1 to receive ETH.
/// @param amount The amount of ETH to withdraw.
/// @param data Optional data to forward to recipient's account.
function finalizeWithdrawETH(
address from,
address to,
uint256 amount,
bytes calldata data
) external payable;
/// @notice Complete ETH withdraw from L2 to L1 and send fund to recipient's account in L1.
/// @dev This function should only be called by L1ScrollMessenger.
/// This function should also only be called by L1ETHGateway in L2.
/// @param from The address of account who withdraw ETH in L2.
/// @param to The address of recipient in L1 to receive ETH.
/// @param amount The amount of ETH to withdraw.
/// @param data Optional data to forward to recipient's account.
function finalizeWithdrawETH(
address from,
address to,
uint256 amount,
bytes calldata data
) external payable;
}

View File

@@ -2,24 +2,24 @@
pragma solidity ^0.8.0;
import { IL1ETHGateway } from "./IL1ETHGateway.sol";
import { IL1ERC20Gateway } from "./IL1ERC20Gateway.sol";
import {IL1ETHGateway} from "./IL1ETHGateway.sol";
import {IL1ERC20Gateway} from "./IL1ERC20Gateway.sol";
interface IL1GatewayRouter is IL1ETHGateway, IL1ERC20Gateway {
/**********
* Events *
**********/
/**********
* Events *
**********/
/// @notice Emitted when the address of ETH Gateway is updated.
/// @param ethGateway The address of new ETH Gateway.
event SetETHGateway(address indexed ethGateway);
/// @notice Emitted when the address of ETH Gateway is updated.
/// @param ethGateway The address of new ETH Gateway.
event SetETHGateway(address indexed ethGateway);
/// @notice Emitted when the address of default ERC20 Gateway is updated.
/// @param defaultERC20Gateway The address of new default ERC20 Gateway.
event SetDefaultERC20Gateway(address indexed defaultERC20Gateway);
/// @notice Emitted when the address of default ERC20 Gateway is updated.
/// @param defaultERC20Gateway The address of new default ERC20 Gateway.
event SetDefaultERC20Gateway(address indexed defaultERC20Gateway);
/// @notice Emitted when the `gateway` for `token` is updated.
/// @param token The address of token updated.
/// @param gateway The corresponding address of gateway updated.
event SetERC20Gateway(address indexed token, address indexed gateway);
/// @notice Emitted when the `gateway` for `token` is updated.
/// @param token The address of token updated.
/// @param gateway The corresponding address of gateway updated.
event SetERC20Gateway(address indexed token, address indexed gateway);
}

View File

@@ -2,16 +2,16 @@
pragma solidity ^0.8.0;
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import {SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import { IL2ERC20Gateway } from "../../L2/gateways/IL2ERC20Gateway.sol";
import { IL1ScrollMessenger } from "../IL1ScrollMessenger.sol";
import { IL1ERC20Gateway } from "./IL1ERC20Gateway.sol";
import {IL2ERC20Gateway} from "../../L2/gateways/IL2ERC20Gateway.sol";
import {IL1ScrollMessenger} from "../IL1ScrollMessenger.sol";
import {IL1ERC20Gateway} from "./IL1ERC20Gateway.sol";
import { ScrollGatewayBase } from "../../libraries/gateway/ScrollGatewayBase.sol";
import { L1ERC20Gateway } from "./L1ERC20Gateway.sol";
import {ScrollGatewayBase} from "../../libraries/gateway/ScrollGatewayBase.sol";
import {L1ERC20Gateway} from "./L1ERC20Gateway.sol";
/// @title L1CustomERC20Gateway
/// @notice The `L1CustomERC20Gateway` is used to deposit custom ERC20 compatible tokens in layer 1 and
@@ -19,139 +19,139 @@ import { L1ERC20Gateway } from "./L1ERC20Gateway.sol";
/// @dev The deposited tokens are held in this gateway. On finalizing withdraw, the corresponding
/// tokens will be transfer to the recipient directly.
contract L1CustomERC20Gateway is OwnableUpgradeable, ScrollGatewayBase, L1ERC20Gateway {
using SafeERC20Upgradeable for IERC20Upgradeable;
using SafeERC20Upgradeable for IERC20Upgradeable;
/**********
* Events *
**********/
/**********
* Events *
**********/
/// @notice Emitted when token mapping for ERC20 token is updated.
/// @param _l1Token The address of ERC20 token in layer 1.
/// @param _l2Token The address of corresponding ERC20 token in layer 2.
event UpdateTokenMapping(address _l1Token, address _l2Token);
/// @notice Emitted when token mapping for ERC20 token is updated.
/// @param _l1Token The address of ERC20 token in layer 1.
/// @param _l2Token The address of corresponding ERC20 token in layer 2.
event UpdateTokenMapping(address _l1Token, address _l2Token);
/*************
* Variables *
*************/
/*************
* Variables *
*************/
/// @notice Mapping from l1 token address to l2 token address for ERC20 token.
mapping(address => address) public tokenMapping;
/// @notice Mapping from l1 token address to l2 token address for ERC20 token.
mapping(address => address) public tokenMapping;
/***************
* Constructor *
***************/
/***************
* Constructor *
***************/
/// @notice Initialize the storage of L1CustomERC20Gateway.
/// @param _counterpart The address of L2CustomERC20Gateway in L2.
/// @param _router The address of L1GatewayRouter.
/// @param _messenger The address of L1ScrollMessenger.
function initialize(
address _counterpart,
address _router,
address _messenger
) external initializer {
require(_router != address(0), "zero router address");
/// @notice Initialize the storage of L1CustomERC20Gateway.
/// @param _counterpart The address of L2CustomERC20Gateway in L2.
/// @param _router The address of L1GatewayRouter.
/// @param _messenger The address of L1ScrollMessenger.
function initialize(
address _counterpart,
address _router,
address _messenger
) external initializer {
require(_router != address(0), "zero router address");
OwnableUpgradeable.__Ownable_init();
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
}
/*************************
* Public View Functions *
*************************/
/// @inheritdoc IL1ERC20Gateway
function getL2ERC20Address(address _l1Token) public view override returns (address) {
return tokenMapping[_l1Token];
}
/****************************
* Public Mutated Functions *
****************************/
/// @inheritdoc IL1ERC20Gateway
function finalizeWithdrawERC20(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256 _amount,
bytes calldata _data
) external payable override onlyCallByCounterpart {
require(msg.value == 0, "nonzero msg.value");
require(_l2Token == tokenMapping[_l1Token], "l2 token mismatch");
// @note can possible trigger reentrant call to this contract or messenger,
// but it seems not a big problem.
IERC20Upgradeable(_l1Token).safeTransfer(_to, _amount);
// @todo forward `_data` to `_to` in the near future
emit FinalizeWithdrawERC20(_l1Token, _l2Token, _from, _to, _amount, _data);
}
/************************
* Restricted Functions *
************************/
/// @notice Update layer 1 to layer 2 token mapping.
/// @param _l1Token The address of ERC20 token in layer 1.
/// @param _l2Token The address of corresponding ERC20 token in layer 2.
function updateTokenMapping(address _l1Token, address _l2Token) external onlyOwner {
require(_l2Token != address(0), "map to zero address");
tokenMapping[_l1Token] = _l2Token;
emit UpdateTokenMapping(_l1Token, _l2Token);
}
/**********************
* Internal Functions *
**********************/
/// @inheritdoc L1ERC20Gateway
function _deposit(
address _token,
address _to,
uint256 _amount,
bytes memory _data,
uint256 _gasLimit
) internal virtual override nonReentrant {
address _l2Token = tokenMapping[_token];
require(_l2Token != address(0), "no corresponding l2 token");
// 1. Extract real sender if this call is from L1GatewayRouter.
address _from = msg.sender;
if (router == msg.sender) {
(_from, _data) = abi.decode(_data, (address, bytes));
OwnableUpgradeable.__Ownable_init();
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
}
// 2. Transfer token into this contract.
{
// common practice to handle fee on transfer token.
uint256 _before = IERC20Upgradeable(_token).balanceOf(address(this));
IERC20Upgradeable(_token).safeTransferFrom(_from, address(this), _amount);
uint256 _after = IERC20Upgradeable(_token).balanceOf(address(this));
// no unchecked here, since some weird token may return arbitrary balance.
_amount = _after - _before;
// ignore weird fee on transfer token
require(_amount > 0, "deposit zero amount");
/*************************
* Public View Functions *
*************************/
/// @inheritdoc IL1ERC20Gateway
function getL2ERC20Address(address _l1Token) public view override returns (address) {
return tokenMapping[_l1Token];
}
// 3. Generate message passed to L2StandardERC20Gateway.
bytes memory _message = abi.encodeWithSelector(
IL2ERC20Gateway.finalizeDepositERC20.selector,
_token,
_l2Token,
_from,
_to,
_amount,
_data
);
/*****************************
* Public Mutating Functions *
*****************************/
// 4. Send message to L1ScrollMessenger.
IL1ScrollMessenger(messenger).sendMessage{ value: msg.value }(counterpart, 0, _message, _gasLimit);
/// @inheritdoc IL1ERC20Gateway
function finalizeWithdrawERC20(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256 _amount,
bytes calldata _data
) external payable override onlyCallByCounterpart {
require(msg.value == 0, "nonzero msg.value");
require(_l2Token == tokenMapping[_l1Token], "l2 token mismatch");
emit DepositERC20(_token, _l2Token, _from, _to, _amount, _data);
}
// @note can possible trigger reentrant call to this contract or messenger,
// but it seems not a big problem.
IERC20Upgradeable(_l1Token).safeTransfer(_to, _amount);
// @todo forward `_data` to `_to` in the near future
emit FinalizeWithdrawERC20(_l1Token, _l2Token, _from, _to, _amount, _data);
}
/************************
* Restricted Functions *
************************/
/// @notice Update layer 1 to layer 2 token mapping.
/// @param _l1Token The address of ERC20 token in layer 1.
/// @param _l2Token The address of corresponding ERC20 token in layer 2.
function updateTokenMapping(address _l1Token, address _l2Token) external onlyOwner {
require(_l2Token != address(0), "map to zero address");
tokenMapping[_l1Token] = _l2Token;
emit UpdateTokenMapping(_l1Token, _l2Token);
}
/**********************
* Internal Functions *
**********************/
/// @inheritdoc L1ERC20Gateway
function _deposit(
address _token,
address _to,
uint256 _amount,
bytes memory _data,
uint256 _gasLimit
) internal virtual override nonReentrant {
address _l2Token = tokenMapping[_token];
require(_l2Token != address(0), "no corresponding l2 token");
// 1. Extract real sender if this call is from L1GatewayRouter.
address _from = msg.sender;
if (router == msg.sender) {
(_from, _data) = abi.decode(_data, (address, bytes));
}
// 2. Transfer token into this contract.
{
// common practice to handle fee on transfer token.
uint256 _before = IERC20Upgradeable(_token).balanceOf(address(this));
IERC20Upgradeable(_token).safeTransferFrom(_from, address(this), _amount);
uint256 _after = IERC20Upgradeable(_token).balanceOf(address(this));
// no unchecked here, since some weird token may return arbitrary balance.
_amount = _after - _before;
// ignore weird fee on transfer token
require(_amount > 0, "deposit zero amount");
}
// 3. Generate message passed to L2StandardERC20Gateway.
bytes memory _message = abi.encodeWithSelector(
IL2ERC20Gateway.finalizeDepositERC20.selector,
_token,
_l2Token,
_from,
_to,
_amount,
_data
);
// 4. Send message to L1ScrollMessenger.
IL1ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, _gasLimit);
emit DepositERC20(_token, _l2Token, _from, _to, _amount, _data);
}
}

View File

@@ -2,15 +2,15 @@
pragma solidity ^0.8.0;
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { IERC1155Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC1155/IERC1155Upgradeable.sol";
import { ERC1155HolderUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC1155/utils/ERC1155HolderUpgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {IERC1155Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC1155/IERC1155Upgradeable.sol";
import {ERC1155HolderUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC1155/utils/ERC1155HolderUpgradeable.sol";
import { IL2ERC1155Gateway } from "../../L2/gateways/IL2ERC1155Gateway.sol";
import { IL1ScrollMessenger } from "../IL1ScrollMessenger.sol";
import { IL1ERC1155Gateway } from "./IL1ERC1155Gateway.sol";
import {IL2ERC1155Gateway} from "../../L2/gateways/IL2ERC1155Gateway.sol";
import {IL1ScrollMessenger} from "../IL1ScrollMessenger.sol";
import {IL1ERC1155Gateway} from "./IL1ERC1155Gateway.sol";
import { ScrollGatewayBase } from "../../libraries/gateway/ScrollGatewayBase.sol";
import {ScrollGatewayBase} from "../../libraries/gateway/ScrollGatewayBase.sol";
/// @title L1ERC1155Gateway
/// @notice The `L1ERC1155Gateway` is used to deposit ERC1155 compatible NFT in layer 1 and
@@ -21,209 +21,209 @@ import { ScrollGatewayBase } from "../../libraries/gateway/ScrollGatewayBase.sol
/// This will be changed if we have more specific scenarios.
// @todo Current implementation doesn't support calling from `L1GatewayRouter`.
contract L1ERC1155Gateway is OwnableUpgradeable, ERC1155HolderUpgradeable, ScrollGatewayBase, IL1ERC1155Gateway {
/**********
* Events *
**********/
/**********
* Events *
**********/
/// @notice Emitted when token mapping for ERC1155 token is updated.
/// @param _l1Token The address of ERC1155 token in layer 1.
/// @param _l1Token The address of corresponding ERC1155 token in layer 2.
event UpdateTokenMapping(address _l1Token, address _l2Token);
/// @notice Emitted when token mapping for ERC1155 token is updated.
/// @param _l1Token The address of ERC1155 token in layer 1.
/// @param _l1Token The address of corresponding ERC1155 token in layer 2.
event UpdateTokenMapping(address _l1Token, address _l2Token);
/*************
* Variables *
*************/
/*************
* Variables *
*************/
/// @notice Mapping from l1 token address to l2 token address for ERC1155 NFT.
mapping(address => address) public tokenMapping;
/// @notice Mapping from l1 token address to l2 token address for ERC1155 NFT.
mapping(address => address) public tokenMapping;
/***************
* Constructor *
***************/
/***************
* Constructor *
***************/
/// @notice Initialize the storage of L1ERC1155Gateway.
/// @param _counterpart The address of L2ERC1155Gateway in L2.
/// @param _messenger The address of L1ScrollMessenger.
function initialize(address _counterpart, address _messenger) external initializer {
OwnableUpgradeable.__Ownable_init();
ScrollGatewayBase._initialize(_counterpart, address(0), _messenger);
}
/****************************
* Public Mutated Functions *
****************************/
/// @inheritdoc IL1ERC1155Gateway
function depositERC1155(
address _token,
uint256 _tokenId,
uint256 _amount,
uint256 _gasLimit
) external payable override {
_depositERC1155(_token, msg.sender, _tokenId, _amount, _gasLimit);
}
/// @inheritdoc IL1ERC1155Gateway
function depositERC1155(
address _token,
address _to,
uint256 _tokenId,
uint256 _amount,
uint256 _gasLimit
) external payable override {
_depositERC1155(_token, _to, _tokenId, _amount, _gasLimit);
}
/// @inheritdoc IL1ERC1155Gateway
function batchDepositERC1155(
address _token,
uint256[] calldata _tokenIds,
uint256[] calldata _amounts,
uint256 _gasLimit
) external payable override {
_batchDepositERC1155(_token, msg.sender, _tokenIds, _amounts, _gasLimit);
}
/// @inheritdoc IL1ERC1155Gateway
function batchDepositERC1155(
address _token,
address _to,
uint256[] calldata _tokenIds,
uint256[] calldata _amounts,
uint256 _gasLimit
) external payable override {
_batchDepositERC1155(_token, _to, _tokenIds, _amounts, _gasLimit);
}
/// @inheritdoc IL1ERC1155Gateway
function finalizeWithdrawERC1155(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256 _tokenId,
uint256 _amount
) external override nonReentrant onlyCallByCounterpart {
require(_l2Token == tokenMapping[_l1Token], "l2 token mismatch");
IERC1155Upgradeable(_l1Token).safeTransferFrom(address(this), _to, _tokenId, _amount, "");
emit FinalizeWithdrawERC1155(_l1Token, _l2Token, _from, _to, _tokenId, _amount);
}
/// @inheritdoc IL1ERC1155Gateway
function finalizeBatchWithdrawERC1155(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256[] calldata _tokenIds,
uint256[] calldata _amounts
) external override nonReentrant onlyCallByCounterpart {
require(_l2Token == tokenMapping[_l1Token], "l2 token mismatch");
IERC1155Upgradeable(_l1Token).safeBatchTransferFrom(address(this), _to, _tokenIds, _amounts, "");
emit FinalizeBatchWithdrawERC1155(_l1Token, _l2Token, _from, _to, _tokenIds, _amounts);
}
/************************
* Restricted Functions *
************************/
/// @notice Update layer 2 to layer 2 token mapping.
/// @param _l1Token The address of ERC1155 token in layer 1.
/// @param _l1Token The address of corresponding ERC1155 token in layer 2.
function updateTokenMapping(address _l1Token, address _l2Token) external onlyOwner {
require(_l2Token != address(0), "map to zero address");
tokenMapping[_l1Token] = _l2Token;
emit UpdateTokenMapping(_l1Token, _l2Token);
}
/**********************
* Internal Functions *
**********************/
/// @dev Internal function to deposit ERC1155 NFT to layer 2.
/// @param _token The address of ERC1155 NFT in layer 1.
/// @param _to The address of recipient in layer 2.
/// @param _tokenId The token id to deposit.
/// @param _amount The amount of token to deposit.
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
function _depositERC1155(
address _token,
address _to,
uint256 _tokenId,
uint256 _amount,
uint256 _gasLimit
) internal nonReentrant {
require(_amount > 0, "deposit zero amount");
address _l2Token = tokenMapping[_token];
require(_l2Token != address(0), "token not supported");
// 1. transfer token to this contract
IERC1155Upgradeable(_token).safeTransferFrom(msg.sender, address(this), _tokenId, _amount, "");
// 2. Generate message passed to L2ERC1155Gateway.
bytes memory _message = abi.encodeWithSelector(
IL2ERC1155Gateway.finalizeDepositERC1155.selector,
_token,
_l2Token,
msg.sender,
_to,
_tokenId,
_amount
);
// 3. Send message to L1ScrollMessenger.
IL1ScrollMessenger(messenger).sendMessage{ value: msg.value }(counterpart, 0, _message, _gasLimit);
emit DepositERC1155(_token, _l2Token, msg.sender, _to, _tokenId, _amount);
}
/// @dev Internal function to batch deposit ERC1155 NFT to layer 2.
/// @param _token The address of ERC1155 NFT in layer 1.
/// @param _to The address of recipient in layer 2.
/// @param _tokenIds The list of token ids to deposit.
/// @param _amounts The list of corresponding number of token to deposit.
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
function _batchDepositERC1155(
address _token,
address _to,
uint256[] calldata _tokenIds,
uint256[] calldata _amounts,
uint256 _gasLimit
) internal nonReentrant {
require(_tokenIds.length > 0, "no token to deposit");
require(_tokenIds.length == _amounts.length, "length mismatch");
for (uint256 i = 0; i < _amounts.length; i++) {
require(_amounts[i] > 0, "deposit zero amount");
/// @notice Initialize the storage of L1ERC1155Gateway.
/// @param _counterpart The address of L2ERC1155Gateway in L2.
/// @param _messenger The address of L1ScrollMessenger.
function initialize(address _counterpart, address _messenger) external initializer {
OwnableUpgradeable.__Ownable_init();
ScrollGatewayBase._initialize(_counterpart, address(0), _messenger);
}
address _l2Token = tokenMapping[_token];
require(_l2Token != address(0), "token not supported");
/*****************************
* Public Mutating Functions *
*****************************/
// 1. transfer token to this contract
IERC1155Upgradeable(_token).safeBatchTransferFrom(msg.sender, address(this), _tokenIds, _amounts, "");
/// @inheritdoc IL1ERC1155Gateway
function depositERC1155(
address _token,
uint256 _tokenId,
uint256 _amount,
uint256 _gasLimit
) external payable override {
_depositERC1155(_token, msg.sender, _tokenId, _amount, _gasLimit);
}
// 2. Generate message passed to L2ERC1155Gateway.
bytes memory _message = abi.encodeWithSelector(
IL2ERC1155Gateway.finalizeBatchDepositERC1155.selector,
_token,
_l2Token,
msg.sender,
_to,
_tokenIds,
_amounts
);
/// @inheritdoc IL1ERC1155Gateway
function depositERC1155(
address _token,
address _to,
uint256 _tokenId,
uint256 _amount,
uint256 _gasLimit
) external payable override {
_depositERC1155(_token, _to, _tokenId, _amount, _gasLimit);
}
// 3. Send message to L1ScrollMessenger.
IL1ScrollMessenger(messenger).sendMessage{ value: msg.value }(counterpart, 0, _message, _gasLimit);
/// @inheritdoc IL1ERC1155Gateway
function batchDepositERC1155(
address _token,
uint256[] calldata _tokenIds,
uint256[] calldata _amounts,
uint256 _gasLimit
) external payable override {
_batchDepositERC1155(_token, msg.sender, _tokenIds, _amounts, _gasLimit);
}
emit BatchDepositERC1155(_token, _l2Token, msg.sender, _to, _tokenIds, _amounts);
}
/// @inheritdoc IL1ERC1155Gateway
function batchDepositERC1155(
address _token,
address _to,
uint256[] calldata _tokenIds,
uint256[] calldata _amounts,
uint256 _gasLimit
) external payable override {
_batchDepositERC1155(_token, _to, _tokenIds, _amounts, _gasLimit);
}
/// @inheritdoc IL1ERC1155Gateway
function finalizeWithdrawERC1155(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256 _tokenId,
uint256 _amount
) external override nonReentrant onlyCallByCounterpart {
require(_l2Token == tokenMapping[_l1Token], "l2 token mismatch");
IERC1155Upgradeable(_l1Token).safeTransferFrom(address(this), _to, _tokenId, _amount, "");
emit FinalizeWithdrawERC1155(_l1Token, _l2Token, _from, _to, _tokenId, _amount);
}
/// @inheritdoc IL1ERC1155Gateway
function finalizeBatchWithdrawERC1155(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256[] calldata _tokenIds,
uint256[] calldata _amounts
) external override nonReentrant onlyCallByCounterpart {
require(_l2Token == tokenMapping[_l1Token], "l2 token mismatch");
IERC1155Upgradeable(_l1Token).safeBatchTransferFrom(address(this), _to, _tokenIds, _amounts, "");
emit FinalizeBatchWithdrawERC1155(_l1Token, _l2Token, _from, _to, _tokenIds, _amounts);
}
/************************
* Restricted Functions *
************************/
/// @notice Update layer 2 to layer 2 token mapping.
/// @param _l1Token The address of ERC1155 token in layer 1.
/// @param _l1Token The address of corresponding ERC1155 token in layer 2.
function updateTokenMapping(address _l1Token, address _l2Token) external onlyOwner {
require(_l2Token != address(0), "map to zero address");
tokenMapping[_l1Token] = _l2Token;
emit UpdateTokenMapping(_l1Token, _l2Token);
}
/**********************
* Internal Functions *
**********************/
/// @dev Internal function to deposit ERC1155 NFT to layer 2.
/// @param _token The address of ERC1155 NFT in layer 1.
/// @param _to The address of recipient in layer 2.
/// @param _tokenId The token id to deposit.
/// @param _amount The amount of token to deposit.
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
function _depositERC1155(
address _token,
address _to,
uint256 _tokenId,
uint256 _amount,
uint256 _gasLimit
) internal nonReentrant {
require(_amount > 0, "deposit zero amount");
address _l2Token = tokenMapping[_token];
require(_l2Token != address(0), "token not supported");
// 1. transfer token to this contract
IERC1155Upgradeable(_token).safeTransferFrom(msg.sender, address(this), _tokenId, _amount, "");
// 2. Generate message passed to L2ERC1155Gateway.
bytes memory _message = abi.encodeWithSelector(
IL2ERC1155Gateway.finalizeDepositERC1155.selector,
_token,
_l2Token,
msg.sender,
_to,
_tokenId,
_amount
);
// 3. Send message to L1ScrollMessenger.
IL1ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, _gasLimit);
emit DepositERC1155(_token, _l2Token, msg.sender, _to, _tokenId, _amount);
}
/// @dev Internal function to batch deposit ERC1155 NFT to layer 2.
/// @param _token The address of ERC1155 NFT in layer 1.
/// @param _to The address of recipient in layer 2.
/// @param _tokenIds The list of token ids to deposit.
/// @param _amounts The list of corresponding number of token to deposit.
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
function _batchDepositERC1155(
address _token,
address _to,
uint256[] calldata _tokenIds,
uint256[] calldata _amounts,
uint256 _gasLimit
) internal nonReentrant {
require(_tokenIds.length > 0, "no token to deposit");
require(_tokenIds.length == _amounts.length, "length mismatch");
for (uint256 i = 0; i < _amounts.length; i++) {
require(_amounts[i] > 0, "deposit zero amount");
}
address _l2Token = tokenMapping[_token];
require(_l2Token != address(0), "token not supported");
// 1. transfer token to this contract
IERC1155Upgradeable(_token).safeBatchTransferFrom(msg.sender, address(this), _tokenIds, _amounts, "");
// 2. Generate message passed to L2ERC1155Gateway.
bytes memory _message = abi.encodeWithSelector(
IL2ERC1155Gateway.finalizeBatchDepositERC1155.selector,
_token,
_l2Token,
msg.sender,
_to,
_tokenIds,
_amounts
);
// 3. Send message to L1ScrollMessenger.
IL1ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, _gasLimit);
emit BatchDepositERC1155(_token, _l2Token, msg.sender, _to, _tokenIds, _amounts);
}
}

View File

@@ -2,61 +2,61 @@
pragma solidity ^0.8.0;
import { IL1ERC20Gateway } from "./IL1ERC20Gateway.sol";
import {IL1ERC20Gateway} from "./IL1ERC20Gateway.sol";
// solhint-disable no-empty-blocks
abstract contract L1ERC20Gateway is IL1ERC20Gateway {
/****************************
* Public Mutated Functions *
****************************/
/*****************************
* Public Mutating Functions *
*****************************/
/// @inheritdoc IL1ERC20Gateway
function depositERC20(
address _token,
uint256 _amount,
uint256 _gasLimit
) external payable override {
_deposit(_token, msg.sender, _amount, new bytes(0), _gasLimit);
}
/// @inheritdoc IL1ERC20Gateway
function depositERC20(
address _token,
uint256 _amount,
uint256 _gasLimit
) external payable override {
_deposit(_token, msg.sender, _amount, new bytes(0), _gasLimit);
}
/// @inheritdoc IL1ERC20Gateway
function depositERC20(
address _token,
address _to,
uint256 _amount,
uint256 _gasLimit
) external payable override {
_deposit(_token, _to, _amount, new bytes(0), _gasLimit);
}
/// @inheritdoc IL1ERC20Gateway
function depositERC20(
address _token,
address _to,
uint256 _amount,
uint256 _gasLimit
) external payable override {
_deposit(_token, _to, _amount, new bytes(0), _gasLimit);
}
/// @inheritdoc IL1ERC20Gateway
function depositERC20AndCall(
address _token,
address _to,
uint256 _amount,
bytes memory _data,
uint256 _gasLimit
) external payable override {
_deposit(_token, _to, _amount, _data, _gasLimit);
}
/// @inheritdoc IL1ERC20Gateway
function depositERC20AndCall(
address _token,
address _to,
uint256 _amount,
bytes memory _data,
uint256 _gasLimit
) external payable override {
_deposit(_token, _to, _amount, _data, _gasLimit);
}
/**********************
* Internal Functions *
**********************/
/**********************
* Internal Functions *
**********************/
/// @dev Internal function to do all the deposit operations.
///
/// @param _token The token to deposit.
/// @param _to The recipient address to recieve the token in L2.
/// @param _amount The amount of token to deposit.
/// @param _data Optional data to forward to recipient's account.
/// @param _gasLimit Gas limit required to complete the deposit on L2.
function _deposit(
address _token,
address _to,
uint256 _amount,
bytes memory _data,
uint256 _gasLimit
) internal virtual;
/// @dev Internal function to do all the deposit operations.
///
/// @param _token The token to deposit.
/// @param _to The recipient address to recieve the token in L2.
/// @param _amount The amount of token to deposit.
/// @param _data Optional data to forward to recipient's account.
/// @param _gasLimit Gas limit required to complete the deposit on L2.
function _deposit(
address _token,
address _to,
uint256 _amount,
bytes memory _data,
uint256 _gasLimit
) internal virtual;
}

View File

@@ -2,15 +2,15 @@
pragma solidity ^0.8.0;
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { IERC721Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol";
import { ERC721HolderUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/utils/ERC721HolderUpgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {IERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol";
import {ERC721HolderUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/utils/ERC721HolderUpgradeable.sol";
import { IL2ERC721Gateway } from "../../L2/gateways/IL2ERC721Gateway.sol";
import { IL1ScrollMessenger } from "../IL1ScrollMessenger.sol";
import { IL1ERC721Gateway } from "./IL1ERC721Gateway.sol";
import {IL2ERC721Gateway} from "../../L2/gateways/IL2ERC721Gateway.sol";
import {IL1ScrollMessenger} from "../IL1ScrollMessenger.sol";
import {IL1ERC721Gateway} from "./IL1ERC721Gateway.sol";
import { ScrollGatewayBase } from "../../libraries/gateway/ScrollGatewayBase.sol";
import {ScrollGatewayBase} from "../../libraries/gateway/ScrollGatewayBase.sol";
/// @title L1ERC721Gateway
/// @notice The `L1ERC721Gateway` is used to deposit ERC721 compatible NFT in layer 1 and
@@ -21,194 +21,194 @@ import { ScrollGatewayBase } from "../../libraries/gateway/ScrollGatewayBase.sol
/// This will be changed if we have more specific scenarios.
// @todo Current implementation doesn't support calling from `L1GatewayRouter`.
contract L1ERC721Gateway is OwnableUpgradeable, ERC721HolderUpgradeable, ScrollGatewayBase, IL1ERC721Gateway {
/**********
* Events *
**********/
/**********
* Events *
**********/
/// @notice Emitted when token mapping for ERC721 token is updated.
/// @param _l1Token The address of ERC721 token in layer 1.
/// @param _l1Token The address of corresponding ERC721 token in layer 2.
event UpdateTokenMapping(address _l1Token, address _l2Token);
/// @notice Emitted when token mapping for ERC721 token is updated.
/// @param _l1Token The address of ERC721 token in layer 1.
/// @param _l1Token The address of corresponding ERC721 token in layer 2.
event UpdateTokenMapping(address _l1Token, address _l2Token);
/*************
* Variables *
*************/
/*************
* Variables *
*************/
/// @notice Mapping from l1 token address to l2 token address for ERC721 NFT.
mapping(address => address) public tokenMapping;
/// @notice Mapping from l1 token address to l2 token address for ERC721 NFT.
mapping(address => address) public tokenMapping;
/***************
* Constructor *
***************/
/***************
* Constructor *
***************/
/// @notice Initialize the storage of L1ERC721Gateway.
/// @param _counterpart The address of L2ERC721Gateway in L2.
/// @param _messenger The address of L1ScrollMessenger.
function initialize(address _counterpart, address _messenger) external initializer {
OwnableUpgradeable.__Ownable_init();
ScrollGatewayBase._initialize(_counterpart, address(0), _messenger);
}
/****************************
* Public Mutated Functions *
****************************/
/// @inheritdoc IL1ERC721Gateway
function depositERC721(
address _token,
uint256 _tokenId,
uint256 _gasLimit
) external payable override {
_depositERC721(_token, msg.sender, _tokenId, _gasLimit);
}
/// @inheritdoc IL1ERC721Gateway
function depositERC721(
address _token,
address _to,
uint256 _tokenId,
uint256 _gasLimit
) external payable override {
_depositERC721(_token, _to, _tokenId, _gasLimit);
}
/// @inheritdoc IL1ERC721Gateway
function batchDepositERC721(
address _token,
uint256[] calldata _tokenIds,
uint256 _gasLimit
) external payable override {
_batchDepositERC721(_token, msg.sender, _tokenIds, _gasLimit);
}
/// @inheritdoc IL1ERC721Gateway
function batchDepositERC721(
address _token,
address _to,
uint256[] calldata _tokenIds,
uint256 _gasLimit
) external payable override {
_batchDepositERC721(_token, _to, _tokenIds, _gasLimit);
}
/// @inheritdoc IL1ERC721Gateway
function finalizeWithdrawERC721(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256 _tokenId
) external override nonReentrant onlyCallByCounterpart {
require(_l2Token == tokenMapping[_l1Token], "l2 token mismatch");
IERC721Upgradeable(_l1Token).safeTransferFrom(address(this), _to, _tokenId);
emit FinalizeWithdrawERC721(_l1Token, _l2Token, _from, _to, _tokenId);
}
/// @inheritdoc IL1ERC721Gateway
function finalizeBatchWithdrawERC721(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256[] calldata _tokenIds
) external override nonReentrant onlyCallByCounterpart {
require(_l2Token == tokenMapping[_l1Token], "l2 token mismatch");
for (uint256 i = 0; i < _tokenIds.length; i++) {
IERC721Upgradeable(_l1Token).safeTransferFrom(address(this), _to, _tokenIds[i]);
/// @notice Initialize the storage of L1ERC721Gateway.
/// @param _counterpart The address of L2ERC721Gateway in L2.
/// @param _messenger The address of L1ScrollMessenger.
function initialize(address _counterpart, address _messenger) external initializer {
OwnableUpgradeable.__Ownable_init();
ScrollGatewayBase._initialize(_counterpart, address(0), _messenger);
}
emit FinalizeBatchWithdrawERC721(_l1Token, _l2Token, _from, _to, _tokenIds);
}
/*****************************
* Public Mutating Functions *
*****************************/
/************************
* Restricted Functions *
************************/
/// @notice Update layer 2 to layer 2 token mapping.
/// @param _l1Token The address of ERC721 token in layer 1.
/// @param _l1Token The address of corresponding ERC721 token in layer 2.
function updateTokenMapping(address _l1Token, address _l2Token) external onlyOwner {
require(_l2Token != address(0), "map to zero address");
tokenMapping[_l1Token] = _l2Token;
emit UpdateTokenMapping(_l1Token, _l2Token);
}
/**********************
* Internal Functions *
**********************/
/// @dev Internal function to deposit ERC721 NFT to layer 2.
/// @param _token The address of ERC721 NFT in layer 1.
/// @param _to The address of recipient in layer 2.
/// @param _tokenId The token id to deposit.
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
function _depositERC721(
address _token,
address _to,
uint256 _tokenId,
uint256 _gasLimit
) internal nonReentrant {
address _l2Token = tokenMapping[_token];
require(_l2Token != address(0), "token not supported");
// 1. transfer token to this contract
IERC721Upgradeable(_token).safeTransferFrom(msg.sender, address(this), _tokenId);
// 2. Generate message passed to L2ERC721Gateway.
bytes memory _message = abi.encodeWithSelector(
IL2ERC721Gateway.finalizeDepositERC721.selector,
_token,
_l2Token,
msg.sender,
_to,
_tokenId
);
// 3. Send message to L1ScrollMessenger.
IL1ScrollMessenger(messenger).sendMessage{ value: msg.value }(counterpart, 0, _message, _gasLimit);
emit DepositERC721(_token, _l2Token, msg.sender, _to, _tokenId);
}
/// @dev Internal function to batch deposit ERC721 NFT to layer 2.
/// @param _token The address of ERC721 NFT in layer 1.
/// @param _to The address of recipient in layer 2.
/// @param _tokenIds The list of token ids to deposit.
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
function _batchDepositERC721(
address _token,
address _to,
uint256[] calldata _tokenIds,
uint256 _gasLimit
) internal nonReentrant {
require(_tokenIds.length > 0, "no token to deposit");
address _l2Token = tokenMapping[_token];
require(_l2Token != address(0), "token not supported");
// 1. transfer token to this contract
for (uint256 i = 0; i < _tokenIds.length; i++) {
IERC721Upgradeable(_token).safeTransferFrom(msg.sender, address(this), _tokenIds[i]);
/// @inheritdoc IL1ERC721Gateway
function depositERC721(
address _token,
uint256 _tokenId,
uint256 _gasLimit
) external payable override {
_depositERC721(_token, msg.sender, _tokenId, _gasLimit);
}
// 2. Generate message passed to L2ERC721Gateway.
bytes memory _message = abi.encodeWithSelector(
IL2ERC721Gateway.finalizeBatchDepositERC721.selector,
_token,
_l2Token,
msg.sender,
_to,
_tokenIds
);
/// @inheritdoc IL1ERC721Gateway
function depositERC721(
address _token,
address _to,
uint256 _tokenId,
uint256 _gasLimit
) external payable override {
_depositERC721(_token, _to, _tokenId, _gasLimit);
}
// 3. Send message to L1ScrollMessenger.
IL1ScrollMessenger(messenger).sendMessage{ value: msg.value }(counterpart, 0, _message, _gasLimit);
/// @inheritdoc IL1ERC721Gateway
function batchDepositERC721(
address _token,
uint256[] calldata _tokenIds,
uint256 _gasLimit
) external payable override {
_batchDepositERC721(_token, msg.sender, _tokenIds, _gasLimit);
}
emit BatchDepositERC721(_token, _l2Token, msg.sender, _to, _tokenIds);
}
/// @inheritdoc IL1ERC721Gateway
function batchDepositERC721(
address _token,
address _to,
uint256[] calldata _tokenIds,
uint256 _gasLimit
) external payable override {
_batchDepositERC721(_token, _to, _tokenIds, _gasLimit);
}
/// @inheritdoc IL1ERC721Gateway
function finalizeWithdrawERC721(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256 _tokenId
) external override nonReentrant onlyCallByCounterpart {
require(_l2Token == tokenMapping[_l1Token], "l2 token mismatch");
IERC721Upgradeable(_l1Token).safeTransferFrom(address(this), _to, _tokenId);
emit FinalizeWithdrawERC721(_l1Token, _l2Token, _from, _to, _tokenId);
}
/// @inheritdoc IL1ERC721Gateway
function finalizeBatchWithdrawERC721(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256[] calldata _tokenIds
) external override nonReentrant onlyCallByCounterpart {
require(_l2Token == tokenMapping[_l1Token], "l2 token mismatch");
for (uint256 i = 0; i < _tokenIds.length; i++) {
IERC721Upgradeable(_l1Token).safeTransferFrom(address(this), _to, _tokenIds[i]);
}
emit FinalizeBatchWithdrawERC721(_l1Token, _l2Token, _from, _to, _tokenIds);
}
/************************
* Restricted Functions *
************************/
/// @notice Update layer 2 to layer 2 token mapping.
/// @param _l1Token The address of ERC721 token in layer 1.
/// @param _l1Token The address of corresponding ERC721 token in layer 2.
function updateTokenMapping(address _l1Token, address _l2Token) external onlyOwner {
require(_l2Token != address(0), "map to zero address");
tokenMapping[_l1Token] = _l2Token;
emit UpdateTokenMapping(_l1Token, _l2Token);
}
/**********************
* Internal Functions *
**********************/
/// @dev Internal function to deposit ERC721 NFT to layer 2.
/// @param _token The address of ERC721 NFT in layer 1.
/// @param _to The address of recipient in layer 2.
/// @param _tokenId The token id to deposit.
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
function _depositERC721(
address _token,
address _to,
uint256 _tokenId,
uint256 _gasLimit
) internal nonReentrant {
address _l2Token = tokenMapping[_token];
require(_l2Token != address(0), "token not supported");
// 1. transfer token to this contract
IERC721Upgradeable(_token).safeTransferFrom(msg.sender, address(this), _tokenId);
// 2. Generate message passed to L2ERC721Gateway.
bytes memory _message = abi.encodeWithSelector(
IL2ERC721Gateway.finalizeDepositERC721.selector,
_token,
_l2Token,
msg.sender,
_to,
_tokenId
);
// 3. Send message to L1ScrollMessenger.
IL1ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, _gasLimit);
emit DepositERC721(_token, _l2Token, msg.sender, _to, _tokenId);
}
/// @dev Internal function to batch deposit ERC721 NFT to layer 2.
/// @param _token The address of ERC721 NFT in layer 1.
/// @param _to The address of recipient in layer 2.
/// @param _tokenIds The list of token ids to deposit.
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
function _batchDepositERC721(
address _token,
address _to,
uint256[] calldata _tokenIds,
uint256 _gasLimit
) internal nonReentrant {
require(_tokenIds.length > 0, "no token to deposit");
address _l2Token = tokenMapping[_token];
require(_l2Token != address(0), "token not supported");
// 1. transfer token to this contract
for (uint256 i = 0; i < _tokenIds.length; i++) {
IERC721Upgradeable(_token).safeTransferFrom(msg.sender, address(this), _tokenIds[i]);
}
// 2. Generate message passed to L2ERC721Gateway.
bytes memory _message = abi.encodeWithSelector(
IL2ERC721Gateway.finalizeBatchDepositERC721.selector,
_token,
_l2Token,
msg.sender,
_to,
_tokenIds
);
// 3. Send message to L1ScrollMessenger.
IL1ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, _gasLimit);
emit BatchDepositERC721(_token, _l2Token, msg.sender, _to, _tokenIds);
}
}

View File

@@ -2,13 +2,13 @@
pragma solidity ^0.8.0;
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import { IL2ETHGateway } from "../../L2/gateways/IL2ETHGateway.sol";
import { IL1ScrollMessenger } from "../IL1ScrollMessenger.sol";
import { IL1ETHGateway } from "./IL1ETHGateway.sol";
import {IL2ETHGateway} from "../../L2/gateways/IL2ETHGateway.sol";
import {IL1ScrollMessenger} from "../IL1ScrollMessenger.sol";
import {IL1ETHGateway} from "./IL1ETHGateway.sol";
import { ScrollGatewayBase } from "../../libraries/gateway/ScrollGatewayBase.sol";
import {ScrollGatewayBase} from "../../libraries/gateway/ScrollGatewayBase.sol";
/// @title L1ETHGateway
/// @notice The `L1ETHGateway` is used to deposit ETH in layer 1 and
@@ -16,103 +16,103 @@ import { ScrollGatewayBase } from "../../libraries/gateway/ScrollGatewayBase.sol
/// @dev The deposited ETH tokens are held in this gateway. On finalizing withdraw, the corresponding
/// ETH will be transfer to the recipient directly.
contract L1ETHGateway is Initializable, ScrollGatewayBase, IL1ETHGateway {
/***************
* Constructor *
***************/
/***************
* Constructor *
***************/
/// @notice Initialize the storage of L1ETHGateway.
/// @param _counterpart The address of L2ETHGateway in L2.
/// @param _router The address of L1GatewayRouter.
/// @param _messenger The address of L1ScrollMessenger.
function initialize(
address _counterpart,
address _router,
address _messenger
) external initializer {
require(_router != address(0), "zero router address");
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
}
/****************************
* Public Mutated Functions *
****************************/
/// @inheritdoc IL1ETHGateway
function depositETH(uint256 _amount, uint256 _gasLimit) external payable override {
_deposit(msg.sender, _amount, new bytes(0), _gasLimit);
}
/// @inheritdoc IL1ETHGateway
function depositETH(
address _to,
uint256 _amount,
uint256 _gasLimit
) public payable override {
_deposit(_to, _amount, new bytes(0), _gasLimit);
}
/// @inheritdoc IL1ETHGateway
function depositETHAndCall(
address _to,
uint256 _amount,
bytes calldata _data,
uint256 _gasLimit
) external payable override {
_deposit(_to, _amount, _data, _gasLimit);
}
/// @inheritdoc IL1ETHGateway
function finalizeWithdrawETH(
address _from,
address _to,
uint256 _amount,
bytes calldata _data
) external payable override onlyCallByCounterpart {
// @note can possible trigger reentrant call to this contract or messenger,
// but it seems not a big problem.
// solhint-disable-next-line avoid-low-level-calls
(bool _success, ) = _to.call{ value: _amount }("");
require(_success, "ETH transfer failed");
// @todo farward _data to `_to` in near future.
emit FinalizeWithdrawETH(_from, _to, _amount, _data);
}
/**********************
* Internal Functions *
**********************/
/// @dev The internal ETH deposit implementation.
/// @param _to The address of recipient's account on L2.
/// @param _amount The amount of ETH to be deposited.
/// @param _data Optional data to forward to recipient's account.
/// @param _gasLimit Gas limit required to complete the deposit on L2.
function _deposit(
address _to,
uint256 _amount,
bytes memory _data,
uint256 _gasLimit
) internal nonReentrant {
require(_amount > 0, "deposit zero eth");
// 1. Extract real sender if this call is from L1GatewayRouter.
address _from = msg.sender;
if (router == msg.sender) {
(_from, _data) = abi.decode(_data, (address, bytes));
/// @notice Initialize the storage of L1ETHGateway.
/// @param _counterpart The address of L2ETHGateway in L2.
/// @param _router The address of L1GatewayRouter.
/// @param _messenger The address of L1ScrollMessenger.
function initialize(
address _counterpart,
address _router,
address _messenger
) external initializer {
require(_router != address(0), "zero router address");
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
}
// 2. Generate message passed to L1ScrollMessenger.
bytes memory _message = abi.encodeWithSelector(
IL2ETHGateway.finalizeDepositETH.selector,
_from,
_to,
_amount,
_data
);
/*****************************
* Public Mutating Functions *
*****************************/
IL1ScrollMessenger(messenger).sendMessage{ value: msg.value }(counterpart, _amount, _message, _gasLimit);
/// @inheritdoc IL1ETHGateway
function depositETH(uint256 _amount, uint256 _gasLimit) external payable override {
_deposit(msg.sender, _amount, new bytes(0), _gasLimit);
}
emit DepositETH(_from, _to, _amount, _data);
}
/// @inheritdoc IL1ETHGateway
function depositETH(
address _to,
uint256 _amount,
uint256 _gasLimit
) public payable override {
_deposit(_to, _amount, new bytes(0), _gasLimit);
}
/// @inheritdoc IL1ETHGateway
function depositETHAndCall(
address _to,
uint256 _amount,
bytes calldata _data,
uint256 _gasLimit
) external payable override {
_deposit(_to, _amount, _data, _gasLimit);
}
/// @inheritdoc IL1ETHGateway
function finalizeWithdrawETH(
address _from,
address _to,
uint256 _amount,
bytes calldata _data
) external payable override onlyCallByCounterpart {
// @note can possible trigger reentrant call to this contract or messenger,
// but it seems not a big problem.
// solhint-disable-next-line avoid-low-level-calls
(bool _success, ) = _to.call{value: _amount}("");
require(_success, "ETH transfer failed");
// @todo farward _data to `_to` in near future.
emit FinalizeWithdrawETH(_from, _to, _amount, _data);
}
/**********************
* Internal Functions *
**********************/
/// @dev The internal ETH deposit implementation.
/// @param _to The address of recipient's account on L2.
/// @param _amount The amount of ETH to be deposited.
/// @param _data Optional data to forward to recipient's account.
/// @param _gasLimit Gas limit required to complete the deposit on L2.
function _deposit(
address _to,
uint256 _amount,
bytes memory _data,
uint256 _gasLimit
) internal nonReentrant {
require(_amount > 0, "deposit zero eth");
// 1. Extract real sender if this call is from L1GatewayRouter.
address _from = msg.sender;
if (router == msg.sender) {
(_from, _data) = abi.decode(_data, (address, bytes));
}
// 2. Generate message passed to L1ScrollMessenger.
bytes memory _message = abi.encodeWithSelector(
IL2ETHGateway.finalizeDepositETH.selector,
_from,
_to,
_amount,
_data
);
IL1ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, _amount, _message, _gasLimit);
emit DepositETH(_from, _to, _amount, _data);
}
}

View File

@@ -2,14 +2,14 @@
pragma solidity ^0.8.0;
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { IL2GatewayRouter } from "../../L2/gateways/IL2GatewayRouter.sol";
import { IScrollGateway } from "../../libraries/gateway/IScrollGateway.sol";
import { IL1ScrollMessenger } from "../IL1ScrollMessenger.sol";
import { IL1ETHGateway } from "./IL1ETHGateway.sol";
import { IL1ERC20Gateway } from "./IL1ERC20Gateway.sol";
import { IL1GatewayRouter } from "./IL1GatewayRouter.sol";
import {IL2GatewayRouter} from "../../L2/gateways/IL2GatewayRouter.sol";
import {IScrollGateway} from "../../libraries/gateway/IScrollGateway.sol";
import {IL1ScrollMessenger} from "../IL1ScrollMessenger.sol";
import {IL1ETHGateway} from "./IL1ETHGateway.sol";
import {IL1ERC20Gateway} from "./IL1ERC20Gateway.sol";
import {IL1GatewayRouter} from "./IL1GatewayRouter.sol";
/// @title L1GatewayRouter
/// @notice The `L1GatewayRouter` is the main entry for depositing Ether and ERC20 tokens.
@@ -17,198 +17,198 @@ import { IL1GatewayRouter } from "./IL1GatewayRouter.sol";
/// @dev One can also use this contract to query L1/L2 token address mapping.
/// In the future, ERC-721 and ERC-1155 tokens will be added to the router too.
contract L1GatewayRouter is OwnableUpgradeable, IL1GatewayRouter {
/*************
* Variables *
*************/
/*************
* Variables *
*************/
/// @notice The address of L1ETHGateway.
address public ethGateway;
/// @notice The address of L1ETHGateway.
address public ethGateway;
/// @notice The addess of default ERC20 gateway, normally the L1StandardERC20Gateway contract.
address public defaultERC20Gateway;
/// @notice The addess of default ERC20 gateway, normally the L1StandardERC20Gateway contract.
address public defaultERC20Gateway;
/// @notice Mapping from ERC20 token address to corresponding L1ERC20Gateway.
// solhint-disable-next-line var-name-mixedcase
mapping(address => address) public ERC20Gateway;
/// @notice Mapping from ERC20 token address to corresponding L1ERC20Gateway.
// solhint-disable-next-line var-name-mixedcase
mapping(address => address) public ERC20Gateway;
// @todo: add ERC721/ERC1155 Gateway mapping.
// @todo: add ERC721/ERC1155 Gateway mapping.
/***************
* Constructor *
***************/
/***************
* Constructor *
***************/
/// @notice Initialize the storage of L1GatewayRouter.
/// @param _ethGateway The address of L1ETHGateway contract.
/// @param _defaultERC20Gateway The address of default ERC20 Gateway contract.
function initialize(address _ethGateway, address _defaultERC20Gateway) external initializer {
OwnableUpgradeable.__Ownable_init();
/// @notice Initialize the storage of L1GatewayRouter.
/// @param _ethGateway The address of L1ETHGateway contract.
/// @param _defaultERC20Gateway The address of default ERC20 Gateway contract.
function initialize(address _ethGateway, address _defaultERC20Gateway) external initializer {
OwnableUpgradeable.__Ownable_init();
// it can be zero during initialization
if (_defaultERC20Gateway != address(0)) {
defaultERC20Gateway = _defaultERC20Gateway;
emit SetDefaultERC20Gateway(_defaultERC20Gateway);
// it can be zero during initialization
if (_defaultERC20Gateway != address(0)) {
defaultERC20Gateway = _defaultERC20Gateway;
emit SetDefaultERC20Gateway(_defaultERC20Gateway);
}
// it can be zero during initialization
if (_ethGateway != address(0)) {
ethGateway = _ethGateway;
emit SetETHGateway(_ethGateway);
}
}
// it can be zero during initialization
if (_ethGateway != address(0)) {
ethGateway = _ethGateway;
emit SetETHGateway(_ethGateway);
}
}
/*************************
* Public View Functions *
*************************/
/*************************
* Public View Functions *
*************************/
/// @inheritdoc IL1ERC20Gateway
function getL2ERC20Address(address _l1Address) external view override returns (address) {
address _gateway = getERC20Gateway(_l1Address);
if (_gateway == address(0)) {
return address(0);
}
/// @inheritdoc IL1ERC20Gateway
function getL2ERC20Address(address _l1Address) external view override returns (address) {
address _gateway = getERC20Gateway(_l1Address);
if (_gateway == address(0)) {
return address(0);
return IL1ERC20Gateway(_gateway).getL2ERC20Address(_l1Address);
}
return IL1ERC20Gateway(_gateway).getL2ERC20Address(_l1Address);
}
/// @notice Return the corresponding gateway address for given token address.
/// @param _token The address of token to query.
function getERC20Gateway(address _token) public view returns (address) {
address _gateway = ERC20Gateway[_token];
if (_gateway == address(0)) {
_gateway = defaultERC20Gateway;
/// @notice Return the corresponding gateway address for given token address.
/// @param _token The address of token to query.
function getERC20Gateway(address _token) public view returns (address) {
address _gateway = ERC20Gateway[_token];
if (_gateway == address(0)) {
_gateway = defaultERC20Gateway;
}
return _gateway;
}
return _gateway;
}
/************************************************
* Public Mutated Functions from L1ERC20Gateway *
************************************************/
/*************************************************
* Public Mutating Functions from L1ERC20Gateway *
*************************************************/
/// @inheritdoc IL1ERC20Gateway
function depositERC20(
address _token,
uint256 _amount,
uint256 _gasLimit
) external payable override {
depositERC20AndCall(_token, msg.sender, _amount, new bytes(0), _gasLimit);
}
/// @inheritdoc IL1ERC20Gateway
function depositERC20(
address _token,
address _to,
uint256 _amount,
uint256 _gasLimit
) external payable override {
depositERC20AndCall(_token, _to, _amount, new bytes(0), _gasLimit);
}
/// @inheritdoc IL1ERC20Gateway
function depositERC20AndCall(
address _token,
address _to,
uint256 _amount,
bytes memory _data,
uint256 _gasLimit
) public payable override {
address _gateway = getERC20Gateway(_token);
require(_gateway != address(0), "no gateway available");
// encode msg.sender with _data
bytes memory _routerData = abi.encode(msg.sender, _data);
IL1ERC20Gateway(_gateway).depositERC20AndCall{ value: msg.value }(_token, _to, _amount, _routerData, _gasLimit);
}
/// @inheritdoc IL1ERC20Gateway
function finalizeWithdrawERC20(
address,
address,
address,
address,
uint256,
bytes calldata
) external payable virtual override {
revert("should never be called");
}
/**********************************************
* Public Mutated Functions from L1ETHGateway *
**********************************************/
/// @inheritdoc IL1ETHGateway
function depositETH(uint256 _amount, uint256 _gasLimit) external payable override {
depositETHAndCall(msg.sender, _amount, new bytes(0), _gasLimit);
}
/// @inheritdoc IL1ETHGateway
function depositETH(
address _to,
uint256 _amount,
uint256 _gasLimit
) external payable override {
depositETHAndCall(_to, _amount, new bytes(0), _gasLimit);
}
/// @inheritdoc IL1ETHGateway
function depositETHAndCall(
address _to,
uint256 _amount,
bytes memory _data,
uint256 _gasLimit
) public payable override {
address _gateway = ethGateway;
require(_gateway != address(0), "eth gateway available");
// encode msg.sender with _data
bytes memory _routerData = abi.encode(msg.sender, _data);
IL1ETHGateway(_gateway).depositETHAndCall{ value: msg.value }(_to, _amount, _routerData, _gasLimit);
}
/// @inheritdoc IL1ETHGateway
function finalizeWithdrawETH(
address,
address,
uint256,
bytes calldata
) external payable virtual override {
revert("should never be called");
}
/************************
* Restricted Functions *
************************/
/// @notice Update the address of ETH gateway contract.
/// @dev This function should only be called by contract owner.
/// @param _ethGateway The address to update.
function setETHGateway(address _ethGateway) external onlyOwner {
ethGateway = _ethGateway;
emit SetETHGateway(_ethGateway);
}
/// @notice Update the address of default ERC20 gateway contract.
/// @dev This function should only be called by contract owner.
/// @param _defaultERC20Gateway The address to update.
function setDefaultERC20Gateway(address _defaultERC20Gateway) external onlyOwner {
defaultERC20Gateway = _defaultERC20Gateway;
emit SetDefaultERC20Gateway(_defaultERC20Gateway);
}
/// @notice Update the mapping from token address to gateway address.
/// @dev This function should only be called by contract owner.
/// @param _tokens The list of addresses of tokens to update.
/// @param _gateways The list of addresses of gateways to update.
function setERC20Gateway(address[] memory _tokens, address[] memory _gateways) external onlyOwner {
require(_tokens.length == _gateways.length, "length mismatch");
for (uint256 i = 0; i < _tokens.length; i++) {
ERC20Gateway[_tokens[i]] = _gateways[i];
emit SetERC20Gateway(_tokens[i], _gateways[i]);
/// @inheritdoc IL1ERC20Gateway
function depositERC20(
address _token,
uint256 _amount,
uint256 _gasLimit
) external payable override {
depositERC20AndCall(_token, msg.sender, _amount, new bytes(0), _gasLimit);
}
/// @inheritdoc IL1ERC20Gateway
function depositERC20(
address _token,
address _to,
uint256 _amount,
uint256 _gasLimit
) external payable override {
depositERC20AndCall(_token, _to, _amount, new bytes(0), _gasLimit);
}
/// @inheritdoc IL1ERC20Gateway
function depositERC20AndCall(
address _token,
address _to,
uint256 _amount,
bytes memory _data,
uint256 _gasLimit
) public payable override {
address _gateway = getERC20Gateway(_token);
require(_gateway != address(0), "no gateway available");
// encode msg.sender with _data
bytes memory _routerData = abi.encode(msg.sender, _data);
IL1ERC20Gateway(_gateway).depositERC20AndCall{value: msg.value}(_token, _to, _amount, _routerData, _gasLimit);
}
/// @inheritdoc IL1ERC20Gateway
function finalizeWithdrawERC20(
address,
address,
address,
address,
uint256,
bytes calldata
) external payable virtual override {
revert("should never be called");
}
/***********************************************
* Public Mutating Functions from L1ETHGateway *
***********************************************/
/// @inheritdoc IL1ETHGateway
function depositETH(uint256 _amount, uint256 _gasLimit) external payable override {
depositETHAndCall(msg.sender, _amount, new bytes(0), _gasLimit);
}
/// @inheritdoc IL1ETHGateway
function depositETH(
address _to,
uint256 _amount,
uint256 _gasLimit
) external payable override {
depositETHAndCall(_to, _amount, new bytes(0), _gasLimit);
}
/// @inheritdoc IL1ETHGateway
function depositETHAndCall(
address _to,
uint256 _amount,
bytes memory _data,
uint256 _gasLimit
) public payable override {
address _gateway = ethGateway;
require(_gateway != address(0), "eth gateway available");
// encode msg.sender with _data
bytes memory _routerData = abi.encode(msg.sender, _data);
IL1ETHGateway(_gateway).depositETHAndCall{value: msg.value}(_to, _amount, _routerData, _gasLimit);
}
/// @inheritdoc IL1ETHGateway
function finalizeWithdrawETH(
address,
address,
uint256,
bytes calldata
) external payable virtual override {
revert("should never be called");
}
/************************
* Restricted Functions *
************************/
/// @notice Update the address of ETH gateway contract.
/// @dev This function should only be called by contract owner.
/// @param _ethGateway The address to update.
function setETHGateway(address _ethGateway) external onlyOwner {
ethGateway = _ethGateway;
emit SetETHGateway(_ethGateway);
}
/// @notice Update the address of default ERC20 gateway contract.
/// @dev This function should only be called by contract owner.
/// @param _defaultERC20Gateway The address to update.
function setDefaultERC20Gateway(address _defaultERC20Gateway) external onlyOwner {
defaultERC20Gateway = _defaultERC20Gateway;
emit SetDefaultERC20Gateway(_defaultERC20Gateway);
}
/// @notice Update the mapping from token address to gateway address.
/// @dev This function should only be called by contract owner.
/// @param _tokens The list of addresses of tokens to update.
/// @param _gateways The list of addresses of gateways to update.
function setERC20Gateway(address[] memory _tokens, address[] memory _gateways) external onlyOwner {
require(_tokens.length == _gateways.length, "length mismatch");
for (uint256 i = 0; i < _tokens.length; i++) {
ERC20Gateway[_tokens[i]] = _gateways[i];
emit SetERC20Gateway(_tokens[i], _gateways[i]);
}
}
}
}

View File

@@ -2,18 +2,18 @@
pragma solidity ^0.8.0;
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { Clones } from "@openzeppelin/contracts/proxy/Clones.sol";
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import { IERC20Metadata } from "../../interfaces/IERC20Metadata.sol";
import { IL2ERC20Gateway } from "../../L2/gateways/IL2ERC20Gateway.sol";
import { IL1ScrollMessenger } from "../IL1ScrollMessenger.sol";
import { IL1ERC20Gateway } from "./IL1ERC20Gateway.sol";
import {IERC20Metadata} from "../../interfaces/IERC20Metadata.sol";
import {IL2ERC20Gateway} from "../../L2/gateways/IL2ERC20Gateway.sol";
import {IL1ScrollMessenger} from "../IL1ScrollMessenger.sol";
import {IL1ERC20Gateway} from "./IL1ERC20Gateway.sol";
import { ScrollGatewayBase } from "../../libraries/gateway/ScrollGatewayBase.sol";
import { L1ERC20Gateway } from "./L1ERC20Gateway.sol";
import {ScrollGatewayBase} from "../../libraries/gateway/ScrollGatewayBase.sol";
import {L1ERC20Gateway} from "./L1ERC20Gateway.sol";
/// @title L1StandardERC20Gateway
/// @notice The `L1StandardERC20Gateway` is used to deposit standard ERC20 tokens in layer 1 and
@@ -22,147 +22,147 @@ import { L1ERC20Gateway } from "./L1ERC20Gateway.sol";
/// token will be transfer to the recipient directly. Any ERC20 that requires non-standard functionality
/// should use a separate gateway.
contract L1StandardERC20Gateway is Initializable, ScrollGatewayBase, L1ERC20Gateway {
using SafeERC20 for IERC20;
using SafeERC20 for IERC20;
/*************
* Variables *
*************/
/*************
* Variables *
*************/
/// @notice The address of ScrollStandardERC20 implementation in L2.
address public l2TokenImplementation;
/// @notice The address of ScrollStandardERC20 implementation in L2.
address public l2TokenImplementation;
/// @notice The address of ScrollStandardERC20Factory contract in L2.
address public l2TokenFactory;
/// @notice The address of ScrollStandardERC20Factory contract in L2.
address public l2TokenFactory;
/// @notice Mapping from l1 token address to l2 token address.
/// @dev This is not necessary, since we can compute the address directly. But, we use this mapping
/// to keep track on whether we have deployed the token in L2 using the L2ScrollStandardERC20Factory and
/// pass deploy data on first call to the token.
mapping(address => address) private tokenMapping;
/// @notice Mapping from l1 token address to l2 token address.
/// @dev This is not necessary, since we can compute the address directly. But, we use this mapping
/// to keep track on whether we have deployed the token in L2 using the L2ScrollStandardERC20Factory and
/// pass deploy data on first call to the token.
mapping(address => address) private tokenMapping;
/***************
* Constructor *
***************/
/***************
* Constructor *
***************/
/// @notice Initialize the storage of L1StandardERC20Gateway.
/// @param _counterpart The address of L2StandardERC20Gateway in L2.
/// @param _router The address of L1GatewayRouter.
/// @param _messenger The address of L1ScrollMessenger.
/// @param _l2TokenImplementation The address of ScrollStandardERC20 implementation in L2.
/// @param _l2TokenFactory The address of ScrollStandardERC20Factory contract in L2.
function initialize(
address _counterpart,
address _router,
address _messenger,
address _l2TokenImplementation,
address _l2TokenFactory
) external initializer {
require(_router != address(0), "zero router address");
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
/// @notice Initialize the storage of L1StandardERC20Gateway.
/// @param _counterpart The address of L2StandardERC20Gateway in L2.
/// @param _router The address of L1GatewayRouter.
/// @param _messenger The address of L1ScrollMessenger.
/// @param _l2TokenImplementation The address of ScrollStandardERC20 implementation in L2.
/// @param _l2TokenFactory The address of ScrollStandardERC20Factory contract in L2.
function initialize(
address _counterpart,
address _router,
address _messenger,
address _l2TokenImplementation,
address _l2TokenFactory
) external initializer {
require(_router != address(0), "zero router address");
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
require(_l2TokenImplementation != address(0), "zero implementation hash");
require(_l2TokenFactory != address(0), "zero factory address");
require(_l2TokenImplementation != address(0), "zero implementation hash");
require(_l2TokenFactory != address(0), "zero factory address");
l2TokenImplementation = _l2TokenImplementation;
l2TokenFactory = _l2TokenFactory;
}
/*************************
* Public View Functions *
*************************/
/// @inheritdoc IL1ERC20Gateway
function getL2ERC20Address(address _l1Token) public view override returns (address) {
// In StandardERC20Gateway, all corresponding l2 tokens are depoyed by Create2 with salt,
// we can calculate the l2 address directly.
bytes32 _salt = keccak256(abi.encodePacked(counterpart, keccak256(abi.encodePacked(_l1Token))));
return Clones.predictDeterministicAddress(l2TokenImplementation, _salt, l2TokenFactory);
}
/****************************
* Public Mutated Functions *
****************************/
/// @inheritdoc IL1ERC20Gateway
function finalizeWithdrawERC20(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256 _amount,
bytes calldata _data
) external payable override onlyCallByCounterpart {
require(msg.value == 0, "nonzero msg.value");
// @note can possible trigger reentrant call to this contract or messenger,
// but it seems not a big problem.
IERC20(_l1Token).safeTransfer(_to, _amount);
// @todo forward `_data` to `_to` in the near future
emit FinalizeWithdrawERC20(_l1Token, _l2Token, _from, _to, _amount, _data);
}
/**********************
* Internal Functions *
**********************/
/// @inheritdoc L1ERC20Gateway
function _deposit(
address _token,
address _to,
uint256 _amount,
bytes memory _data,
uint256 _gasLimit
) internal virtual override nonReentrant {
require(_amount > 0, "deposit zero amount");
// 1. Extract real sender if this call is from L1GatewayRouter.
address _from = msg.sender;
if (router == msg.sender) {
(_from, _data) = abi.decode(_data, (address, bytes));
l2TokenImplementation = _l2TokenImplementation;
l2TokenFactory = _l2TokenFactory;
}
// 2. Transfer token into this contract.
{
// common practice to handle fee on transfer token.
uint256 _before = IERC20(_token).balanceOf(address(this));
IERC20(_token).safeTransferFrom(_from, address(this), _amount);
uint256 _after = IERC20(_token).balanceOf(address(this));
// no unchecked here, since some weird token may return arbitrary balance.
_amount = _after - _before;
// ignore weird fee on transfer token
require(_amount > 0, "deposit zero amount");
/*************************
* Public View Functions *
*************************/
/// @inheritdoc IL1ERC20Gateway
function getL2ERC20Address(address _l1Token) public view override returns (address) {
// In StandardERC20Gateway, all corresponding l2 tokens are depoyed by Create2 with salt,
// we can calculate the l2 address directly.
bytes32 _salt = keccak256(abi.encodePacked(counterpart, keccak256(abi.encodePacked(_l1Token))));
return Clones.predictDeterministicAddress(l2TokenImplementation, _salt, l2TokenFactory);
}
// 3. Generate message passed to L2StandardERC20Gateway.
address _l2Token = tokenMapping[_token];
bytes memory _l2Data = _data;
if (_l2Token == address(0)) {
// It is a new token, compute and store mapping in storage.
_l2Token = getL2ERC20Address(_token);
tokenMapping[_token] = _l2Token;
/*****************************
* Public Mutating Functions *
*****************************/
// passing symbol/name/decimal in order to deploy in L2.
string memory _symbol = IERC20Metadata(_token).symbol();
string memory _name = IERC20Metadata(_token).name();
uint8 _decimals = IERC20Metadata(_token).decimals();
_l2Data = abi.encode(_data, abi.encode(_symbol, _name, _decimals));
/// @inheritdoc IL1ERC20Gateway
function finalizeWithdrawERC20(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256 _amount,
bytes calldata _data
) external payable override onlyCallByCounterpart {
require(msg.value == 0, "nonzero msg.value");
// @note can possible trigger reentrant call to this contract or messenger,
// but it seems not a big problem.
IERC20(_l1Token).safeTransfer(_to, _amount);
// @todo forward `_data` to `_to` in the near future
emit FinalizeWithdrawERC20(_l1Token, _l2Token, _from, _to, _amount, _data);
}
bytes memory _message = abi.encodeWithSelector(
IL2ERC20Gateway.finalizeDepositERC20.selector,
_token,
_l2Token,
_from,
_to,
_amount,
_l2Data
);
// 4. Send message to L1ScrollMessenger.
IL1ScrollMessenger(messenger).sendMessage{ value: msg.value }(counterpart, 0, _message, _gasLimit);
/**********************
* Internal Functions *
**********************/
emit DepositERC20(_token, _l2Token, _from, _to, _amount, _data);
}
/// @inheritdoc L1ERC20Gateway
function _deposit(
address _token,
address _to,
uint256 _amount,
bytes memory _data,
uint256 _gasLimit
) internal virtual override nonReentrant {
require(_amount > 0, "deposit zero amount");
// 1. Extract real sender if this call is from L1GatewayRouter.
address _from = msg.sender;
if (router == msg.sender) {
(_from, _data) = abi.decode(_data, (address, bytes));
}
// 2. Transfer token into this contract.
{
// common practice to handle fee on transfer token.
uint256 _before = IERC20(_token).balanceOf(address(this));
IERC20(_token).safeTransferFrom(_from, address(this), _amount);
uint256 _after = IERC20(_token).balanceOf(address(this));
// no unchecked here, since some weird token may return arbitrary balance.
_amount = _after - _before;
// ignore weird fee on transfer token
require(_amount > 0, "deposit zero amount");
}
// 3. Generate message passed to L2StandardERC20Gateway.
address _l2Token = tokenMapping[_token];
bytes memory _l2Data = _data;
if (_l2Token == address(0)) {
// It is a new token, compute and store mapping in storage.
_l2Token = getL2ERC20Address(_token);
tokenMapping[_token] = _l2Token;
// passing symbol/name/decimal in order to deploy in L2.
string memory _symbol = IERC20Metadata(_token).symbol();
string memory _name = IERC20Metadata(_token).name();
uint8 _decimals = IERC20Metadata(_token).decimals();
_l2Data = abi.encode(_data, abi.encode(_symbol, _name, _decimals));
}
bytes memory _message = abi.encodeWithSelector(
IL2ERC20Gateway.finalizeDepositERC20.selector,
_token,
_l2Token,
_from,
_to,
_amount,
_l2Data
);
// 4. Send message to L1ScrollMessenger.
IL1ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, _gasLimit);
emit DepositERC20(_token, _l2Token, _from, _to, _amount, _data);
}
}

View File

@@ -2,17 +2,17 @@
pragma solidity ^0.8.0;
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IWETH } from "../../interfaces/IWETH.sol";
import { IL2ERC20Gateway } from "../../L2/gateways/IL2ERC20Gateway.sol";
import { IL1ScrollMessenger } from "../IL1ScrollMessenger.sol";
import { IL1ERC20Gateway } from "./IL1ERC20Gateway.sol";
import {IWETH} from "../../interfaces/IWETH.sol";
import {IL2ERC20Gateway} from "../../L2/gateways/IL2ERC20Gateway.sol";
import {IL1ScrollMessenger} from "../IL1ScrollMessenger.sol";
import {IL1ERC20Gateway} from "./IL1ERC20Gateway.sol";
import { ScrollGatewayBase } from "../../libraries/gateway/ScrollGatewayBase.sol";
import { L1ERC20Gateway } from "./L1ERC20Gateway.sol";
import {ScrollGatewayBase} from "../../libraries/gateway/ScrollGatewayBase.sol";
import {L1ERC20Gateway} from "./L1ERC20Gateway.sol";
/// @title L1WETHGateway
/// @notice The `L1WETHGateway` contract is used to deposit `WETH` token in layer 1 and
@@ -22,118 +22,123 @@ import { L1ERC20Gateway } from "./L1ERC20Gateway.sol";
/// On finalizing withdraw, the Ether will be transfered from `L1ScrollMessenger`, then
/// wrapped as WETH and finally transfer to recipient.
contract L1WETHGateway is Initializable, ScrollGatewayBase, L1ERC20Gateway {
using SafeERC20 for IERC20;
using SafeERC20 for IERC20;
/*************
* Constants *
*************/
/*************
* Constants *
*************/
/// @notice The address of L2 WETH address.
address public immutable l2WETH;
/// @notice The address of L2 WETH address.
address public immutable l2WETH;
/// @notice The address of L1 WETH address.
// solhint-disable-next-line var-name-mixedcase
address public immutable WETH;
/// @notice The address of L1 WETH address.
// solhint-disable-next-line var-name-mixedcase
address public immutable WETH;
/***************
* Constructor *
***************/
/***************
* Constructor *
***************/
constructor(address _WETH, address _l2WETH) {
WETH = _WETH;
l2WETH = _l2WETH;
}
/// @notice Initialize the storage of L1WETHGateway.
/// @param _counterpart The address of L2ETHGateway in L2.
/// @param _router The address of L1GatewayRouter.
/// @param _messenger The address of L1ScrollMessenger.
function initialize(
address _counterpart,
address _router,
address _messenger
) external initializer {
require(_router != address(0), "zero router address");
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
}
receive() external payable {
require(msg.sender == WETH, "only WETH");
}
/*************************
* Public View Functions *
*************************/
/// @inheritdoc IL1ERC20Gateway
function getL2ERC20Address(address) public view override returns (address) {
return l2WETH;
}
/****************************
* Public Mutated Functions *
****************************/
/// @inheritdoc IL1ERC20Gateway
function finalizeWithdrawERC20(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256 _amount,
bytes calldata _data
) external payable override onlyCallByCounterpart {
require(_l1Token == WETH, "l1 token not WETH");
require(_l2Token == l2WETH, "l2 token not WETH");
require(_amount == msg.value, "msg.value mismatch");
IWETH(_l1Token).deposit{ value: _amount }();
IERC20(_l1Token).safeTransfer(_to, _amount);
// @todo forward `_data` to `_to`.
emit FinalizeWithdrawERC20(_l1Token, _l2Token, _from, _to, _amount, _data);
}
/**********************
* Internal Functions *
**********************/
/// @inheritdoc L1ERC20Gateway
function _deposit(
address _token,
address _to,
uint256 _amount,
bytes memory _data,
uint256 _gasLimit
) internal virtual override nonReentrant {
require(_amount > 0, "deposit zero amount");
require(_token == WETH, "only WETH is allowed");
// 1. Extract real sender if this call is from L1GatewayRouter.
address _from = msg.sender;
if (router == msg.sender) {
(_from, _data) = abi.decode(_data, (address, bytes));
constructor(address _WETH, address _l2WETH) {
WETH = _WETH;
l2WETH = _l2WETH;
}
// 2. Transfer token into this contract.
IERC20(_token).safeTransferFrom(_from, address(this), _amount);
IWETH(_token).withdraw(_amount);
/// @notice Initialize the storage of L1WETHGateway.
/// @param _counterpart The address of L2ETHGateway in L2.
/// @param _router The address of L1GatewayRouter.
/// @param _messenger The address of L1ScrollMessenger.
function initialize(
address _counterpart,
address _router,
address _messenger
) external initializer {
require(_router != address(0), "zero router address");
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
}
// 3. Generate message passed to L2StandardERC20Gateway.
bytes memory _message = abi.encodeWithSelector(
IL2ERC20Gateway.finalizeDepositERC20.selector,
_token,
l2WETH,
_from,
_to,
_amount,
_data
);
receive() external payable {
require(msg.sender == WETH, "only WETH");
}
// 4. Send message to L1ScrollMessenger.
IL1ScrollMessenger(messenger).sendMessage{ value: _amount + msg.value }(counterpart, _amount, _message, _gasLimit);
/*************************
* Public View Functions *
*************************/
emit DepositERC20(_token, l2WETH, _from, _to, _amount, _data);
}
/// @inheritdoc IL1ERC20Gateway
function getL2ERC20Address(address) public view override returns (address) {
return l2WETH;
}
/*****************************
* Public Mutating Functions *
*****************************/
/// @inheritdoc IL1ERC20Gateway
function finalizeWithdrawERC20(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256 _amount,
bytes calldata _data
) external payable override onlyCallByCounterpart {
require(_l1Token == WETH, "l1 token not WETH");
require(_l2Token == l2WETH, "l2 token not WETH");
require(_amount == msg.value, "msg.value mismatch");
IWETH(_l1Token).deposit{value: _amount}();
IERC20(_l1Token).safeTransfer(_to, _amount);
// @todo forward `_data` to `_to`.
emit FinalizeWithdrawERC20(_l1Token, _l2Token, _from, _to, _amount, _data);
}
/**********************
* Internal Functions *
**********************/
/// @inheritdoc L1ERC20Gateway
function _deposit(
address _token,
address _to,
uint256 _amount,
bytes memory _data,
uint256 _gasLimit
) internal virtual override nonReentrant {
require(_amount > 0, "deposit zero amount");
require(_token == WETH, "only WETH is allowed");
// 1. Extract real sender if this call is from L1GatewayRouter.
address _from = msg.sender;
if (router == msg.sender) {
(_from, _data) = abi.decode(_data, (address, bytes));
}
// 2. Transfer token into this contract.
IERC20(_token).safeTransferFrom(_from, address(this), _amount);
IWETH(_token).withdraw(_amount);
// 3. Generate message passed to L2StandardERC20Gateway.
bytes memory _message = abi.encodeWithSelector(
IL2ERC20Gateway.finalizeDepositERC20.selector,
_token,
l2WETH,
_from,
_to,
_amount,
_data
);
// 4. Send message to L1ScrollMessenger.
IL1ScrollMessenger(messenger).sendMessage{value: _amount + msg.value}(
counterpart,
_amount,
_message,
_gasLimit
);
emit DepositERC20(_token, l2WETH, _from, _to, _amount, _data);
}
}

View File

@@ -3,76 +3,76 @@
pragma solidity ^0.8.0;
interface IL1MessageQueue {
/**********
* Events *
**********/
/**********
* Events *
**********/
/// @notice Emitted when a new L1 => L2 transaction is appended to the queue.
/// @param sender The address of account who initiates the transaction.
/// @param target The address of account who will recieve the transaction.
/// @param value The value passed with the transaction.
/// @param queueIndex The index of this transaction in the queue.
/// @param gasLimit Gas limit required to complete the message relay on L2.
/// @param data The calldata of the transaction.
event QueueTransaction(
address indexed sender,
address indexed target,
uint256 value,
uint256 queueIndex,
uint256 gasLimit,
bytes data
);
/// @notice Emitted when a new L1 => L2 transaction is appended to the queue.
/// @param sender The address of account who initiates the transaction.
/// @param target The address of account who will recieve the transaction.
/// @param value The value passed with the transaction.
/// @param queueIndex The index of this transaction in the queue.
/// @param gasLimit Gas limit required to complete the message relay on L2.
/// @param data The calldata of the transaction.
event QueueTransaction(
address indexed sender,
address indexed target,
uint256 value,
uint256 queueIndex,
uint256 gasLimit,
bytes data
);
/*************************
* Public View Functions *
*************************/
/*************************
* Public View Functions *
*************************/
/// @notice Return the index of next appended message.
/// @dev Also the total number of appended messages.
function nextCrossDomainMessageIndex() external view returns (uint256);
/// @notice Return the index of next appended message.
/// @dev Also the total number of appended messages.
function nextCrossDomainMessageIndex() external view returns (uint256);
/// @notice Return the message of in `queueIndex`.
/// @param queueIndex The index to query.
function getCrossDomainMessage(uint256 queueIndex) external view returns (bytes32);
/// @notice Return the message of in `queueIndex`.
/// @param queueIndex The index to query.
function getCrossDomainMessage(uint256 queueIndex) external view returns (bytes32);
/// @notice Return the amount of ETH should pay for cross domain message.
/// @param sender The address of account who initiates the message in L1.
/// @param target The address of account who will recieve the message in L2.
/// @param message The content of the message.
/// @param gasLimit Gas limit required to complete the message relay on L2.
function estimateCrossDomainMessageFee(
address sender,
address target,
bytes memory message,
uint256 gasLimit
) external view returns (uint256);
/// @notice Return the amount of ETH should pay for cross domain message.
/// @param sender The address of account who initiates the message in L1.
/// @param target The address of account who will recieve the message in L2.
/// @param message The content of the message.
/// @param gasLimit Gas limit required to complete the message relay on L2.
function estimateCrossDomainMessageFee(
address sender,
address target,
bytes memory message,
uint256 gasLimit
) external view returns (uint256);
/****************************
* Public Mutated Functions *
****************************/
/*****************************
* Public Mutating Functions *
*****************************/
/// @notice Append a L1 to L2 message into this contract.
/// @param target The address of target contract to call in L2.
/// @param gasLimit The maximum gas should be used for relay this message in L2.
/// @param data The calldata passed to target contract.
function appendCrossDomainMessage(
address target,
uint256 gasLimit,
bytes calldata data
) external;
/// @notice Append a L1 to L2 message into this contract.
/// @param target The address of target contract to call in L2.
/// @param gasLimit The maximum gas should be used for relay this message in L2.
/// @param data The calldata passed to target contract.
function appendCrossDomainMessage(
address target,
uint256 gasLimit,
bytes calldata data
) external;
/// @notice Append an enforced transaction to this contract.
/// @dev The address of sender should be an EOA.
/// @param sender The address of sender who will initiate this transaction in L2.
/// @param target The address of target contract to call in L2.
/// @param value The value passed
/// @param gasLimit The maximum gas should be used for this transaction in L2.
/// @param data The calldata passed to target contract.
function appendEnforcedTransaction(
address sender,
address target,
uint256 value,
uint256 gasLimit,
bytes calldata data
) external;
/// @notice Append an enforced transaction to this contract.
/// @dev The address of sender should be an EOA.
/// @param sender The address of sender who will initiate this transaction in L2.
/// @param target The address of target contract to call in L2.
/// @param value The value passed
/// @param gasLimit The maximum gas should be used for this transaction in L2.
/// @param data The calldata passed to target contract.
function appendEnforcedTransaction(
address sender,
address target,
uint256 value,
uint256 gasLimit,
bytes calldata data
) external;
}

View File

@@ -3,14 +3,14 @@
pragma solidity ^0.8.0;
interface IL2GasPriceOracle {
/// @notice Estimate fee for cross chain message call.
/// @param _sender The address of sender who invoke the call.
/// @param _to The target address to receive the call.
/// @param _message The message will be passed to the target address.
function estimateCrossDomainMessageFee(
address _sender,
address _to,
bytes memory _message,
uint256 _gasLimit
) external view returns (uint256);
/// @notice Estimate fee for cross chain message call.
/// @param _sender The address of sender who invoke the call.
/// @param _to The target address to receive the call.
/// @param _message The message will be passed to the target address.
function estimateCrossDomainMessageFee(
address _sender,
address _to,
bytes memory _message,
uint256 _gasLimit
) external view returns (uint256);
}

View File

@@ -3,102 +3,102 @@
pragma solidity ^0.8.0;
interface IScrollChain {
/**********
* Events *
**********/
/**********
* Events *
**********/
/// @notice Emitted when a new batch is commited.
/// @param batchHash The hash of the batch
event CommitBatch(bytes32 indexed batchHash);
/// @notice Emitted when a new batch is commited.
/// @param batchHash The hash of the batch
event CommitBatch(bytes32 indexed batchHash);
/// @notice Emitted when a batch is reverted.
/// @param batchHash The identification of the batch.
event RevertBatch(bytes32 indexed batchHash);
/// @notice Emitted when a batch is reverted.
/// @param batchHash The identification of the batch.
event RevertBatch(bytes32 indexed batchHash);
/// @notice Emitted when a batch is finalized.
/// @param batchHash The hash of the batch
event FinalizeBatch(bytes32 indexed batchHash);
/// @notice Emitted when a batch is finalized.
/// @param batchHash The hash of the batch
event FinalizeBatch(bytes32 indexed batchHash);
/***********
* Structs *
***********/
/***********
* Structs *
***********/
struct BlockContext {
// The hash of this block.
bytes32 blockHash;
// The parent hash of this block.
bytes32 parentHash;
// The height of this block.
uint64 blockNumber;
// The timestamp of this block.
uint64 timestamp;
// The base fee of this block.
// Currently, it is not used, because we disable EIP-1559.
// We keep it for future proof.
uint256 baseFee;
// The gas limit of this block.
uint64 gasLimit;
// The number of transactions in this block, both L1 & L2 txs.
uint16 numTransactions;
// The number of l1 messages in this block.
uint16 numL1Messages;
}
struct BlockContext {
// The hash of this block.
bytes32 blockHash;
// The parent hash of this block.
bytes32 parentHash;
// The height of this block.
uint64 blockNumber;
// The timestamp of this block.
uint64 timestamp;
// The base fee of this block.
// Currently, it is not used, because we disable EIP-1559.
// We keep it for future proof.
uint256 baseFee;
// The gas limit of this block.
uint64 gasLimit;
// The number of transactions in this block, both L1 & L2 txs.
uint16 numTransactions;
// The number of l1 messages in this block.
uint16 numL1Messages;
}
struct Batch {
// The list of blocks in this batch
BlockContext[] blocks; // MAX_NUM_BLOCKS = 100, about 5 min
// The state root of previous batch.
// The first batch will use 0x0 for prevStateRoot
bytes32 prevStateRoot;
// The state root of the last block in this batch.
bytes32 newStateRoot;
// The withdraw trie root of the last block in this batch.
bytes32 withdrawTrieRoot;
// The index of the batch.
uint64 batchIndex;
// The parent batch hash.
bytes32 parentBatchHash;
// Concatenated raw data of RLP encoded L2 txs
bytes l2Transactions;
}
struct Batch {
// The list of blocks in this batch
BlockContext[] blocks; // MAX_NUM_BLOCKS = 100, about 5 min
// The state root of previous batch.
// The first batch will use 0x0 for prevStateRoot
bytes32 prevStateRoot;
// The state root of the last block in this batch.
bytes32 newStateRoot;
// The withdraw trie root of the last block in this batch.
bytes32 withdrawTrieRoot;
// The index of the batch.
uint64 batchIndex;
// The parent batch hash.
bytes32 parentBatchHash;
// Concatenated raw data of RLP encoded L2 txs
bytes l2Transactions;
}
/*************************
* Public View Functions *
*************************/
/*************************
* Public View Functions *
*************************/
/// @notice Return whether the batch is finalized by batch hash.
/// @param batchHash The hash of the batch to query.
function isBatchFinalized(bytes32 batchHash) external view returns (bool);
/// @notice Return whether the batch is finalized by batch hash.
/// @param batchHash The hash of the batch to query.
function isBatchFinalized(bytes32 batchHash) external view returns (bool);
/// @notice Return the merkle root of L2 message tree.
/// @param batchHash The hash of the batch to query.
function getL2MessageRoot(bytes32 batchHash) external view returns (bytes32);
/// @notice Return the merkle root of L2 message tree.
/// @param batchHash The hash of the batch to query.
function getL2MessageRoot(bytes32 batchHash) external view returns (bytes32);
/****************************
* Public Mutated Functions *
****************************/
/*****************************
* Public Mutating Functions *
*****************************/
/// @notice commit a batch in layer 1
/// @param batch The layer2 batch to commit.
function commitBatch(Batch memory batch) external;
/// @notice commit a batch in layer 1
/// @param batch The layer2 batch to commit.
function commitBatch(Batch memory batch) external;
/// @notice commit a list of batches in layer 1
/// @param batches The list of layer2 batches to commit.
function commitBatches(Batch[] memory batches) external;
/// @notice commit a list of batches in layer 1
/// @param batches The list of layer2 batches to commit.
function commitBatches(Batch[] memory batches) external;
/// @notice revert a pending batch.
/// @dev one can only revert unfinalized batches.
/// @param batchId The identification of the batch.
function revertBatch(bytes32 batchId) external;
/// @notice revert a pending batch.
/// @dev one can only revert unfinalized batches.
/// @param batchId The identification of the batch.
function revertBatch(bytes32 batchId) external;
/// @notice finalize commited batch in layer 1
/// @dev will add more parameters if needed.
/// @param batchId The identification of the commited batch.
/// @param proof The corresponding proof of the commited batch.
/// @param instances Instance used to verify, generated from batch.
function finalizeBatchWithProof(
bytes32 batchId,
uint256[] memory proof,
uint256[] memory instances
) external;
/// @notice finalize commited batch in layer 1
/// @dev will add more parameters if needed.
/// @param batchId The identification of the commited batch.
/// @param proof The corresponding proof of the commited batch.
/// @param instances Instance used to verify, generated from batch.
function finalizeBatchWithProof(
bytes32 batchId,
uint256[] memory proof,
uint256[] memory instances
) external;
}

View File

@@ -2,122 +2,122 @@
pragma solidity ^0.8.0;
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { IL2GasPriceOracle } from "./IL2GasPriceOracle.sol";
import { IL1MessageQueue } from "./IL1MessageQueue.sol";
import {IL2GasPriceOracle} from "./IL2GasPriceOracle.sol";
import {IL1MessageQueue} from "./IL1MessageQueue.sol";
import { AddressAliasHelper } from "../../libraries/common/AddressAliasHelper.sol";
import {AddressAliasHelper} from "../../libraries/common/AddressAliasHelper.sol";
/// @title L1MessageQueue
/// @notice This contract will hold all L1 to L2 messages.
/// Each appended message is assigned with a unique and increasing `uint256` index denoting the message nonce.
contract L1MessageQueue is OwnableUpgradeable, IL1MessageQueue {
/**********
* Events *
**********/
/**********
* Events *
**********/
/// @notice Emitted when owner updates gas oracle contract.
/// @param _oldGasOracle The address of old gas oracle contract.
/// @param _newGasOracle The address of new gas oracle contract.
event UpdateGasOracle(address _oldGasOracle, address _newGasOracle);
/// @notice Emitted when owner updates gas oracle contract.
/// @param _oldGasOracle The address of old gas oracle contract.
/// @param _newGasOracle The address of new gas oracle contract.
event UpdateGasOracle(address _oldGasOracle, address _newGasOracle);
/*************
* Variables *
*************/
/*************
* Variables *
*************/
/// @notice The address of L1ScrollMessenger contract.
address public messenger;
/// @notice The address of L1ScrollMessenger contract.
address public messenger;
/// @notice The address of GasOracle contract.
address public gasOracle;
/// @notice The address of GasOracle contract.
address public gasOracle;
/// @notice The list of queued cross domain messages.
bytes32[] public messageQueue;
/// @notice The list of queued cross domain messages.
bytes32[] public messageQueue;
/***************
* Constructor *
***************/
/***************
* Constructor *
***************/
function initialize(address _messenger, address _gasOracle) external initializer {
OwnableUpgradeable.__Ownable_init();
function initialize(address _messenger, address _gasOracle) external initializer {
OwnableUpgradeable.__Ownable_init();
messenger = _messenger;
gasOracle = _gasOracle;
}
messenger = _messenger;
gasOracle = _gasOracle;
}
/*************************
* Public View Functions *
*************************/
/*************************
* Public View Functions *
*************************/
/// @inheritdoc IL1MessageQueue
function nextCrossDomainMessageIndex() external view returns (uint256) {
return messageQueue.length;
}
/// @inheritdoc IL1MessageQueue
function nextCrossDomainMessageIndex() external view returns (uint256) {
return messageQueue.length;
}
/// @inheritdoc IL1MessageQueue
function getCrossDomainMessage(uint256 _queueIndex) external view returns (bytes32) {
return messageQueue[_queueIndex];
}
/// @inheritdoc IL1MessageQueue
function getCrossDomainMessage(uint256 _queueIndex) external view returns (bytes32) {
return messageQueue[_queueIndex];
}
/// @inheritdoc IL1MessageQueue
function estimateCrossDomainMessageFee(
address _sender,
address _target,
bytes memory _message,
uint256 _gasLimit
) external view override returns (uint256) {
address _oracle = gasOracle;
if (_oracle == address(0)) return 0;
return IL2GasPriceOracle(_oracle).estimateCrossDomainMessageFee(_sender, _target, _message, _gasLimit);
}
/// @inheritdoc IL1MessageQueue
function estimateCrossDomainMessageFee(
address _sender,
address _target,
bytes memory _message,
uint256 _gasLimit
) external view override returns (uint256) {
address _oracle = gasOracle;
if (_oracle == address(0)) return 0;
return IL2GasPriceOracle(_oracle).estimateCrossDomainMessageFee(_sender, _target, _message, _gasLimit);
}
/****************************
* Public Mutated Functions *
****************************/
/*****************************
* Public Mutating Functions *
*****************************/
/// @inheritdoc IL1MessageQueue
function appendCrossDomainMessage(
address _target,
uint256 _gasLimit,
bytes calldata _data
) external override {
require(msg.sender == messenger, "Only callable by the L1ScrollMessenger");
/// @inheritdoc IL1MessageQueue
function appendCrossDomainMessage(
address _target,
uint256 _gasLimit,
bytes calldata _data
) external override {
require(msg.sender == messenger, "Only callable by the L1ScrollMessenger");
// do address alias to avoid replay attack in L2.
address _sender = AddressAliasHelper.applyL1ToL2Alias(msg.sender);
// do address alias to avoid replay attack in L2.
address _sender = AddressAliasHelper.applyL1ToL2Alias(msg.sender);
// @todo Change it to rlp encoding later.
bytes32 _hash = keccak256(abi.encode(_sender, _target, 0, _gasLimit, _data));
// @todo Change it to rlp encoding later.
bytes32 _hash = keccak256(abi.encode(_sender, _target, 0, _gasLimit, _data));
uint256 _queueIndex = messageQueue.length;
emit QueueTransaction(_sender, _target, 0, _queueIndex, _gasLimit, _data);
uint256 _queueIndex = messageQueue.length;
emit QueueTransaction(_sender, _target, 0, _queueIndex, _gasLimit, _data);
messageQueue.push(_hash);
}
messageQueue.push(_hash);
}
/// @inheritdoc IL1MessageQueue
function appendEnforcedTransaction(
address,
address,
uint256,
uint256,
bytes calldata
) external override {
// @todo
}
/// @inheritdoc IL1MessageQueue
function appendEnforcedTransaction(
address,
address,
uint256,
uint256,
bytes calldata
) external override {
// @todo
}
/************************
* Restricted Functions *
************************/
/************************
* Restricted Functions *
************************/
/// @notice Update the address of gas oracle.
/// @dev This function can only called by contract owner.
/// @param _newGasOracle The address to update.
function updateGasOracle(address _newGasOracle) external onlyOwner {
address _oldGasOracle = gasOracle;
gasOracle = _newGasOracle;
/// @notice Update the address of gas oracle.
/// @dev This function can only called by contract owner.
/// @param _newGasOracle The address to update.
function updateGasOracle(address _newGasOracle) external onlyOwner {
address _oldGasOracle = gasOracle;
gasOracle = _newGasOracle;
emit UpdateGasOracle(_oldGasOracle, _newGasOracle);
}
emit UpdateGasOracle(_oldGasOracle, _newGasOracle);
}
}

Some files were not shown because too many files have changed in this diff Show More