Compare commits

..

64 Commits

Author SHA1 Message Date
maskpp
87f6d95406 Merge branch 'develop' into feat/integration-test_scroll_contracts_test 2023-06-14 15:00:06 +08:00
maskpp
1e29d9e324 Merge branch 'develop' into feat/integration-test_scroll_contracts_test 2023-06-11 09:13:24 +08:00
maskpp
365eebf0bd Merge branch 'develop' into feat/integration-test_scroll_contracts_test 2023-05-31 22:26:26 +08:00
maskpp
2ef3947684 Merge branch 'develop' into feat/integration-test_scroll_contracts_test 2023-05-30 09:39:13 +08:00
maskpp
f3d3162ac2 trigger ci 2023-05-29 22:37:45 +08:00
maskpp
bff51f56b8 fix ci 2023-05-29 22:33:10 +08:00
maskpp
1e2b89aa58 Merge branch 'develop' into feat/integration-test_scroll_contracts_test 2023-05-29 15:46:46 +08:00
maskpp
2957bd70b2 merge develop and fix conflict 2023-05-26 14:54:30 +08:00
maskpp
60e901cd36 Merge branch 'develop' into feat/integration-test_scroll_contracts_test 2023-05-26 11:25:22 +08:00
maskpp
81cc4b7882 Revert change 2023-05-25 15:47:15 +08:00
maskpp
8ec16fe4f3 Set baseFee = 0 2023-05-25 14:53:23 +08:00
maskpp
22044235e4 merge develop and fix conflict 2023-05-25 09:57:26 +08:00
maskpp
5232bdbc81 Merge branch 'develop' into feat/integration-test_scroll_contracts_test 2023-05-24 16:51:38 +08:00
maskpp
e88ab5a72d revert change 2023-05-24 16:49:52 +08:00
maskpp
7b72259939 merge develop and rm mistake change 2023-05-23 11:18:54 +08:00
maskpp
9baaddfdfe Merge branch 'develop' into feat/integration-test_scroll_contracts_test 2023-05-23 11:14:05 +08:00
maskpp
7385ce437e Update bytecode makefile 2023-05-23 11:11:38 +08:00
maskpp
2f459d9995 Update bytecode makefile 2023-05-23 09:57:23 +08:00
maskpp
23d2c98735 add git ignore file in scroll 2023-05-23 09:56:26 +08:00
maskpp
baf2aebecb chore: forge init 2023-05-23 07:53:33 +08:00
maskpp
f327b4abbe Merge branch 'develop' into feat/integration-test_scroll_contracts_test 2023-05-22 15:44:25 +08:00
maskpp
b25c593a88 Use abigen with new flag --contract 2023-05-22 14:35:25 +08:00
maskpp
b77905b6bf Merge branch 'develop' into feat/integration-test_scroll_contracts_test 2023-05-22 14:03:45 +08:00
maskpp
bd2303a4d2 Use abigen with new flag --contract 2023-05-22 14:02:16 +08:00
maskpp
0bab8f95c1 trigger ci 2023-05-19 17:05:57 +08:00
maskpp
6b38531a09 Merge branch 'develop' into feat/integration-test_scroll_contracts_test 2023-05-19 16:51:05 +08:00
maskpp
88bcf13515 fix ci 2023-05-19 16:46:38 +08:00
maskpp
d8d1669a67 just use sol file 2023-05-18 17:31:25 +08:00
maskpp
109632ca48 just use sol file 2023-05-18 17:20:46 +08:00
maskpp
6470092742 just use sol file 2023-05-18 17:20:35 +08:00
maskpp
3e9ac08533 just use sol file 2023-05-18 17:13:09 +08:00
maskpp
44925be3f3 Update bytecode git ignore file. 2023-05-18 16:53:09 +08:00
maskpp
891b8e14f1 Revert unnecessary change. 2023-05-18 16:03:08 +08:00
maskpp
b02c16730c Revert unnecessary change. 2023-05-18 15:27:43 +08:00
maskpp
f75b18395e Merge branch 'develop' into feat/integration-test_scroll_contracts_test 2023-05-18 14:51:58 +08:00
maskpp
e08d94990b Finish deposit and withdraw contract test. 2023-05-18 14:50:10 +08:00
maskpp
61dbb27159 Finish deposit and withdraw contract test. 2023-05-18 14:47:35 +08:00
maskpp
f855f4db9c deploy contracts 2023-05-18 14:13:10 +08:00
maskpp
4dcaf468cb deploy contracts 2023-05-18 11:39:25 +08:00
maskpp
8754645f6a deploy contracts 2023-05-18 11:37:59 +08:00
maskpp
6af6e12522 deploy contracts 2023-05-18 11:32:32 +08:00
maskpp
baf678bbe4 temporary code for test 2023-05-16 10:35:35 +08:00
maskpp
5a9a82993c revert genesis file 2023-05-15 17:37:54 +08:00
maskpp
a62309dbb3 deploy scroll contracts 2023-05-15 17:31:14 +08:00
maskpp
f2b5d66cb1 revert genesis file 2023-05-15 17:22:00 +08:00
maskpp
54b44a0a56 merge develop branch and fix conflict 2023-05-15 14:53:26 +08:00
maskpp
fe6948428c revert genesis file 2023-05-15 14:24:13 +08:00
maskpp
f71e9cecbf withdraw contract test 2023-05-12 14:51:05 +08:00
maskpp
72f5cc4891 merge develop branch 2023-05-12 10:31:48 +08:00
maskpp
7179080cdc fix ci 2023-05-11 10:46:19 +08:00
maskpp
50d89f82be merge develop branch 2023-05-11 10:30:31 +08:00
maskpp
62b92f0650 Add scroll eth deposit test. 2023-05-11 10:20:16 +08:00
maskpp
bad7e372a2 temporary code for test 2023-05-09 09:36:55 +08:00
maskpp
1ea0377bdc revert genesis file 2023-05-05 18:52:44 +08:00
maskpp
7efb5a803b Update makefile. 2023-05-04 16:34:52 +08:00
maskpp
f0d6590406 Update makefile. 2023-05-04 16:24:58 +08:00
maskpp
357fe3cf2c Merge branch 'develop' into feat/integration-test_scroll_contracts 2023-05-04 16:08:48 +08:00
maskpp
d62f9e4f5a Add a test account in genesis file. 2023-05-04 16:07:02 +08:00
maskpp
59a47b3d2f fix ci 2023-05-04 14:40:42 +08:00
maskpp
a2a8f85107 fix ci 2023-05-04 14:35:30 +08:00
maskpp
84c76050c2 Merge branch 'develop' into feat/integration-test_scroll_contracts 2023-05-04 14:29:00 +08:00
maskpp
100713c7c0 Deposit test case. 2023-05-04 14:28:15 +08:00
maskpp
40654cc35c revert genesis.json 2023-04-28 17:22:17 +08:00
maskpp
d3c5946ddf Add scroll abi and contracts code. 2023-04-25 11:05:11 +08:00
85 changed files with 10866 additions and 12573 deletions

View File

@@ -103,13 +103,7 @@ jobs:
- name: Test bridge packages
working-directory: 'bridge'
run: |
go test -v -race -gcflags="-l" -ldflags="-s=false" -coverprofile=coverage.txt -covermode=atomic ./...
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
flags: bridge
go test -v -race -gcflags="-l" -ldflags="-s=false" -covermode=atomic ./...
# docker-build:
# if: github.event.pull_request.draft == false
# runs-on: ubuntu-latest

View File

@@ -53,12 +53,6 @@ jobs:
run: |
go get ./...
make test
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
flags: bridge-history-api
goimports-lint:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest

View File

@@ -30,6 +30,12 @@ jobs:
toolchain: nightly-2022-12-10
override: true
components: rustfmt, clippy
- name: Install Solc
uses: supplypike/setup-bin@v3
with:
uri: 'https://github.com/ethereum/solidity/releases/download/v0.8.16/solc-static-linux'
name: 'solc'
version: '0.8.16'
- name: Install Go
uses: actions/setup-go@v2
with:
@@ -94,10 +100,4 @@ jobs:
- name: Test common packages
working-directory: 'common'
run: |
go test -v -race -gcflags="-l" -ldflags="-s=false" -coverprofile=coverage.txt -covermode=atomic ./...
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
flags: common
go test -v -race -gcflags="-l" -ldflags="-s=false" -covermode=atomic ./...

View File

@@ -110,11 +110,4 @@ jobs:
- name: Test coordinator packages
working-directory: 'coordinator'
run: |
# go test -exec "env LD_LIBRARY_PATH=${PWD}/verifier/lib" -v -race -gcflags="-l" -ldflags="-s=false" -coverpkg="scroll-tech/coordinator" -coverprofile=coverage.txt -covermode=atomic ./...
go test -v -race -gcflags="-l" -ldflags="-s=false" -coverprofile=coverage.txt -covermode=atomic -tags mock_verifier ./...
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
flags: coordinator
go test -v -race -gcflags="-l" -ldflags="-s=false" -covermode=atomic -tags mock_verifier ./...

View File

@@ -87,10 +87,4 @@ jobs:
- name: Test database packages
working-directory: 'database'
run: |
go test -v -race -gcflags="-l" -ldflags="-s=false" -coverprofile=coverage.txt -covermode=atomic ./...
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
flags: database
go test -v -race -gcflags="-l" -ldflags="-s=false" -covermode=atomic ./...

View File

@@ -40,4 +40,4 @@ jobs:
make -C common/bytecode all
- name: Run integration tests
run: |
go test -v -tags="mock_prover mock_verifier" -p 1 -coverprofile=coverage.txt scroll-tech/integration-test/...
go test -v -tags="mock_prover mock_verifier" -p 1 scroll-tech/integration-test/...

View File

@@ -47,13 +47,7 @@ jobs:
- name: Test
run: |
make roller
go test -tags="mock_prover" -v -coverprofile=coverage.txt ./...
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
flags: roller
go test -tags="mock_prover" -v ./...
check:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest

100
Jenkinsfile vendored Normal file
View File

@@ -0,0 +1,100 @@
imagePrefix = 'scrolltech'
credentialDocker = 'dockerhub'
pipeline {
agent any
options {
timeout (20)
}
tools {
nodejs "nodejs"
go 'go-1.19'
}
environment {
GOBIN = '/home/ubuntu/go/bin/'
GO111MODULE = 'on'
PATH="/home/ubuntu/.cargo/bin:$PATH"
LD_LIBRARY_PATH="$LD_LIBRARY_PATH:./coordinator/verifier/lib"
CHAIN_ID='534353'
// LOG_DOCKER = 'true'
}
stages {
stage('Build') {
parallel {
stage('Build Prerequisite') {
steps {
sh 'make dev_docker'
sh 'make -C bridge mock_abi'
sh 'make -C common/bytecode all'
}
}
stage('Check Bridge Compilation') {
steps {
sh 'make -C bridge bridge_bins'
}
}
stage('Check Coordinator Compilation') {
steps {
sh 'export PATH=/home/ubuntu/go/bin:$PATH'
sh 'make -C coordinator coordinator'
}
}
stage('Check Database Compilation') {
steps {
sh 'make -C database db_cli'
}
}
stage('Check Database Docker Build') {
steps {
sh 'make -C database docker'
}
}
}
}
stage('Parallel Test') {
parallel{
stage('Race test common package') {
steps {
sh 'go test -v -race -coverprofile=coverage.common.txt -covermode=atomic scroll-tech/common/...'
}
}
stage('Race test bridge package') {
steps {
sh "cd ./bridge && ../build/run_tests.sh bridge"
}
}
stage('Race test coordinator package') {
steps {
sh 'cd ./coordinator && go test -exec "env LD_LIBRARY_PATH=${PWD}/verifier/lib" -v -race -gcflags="-l" -ldflags="-s=false" -coverpkg="scroll-tech/coordinator" -coverprofile=../coverage.coordinator.txt -covermode=atomic ./...'
}
}
stage('Race test database package') {
steps {
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/...'
}
}
}
}
stage('Compare Coverage') {
steps {
sh './build/post-test-report-coverage.sh'
script {
currentBuild.result = 'SUCCESS'
}
step([$class: 'CompareCoverageAction', publishResultAs: 'Comment', scmVars: [GIT_URL: env.GIT_URL]])
}
}
}
post {
always {
publishCoverage adapters: [coberturaReportAdapter(path: 'cobertura.xml', thresholds: [[thresholdTarget: 'Aggregated Report', unhealthyThreshold: 40.0]])], checksName: '', sourceFileResolver: sourceFiles('NEVER_STORE')
cleanWs()
slackSend(message: "${JOB_BASE_NAME} ${GIT_COMMIT} #${BUILD_NUMBER} deploy ${currentBuild.result}")
}
}
}

View File

@@ -1,7 +1,5 @@
# Scroll Monorepo
[![codecov](https://codecov.io/gh/scroll-tech/scroll/branch/develop/graph/badge.svg?token=VJVHNQWGGW)](https://codecov.io/gh/scroll-tech/scroll)
## Prerequisites
+ Go 1.19
+ Rust (for version, see [rust-toolchain](./common/libzkp/impl/rust-toolchain))

View File

@@ -325,26 +325,3 @@ type L2FailedRelayedMessageEvent struct {
type L2RelayedMessageEvent struct {
MessageHash common.Hash
}
// IScrollChainBatch is an auto generated low-level Go binding around an user-defined struct.
type IScrollChainBatch struct {
Blocks []IScrollChainBlockContext
PrevStateRoot common.Hash
NewStateRoot common.Hash
WithdrawTrieRoot common.Hash
BatchIndex uint64
ParentBatchHash common.Hash
L2Transactions []byte
}
// IScrollChainBlockContext is an auto generated low-level Go binding around an user-defined struct.
type IScrollChainBlockContext struct {
BlockHash common.Hash
ParentHash common.Hash
BlockNumber uint64
Timestamp uint64
BaseFee *big.Int
GasLimit uint64
NumTransactions uint16
NumL1Messages uint16
}

View File

@@ -5,7 +5,6 @@ import (
"os"
"github.com/ethereum/go-ethereum/log"
"github.com/iris-contrib/middleware/cors"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
"github.com/urfave/cli/v2"
@@ -61,11 +60,6 @@ func init() {
}
func action(ctx *cli.Context) error {
corsOptions := cors.New(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
AllowCredentials: true,
})
// Load config file.
cfgFile := ctx.String(cutils.ConfigFileFlag.Name)
cfg, err := config.NewConfig(cfgFile)
@@ -78,7 +72,6 @@ func action(ctx *cli.Context) error {
}
defer database.Close()
bridgeApp := iris.New()
bridgeApp.UseRouter(corsOptions)
bridgeApp.Get("/ping", pong).Describe("healthcheck")
mvc.Configure(bridgeApp.Party("/api/txs"), setupQueryByAddressHandler)

View File

@@ -2,28 +2,28 @@
"l1": {
"confirmation": 64,
"endpoint": "https://rpc.ankr.com/eth_goerli",
"startHeight": 9090194 ,
"startHeight": 9890194 ,
"blockTime": 10,
"MessengerAddr": "0x326517Eb8eB1Ce5eaB5b513C2e9A24839b402d90",
"ETHGatewayAddr": "0x8305cB7B8448677736095965B63d7431017328fe",
"WETHGatewayAddr": "0xe3bA3c60d99a2d9a5f817734bC85353470b23931",
"StandardERC20Gateway": "0x16c1079B27eD9c363B7D08aC5Ae937A398972A5C",
"CustomERC20GatewayAddr": "0x61f08caD3d6F77801167d3bA8669433701586643",
"ERC721GatewayAddr": "0x4A73D25A4C99CB912acaf6C5B5e554f2982201c5",
"ERC1155GatewayAddr": "0xa3F5DD3033698c2832C53f3C3Fe6E062F58cD808"
"MessengerAddr": "0x5260e38080BFe97e6C4925d9209eCc5f964373b6",
"ETHGatewayAddr": "0x429b73A21cF3BF1f3E696a21A95408161daF311f",
"WETHGatewayAddr": "0x8be69E499D8848DfFb4cF9bac909f3e2cF2FeFa0",
"StandardERC20Gateway": "0xeF37207c1A1efF6D6a9d7BfF3cF4270e406d319b",
"CustomERC20GatewayAddr": "0x920f906B814597cF5DC76F95100F09CBAF9c5748",
"ERC721GatewayAddr": "0x1C441Dfc5C2eD7A2AA8636748A664E59CB029157",
"ERC1155GatewayAddr": "0xd1bE599aaCBC21448fD6373bbc7c1b4c7806f135"
},
"l2": {
"confirmation": 1,
"endpoint": "http://staging-l2geth-rpc0.scroll.tech:8545",
"endpoint": "https://alpha-rpc.scroll.io/l2",
"blockTime": 3,
"startHeight": 0,
"CustomERC20GatewayAddr": "0x905db21f836749fEeD12de781afc4A5Ab4Dd0d51",
"ERC721GatewayAddr": "0xC53D835514780664BCd7eCfcE7c2E5d9554dc41B",
"StandardERC20Gateway": "0x90271634BCB020e06ea4840C3f7aa61b8F860651",
"MessengerAddr": "0xE8b0956Ac75c65Aa1669e83888DA13afF2E108f4",
"ETHGatewayAddr": "0xD5938590D5dD8ce95812D4D515a219C12C551D67",
"WETHGatewayAddr": "0xb0aaA582564fade4232a16fdB1383004A6A7247F",
"ERC1155GatewayAddr": "0x4f33B1655619c2C0B7C450128Df760B4365Cb549"
"startHeight": 1900068,
"CustomERC20GatewayAddr": "0xa07Cb742657294C339fB4d5d6CdF3fdBeE8C1c68",
"ERC721GatewayAddr": "0x8Fee20e0C0Ef16f2898a8073531a857D11b9C700",
"StandardERC20Gateway": "0xB878F37BB278bf0e4974856fFe86f5e6F66BD725",
"MessengerAddr": "0xb75d7e84517e1504C151B270255B087Fd746D34C",
"ETHGatewayAddr": "0x32139B5C8838E94fFcD83E60dff95Daa7F0bA14c",
"WETHGatewayAddr": "0xBb88bF582F2BBa46702621dae5CB9271057bC85b",
"ERC1155GatewayAddr": "0x2946cB860028276b3C4bccE1767841641C2E0828"
},
"db": {
"dsn": "postgres://postgres:1234@localhost:5444/test?sslmode=disable",

View File

@@ -16,7 +16,7 @@ type QueryHashController struct {
}
func (c *QueryAddressController) Get(req model.QueryByAddressRequest) (*model.QueryByAddressResponse, error) {
message, total, err := c.Service.GetTxsByAddress(common.HexToAddress(req.Address), int64(req.Offset), int64(req.Limit))
message, err := c.Service.GetTxsByAddress(common.HexToAddress(req.Address), int64(req.Offset), int64(req.Limit))
if err != nil {
return &model.QueryByAddressResponse{Message: "500", Data: &model.Data{}}, err
}
@@ -24,7 +24,7 @@ func (c *QueryAddressController) Get(req model.QueryByAddressRequest) (*model.Qu
return &model.QueryByAddressResponse{Message: "ok",
Data: &model.Data{
Result: message,
Total: total,
Total: len(message),
}}, nil
}

View File

@@ -49,20 +49,20 @@ CREATE TRIGGER update_timestamp BEFORE UPDATE
ON cross_message FOR EACH ROW EXECUTE PROCEDURE
update_timestamp();
CREATE OR REPLACE FUNCTION deleted_at_trigger()
CREATE OR REPLACE FUNCTION delete_at_trigger()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.is_deleted AND OLD.is_deleted != NEW.is_deleted THEN
UPDATE cross_message SET deleted_at = NOW() WHERE id = NEW.id;
UPDATE cross_message SET delete_at = NOW() WHERE id = NEW.id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER deleted_at_trigger
CREATE TRIGGER delete_at_trigger
AFTER UPDATE ON cross_message
FOR EACH ROW
EXECUTE FUNCTION deleted_at_trigger();
EXECUTE FUNCTION delete_at_trigger();
-- +goose StatementEnd

View File

@@ -31,20 +31,20 @@ CREATE TRIGGER update_timestamp BEFORE UPDATE
ON relayed_msg FOR EACH ROW EXECUTE PROCEDURE
update_timestamp();
CREATE OR REPLACE FUNCTION deleted_at_trigger()
CREATE OR REPLACE FUNCTION delete_at_trigger()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.is_deleted AND OLD.is_deleted != NEW.is_deleted THEN
UPDATE relayed_msg SET deleted_at = NOW() WHERE id = NEW.id;
UPDATE relayed_msg SET delete_at = NOW() WHERE id = NEW.id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER deleted_at_trigger
CREATE TRIGGER delete_at_trigger
AFTER UPDATE ON relayed_msg
FOR EACH ROW
EXECUTE FUNCTION deleted_at_trigger();
EXECUTE FUNCTION delete_at_trigger();
-- +goose StatementEnd

View File

@@ -22,7 +22,7 @@ func NewL2CrossMsgOrm(db *sqlx.DB) L2CrossMsgOrm {
func (l *l2CrossMsgOrm) GetL2CrossMsgByHash(l2Hash common.Hash) (*CrossMsg, error) {
result := &CrossMsg{}
row := l.db.QueryRowx(`SELECT * FROM cross_message WHERE layer2_hash = $1 AND NOT is_deleted;`, l2Hash.String())
row := l.db.QueryRowx(`SELECT * FROM l2_cross_message WHERE layer2_hash = $1 AND NOT is_deleted;`, l2Hash.String())
if err := row.StructScan(result); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil

View File

@@ -16,7 +16,6 @@ type OrmFactory interface {
orm.L1CrossMsgOrm
orm.L2CrossMsgOrm
orm.RelayedMsgOrm
GetTotalCrossMsgCountByAddress(sender string) (uint64, error)
GetCrossMsgsByAddressWithOffset(sender string, offset int64, limit int64) ([]*orm.CrossMsg, error)
GetDB() *sqlx.DB
Beginx() (*sqlx.Tx, error)
@@ -60,15 +59,6 @@ func (o *ormFactory) Beginx() (*sqlx.Tx, error) {
return o.DB.Beginx()
}
func (o *ormFactory) GetTotalCrossMsgCountByAddress(sender string) (uint64, error) {
var count uint64
row := o.DB.QueryRowx(`SELECT COUNT(*) FROM cross_message WHERE sender = $1 AND NOT is_deleted;`, sender)
if err := row.Scan(&count); err != nil {
return 0, err
}
return count, nil
}
func (o *ormFactory) GetCrossMsgsByAddressWithOffset(sender string, offset int64, limit int64) ([]*orm.CrossMsg, error) {
para := sender
var results []*orm.CrossMsg

View File

@@ -4,7 +4,6 @@ go 1.19
require (
github.com/ethereum/go-ethereum v1.12.0
github.com/iris-contrib/middleware/cors v0.0.0-20230531125531-980d3a09a458
github.com/jmoiron/sqlx v1.3.5
github.com/kataras/iris/v12 v12.2.0
github.com/lib/pq v1.10.7

View File

@@ -242,8 +242,6 @@ github.com/iris-contrib/go.uuid v2.0.0+incompatible h1:XZubAYg61/JwnJNbZilGjf3b3
github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0=
github.com/iris-contrib/httpexpect/v2 v2.12.1 h1:3cTZSyBBen/kfjCtgNFoUKi1u0FVXNaAjyRJOo6AVS4=
github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk=
github.com/iris-contrib/middleware/cors v0.0.0-20230531125531-980d3a09a458 h1:V60rHQJc6DieKV1BqHIGclraPdO4kinuFAZIrPGHN7s=
github.com/iris-contrib/middleware/cors v0.0.0-20230531125531-980d3a09a458/go.mod h1:7eVziAp1yUwFB/ZMg71n84VWQH+7wukvxcHuF2e7cbg=
github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g=
github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw=

View File

@@ -4,7 +4,7 @@ import "bridge-history-api/service"
type Data struct {
Result []*service.TxHistoryInfo `json:"result"`
Total uint64 `json:"total"`
Total int `json:"total"`
}
type QueryByAddressResponse struct {

View File

@@ -32,7 +32,7 @@ type TxHistoryInfo struct {
// HistoryService example service.
type HistoryService interface {
GetTxsByAddress(address common.Address, offset int64, limit int64) ([]*TxHistoryInfo, uint64, error)
GetTxsByAddress(address common.Address, offset int64, limit int64) ([]*TxHistoryInfo, error)
GetTxsByHashes(hashes []string) ([]*TxHistoryInfo, error)
}
@@ -69,19 +69,15 @@ func updateCrossTxHash(msgHash string, txInfo *TxHistoryInfo, db db.OrmFactory)
}
func (h *historyBackend) GetTxsByAddress(address common.Address, offset int64, limit int64) ([]*TxHistoryInfo, uint64, error) {
var txHistories []*TxHistoryInfo
total, err := h.db.GetTotalCrossMsgCountByAddress(address.String())
if err != nil || total == 0 {
return txHistories, 0, err
}
func (h *historyBackend) GetTxsByAddress(address common.Address, offset int64, limit int64) ([]*TxHistoryInfo, error) {
txHistories := make([]*TxHistoryInfo, 0)
result, err := h.db.GetCrossMsgsByAddressWithOffset(address.String(), offset, limit)
if err != nil {
return nil, 0, err
return nil, err
}
for _, msg := range result {
txHistory := &TxHistoryInfo{
Hash: msg.Layer1Hash + msg.Layer2Hash,
Hash: msg.MsgHash,
Amount: msg.Amount,
To: msg.Target,
IsL1: msg.MsgType == int(orm.Layer1Msg),
@@ -95,7 +91,7 @@ func (h *historyBackend) GetTxsByAddress(address common.Address, offset int64, l
updateCrossTxHash(msg.MsgHash, txHistory, h.db)
txHistories = append(txHistories, txHistory)
}
return txHistories, total, nil
return txHistories, nil
}
func (h *historyBackend) GetTxsByHashes(hashes []string) ([]*TxHistoryInfo, error) {

View File

@@ -1,9 +1,7 @@
package utils
import (
"bytes"
"context"
"errors"
"fmt"
"math/big"
@@ -61,48 +59,3 @@ func ComputeMessageHash(
data, _ := backendabi.L2ScrollMessengerABI.Pack("relayMessage", sender, target, value, messageNonce, message)
return common.BytesToHash(crypto.Keccak256(data))
}
// GetBatchRangeFromCalldataV1 find the block range from calldata, both inclusive.
func GetBatchRangeFromCalldataV1(calldata []byte) ([]uint64, []uint64, []uint64, error) {
var batchIndices []uint64
var startBlocks []uint64
var finishBlocks []uint64
if bytes.Equal(calldata[0:4], common.Hex2Bytes("cb905499")) {
// commitBatches
method := backendabi.ScrollChainABI.Methods["commitBatches"]
values, err := method.Inputs.Unpack(calldata[4:])
if err != nil {
return batchIndices, startBlocks, finishBlocks, err
}
args := make([]backendabi.IScrollChainBatch, len(values))
err = method.Inputs.Copy(&args, values)
if err != nil {
return batchIndices, startBlocks, finishBlocks, err
}
for i := 0; i < len(args); i++ {
batchIndices = append(batchIndices, args[i].BatchIndex)
startBlocks = append(startBlocks, args[i].Blocks[0].BlockNumber)
finishBlocks = append(finishBlocks, args[i].Blocks[len(args[i].Blocks)-1].BlockNumber)
}
} else if bytes.Equal(calldata[0:4], common.Hex2Bytes("8c73235d")) {
// commitBatch
method := backendabi.ScrollChainABI.Methods["commitBatch"]
values, err := method.Inputs.Unpack(calldata[4:])
if err != nil {
return batchIndices, startBlocks, finishBlocks, err
}
args := backendabi.IScrollChainBatch{}
err = method.Inputs.Copy(&args, values)
if err != nil {
return batchIndices, startBlocks, finishBlocks, err
}
batchIndices = append(batchIndices, args.BatchIndex)
startBlocks = append(startBlocks, args.Blocks[0].BlockNumber)
finishBlocks = append(finishBlocks, args.Blocks[len(args.Blocks)-1].BlockNumber)
} else {
return batchIndices, startBlocks, finishBlocks, errors.New("invalid selector")
}
return batchIndices, startBlocks, finishBlocks, nil
}

View File

@@ -1,7 +1,6 @@
package utils_test
import (
"os"
"testing"
"github.com/ethereum/go-ethereum/common"
@@ -19,30 +18,3 @@ func TestKeccak2(t *testing.T) {
assert.NotEqual(t, b, c)
assert.Equal(t, "0xc0ffbd7f501bd3d49721b0724b2bff657cb2378f15d5a9b97cd7ea5bf630d512", c.Hex())
}
func TestGetBatchRangeFromCalldataV1(t *testing.T) {
calldata, err := os.ReadFile("../testdata/commit-batches-0x3095e91db7ba4a6fbf4654d607db322e58ff5579c502219c8024acaea74cf311.txt")
assert.NoError(t, err)
// multiple batches
batchIndices, startBlocks, finishBlocks, err := utils.GetBatchRangeFromCalldataV1(common.Hex2Bytes(string(calldata[:])))
assert.NoError(t, err)
assert.Equal(t, len(batchIndices), 5)
assert.Equal(t, len(startBlocks), 5)
assert.Equal(t, len(finishBlocks), 5)
assert.Equal(t, batchIndices[0], uint64(1))
assert.Equal(t, batchIndices[1], uint64(2))
assert.Equal(t, batchIndices[2], uint64(3))
assert.Equal(t, batchIndices[3], uint64(4))
assert.Equal(t, batchIndices[4], uint64(5))
assert.Equal(t, startBlocks[0], uint64(1))
assert.Equal(t, startBlocks[1], uint64(6))
assert.Equal(t, startBlocks[2], uint64(7))
assert.Equal(t, startBlocks[3], uint64(19))
assert.Equal(t, startBlocks[4], uint64(20))
assert.Equal(t, finishBlocks[0], uint64(5))
assert.Equal(t, finishBlocks[1], uint64(6))
assert.Equal(t, finishBlocks[2], uint64(18))
assert.Equal(t, finishBlocks[3], uint64(19))
assert.Equal(t, finishBlocks[4], uint64(20))
}

View File

@@ -87,11 +87,39 @@ func (b *MockApp) MockConfig(store bool) error {
return err
}
cfg.L1Config.Endpoint = base.L1gethImg.Endpoint()
cfg.L2Config.RelayerConfig.SenderConfig.Endpoint = base.L1gethImg.Endpoint()
cfg.L2Config.Endpoint = base.L2gethImg.Endpoint()
cfg.L1Config.RelayerConfig.SenderConfig.Endpoint = base.L2gethImg.Endpoint()
var (
l1Cfg, l2Cfg = cfg.L1Config, cfg.L2Config
l1Contracts, l2Contracts = base.L1Contracts, base.L2Contracts
)
// set confirms.
l1Cfg.Confirmations = 0
l1Cfg.RelayerConfig.SenderConfig.Confirmations = 0
l2Cfg.Confirmations = 0
l2Cfg.RelayerConfig.SenderConfig.Confirmations = 0
l2Cfg.BatchProposerConfig.CommitTxCalldataSizeLimit = 1
// set l1 and l2 chain endpoint.
l1Cfg.Endpoint = base.L1gethImg.Endpoint()
l2Cfg.RelayerConfig.SenderConfig.Endpoint = base.L1gethImg.Endpoint()
l2Cfg.Endpoint = base.L2gethImg.Endpoint()
l1Cfg.RelayerConfig.SenderConfig.Endpoint = base.L2gethImg.Endpoint()
cfg.DBConfig.DSN = base.DBImg.Endpoint()
// set l1 scroll contracts addresses.
l1Cfg.L1MessageQueueAddress = l1Contracts.L1MessageQueue
l1Cfg.ScrollChainContractAddress = l1Contracts.L1ScrollChain
l1Cfg.L1MessengerAddress = l1Contracts.L1ScrollMessenger
l1Cfg.RelayerConfig.MessengerContractAddress = l2Contracts.L2ScrollMessenger
l1Cfg.RelayerConfig.GasPriceOracleContractAddress = l2Contracts.L1GasPriceOracle
// set l2 scroll contracts addresses.
l2Cfg.L2MessageQueueAddress = l2Contracts.L2MessageQueue
l2Cfg.L2MessengerAddress = l2Contracts.L2ScrollMessenger
l2Cfg.RelayerConfig.RollupContractAddress = l1Contracts.L1ScrollChain
l2Cfg.RelayerConfig.MessengerContractAddress = l1Contracts.L1ScrollMessenger
l2Cfg.RelayerConfig.GasPriceOracleContractAddress = l1Contracts.L2GasPriceOracle
b.Config = cfg
if !store {

View File

@@ -0,0 +1,14 @@
#!/bin/bash
set -uex
${GOBIN}/gocover-cobertura < coverage.bridge.txt > coverage.bridge.xml
${GOBIN}/gocover-cobertura < coverage.db.txt > coverage.db.xml
${GOBIN}/gocover-cobertura < coverage.common.txt > coverage.common.xml
${GOBIN}/gocover-cobertura < coverage.coordinator.txt > coverage.coordinator.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

View File

@@ -0,0 +1,85 @@
imagePrefix = 'scrolltech'
credentialDocker = 'dockerhub'
TAGNAME = ''
pipeline {
agent any
options {
timeout (20)
}
tools {
go 'go-1.19'
nodejs "nodejs"
}
environment {
GO111MODULE = 'on'
PATH="/home/ubuntu/.cargo/bin:$PATH"
// LOG_DOCKER = 'true'
}
stages {
stage('Tag') {
steps {
script {
TAGNAME = sh(returnStdout: true, script: 'git tag -l --points-at HEAD')
sh "echo ${TAGNAME}"
// ...
}
}
}
stage('Build') {
environment {
// Extract the username and password of our credentials into "DOCKER_CREDENTIALS_USR" and "DOCKER_CREDENTIALS_PSW".
// (NOTE 1: DOCKER_CREDENTIALS will be set to "your_username:your_password".)
// The new variables will always be YOUR_VARIABLE_NAME + _USR and _PSW.
// (NOTE 2: You can't print credentials in the pipeline for security reasons.)
DOCKER_CREDENTIALS = credentials('dockerhub')
}
steps {
withCredentials([usernamePassword(credentialsId: "${credentialDocker}", passwordVariable: 'dockerPassword', usernameVariable: 'dockerUser')]) {
// Use a scripted pipeline.
script {
stage('Push image') {
if (TAGNAME == ""){
return;
}
sh "docker login --username=$dockerUser --password=$dockerPassword"
catchError(buildResult: 'SUCCESS', stageResult: 'SUCCESS') {
script {
try {
sh "docker manifest inspect scrolltech/bridge:$TAGNAME > /dev/null"
} catch (e) {
// only build if the tag non existed
//sh "docker login --username=${dockerUser} --password=${dockerPassword}"
sh "make -C bridge docker"
sh "docker tag scrolltech/bridge:latest scrolltech/bridge:${TAGNAME}"
sh "docker push scrolltech/bridge:${TAGNAME}"
throw e
}
}
}
catchError(buildResult: 'SUCCESS', stageResult: 'SUCCESS') {
script {
try {
sh "docker manifest inspect scrolltech/coordinator:$TAGNAME > /dev/null"
} catch (e) {
// only build if the tag non existed
//sh "docker login --username=${dockerUser} --password=${dockerPassword}"
sh "make -C coordinator docker"
sh "docker tag scrolltech/coordinator:latest scrolltech/coordinator:${TAGNAME}"
sh "docker push scrolltech/coordinator:${TAGNAME}"
throw e
}
}
}
}
}
}
}
}
}
post {
always {
cleanWs()
slackSend(message: "${JOB_BASE_NAME} ${GIT_COMMIT} #${TAGNAME} Tag build ${currentBuild.result}")
}
}
}

View File

@@ -1,38 +0,0 @@
coverage:
status:
project: off
patch: off
flag_management:
default_rules:
carryforward: true
individual_flags:
- name: bridge
statuses:
- type: project
target: auto
threshold: 1%
- name: bridge-history-api
statuses:
- type: project
target: auto
threshold: 1%
- name: common
statuses:
- type: project
target: auto
threshold: 1%
- name: coordinator
statuses:
- type: project
target: auto
threshold: 1%
- name: database
statuses:
- type: project
target: auto
threshold: 1%
- name: roller
statuses:
- type: project
target: auto
threshold: 1%

View File

@@ -1,2 +1,2 @@
*.go
*.sol
*.json

View File

@@ -1,9 +1,54 @@
.PHONY: all erc20 greeter
.PHONY: all erc20 greeter scroll scroll_sol clean
all: erc20 greeter
all: erc20 greeter scroll
erc20:
go run github.com/scroll-tech/go-ethereum/cmd/abigen --combined-json ./erc20/ERC20Mock.json --pkg erc20 --out ./erc20/ERC20Mock.go
go run github.com/scroll-tech/go-ethereum/cmd/abigen@develop --sol erc20/ERC20Mock.sol --pkg erc20 --out erc20/ERC20Mock.go --contract ERC20Mock
greeter:
go run github.com/scroll-tech/go-ethereum/cmd/abigen --combined-json ./greeter/Greeter.json --pkg greeter --out ./greeter/Greeter.go
go run github.com/scroll-tech/go-ethereum/cmd/abigen@develop --sol ./greeter/Greeter.sol --pkg greeter --out ./greeter/Greeter.go --contract Greeter
# TODO: This cmd can just used in local env, and it's not suitable used in automatic process. Because bridge's update can't keep consistent with contracts' update.
scroll_sol:
cd ../../contracts && forge install
forge flatten --root ../../contracts ../../contracts/src/L1/L1ScrollMessenger.sol | sed '/SPDX-License-Identifier/d' > ./scroll/L1/L1ScrollMessenger.sol
#L1/gateways
forge flatten --root ../../contracts ../../contracts/src/L1/gateways/L1ETHGateway.sol | sed '/SPDX-License-Identifier/d' > ./scroll/L1/gateways/L1ETHGateway.sol
#L1/rollup
forge flatten --root ../../contracts ../../contracts/src/L1/rollup/L1MessageQueue.sol | sed '/SPDX-License-Identifier/d' > ./scroll/L1/rollup/L1MessageQueue.sol
forge flatten --root ../../contracts ../../contracts/src/L1/rollup/L2GasPriceOracle.sol | sed '/SPDX-License-Identifier/d' > ./scroll/L1/rollup/L2GasPriceOracle.sol
forge flatten --root ../../contracts ../../contracts/src/L1/rollup/ScrollChain.sol | sed '/SPDX-License-Identifier/d' > ./scroll/L1/rollup/ScrollChain.sol
#L1
forge flatten --root ../../contracts ../../contracts/src/L1/L1ScrollMessenger.sol | sed '/SPDX-License-Identifier/d' > ./scroll/L1/L1ScrollMessenger.sol
#L2/gateways
forge flatten --root ../../contracts ../../contracts/src/L2/gateways/L2ETHGateway.sol | sed '/SPDX-License-Identifier/d' > ./scroll/L2/gateways/L2ETHGateway.sol
#L2/predeploys
forge flatten --root ../../contracts ../../contracts/src/L2/predeploys/L1BlockContainer.sol | sed '/SPDX-License-Identifier/d' > ./scroll/L2/predeploys/L1BlockContainer.sol
forge flatten --root ../../contracts ../../contracts/src/L2/predeploys/L1GasPriceOracle.sol | sed '/SPDX-License-Identifier/d' > ./scroll/L2/predeploys/L1GasPriceOracle.sol
forge flatten --root ../../contracts ../../contracts/src/L2/predeploys/L2MessageQueue.sol | sed '/SPDX-License-Identifier/d' > ./scroll/L2/predeploys/L2MessageQueue.sol
forge flatten --root ../../contracts ../../contracts/src/L2/predeploys/L2TxFeeVault.sol | sed '/SPDX-License-Identifier/d' > ./scroll/L2/predeploys/L2TxFeeVault.sol
#L2
forge flatten --root ../../contracts ../../contracts/src/L2/L2ScrollMessenger.sol | sed '/SPDX-License-Identifier/d' > ./scroll/L2/L2ScrollMessenger.sol
scroll:
#L1/gateways
go run github.com/scroll-tech/go-ethereum/cmd/abigen@develop --sol scroll/L1/gateways/L1ETHGateway.sol --pkg gateways --out ./scroll/L1/gateways/L1ETHGateway.go --contract L1ETHGateway
#L1/rollup
go run github.com/scroll-tech/go-ethereum/cmd/abigen@develop --sol ./scroll/L1/rollup/L1MessageQueue.sol --pkg rollup --out ./scroll/L1/rollup/L1MessageQueue.go --contract L1MessageQueue
go run github.com/scroll-tech/go-ethereum/cmd/abigen@develop --sol ./scroll/L1/rollup/L2GasPriceOracle.sol --pkg rollup --out ./scroll/L1/rollup/L2GasPriceOracle.go --contract L2GasPriceOracle
go run github.com/scroll-tech/go-ethereum/cmd/abigen@develop --sol ./scroll/L1/rollup/ScrollChain.sol --pkg rollup --out ./scroll/L1/rollup/ScrollChain.go --contract ScrollChain
#L1
go run github.com/scroll-tech/go-ethereum/cmd/abigen@develop --sol ./scroll/L1/L1ScrollMessenger.sol --pkg l1 --out ./scroll/L1/L1ScrollMessenger.go --contract L1ScrollMessenger
#L2/gateways
go run github.com/scroll-tech/go-ethereum/cmd/abigen@develop --sol ./scroll/L2/gateways/L2ETHGateway.sol --pkg gateways --out ./scroll/L2/gateways/L2ETHGateway.go --contract L2ETHGateway
#L2/predeploys
go run github.com/scroll-tech/go-ethereum/cmd/abigen@develop --sol ./scroll/L2/predeploys/L1BlockContainer.sol --pkg predeploys --out ./scroll/L2/predeploys/L1BlockContainer.go --contract L1BlockContainer
go run github.com/scroll-tech/go-ethereum/cmd/abigen@develop --sol ./scroll/L2/predeploys/L1GasPriceOracle.sol --pkg predeploys --out ./scroll/L2/predeploys/L1GasPriceOracle.go --contract L1GasPriceOracle
go run github.com/scroll-tech/go-ethereum/cmd/abigen@develop --sol ./scroll/L2/predeploys/L2MessageQueue.sol --pkg predeploys --out ./scroll/L2/predeploys/L2MessageQueue.go --contract L2MessageQueue
go run github.com/scroll-tech/go-ethereum/cmd/abigen@develop --sol ./scroll/L2/predeploys/L2TxFeeVault.sol --pkg predeploys --out ./scroll/L2/predeploys/L2TxFeeVault.go --contract L2TxFeeVault
#L2
go run github.com/scroll-tech/go-ethereum/cmd/abigen@develop --sol ./scroll/L2/L2ScrollMessenger.sol --pkg l2 --out ./scroll/L2/L2ScrollMessenger.go --contract L2ScrollMessenger
clean:
find ./ -type f -name "*.go" | xargs rm -r
find scroll/ -type f -name "*.sol" | xargs rm -f

View File

@@ -1,387 +0,0 @@
{
"contracts": {
"tests/contracts/erc20/erc20.sol:ERC20Mock": {
"abi": [
{
"inputs": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "address",
"name": "initialAccount",
"type": "address"
},
{
"internalType": "uint256",
"name": "initialBalance",
"type": "uint256"
}
],
"stateMutability": "payable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "approveInternal",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "burn",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "decimals",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "subtractedValue",
"type": "uint256"
}
],
"name": "decreaseAllowance",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "addedValue",
"type": "uint256"
}
],
"name": "increaseAllowance",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "mint",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "transferInternal",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
}
},
"version": "0.8.16+commit.07a7930e.Darwin.appleclang"
}

View File

@@ -0,0 +1,572 @@
// File: contracts/token/ERC20/IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
}
// File: contracts/token/ERC20/extensions/IERC20Metadata.sol
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
// File: contracts/utils/Context.sol
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
// File: contracts/token/ERC20/ERC20.sol
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
* For a generic mechanism see {ERC20PresetMinterPauser}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC20
* applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*
* Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
* functions have been added to mitigate the well-known issues around setting
* allowances. See {IERC20-approve}.
*/
contract ERC20 is Context, IERC20, IERC20Metadata {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* The default value of {decimals} is 18. To select a different value for
* {decimals} you should overload it.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual override returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the value {ERC20} uses, unless this function is
* overridden;
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual override returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual override returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual override returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/
function transfer(address to, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_transfer(owner, to, amount);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual override returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_approve(owner, spender, amount);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
* - the caller must have allowance for ``from``'s tokens of at least
* `amount`.
*/
function transferFrom(
address from,
address to,
uint256 amount
) public virtual override returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);
return true;
}
/**
* @dev Atomically increases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, allowance(owner, spender) + addedValue);
return true;
}
/**
* @dev Atomically decreases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `spender` must have allowance for the caller of at least
* `subtractedValue`.
*/
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
address owner = _msgSender();
uint256 currentAllowance = allowance(owner, spender);
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
unchecked {
_approve(owner, spender, currentAllowance - subtractedValue);
}
return true;
}
/**
* @dev Moves `amount` of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
*/
function _transfer(
address from,
address to,
uint256 amount
) internal virtual {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(from, to, amount);
uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[from] = fromBalance - amount;
// Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
// decrementing then incrementing.
_balances[to] += amount;
}
emit Transfer(from, to, amount);
_afterTokenTransfer(from, to, amount);
}
/** @dev Creates `amount` tokens and assigns them to `account`, increasing
* the total supply.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
*/
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply += amount;
unchecked {
// Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
_balances[account] += amount;
}
emit Transfer(address(0), account, amount);
_afterTokenTransfer(address(0), account, amount);
}
/**
* @dev Destroys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
* - `account` must have at least `amount` tokens.
*/
function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
uint256 accountBalance = _balances[account];
require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
unchecked {
_balances[account] = accountBalance - amount;
// Overflow not possible: amount <= accountBalance <= totalSupply.
_totalSupply -= amount;
}
emit Transfer(account, address(0), amount);
_afterTokenTransfer(account, address(0), amount);
}
/**
* @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*/
function _approve(
address owner,
address spender,
uint256 amount
) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `amount`.
*
* Does not update the allowance amount in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Might emit an {Approval} event.
*/
function _spendAllowance(
address owner,
address spender,
uint256 amount
) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
/**
* @dev Hook that is called before any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* will be transferred to `to`.
* - when `from` is zero, `amount` tokens will be minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual {}
/**
* @dev Hook that is called after any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* has been transferred to `to`.
* - when `from` is zero, `amount` tokens have been minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens have been burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _afterTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual {}
}
// File: contracts/mocks/ERC20Mock.sol
pragma solidity ^0.8.0;
// mock class using ERC20
contract ERC20Mock is ERC20 {
constructor(
string memory name,
string memory symbol,
address initialAccount,
uint256 initialBalance
) payable ERC20(name, symbol) {
_mint(initialAccount, initialBalance);
}
function mint(address account, uint256 amount) public {
_mint(account, amount);
}
function burn(address account, uint256 amount) public {
_burn(account, amount);
}
function transferInternal(
address from,
address to,
uint256 value
) public {
_transfer(from, to, value);
}
function approveInternal(
address owner,
address spender,
uint256 value
) public {
_approve(owner, spender, value);
}
}

View File

@@ -1,72 +0,0 @@
{
"contracts": {
"greeter/Greeter.sol:Greeter": {
"abi": [
{
"inputs": [
{
"internalType": "uint256",
"name": "num",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "retrieve",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "retrieve_failing",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "num",
"type": "uint256"
}
],
"name": "set_value",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "num",
"type": "uint256"
}
],
"name": "set_value_failing",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
}
},
"version": "0.8.16+commit.07a7930e.Darwin.appleclang"
}

View File

@@ -0,0 +1,34 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
/**
* @title Greeter
* @dev Store & retrieve value in a variable
*/
contract Greeter {
uint256 number;
constructor(uint256 num) {
number = num;
}
function retrieve() public view returns (uint256){
return number;
}
function retrieve_failing() public view returns (uint256){
require(false);
return number;
}
function set_value(uint256 num) public{
number = num;
}
function set_value_failing(uint256 num) public{
number = num;
require(false);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,782 @@
// File: @openzeppelin/contracts/utils/Address.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
require(isContract(target), "Address: delegate call to non-contract");
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
// File: @openzeppelin/contracts/proxy/utils/Initializable.sol
// OpenZeppelin Contracts (last updated v4.5.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.0;
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To initialize the implementation contract, you can either invoke the
* initializer manually, or you can include a constructor to automatically mark it as initialized when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() initializer {}
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
*/
bool private _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
/**
* @dev Modifier to protect an initializer function from being invoked twice.
*/
modifier initializer() {
// If the contract is initializing we ignore whether _initialized is set in order to support multiple
// inheritance patterns, but we only do this in the context of a constructor, because in other contexts the
// contract may have been reentered.
require(_initializing ? _isConstructor() : !_initialized, "Initializable: contract is already initialized");
bool isTopLevelCall = !_initializing;
if (isTopLevelCall) {
_initializing = true;
_initialized = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
}
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} modifier, directly or indirectly.
*/
modifier onlyInitializing() {
require(_initializing, "Initializable: contract is not initializing");
_;
}
function _isConstructor() private view returns (bool) {
return !Address.isContract(address(this));
}
}
// File: src/L2/gateways/IL2ETHGateway.sol
pragma solidity ^0.8.0;
interface IL2ETHGateway {
/**********
* Events *
**********/
/// @notice Emitted when someone withdraw ETH from L2 to L1.
/// @param from The address of sender in L2.
/// @param to The address of recipient in L1.
/// @param amount The amount of ETH will be deposited from L2 to L1.
/// @param data The optional calldata passed to recipient in L1.
event WithdrawETH(address indexed from, address indexed to, uint256 amount, bytes data);
/// @notice Emitted when ETH is deposited from L1 to L2 and transfer to recipient.
/// @param from The address of sender in L1.
/// @param to The address of recipient in L2.
/// @param amount The amount of ETH deposited from L1 to L2.
/// @param data The optional calldata passed to recipient in L2.
event FinalizeDepositETH(address indexed from, address indexed to, uint256 amount, bytes data);
/****************************
* Public Mutated Functions *
****************************/
/// @notice Withdraw ETH to caller's account in L1.
/// @param amount The amount of ETH to be withdrawn.
/// @param gasLimit Optional, gas limit used to complete the withdraw on L1.
function withdrawETH(uint256 amount, uint256 gasLimit) external payable;
/// @notice Withdraw ETH to caller's account in L1.
/// @param to The address of recipient's account on L1.
/// @param amount The amount of ETH to be withdrawn.
/// @param gasLimit Optional, gas limit used to complete the withdraw on L1.
function withdrawETH(
address to,
uint256 amount,
uint256 gasLimit
) external payable;
/// @notice Withdraw ETH to caller's account in L1.
/// @param to The address of recipient's account on L1.
/// @param amount The amount of ETH to be withdrawn.
/// @param data Optional data to forward to recipient's account.
/// @param gasLimit Optional, gas limit used to complete the withdraw on L1.
function withdrawETHAndCall(
address to,
uint256 amount,
bytes calldata data,
uint256 gasLimit
) external payable;
/// @notice Complete ETH deposit from L1 to L2 and send fund to recipient's account in L2.
/// @dev This function should only be called by L2ScrollMessenger.
/// This function should also only be called by L1GatewayRouter in L1.
/// @param _from The address of account who deposit ETH in L1.
/// @param _to The address of recipient in L2 to receive ETH.
/// @param _amount The amount of ETH to deposit.
/// @param _data Optional data to forward to recipient's account.
function finalizeDepositETH(
address _from,
address _to,
uint256 _amount,
bytes calldata _data
) external payable;
}
// File: src/libraries/IScrollMessenger.sol
pragma solidity ^0.8.0;
interface IScrollMessenger {
/**********
* Events *
**********/
/// @notice Emitted when a cross domain message is sent.
/// @param sender The address of the sender who initiates the message.
/// @param target The address of target contract to call.
/// @param value The amount of value passed to the target contract.
/// @param messageNonce The nonce of the message.
/// @param gasLimit The optional gas limit passed to L1 or L2.
/// @param message The calldata passed to the target contract.
event SentMessage(
address indexed sender,
address indexed target,
uint256 value,
uint256 messageNonce,
uint256 gasLimit,
bytes message
);
/// @notice Emitted when a cross domain message is relayed successfully.
/// @param messageHash The hash of the message.
event RelayedMessage(bytes32 indexed messageHash);
/// @notice Emitted when a cross domain message is failed to relay.
/// @param messageHash The hash of the message.
event FailedRelayedMessage(bytes32 indexed messageHash);
/*************************
* Public View Functions *
*************************/
/// @notice Return the sender of a cross domain message.
function xDomainMessageSender() external view returns (address);
/****************************
* Public Mutated Functions *
****************************/
/// @notice Send cross chain message from L1 to L2 or L2 to L1.
/// @param target The address of account who recieve the message.
/// @param value The amount of ether passed when call target contract.
/// @param message The content of the message.
/// @param gasLimit Gas limit required to complete the message relay on corresponding chain.
function sendMessage(
address target,
uint256 value,
bytes calldata message,
uint256 gasLimit
) external payable;
}
// File: src/L1/IL1ScrollMessenger.sol
pragma solidity ^0.8.0;
interface IL1ScrollMessenger is IScrollMessenger {
/***********
* 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;
}
/****************************
* Public Mutated 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 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;
}
// File: src/L1/gateways/IL1ETHGateway.sol
pragma solidity ^0.8.0;
interface IL1ETHGateway {
/**********
* 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 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 *
****************************/
/// @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 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;
}
// File: src/libraries/gateway/IScrollGateway.sol
pragma solidity ^0.8.0;
interface IScrollGateway {
/// @notice The address of corresponding L1/L2 Gateway contract.
function counterpart() external view returns (address);
/// @notice The address of L1GatewayRouter/L2GatewayRouter contract.
function router() external view returns (address);
/// @notice The address of corresponding L1ScrollMessenger/L2ScrollMessenger contract.
function messenger() external view returns (address);
}
// File: src/libraries/gateway/ScrollGatewayBase.sol
pragma solidity ^0.8.0;
abstract contract ScrollGatewayBase is IScrollGateway {
/*************
* Constants *
*************/
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.5.0/contracts/security/ReentrancyGuard.sol
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
/*************
* Variables *
*************/
/// @inheritdoc IScrollGateway
address public override counterpart;
/// @inheritdoc IScrollGateway
address public override router;
/// @inheritdoc IScrollGateway
address public override messenger;
/// @dev The status of for non-reentrant check.
uint256 private _status;
/**********************
* Function Modifiers *
**********************/
modifier nonReentrant() {
// On the first call to nonReentrant, _notEntered will be true
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
modifier onlyMessenger() {
require(msg.sender == messenger, "only messenger can call");
_;
}
modifier onlyCallByCounterpart() {
address _messenger = messenger; // gas saving
require(msg.sender == _messenger, "only messenger can call");
require(counterpart == IScrollMessenger(_messenger).xDomainMessageSender(), "only call by conterpart");
_;
}
/***************
* Constructor *
***************/
function _initialize(
address _counterpart,
address _router,
address _messenger
) internal {
require(_counterpart != address(0), "zero counterpart address");
require(_messenger != address(0), "zero messenger address");
counterpart = _counterpart;
messenger = _messenger;
// @note: the address of router could be zero, if this contract is GatewayRouter.
if (_router != address(0)) {
router = _router;
}
// for reentrancy guard
_status = _NOT_ENTERED;
}
}
// File: src/L1/gateways/L1ETHGateway.sol
pragma solidity ^0.8.0;
/// @title L1ETHGateway
/// @notice The `L1ETHGateway` is used to deposit ETH in layer 1 and
/// finalize withdraw ETH from layer 2.
/// @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 *
***************/
/// @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));
}
// 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

@@ -0,0 +1,656 @@
// File: @openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library AddressUpgradeable {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
// File: @openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol
// OpenZeppelin Contracts (last updated v4.5.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.0;
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To initialize the implementation contract, you can either invoke the
* initializer manually, or you can include a constructor to automatically mark it as initialized when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() initializer {}
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
*/
bool private _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
/**
* @dev Modifier to protect an initializer function from being invoked twice.
*/
modifier initializer() {
// If the contract is initializing we ignore whether _initialized is set in order to support multiple
// inheritance patterns, but we only do this in the context of a constructor, because in other contexts the
// contract may have been reentered.
require(_initializing ? _isConstructor() : !_initialized, "Initializable: contract is already initialized");
bool isTopLevelCall = !_initializing;
if (isTopLevelCall) {
_initializing = true;
_initialized = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
}
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} modifier, directly or indirectly.
*/
modifier onlyInitializing() {
require(_initializing, "Initializable: contract is not initializing");
_;
}
function _isConstructor() private view returns (bool) {
return !AddressUpgradeable.isContract(address(this));
}
}
// File: @openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract ContextUpgradeable is Initializable {
function __Context_init() internal onlyInitializing {
}
function __Context_init_unchained() internal onlyInitializing {
}
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
// File: @openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol
// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)
pragma solidity ^0.8.0;
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
function __Ownable_init() internal onlyInitializing {
__Ownable_init_unchained();
}
function __Ownable_init_unchained() internal onlyInitializing {
_transferOwnership(_msgSender());
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}
// File: src/L1/rollup/IL2GasPriceOracle.sol
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);
}
// File: src/L1/rollup/IL1MessageQueue.sol
pragma solidity ^0.8.0;
interface IL1MessageQueue {
/**********
* 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
);
/*************************
* 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 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);
/****************************
* Public Mutated 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 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;
}
// File: src/libraries/common/AddressAliasHelper.sol
pragma solidity ^0.8.0;
library AddressAliasHelper {
uint160 constant offset = uint160(0x1111000000000000000000000000000000001111);
/// @notice Utility function that converts the address in the L1 that submitted a tx to
/// the inbox to the msg.sender viewed in the L2
/// @param l1Address the address in the L1 that triggered the tx to L2
/// @return l2Address L2 address as viewed in msg.sender
function applyL1ToL2Alias(address l1Address) internal pure returns (address l2Address) {
unchecked {
l2Address = address(uint160(l1Address) + offset);
}
}
/// @notice Utility function that converts the msg.sender viewed in the L2 to the
/// address in the L1 that submitted a tx to the inbox
/// @param l2Address L2 address as viewed in msg.sender
/// @return l1Address the address in the L1 that triggered the tx to L2
function undoL1ToL2Alias(address l2Address) internal pure returns (address l1Address) {
unchecked {
l1Address = address(uint160(l2Address) - offset);
}
}
}
// File: src/L1/rollup/L1MessageQueue.sol
pragma solidity ^0.8.0;
/// @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 *
**********/
/// @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 *
*************/
/// @notice The address of L1ScrollMessenger contract.
address public messenger;
/// @notice The address of GasOracle contract.
address public gasOracle;
/// @notice The list of queued cross domain messages.
bytes32[] public messageQueue;
/***************
* Constructor *
***************/
function initialize(address _messenger, address _gasOracle) external initializer {
OwnableUpgradeable.__Ownable_init();
messenger = _messenger;
gasOracle = _gasOracle;
}
/*************************
* Public View Functions *
*************************/
/// @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 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 *
****************************/
/// @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);
// @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);
messageQueue.push(_hash);
}
/// @inheritdoc IL1MessageQueue
function appendEnforcedTransaction(
address,
address,
uint256,
uint256,
bytes calldata
) external override {
// @todo
}
/************************
* 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;
emit UpdateGasOracle(_oldGasOracle, _newGasOracle);
}
}

View File

@@ -0,0 +1,597 @@
// File: @openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library AddressUpgradeable {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
// File: @openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol
// OpenZeppelin Contracts (last updated v4.5.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.0;
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To initialize the implementation contract, you can either invoke the
* initializer manually, or you can include a constructor to automatically mark it as initialized when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() initializer {}
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
*/
bool private _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
/**
* @dev Modifier to protect an initializer function from being invoked twice.
*/
modifier initializer() {
// If the contract is initializing we ignore whether _initialized is set in order to support multiple
// inheritance patterns, but we only do this in the context of a constructor, because in other contexts the
// contract may have been reentered.
require(_initializing ? _isConstructor() : !_initialized, "Initializable: contract is already initialized");
bool isTopLevelCall = !_initializing;
if (isTopLevelCall) {
_initializing = true;
_initialized = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
}
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} modifier, directly or indirectly.
*/
modifier onlyInitializing() {
require(_initializing, "Initializable: contract is not initializing");
_;
}
function _isConstructor() private view returns (bool) {
return !AddressUpgradeable.isContract(address(this));
}
}
// File: @openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract ContextUpgradeable is Initializable {
function __Context_init() internal onlyInitializing {
}
function __Context_init_unchained() internal onlyInitializing {
}
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
// File: @openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol
// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)
pragma solidity ^0.8.0;
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
function __Ownable_init() internal onlyInitializing {
__Ownable_init_unchained();
}
function __Ownable_init_unchained() internal onlyInitializing {
_transferOwnership(_msgSender());
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}
// File: src/libraries/common/IWhitelist.sol
pragma solidity ^0.8.0;
interface IWhitelist {
/// @notice Check whether the sender is allowed to do something.
/// @param _sender The address of sender.
function isSenderAllowed(address _sender) external view returns (bool);
}
// File: src/L1/rollup/IL2GasPriceOracle.sol
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);
}
// File: src/L1/rollup/L2GasPriceOracle.sol
pragma solidity ^0.8.0;
contract L2GasPriceOracle is OwnableUpgradeable, IL2GasPriceOracle {
/**********
* Events *
**********/
/// @notice Emitted when owner updates whitelist contract.
/// @param _oldWhitelist The address of old whitelist contract.
/// @param _newWhitelist The address of new whitelist contract.
event UpdateWhitelist(address _oldWhitelist, address _newWhitelist);
/// @notice Emitted when current fee overhead is updated.
/// @param overhead The current fee overhead updated.
event OverheadUpdated(uint256 overhead);
/// @notice Emitted when current fee scalar is updated.
/// @param scalar The current fee scalar updated.
event ScalarUpdated(uint256 scalar);
/// @notice Emitted when current l2 base fee is updated.
/// @param l2BaseFee The current l2 base fee updated.
event L2BaseFeeUpdated(uint256 l2BaseFee);
/*************
* Constants *
*************/
/// @dev The precision used in the scalar.
uint256 private constant PRECISION = 1e9;
/// @dev The maximum possible l1 fee overhead.
/// Computed based on current l1 block gas limit.
uint256 private constant MAX_OVERHEAD = 30000000 / 16;
/// @dev The maximum possible l1 fee scale.
/// x1000 should be enough.
uint256 private constant MAX_SCALE = 1000 * PRECISION;
/*************
* Variables *
*************/
/// @notice The current l1 fee overhead.
uint256 public overhead;
/// @notice The current l1 fee scalar.
uint256 public scalar;
/// @notice The latest known l2 base fee.
uint256 public l2BaseFee;
/// @notice The address of whitelist contract.
IWhitelist public whitelist;
/***************
* Constructor *
***************/
function initialize() external initializer {
OwnableUpgradeable.__Ownable_init();
}
/*************************
* Public View Functions *
*************************/
/// @notice Return the current l1 base fee.
function l1BaseFee() public view returns (uint256) {
return block.basefee;
}
/// @inheritdoc IL2GasPriceOracle
function estimateCrossDomainMessageFee(
address,
address,
bytes memory _message,
uint256 _gasLimit
) external view override returns (uint256) {
unchecked {
uint256 _l1GasUsed = getL1GasUsed(_message);
uint256 _rollupFee = (_l1GasUsed * l1BaseFee() * scalar) / PRECISION;
uint256 _l2Fee = _gasLimit * l2BaseFee;
return _l2Fee + _rollupFee;
}
}
/// @notice Computes the amount of L1 gas used for a transaction. Adds the overhead which
/// represents the per-transaction gas overhead of posting the transaction and state
/// roots to L1. Adds 68 bytes of padding to account for the fact that the input does
/// not have a signature.
/// @param _data Unsigned fully RLP-encoded transaction to get the L1 gas for.
/// @return Amount of L1 gas used to publish the transaction.
function getL1GasUsed(bytes memory _data) public view returns (uint256) {
uint256 _total = 0;
uint256 _length = _data.length;
unchecked {
for (uint256 i = 0; i < _length; i++) {
if (_data[i] == 0) {
_total += 4;
} else {
_total += 16;
}
}
uint256 _unsigned = _total + overhead;
return _unsigned + (68 * 16);
}
}
/****************************
* Public Mutated Functions *
****************************/
/// @notice Allows the owner to modify the l2 base fee.
/// @param _l2BaseFee The new l2 base fee.
function setL2BaseFee(uint256 _l2BaseFee) external {
require(whitelist.isSenderAllowed(msg.sender), "Not whitelisted sender");
l2BaseFee = _l2BaseFee;
emit L2BaseFeeUpdated(_l2BaseFee);
}
/************************
* Restricted Functions *
************************/
/// @notice Allows the owner to modify the overhead.
/// @param _overhead New overhead
function setOverhead(uint256 _overhead) external onlyOwner {
require(_overhead <= MAX_OVERHEAD, "exceed maximum overhead");
overhead = _overhead;
emit OverheadUpdated(_overhead);
}
/// Allows the owner to modify the scalar.
/// @param _scalar The new scalar
function setScalar(uint256 _scalar) external onlyOwner {
require(_scalar <= MAX_SCALE, "exceed maximum scale");
scalar = _scalar;
emit ScalarUpdated(_scalar);
}
/// @notice Update whitelist contract.
/// @dev This function can only called by contract owner.
/// @param _newWhitelist The address of new whitelist contract.
function updateWhitelist(address _newWhitelist) external onlyOwner {
address _oldWhitelist = address(whitelist);
whitelist = IWhitelist(_newWhitelist);
emit UpdateWhitelist(_oldWhitelist, _newWhitelist);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,768 @@
// File: @openzeppelin/contracts/utils/Address.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
require(isContract(target), "Address: delegate call to non-contract");
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
// File: @openzeppelin/contracts/proxy/utils/Initializable.sol
// OpenZeppelin Contracts (last updated v4.5.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.0;
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To initialize the implementation contract, you can either invoke the
* initializer manually, or you can include a constructor to automatically mark it as initialized when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() initializer {}
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
*/
bool private _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
/**
* @dev Modifier to protect an initializer function from being invoked twice.
*/
modifier initializer() {
// If the contract is initializing we ignore whether _initialized is set in order to support multiple
// inheritance patterns, but we only do this in the context of a constructor, because in other contexts the
// contract may have been reentered.
require(_initializing ? _isConstructor() : !_initialized, "Initializable: contract is already initialized");
bool isTopLevelCall = !_initializing;
if (isTopLevelCall) {
_initializing = true;
_initialized = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
}
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} modifier, directly or indirectly.
*/
modifier onlyInitializing() {
require(_initializing, "Initializable: contract is not initializing");
_;
}
function _isConstructor() private view returns (bool) {
return !Address.isContract(address(this));
}
}
// File: src/L1/gateways/IL1ETHGateway.sol
pragma solidity ^0.8.0;
interface IL1ETHGateway {
/**********
* 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 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 *
****************************/
/// @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 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;
}
// File: src/libraries/IScrollMessenger.sol
pragma solidity ^0.8.0;
interface IScrollMessenger {
/**********
* Events *
**********/
/// @notice Emitted when a cross domain message is sent.
/// @param sender The address of the sender who initiates the message.
/// @param target The address of target contract to call.
/// @param value The amount of value passed to the target contract.
/// @param messageNonce The nonce of the message.
/// @param gasLimit The optional gas limit passed to L1 or L2.
/// @param message The calldata passed to the target contract.
event SentMessage(
address indexed sender,
address indexed target,
uint256 value,
uint256 messageNonce,
uint256 gasLimit,
bytes message
);
/// @notice Emitted when a cross domain message is relayed successfully.
/// @param messageHash The hash of the message.
event RelayedMessage(bytes32 indexed messageHash);
/// @notice Emitted when a cross domain message is failed to relay.
/// @param messageHash The hash of the message.
event FailedRelayedMessage(bytes32 indexed messageHash);
/*************************
* Public View Functions *
*************************/
/// @notice Return the sender of a cross domain message.
function xDomainMessageSender() external view returns (address);
/****************************
* Public Mutated Functions *
****************************/
/// @notice Send cross chain message from L1 to L2 or L2 to L1.
/// @param target The address of account who recieve the message.
/// @param value The amount of ether passed when call target contract.
/// @param message The content of the message.
/// @param gasLimit Gas limit required to complete the message relay on corresponding chain.
function sendMessage(
address target,
uint256 value,
bytes calldata message,
uint256 gasLimit
) external payable;
}
// File: src/L2/IL2ScrollMessenger.sol
pragma solidity ^0.8.0;
interface IL2ScrollMessenger is IScrollMessenger {
/***********
* Structs *
***********/
struct L1MessageProof {
bytes32 blockHash;
bytes stateRootProof;
}
/****************************
* Public Mutated Functions *
****************************/
/// @notice execute L1 => L2 message
/// @dev Make sure this is only called by privileged accounts.
/// @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.
function relayMessage(
address from,
address to,
uint256 value,
uint256 nonce,
bytes calldata message
) external;
/// @notice execute L1 => L2 message with 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 message proof.
function retryMessageWithProof(
address from,
address to,
uint256 value,
uint256 nonce,
bytes calldata message,
L1MessageProof calldata proof
) external;
}
// File: src/L2/gateways/IL2ETHGateway.sol
pragma solidity ^0.8.0;
interface IL2ETHGateway {
/**********
* Events *
**********/
/// @notice Emitted when someone withdraw ETH from L2 to L1.
/// @param from The address of sender in L2.
/// @param to The address of recipient in L1.
/// @param amount The amount of ETH will be deposited from L2 to L1.
/// @param data The optional calldata passed to recipient in L1.
event WithdrawETH(address indexed from, address indexed to, uint256 amount, bytes data);
/// @notice Emitted when ETH is deposited from L1 to L2 and transfer to recipient.
/// @param from The address of sender in L1.
/// @param to The address of recipient in L2.
/// @param amount The amount of ETH deposited from L1 to L2.
/// @param data The optional calldata passed to recipient in L2.
event FinalizeDepositETH(address indexed from, address indexed to, uint256 amount, bytes data);
/****************************
* Public Mutated Functions *
****************************/
/// @notice Withdraw ETH to caller's account in L1.
/// @param amount The amount of ETH to be withdrawn.
/// @param gasLimit Optional, gas limit used to complete the withdraw on L1.
function withdrawETH(uint256 amount, uint256 gasLimit) external payable;
/// @notice Withdraw ETH to caller's account in L1.
/// @param to The address of recipient's account on L1.
/// @param amount The amount of ETH to be withdrawn.
/// @param gasLimit Optional, gas limit used to complete the withdraw on L1.
function withdrawETH(
address to,
uint256 amount,
uint256 gasLimit
) external payable;
/// @notice Withdraw ETH to caller's account in L1.
/// @param to The address of recipient's account on L1.
/// @param amount The amount of ETH to be withdrawn.
/// @param data Optional data to forward to recipient's account.
/// @param gasLimit Optional, gas limit used to complete the withdraw on L1.
function withdrawETHAndCall(
address to,
uint256 amount,
bytes calldata data,
uint256 gasLimit
) external payable;
/// @notice Complete ETH deposit from L1 to L2 and send fund to recipient's account in L2.
/// @dev This function should only be called by L2ScrollMessenger.
/// This function should also only be called by L1GatewayRouter in L1.
/// @param _from The address of account who deposit ETH in L1.
/// @param _to The address of recipient in L2 to receive ETH.
/// @param _amount The amount of ETH to deposit.
/// @param _data Optional data to forward to recipient's account.
function finalizeDepositETH(
address _from,
address _to,
uint256 _amount,
bytes calldata _data
) external payable;
}
// File: src/libraries/gateway/IScrollGateway.sol
pragma solidity ^0.8.0;
interface IScrollGateway {
/// @notice The address of corresponding L1/L2 Gateway contract.
function counterpart() external view returns (address);
/// @notice The address of L1GatewayRouter/L2GatewayRouter contract.
function router() external view returns (address);
/// @notice The address of corresponding L1ScrollMessenger/L2ScrollMessenger contract.
function messenger() external view returns (address);
}
// File: src/libraries/gateway/ScrollGatewayBase.sol
pragma solidity ^0.8.0;
abstract contract ScrollGatewayBase is IScrollGateway {
/*************
* Constants *
*************/
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.5.0/contracts/security/ReentrancyGuard.sol
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
/*************
* Variables *
*************/
/// @inheritdoc IScrollGateway
address public override counterpart;
/// @inheritdoc IScrollGateway
address public override router;
/// @inheritdoc IScrollGateway
address public override messenger;
/// @dev The status of for non-reentrant check.
uint256 private _status;
/**********************
* Function Modifiers *
**********************/
modifier nonReentrant() {
// On the first call to nonReentrant, _notEntered will be true
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
modifier onlyMessenger() {
require(msg.sender == messenger, "only messenger can call");
_;
}
modifier onlyCallByCounterpart() {
address _messenger = messenger; // gas saving
require(msg.sender == _messenger, "only messenger can call");
require(counterpart == IScrollMessenger(_messenger).xDomainMessageSender(), "only call by conterpart");
_;
}
/***************
* Constructor *
***************/
function _initialize(
address _counterpart,
address _router,
address _messenger
) internal {
require(_counterpart != address(0), "zero counterpart address");
require(_messenger != address(0), "zero messenger address");
counterpart = _counterpart;
messenger = _messenger;
// @note: the address of router could be zero, if this contract is GatewayRouter.
if (_router != address(0)) {
router = _router;
}
// for reentrancy guard
_status = _NOT_ENTERED;
}
}
// File: src/L2/gateways/L2ETHGateway.sol
pragma solidity ^0.8.0;
/// @title L2ETHGateway
/// @notice The `L2ETHGateway` contract is used to withdraw ETH token in layer 2 and
/// finalize deposit ETH from layer 1.
/// @dev The ETH are not held in the gateway. The ETH will be sent to the `L2ScrollMessenger` contract.
/// On finalizing deposit, the Ether will be transfered from `L2ScrollMessenger`, then transfer to recipient.
contract L2ETHGateway is Initializable, ScrollGatewayBase, IL2ETHGateway {
/***************
* Constructor *
***************/
/// @notice Initialize the storage of L2ETHGateway.
/// @param _counterpart The address of L1ETHGateway in L2.
/// @param _router The address of L2GatewayRouter.
/// @param _messenger The address of L2ScrollMessenger.
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 IL2ETHGateway
function withdrawETH(uint256 _amount, uint256 _gasLimit) external payable override {
_withdraw(msg.sender, _amount, new bytes(0), _gasLimit);
}
/// @inheritdoc IL2ETHGateway
function withdrawETH(
address _to,
uint256 _amount,
uint256 _gasLimit
) public payable override {
_withdraw(_to, _amount, new bytes(0), _gasLimit);
}
/// @inheritdoc IL2ETHGateway
function withdrawETHAndCall(
address _to,
uint256 _amount,
bytes memory _data,
uint256 _gasLimit
) public payable override {
_withdraw(_to, _amount, _data, _gasLimit);
}
/// @inheritdoc IL2ETHGateway
function finalizeDepositETH(
address _from,
address _to,
uint256 _amount,
bytes calldata _data
) external payable override onlyCallByCounterpart {
// 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 FinalizeDepositETH(_from, _to, _amount, _data);
}
/**********************
* Internal Functions *
**********************/
function _withdraw(
address _to,
uint256 _amount,
bytes memory _data,
uint256 _gasLimit
) internal nonReentrant {
require(msg.value > 0, "withdraw 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));
}
bytes memory _message = abi.encodeWithSelector(
IL1ETHGateway.finalizeWithdrawETH.selector,
_from,
_to,
_amount,
_data
);
IL2ScrollMessenger(messenger).sendMessage{ value: msg.value }(counterpart, _amount, _message, _gasLimit);
emit WithdrawETH(_from, _to, _amount, _data);
}
}

View File

@@ -0,0 +1,518 @@
// File: src/L2/predeploys/IL1BlockContainer.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IL1BlockContainer {
/**********
* Events *
**********/
/// @notice Emitted when a block is imported.
/// @param blockHash The hash of the imported block.
/// @param blockHeight The height of the imported block.
/// @param blockTimestamp The timestamp of the imported block.
/// @param baseFee The base fee of the imported block.
/// @param stateRoot The state root of the imported block.
event ImportBlock(
bytes32 indexed blockHash,
uint256 blockHeight,
uint256 blockTimestamp,
uint256 baseFee,
bytes32 stateRoot
);
/*************************
* Public View Functions *
*************************/
/// @notice Return the latest imported block hash
function latestBlockHash() external view returns (bytes32);
/// @notice Return the latest imported L1 base fee
function latestBaseFee() external view returns (uint256);
/// @notice Return the latest imported block number
function latestBlockNumber() external view returns (uint256);
/// @notice Return the latest imported block timestamp
function latestBlockTimestamp() external view returns (uint256);
/// @notice Return the state root of given block.
/// @param blockHash The block hash to query.
/// @return stateRoot The state root of the block.
function getStateRoot(bytes32 blockHash) external view returns (bytes32 stateRoot);
/// @notice Return the block timestamp of given block.
/// @param blockHash The block hash to query.
/// @return timestamp The corresponding block timestamp.
function getBlockTimestamp(bytes32 blockHash) external view returns (uint256 timestamp);
/****************************
* Public Mutated Functions *
****************************/
/// @notice Import L1 block header to this contract.
/// @param blockHash The hash of block.
/// @param blockHeaderRLP The RLP encoding of L1 block.
/// @param updateGasPriceOracle Whether to update gas price oracle.
function importBlockHeader(
bytes32 blockHash,
bytes calldata blockHeaderRLP,
bool updateGasPriceOracle
) external;
}
// File: src/L2/predeploys/IL1GasPriceOracle.sol
pragma solidity ^0.8.0;
interface IL1GasPriceOracle {
/**********
* Events *
**********/
/// @notice Emitted when current fee overhead is updated.
/// @param overhead The current fee overhead updated.
event OverheadUpdated(uint256 overhead);
/// @notice Emitted when current fee scalar is updated.
/// @param scalar The current fee scalar updated.
event ScalarUpdated(uint256 scalar);
/// @notice Emitted when current l1 base fee is updated.
/// @param l1BaseFee The current l1 base fee updated.
event L1BaseFeeUpdated(uint256 l1BaseFee);
/*************************
* Public View Functions *
*************************/
/// @notice Return the current l1 fee overhead.
function overhead() external view returns (uint256);
/// @notice Return the current l1 fee scalar.
function scalar() external view returns (uint256);
/// @notice Return the latest known l1 base fee.
function l1BaseFee() external view returns (uint256);
/// @notice Computes the L1 portion of the fee based on the size of the rlp encoded input
/// transaction, the current L1 base fee, and the various dynamic parameters.
/// @param data Unsigned fully RLP-encoded transaction to get the L1 fee for.
/// @return L1 fee that should be paid for the tx
function getL1Fee(bytes memory data) external view returns (uint256);
/// @notice Computes the amount of L1 gas used for a transaction. Adds the overhead which
/// represents the per-transaction gas overhead of posting the transaction and state
/// roots to L1. Adds 68 bytes of padding to account for the fact that the input does
/// not have a signature.
/// @param data Unsigned fully RLP-encoded transaction to get the L1 gas for.
/// @return Amount of L1 gas used to publish the transaction.
function getL1GasUsed(bytes memory data) external view returns (uint256);
/****************************
* Public Mutated Functions *
****************************/
/// @notice Allows whitelisted caller to modify the l1 base fee.
/// @param _l1BaseFee New l1 base fee.
function setL1BaseFee(uint256 _l1BaseFee) external;
}
// File: src/libraries/common/OwnableBase.sol
pragma solidity ^0.8.0;
abstract contract OwnableBase {
/**********
* Events *
**********/
/// @notice Emitted when owner is changed by current owner.
/// @param _oldOwner The address of previous owner.
/// @param _newOwner The address of new owner.
event OwnershipTransferred(address indexed _oldOwner, address indexed _newOwner);
/*************
* Variables *
*************/
/// @notice The address of the current owner.
address public owner;
/**********************
* Function Modifiers *
**********************/
/// @dev Throws if called by any account other than the owner.
modifier onlyOwner() {
require(owner == msg.sender, "caller is not the owner");
_;
}
/************************
* Restricted Functions *
************************/
/// @notice Leaves the contract without owner. It will not be possible to call
/// `onlyOwner` functions anymore. Can only be called by the current owner.
///
/// @dev Renouncing ownership will leave the contract without an owner,
/// thereby removing any functionality that is only available to the owner.
function renounceOwnership() public onlyOwner {
_transferOwnership(address(0));
}
/// @notice Transfers ownership of the contract to a new account (`newOwner`).
/// Can only be called by the current owner.
function transferOwnership(address _newOwner) public onlyOwner {
require(_newOwner != address(0), "new owner is the zero address");
_transferOwnership(_newOwner);
}
/**********************
* Internal Functions *
**********************/
/// @dev Transfers ownership of the contract to a new account (`newOwner`).
/// Internal function without access restriction.
function _transferOwnership(address _newOwner) internal {
address _oldOwner = owner;
owner = _newOwner;
emit OwnershipTransferred(_oldOwner, _newOwner);
}
}
// File: src/libraries/common/IWhitelist.sol
pragma solidity ^0.8.0;
interface IWhitelist {
/// @notice Check whether the sender is allowed to do something.
/// @param _sender The address of sender.
function isSenderAllowed(address _sender) external view returns (bool);
}
// File: src/libraries/constants/ScrollPredeploy.sol
pragma solidity ^0.8.0;
library ScrollPredeploy {
address internal constant L1_MESSAGE_QUEUE = 0x5300000000000000000000000000000000000000;
address internal constant L1_BLOCK_CONTAINER = 0x5300000000000000000000000000000000000001;
address internal constant L1_GAS_PRICE_ORACLE = 0x5300000000000000000000000000000000000002;
}
// File: src/L2/predeploys/L1BlockContainer.sol
pragma solidity ^0.8.0;
/// @title L1BlockContainer
/// @notice This contract will maintain the list of blocks proposed in L1.
contract L1BlockContainer is OwnableBase, IL1BlockContainer {
/**********
* Events *
**********/
/// @notice Emitted when owner updates whitelist contract.
/// @param _oldWhitelist The address of old whitelist contract.
/// @param _newWhitelist The address of new whitelist contract.
event UpdateWhitelist(address _oldWhitelist, address _newWhitelist);
/***********
* Structs *
***********/
/// @dev Compiler will pack this into single `uint256`.
struct BlockMetadata {
// The block height.
uint64 height;
// The block timestamp.
uint64 timestamp;
// The base fee in the block.
uint128 baseFee;
}
/*************
* Variables *
*************/
/// @notice The address of whitelist contract.
IWhitelist public whitelist;
// @todo change to ring buffer to save gas usage.
/// @inheritdoc IL1BlockContainer
bytes32 public override latestBlockHash;
/// @notice Mapping from block hash to corresponding state root.
mapping(bytes32 => bytes32) public stateRoot;
/// @notice Mapping from block hash to corresponding block metadata,
/// including timestamp and height.
mapping(bytes32 => BlockMetadata) public metadata;
/***************
* Constructor *
***************/
constructor(address _owner) {
_transferOwnership(_owner);
}
function initialize(
bytes32 _startBlockHash,
uint64 _startBlockHeight,
uint64 _startBlockTimestamp,
uint128 _startBlockBaseFee,
bytes32 _startStateRoot
) external onlyOwner {
require(latestBlockHash == bytes32(0), "already initialized");
latestBlockHash = _startBlockHash;
stateRoot[_startBlockHash] = _startStateRoot;
metadata[_startBlockHash] = BlockMetadata(_startBlockHeight, _startBlockTimestamp, _startBlockBaseFee);
emit ImportBlock(_startBlockHash, _startBlockHeight, _startBlockTimestamp, _startBlockBaseFee, _startStateRoot);
}
/*************************
* Public View Functions *
*************************/
/// @inheritdoc IL1BlockContainer
function latestBaseFee() external view override returns (uint256) {
return metadata[latestBlockHash].baseFee;
}
/// @inheritdoc IL1BlockContainer
function latestBlockNumber() external view override returns (uint256) {
return metadata[latestBlockHash].height;
}
/// @inheritdoc IL1BlockContainer
function latestBlockTimestamp() external view override returns (uint256) {
return metadata[latestBlockHash].timestamp;
}
/// @inheritdoc IL1BlockContainer
function getStateRoot(bytes32 _blockHash) external view returns (bytes32) {
return stateRoot[_blockHash];
}
/// @inheritdoc IL1BlockContainer
function getBlockTimestamp(bytes32 _blockHash) external view returns (uint256) {
return metadata[_blockHash].timestamp;
}
/****************************
* Public Mutated Functions *
****************************/
/// @inheritdoc IL1BlockContainer
function importBlockHeader(
bytes32 _blockHash,
bytes calldata _blockHeaderRLP,
bool _updateGasPriceOracle
) external {
// @todo remove this when ETH 2.0 signature verification is ready.
{
IWhitelist _whitelist = whitelist;
require(address(_whitelist) == address(0) || _whitelist.isSenderAllowed(msg.sender), "Not whitelisted sender");
}
// The encoding order in block header is
// 1. ParentHash: 32 bytes
// 2. UncleHash: 32 bytes
// 3. Coinbase: 20 bytes
// 4. StateRoot: 32 bytes
// 5. TransactionsRoot: 32 bytes
// 6. ReceiptsRoot: 32 bytes
// 7. LogsBloom: 256 bytes
// 8. Difficulty: uint
// 9. BlockHeight: uint
// 10. GasLimit: uint64
// 11. GasUsed: uint64
// 12. BlockTimestamp: uint64
// 13. ExtraData: several bytes
// 14. MixHash: 32 bytes
// 15. BlockNonce: 8 bytes
// 16. BaseFee: uint // optional
bytes32 _parentHash;
bytes32 _stateRoot;
uint64 _height;
uint64 _timestamp;
uint128 _baseFee;
assembly {
// reverts with error `msg`.
// make sure the length of error string <= 32
function revertWith(msg) {
// keccak("Error(string)")
mstore(0x00, shl(224, 0x08c379a0))
mstore(0x04, 0x20) // str.offset
mstore(0x44, msg)
let msgLen
for {} msg {} {
msg := shl(8, msg)
msgLen := add(msgLen, 1)
}
mstore(0x24, msgLen) // str.length
revert(0x00, 0x64)
}
// reverts with `msg` when condition is not matched.
// make sure the length of error string <= 32
function require(cond, msg) {
if iszero(cond) {
revertWith(msg)
}
}
// returns the calldata offset of the value and the length in bytes
// for the RLP encoded data item at `ptr`. used in `decodeFlat`
function decodeValue(ptr) -> dataLen, valueOffset {
let b0 := byte(0, calldataload(ptr))
// 0x00 - 0x7f, single byte
if lt(b0, 0x80) {
// for a single byte whose value is in the [0x00, 0x7f] range,
// that byte is its own RLP encoding.
dataLen := 1
valueOffset := ptr
leave
}
// 0x80 - 0xb7, short string/bytes, length <= 55
if lt(b0, 0xb8) {
// the RLP encoding consists of a single byte with value 0x80
// plus the length of the string followed by the string.
dataLen := sub(b0, 0x80)
valueOffset := add(ptr, 1)
leave
}
// 0xb8 - 0xbf, long string/bytes, length > 55
if lt(b0, 0xc0) {
// the RLP encoding consists of a single byte with value 0xb7
// plus the length in bytes of the length of the string in binary form,
// followed by the length of the string, followed by the string.
let lengthBytes := sub(b0, 0xb7)
if gt(lengthBytes, 4) {
invalid()
}
// load the extended length
valueOffset := add(ptr, 1)
let extendedLen := calldataload(valueOffset)
let bits := sub(256, mul(lengthBytes, 8))
extendedLen := shr(bits, extendedLen)
dataLen := extendedLen
valueOffset := add(valueOffset, lengthBytes)
leave
}
revertWith("Not value")
}
let ptr := _blockHeaderRLP.offset
let headerPayloadLength
{
let b0 := byte(0, calldataload(ptr))
// the input should be a long list
if lt(b0, 0xf8) {
invalid()
}
let lengthBytes := sub(b0, 0xf7)
if gt(lengthBytes, 32) {
invalid()
}
// load the extended length
ptr := add(ptr, 1)
headerPayloadLength := calldataload(ptr)
let bits := sub(256, mul(lengthBytes, 8))
// compute payload length: extended length + length bytes + 1
headerPayloadLength := shr(bits, headerPayloadLength)
headerPayloadLength := add(headerPayloadLength, lengthBytes)
headerPayloadLength := add(headerPayloadLength, 1)
ptr := add(ptr, lengthBytes)
}
let memPtr := mload(0x40)
calldatacopy(memPtr, _blockHeaderRLP.offset, headerPayloadLength)
let _computedBlockHash := keccak256(memPtr, headerPayloadLength)
require(eq(_blockHash, _computedBlockHash), "Block hash mismatch")
// load 16 vaules
for { let i := 0 } lt(i, 16) { i := add(i, 1) } {
let len, offset := decodeValue(ptr)
// the value we care must have at most 32 bytes
if lt(len, 33) {
let bits := mul( sub(32, len), 8)
let value := calldataload(offset)
value := shr(bits, value)
mstore(memPtr, value)
}
memPtr := add(memPtr, 0x20)
ptr := add(len, offset)
}
require(eq(ptr, add(_blockHeaderRLP.offset, _blockHeaderRLP.length)), "Header RLP length mismatch")
memPtr := mload(0x40)
// load parent hash, 1-st entry
_parentHash := mload(memPtr)
// load state root, 4-th entry
_stateRoot := mload(add(memPtr, 0x60))
// load block height, 9-th entry
_height := mload(add(memPtr, 0x100))
// load block timestamp, 12-th entry
_timestamp := mload(add(memPtr, 0x160))
// load base fee, 16-th entry
_baseFee := mload(add(memPtr, 0x1e0))
}
require(stateRoot[_parentHash] != bytes32(0), "Parent not imported");
BlockMetadata memory _parentMetadata = metadata[_parentHash];
require(_parentMetadata.height + 1 == _height, "Block height mismatch");
require(_parentMetadata.timestamp <= _timestamp, "Parent block has larger timestamp");
latestBlockHash = _blockHash;
stateRoot[_blockHash] = _stateRoot;
metadata[_blockHash] = BlockMetadata(_height, _timestamp, _baseFee);
emit ImportBlock(_blockHash, _height, _timestamp, _baseFee, _stateRoot);
if (_updateGasPriceOracle) {
IL1GasPriceOracle(ScrollPredeploy.L1_GAS_PRICE_ORACLE).setL1BaseFee(_baseFee);
}
}
/************************
* Restricted Functions *
************************/
/// @notice Update whitelist contract.
/// @dev This function can only called by contract owner.
/// @param _newWhitelist The address of new whitelist contract.
function updateWhitelist(address _newWhitelist) external onlyOwner {
address _oldWhitelist = address(whitelist);
whitelist = IWhitelist(_newWhitelist);
emit UpdateWhitelist(_oldWhitelist, _newWhitelist);
}
}

View File

@@ -0,0 +1,334 @@
// File: src/libraries/common/OwnableBase.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
abstract contract OwnableBase {
/**********
* Events *
**********/
/// @notice Emitted when owner is changed by current owner.
/// @param _oldOwner The address of previous owner.
/// @param _newOwner The address of new owner.
event OwnershipTransferred(address indexed _oldOwner, address indexed _newOwner);
/*************
* Variables *
*************/
/// @notice The address of the current owner.
address public owner;
/**********************
* Function Modifiers *
**********************/
/// @dev Throws if called by any account other than the owner.
modifier onlyOwner() {
require(owner == msg.sender, "caller is not the owner");
_;
}
/************************
* Restricted Functions *
************************/
/// @notice Leaves the contract without owner. It will not be possible to call
/// `onlyOwner` functions anymore. Can only be called by the current owner.
///
/// @dev Renouncing ownership will leave the contract without an owner,
/// thereby removing any functionality that is only available to the owner.
function renounceOwnership() public onlyOwner {
_transferOwnership(address(0));
}
/// @notice Transfers ownership of the contract to a new account (`newOwner`).
/// Can only be called by the current owner.
function transferOwnership(address _newOwner) public onlyOwner {
require(_newOwner != address(0), "new owner is the zero address");
_transferOwnership(_newOwner);
}
/**********************
* Internal Functions *
**********************/
/// @dev Transfers ownership of the contract to a new account (`newOwner`).
/// Internal function without access restriction.
function _transferOwnership(address _newOwner) internal {
address _oldOwner = owner;
owner = _newOwner;
emit OwnershipTransferred(_oldOwner, _newOwner);
}
}
// File: src/libraries/common/IWhitelist.sol
pragma solidity ^0.8.0;
interface IWhitelist {
/// @notice Check whether the sender is allowed to do something.
/// @param _sender The address of sender.
function isSenderAllowed(address _sender) external view returns (bool);
}
// File: src/L2/predeploys/IL1BlockContainer.sol
pragma solidity ^0.8.0;
interface IL1BlockContainer {
/**********
* Events *
**********/
/// @notice Emitted when a block is imported.
/// @param blockHash The hash of the imported block.
/// @param blockHeight The height of the imported block.
/// @param blockTimestamp The timestamp of the imported block.
/// @param baseFee The base fee of the imported block.
/// @param stateRoot The state root of the imported block.
event ImportBlock(
bytes32 indexed blockHash,
uint256 blockHeight,
uint256 blockTimestamp,
uint256 baseFee,
bytes32 stateRoot
);
/*************************
* Public View Functions *
*************************/
/// @notice Return the latest imported block hash
function latestBlockHash() external view returns (bytes32);
/// @notice Return the latest imported L1 base fee
function latestBaseFee() external view returns (uint256);
/// @notice Return the latest imported block number
function latestBlockNumber() external view returns (uint256);
/// @notice Return the latest imported block timestamp
function latestBlockTimestamp() external view returns (uint256);
/// @notice Return the state root of given block.
/// @param blockHash The block hash to query.
/// @return stateRoot The state root of the block.
function getStateRoot(bytes32 blockHash) external view returns (bytes32 stateRoot);
/// @notice Return the block timestamp of given block.
/// @param blockHash The block hash to query.
/// @return timestamp The corresponding block timestamp.
function getBlockTimestamp(bytes32 blockHash) external view returns (uint256 timestamp);
/****************************
* Public Mutated Functions *
****************************/
/// @notice Import L1 block header to this contract.
/// @param blockHash The hash of block.
/// @param blockHeaderRLP The RLP encoding of L1 block.
/// @param updateGasPriceOracle Whether to update gas price oracle.
function importBlockHeader(
bytes32 blockHash,
bytes calldata blockHeaderRLP,
bool updateGasPriceOracle
) external;
}
// File: src/L2/predeploys/IL1GasPriceOracle.sol
pragma solidity ^0.8.0;
interface IL1GasPriceOracle {
/**********
* Events *
**********/
/// @notice Emitted when current fee overhead is updated.
/// @param overhead The current fee overhead updated.
event OverheadUpdated(uint256 overhead);
/// @notice Emitted when current fee scalar is updated.
/// @param scalar The current fee scalar updated.
event ScalarUpdated(uint256 scalar);
/// @notice Emitted when current l1 base fee is updated.
/// @param l1BaseFee The current l1 base fee updated.
event L1BaseFeeUpdated(uint256 l1BaseFee);
/*************************
* Public View Functions *
*************************/
/// @notice Return the current l1 fee overhead.
function overhead() external view returns (uint256);
/// @notice Return the current l1 fee scalar.
function scalar() external view returns (uint256);
/// @notice Return the latest known l1 base fee.
function l1BaseFee() external view returns (uint256);
/// @notice Computes the L1 portion of the fee based on the size of the rlp encoded input
/// transaction, the current L1 base fee, and the various dynamic parameters.
/// @param data Unsigned fully RLP-encoded transaction to get the L1 fee for.
/// @return L1 fee that should be paid for the tx
function getL1Fee(bytes memory data) external view returns (uint256);
/// @notice Computes the amount of L1 gas used for a transaction. Adds the overhead which
/// represents the per-transaction gas overhead of posting the transaction and state
/// roots to L1. Adds 68 bytes of padding to account for the fact that the input does
/// not have a signature.
/// @param data Unsigned fully RLP-encoded transaction to get the L1 gas for.
/// @return Amount of L1 gas used to publish the transaction.
function getL1GasUsed(bytes memory data) external view returns (uint256);
/****************************
* Public Mutated Functions *
****************************/
/// @notice Allows whitelisted caller to modify the l1 base fee.
/// @param _l1BaseFee New l1 base fee.
function setL1BaseFee(uint256 _l1BaseFee) external;
}
// File: src/L2/predeploys/L1GasPriceOracle.sol
pragma solidity ^0.8.0;
contract L1GasPriceOracle is OwnableBase, IL1GasPriceOracle {
/**********
* Events *
**********/
/// @notice Emitted when owner updates whitelist contract.
/// @param _oldWhitelist The address of old whitelist contract.
/// @param _newWhitelist The address of new whitelist contract.
event UpdateWhitelist(address _oldWhitelist, address _newWhitelist);
/*************
* Constants *
*************/
/// @dev The precision used in the scalar.
uint256 private constant PRECISION = 1e9;
/// @dev The maximum possible l1 fee overhead.
/// Computed based on current l1 block gas limit.
uint256 private constant MAX_OVERHEAD = 30000000 / 16;
/// @dev The maximum possible l1 fee scale.
/// x1000 should be enough.
uint256 private constant MAX_SCALE = 1000 * PRECISION;
/*************
* Variables *
*************/
/// @inheritdoc IL1GasPriceOracle
uint256 public l1BaseFee;
/// @inheritdoc IL1GasPriceOracle
uint256 public override overhead;
/// @inheritdoc IL1GasPriceOracle
uint256 public override scalar;
/// @notice The address of whitelist contract.
IWhitelist public whitelist;
/***************
* Constructor *
***************/
constructor(address _owner) {
_transferOwnership(_owner);
}
/*************************
* Public View Functions *
*************************/
/// @inheritdoc IL1GasPriceOracle
function getL1Fee(bytes memory _data) external view override returns (uint256) {
uint256 _l1GasUsed = getL1GasUsed(_data);
uint256 _l1Fee = _l1GasUsed * l1BaseFee;
return (_l1Fee * scalar) / PRECISION;
}
/// @inheritdoc IL1GasPriceOracle
/// @dev See the comments in `OVM_GasPriceOracle1` for more details
/// https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/L2/predeploys/OVM_GasPriceOracle.sol
function getL1GasUsed(bytes memory _data) public view override returns (uint256) {
uint256 _total = 0;
uint256 _length = _data.length;
unchecked {
for (uint256 i = 0; i < _length; i++) {
if (_data[i] == 0) {
_total += 4;
} else {
_total += 16;
}
}
uint256 _unsigned = _total + overhead;
return _unsigned + (68 * 16);
}
}
/****************************
* Public Mutated Functions *
****************************/
/// @inheritdoc IL1GasPriceOracle
function setL1BaseFee(uint256 _l1BaseFee) external override {
require(whitelist.isSenderAllowed(msg.sender), "Not whitelisted sender");
l1BaseFee = _l1BaseFee;
emit L1BaseFeeUpdated(_l1BaseFee);
}
/************************
* Restricted Functions *
************************/
/// @notice Allows the owner to modify the overhead.
/// @param _overhead New overhead
function setOverhead(uint256 _overhead) external onlyOwner {
require(_overhead <= MAX_OVERHEAD, "exceed maximum overhead");
overhead = _overhead;
emit OverheadUpdated(_overhead);
}
/// Allows the owner to modify the scalar.
/// @param _scalar New scalar
function setScalar(uint256 _scalar) external onlyOwner {
require(_scalar <= MAX_SCALE, "exceed maximum scale");
scalar = _scalar;
emit ScalarUpdated(_scalar);
}
/// @notice Update whitelist contract.
/// @dev This function can only called by contract owner.
/// @param _newWhitelist The address of new whitelist contract.
function updateWhitelist(address _newWhitelist) external onlyOwner {
address _oldWhitelist = address(whitelist);
whitelist = IWhitelist(_newWhitelist);
emit UpdateWhitelist(_oldWhitelist, _newWhitelist);
}
}

View File

@@ -0,0 +1,192 @@
// File: src/libraries/common/AppendOnlyMerkleTree.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
abstract contract AppendOnlyMerkleTree {
/// @dev The maximum height of the withdraw merkle tree.
uint256 private constant MAX_TREE_HEIGHT = 40;
/// @notice The merkle root of the current merkle tree.
/// @dev This is actual equal to `branches[n]`.
bytes32 public messageRoot;
/// @notice The next unused message index.
uint256 public nextMessageIndex;
/// @notice The list of zero hash in each height.
bytes32[MAX_TREE_HEIGHT] private zeroHashes;
/// @notice The list of minimum merkle proofs needed to compute next root.
/// @dev Only first `n` elements are used, where `n` is the minimum value that `2^{n-1} >= currentMaxNonce + 1`.
/// It means we only use `currentMaxNonce + 1` leaf nodes to construct the merkle tree.
bytes32[MAX_TREE_HEIGHT] public branches;
function _initializeMerkleTree() internal {
// Compute hashes in empty sparse Merkle tree
for (uint256 height = 0; height + 1 < MAX_TREE_HEIGHT; height++) {
zeroHashes[height + 1] = _efficientHash(zeroHashes[height], zeroHashes[height]);
}
}
function _appendMessageHash(bytes32 _messageHash) internal returns (uint256, bytes32) {
uint256 _currentMessageIndex = nextMessageIndex;
bytes32 _hash = _messageHash;
uint256 _height = 0;
// @todo it can be optimized, since we only need the newly added branch.
while (_currentMessageIndex != 0) {
if (_currentMessageIndex % 2 == 0) {
// it may be used in next round.
branches[_height] = _hash;
// it's a left child, the right child must be null
_hash = _efficientHash(_hash, zeroHashes[_height]);
} else {
// it's a right child, use previously computed hash
_hash = _efficientHash(branches[_height], _hash);
}
unchecked {
_height += 1;
}
_currentMessageIndex >>= 1;
}
branches[_height] = _hash;
messageRoot = _hash;
_currentMessageIndex = nextMessageIndex;
unchecked {
nextMessageIndex = _currentMessageIndex + 1;
}
return (_currentMessageIndex, _hash);
}
function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
// solhint-disable-next-line no-inline-assembly
assembly {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}
// File: src/libraries/common/OwnableBase.sol
pragma solidity ^0.8.0;
abstract contract OwnableBase {
/**********
* Events *
**********/
/// @notice Emitted when owner is changed by current owner.
/// @param _oldOwner The address of previous owner.
/// @param _newOwner The address of new owner.
event OwnershipTransferred(address indexed _oldOwner, address indexed _newOwner);
/*************
* Variables *
*************/
/// @notice The address of the current owner.
address public owner;
/**********************
* Function Modifiers *
**********************/
/// @dev Throws if called by any account other than the owner.
modifier onlyOwner() {
require(owner == msg.sender, "caller is not the owner");
_;
}
/************************
* Restricted Functions *
************************/
/// @notice Leaves the contract without owner. It will not be possible to call
/// `onlyOwner` functions anymore. Can only be called by the current owner.
///
/// @dev Renouncing ownership will leave the contract without an owner,
/// thereby removing any functionality that is only available to the owner.
function renounceOwnership() public onlyOwner {
_transferOwnership(address(0));
}
/// @notice Transfers ownership of the contract to a new account (`newOwner`).
/// Can only be called by the current owner.
function transferOwnership(address _newOwner) public onlyOwner {
require(_newOwner != address(0), "new owner is the zero address");
_transferOwnership(_newOwner);
}
/**********************
* Internal Functions *
**********************/
/// @dev Transfers ownership of the contract to a new account (`newOwner`).
/// Internal function without access restriction.
function _transferOwnership(address _newOwner) internal {
address _oldOwner = owner;
owner = _newOwner;
emit OwnershipTransferred(_oldOwner, _newOwner);
}
}
// File: src/L2/predeploys/L2MessageQueue.sol
pragma solidity ^0.8.0;
/// @title L2MessageQueue
/// @notice The original idea is from Optimism, see [OVM_L2ToL1MessagePasser](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/L2/predeploys/OVM_L2ToL1MessagePasser.sol).
/// The L2 to L1 Message Passer is a utility contract which facilitate an L1 proof of the
/// of a message on L2. The L1 Cross Domain Messenger performs this proof in its
/// _verifyStorageProof function, which verifies the existence of the transaction hash in this
/// contract's `sentMessages` mapping.
contract L2MessageQueue is AppendOnlyMerkleTree, OwnableBase {
/// @notice Emitted when a new message is added to the merkle tree.
/// @param index The index of the corresponding message.
/// @param messageHash The hash of the corresponding message.
event AppendMessage(uint256 index, bytes32 messageHash);
/// @notice The address of L2ScrollMessenger contract.
address public messenger;
constructor(address _owner) {
_transferOwnership(_owner);
}
function initialize() external {
_initializeMerkleTree();
}
/// @notice record the message to merkle tree and compute the new root.
/// @param _messageHash The hash of the new added message.
function appendMessage(bytes32 _messageHash) external returns (bytes32) {
require(msg.sender == messenger, "only messenger");
(uint256 _currentNonce, bytes32 _currentRoot) = _appendMessageHash(_messageHash);
// We can use the event to compute the merkle tree locally.
emit AppendMessage(_currentNonce, _messageHash);
return _currentRoot;
}
/// @notice Update the address of messenger.
/// @dev You are not allowed to update messenger when there are some messages appended.
/// @param _messenger The address of messenger to update.
function updateMessenger(address _messenger) external onlyOwner {
require(nextMessageIndex == 0, "cannot update messenger");
messenger = _messenger;
}
}

View File

@@ -0,0 +1,301 @@
// File: src/libraries/IScrollMessenger.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IScrollMessenger {
/**********
* Events *
**********/
/// @notice Emitted when a cross domain message is sent.
/// @param sender The address of the sender who initiates the message.
/// @param target The address of target contract to call.
/// @param value The amount of value passed to the target contract.
/// @param messageNonce The nonce of the message.
/// @param gasLimit The optional gas limit passed to L1 or L2.
/// @param message The calldata passed to the target contract.
event SentMessage(
address indexed sender,
address indexed target,
uint256 value,
uint256 messageNonce,
uint256 gasLimit,
bytes message
);
/// @notice Emitted when a cross domain message is relayed successfully.
/// @param messageHash The hash of the message.
event RelayedMessage(bytes32 indexed messageHash);
/// @notice Emitted when a cross domain message is failed to relay.
/// @param messageHash The hash of the message.
event FailedRelayedMessage(bytes32 indexed messageHash);
/*************************
* Public View Functions *
*************************/
/// @notice Return the sender of a cross domain message.
function xDomainMessageSender() external view returns (address);
/****************************
* Public Mutated Functions *
****************************/
/// @notice Send cross chain message from L1 to L2 or L2 to L1.
/// @param target The address of account who recieve the message.
/// @param value The amount of ether passed when call target contract.
/// @param message The content of the message.
/// @param gasLimit Gas limit required to complete the message relay on corresponding chain.
function sendMessage(
address target,
uint256 value,
bytes calldata message,
uint256 gasLimit
) external payable;
}
// File: src/L2/IL2ScrollMessenger.sol
pragma solidity ^0.8.0;
interface IL2ScrollMessenger is IScrollMessenger {
/***********
* Structs *
***********/
struct L1MessageProof {
bytes32 blockHash;
bytes stateRootProof;
}
/****************************
* Public Mutated Functions *
****************************/
/// @notice execute L1 => L2 message
/// @dev Make sure this is only called by privileged accounts.
/// @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.
function relayMessage(
address from,
address to,
uint256 value,
uint256 nonce,
bytes calldata message
) external;
/// @notice execute L1 => L2 message with 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 message proof.
function retryMessageWithProof(
address from,
address to,
uint256 value,
uint256 nonce,
bytes calldata message,
L1MessageProof calldata proof
) external;
}
// File: src/libraries/common/OwnableBase.sol
pragma solidity ^0.8.0;
abstract contract OwnableBase {
/**********
* Events *
**********/
/// @notice Emitted when owner is changed by current owner.
/// @param _oldOwner The address of previous owner.
/// @param _newOwner The address of new owner.
event OwnershipTransferred(address indexed _oldOwner, address indexed _newOwner);
/*************
* Variables *
*************/
/// @notice The address of the current owner.
address public owner;
/**********************
* Function Modifiers *
**********************/
/// @dev Throws if called by any account other than the owner.
modifier onlyOwner() {
require(owner == msg.sender, "caller is not the owner");
_;
}
/************************
* Restricted Functions *
************************/
/// @notice Leaves the contract without owner. It will not be possible to call
/// `onlyOwner` functions anymore. Can only be called by the current owner.
///
/// @dev Renouncing ownership will leave the contract without an owner,
/// thereby removing any functionality that is only available to the owner.
function renounceOwnership() public onlyOwner {
_transferOwnership(address(0));
}
/// @notice Transfers ownership of the contract to a new account (`newOwner`).
/// Can only be called by the current owner.
function transferOwnership(address _newOwner) public onlyOwner {
require(_newOwner != address(0), "new owner is the zero address");
_transferOwnership(_newOwner);
}
/**********************
* Internal Functions *
**********************/
/// @dev Transfers ownership of the contract to a new account (`newOwner`).
/// Internal function without access restriction.
function _transferOwnership(address _newOwner) internal {
address _oldOwner = owner;
owner = _newOwner;
emit OwnershipTransferred(_oldOwner, _newOwner);
}
}
// File: src/libraries/FeeVault.sol
// MIT License
// Copyright (c) 2022 Optimism
// Copyright (c) 2022 Scroll
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
pragma solidity ^0.8.0;
/// @title FeeVault
/// @notice The FeeVault contract contains the basic logic for the various different vault contracts
/// used to hold fee revenue generated by the L2 system.
abstract contract FeeVault is OwnableBase {
/// @notice Emits each time that a withdrawal occurs.
///
/// @param value Amount that was withdrawn (in wei).
/// @param to Address that the funds were sent to.
/// @param from Address that triggered the withdrawal.
event Withdrawal(uint256 value, address to, address from);
/// @notice Minimum balance before a withdrawal can be triggered.
uint256 public minWithdrawAmount;
/// @notice Scroll L2 messenger address.
address public messenger;
/// @notice Wallet that will receive the fees on L1.
address public recipient;
/// @notice Total amount of wei processed by the contract.
uint256 public totalProcessed;
/// @param _owner The owner of the contract.
/// @param _recipient Wallet that will receive the fees on L1.
/// @param _minWithdrawalAmount Minimum balance before a withdrawal can be triggered.
constructor(
address _owner,
address _recipient,
uint256 _minWithdrawalAmount
) {
_transferOwnership(_owner);
minWithdrawAmount = _minWithdrawalAmount;
recipient = _recipient;
}
/// @notice Allow the contract to receive ETH.
receive() external payable {}
/// @notice Triggers a withdrawal of funds to the L1 fee wallet.
function withdraw() external {
uint256 value = address(this).balance;
require(value >= minWithdrawAmount, "FeeVault: withdrawal amount must be greater than minimum withdrawal amount");
unchecked {
totalProcessed += value;
}
emit Withdrawal(value, recipient, msg.sender);
// no fee provided
IL2ScrollMessenger(messenger).sendMessage{ value: value }(
recipient,
value,
bytes(""), // no message (simple eth transfer)
0 // _gasLimit can be zero for fee vault.
);
}
/// @notice Update the address of messenger.
/// @param _messenger The address of messenger to update.
function updateMessenger(address _messenger) external onlyOwner {
messenger = _messenger;
}
/// @notice Update the address of recipient.
/// @param _recipient The address of recipient to update.
function updateRecipient(address _recipient) external onlyOwner {
recipient = _recipient;
}
/// @notice Update the minimum withdraw amount.
/// @param _minWithdrawAmount The minimum withdraw amount to update.
function updateMinWithdrawAmount(uint256 _minWithdrawAmount) external onlyOwner {
minWithdrawAmount = _minWithdrawAmount;
}
}
// File: src/L2/predeploys/L2TxFeeVault.sol
pragma solidity ^0.8.0;
/// @title L2TxFeeVault
/// @notice The `L2TxFeeVault` contract collects all L2 transaction fees and allows withdrawing these fees to a predefined L1 address.
/// The minimum withdrawal amount is 10 ether.
contract L2TxFeeVault is FeeVault {
/// @param _owner The owner of the contract.
/// @param _recipient The fee recipient address on L1.
constructor(address _owner, address _recipient) FeeVault(_owner, _recipient, 10 ether) {}
}

35
common/docker/config.go Normal file
View File

@@ -0,0 +1,35 @@
package docker
import (
"github.com/scroll-tech/go-ethereum/common"
)
// L1Contracts stores pre-deployed contracts address of scroll_l1geth
type L1Contracts struct {
L2GasPriceOracle common.Address `json:"L2GasPriceOracle"`
L1Whitelist common.Address `json:"L1Whitelist"`
L1ScrollChain common.Address `json:"L1ScrollChain"`
L1MessageQueue common.Address `json:"L1MessageQueue"`
L1ScrollMessenger common.Address `json:"L1ScrollMessenger"`
L1ETHGateway common.Address `json:"L1ETHGateway"`
}
// L2Contracts stores pre-deployed contracts address of scroll_l2geth
type L2Contracts struct {
L1GasPriceOracle common.Address `json:"L1GasPriceOracle"`
L1BlockContainer common.Address `json:"L1BlockContainer"`
L2Whitelist common.Address `json:"L2Whitelist"`
L2ProxyAdmin common.Address `json:"L2ProxyAdmin"`
L2ScrollMessenger common.Address `json:"L2ScrollMessenger"`
L2MessageQueue common.Address `json:"L2MessageQueue"`
L2TxFeeVault common.Address `json:"L2TxFeeVault"`
L2ETHGateway common.Address `json:"L2ETHGateway"`
}
// ContractsList all contracts addresses which are needed to be tested.
type ContractsList struct {
L1Contracts *L1Contracts `json:"l1_contracts,omitempty"`
L2Contracts *L2Contracts `json:"l2_contracts,omitempty"`
ERC20 common.Address `json:"erc20,omitempty"`
Greeter common.Address `json:"greeter,omitempty"`
}

View File

@@ -0,0 +1,24 @@
{
"l1_contracts": {
"L2GasPriceOracle": "0xf86a9a06e2464539d139ec6d3ad3c63e9bb4bd3c",
"L1Whitelist": "0xf0fb0109ec4c0ffe1c3708010ac39df342ad13c6",
"L1ScrollChain": "0x6edca115a58590c2b236284a6c62dec3103d8fc4",
"L1MessageQueue": "0x4221e6c50c004a0a6d5b83e17077c2dbaf3be239",
"L1ScrollMessenger": "0x24dd98a7f678bfaa5453130ddba5f7aa1b594df0",
"L1GatewayRouter": "0x6edca115a58590c2b236284a6c62dec3103d8fc4",
"L1ETHGateway": "0x6b43c8778d076435ed90384bacb78546513c153d"
},
"l2_contracts": {
"L1GasPriceOracle": "0x5edd6a65e63d4bf5955bf890e1889f5d00d12f4e",
"L1BlockContainer": "0xf0fb0109ec4c0ffe1c3708010ac39df342ad13c6",
"L2Whitelist": "0xf86a9a06e2464539d139ec6d3ad3c63e9bb4bd3c",
"L2ProxyAdmin": "0x5edd6a65e63d4bf5955bf890e1889f5d00d12f4e",
"L2ScrollMessenger": "0x2dfda18f6e0c753f37323a5465a4a2999901f772",
"L2MessageQueue": "0x24dd98a7f678bfaa5453130ddba5f7aa1b594df0",
"L2TxFeeVault": "0x6edca115a58590c2b236284a6c62dec3103d8fc4",
"L2GatewayRouter": "0x7363726f6c6c6c02000000000000000000000007",
"L2ETHGateway": "0x6b43c8778d076435ed90384bacb78546513c153d"
},
"erc20": "0x81bfe9f57ef1a68f09c298fc5d756f657a69d809",
"greeter": "0x52128e5d5812f699f9ed2f56f744e1eefe78db92"
}

View File

@@ -3,6 +3,7 @@ package docker
import (
"crypto/rand"
"database/sql"
_ "embed" //nolint:golint
"encoding/json"
"fmt"
"math/big"
@@ -25,6 +26,9 @@ var (
dbStartPort = 30000
)
//go:embed contracts_list.json
var contractsList []byte
// AppAPI app interface.
type AppAPI interface {
WaitResult(t *testing.T, timeout time.Duration, keyword string) bool
@@ -43,6 +47,9 @@ type App struct {
DBConfig *database.DBConfig
DBConfigFile string
// pre deployed contracts' addresses.
ContractsList
// common time stamp.
Timestamp int
}
@@ -57,6 +64,11 @@ func NewDockerApp() *App {
DBImg: newTestDBDocker("postgres"),
DBConfigFile: fmt.Sprintf("/tmp/%d_db-config.json", timestamp),
}
// Unmarshal contracts addresses.
if err := json.Unmarshal(contractsList, &app.ContractsList); err != nil {
panic(err)
}
if err := app.mockDBConfig(); err != nil {
panic(err)
}
@@ -181,15 +193,17 @@ func (b *App) mockDBConfig() error {
func newTestL1Docker() GethImgInstance {
id, _ := rand.Int(rand.Reader, big.NewInt(2000))
return NewImgGeth("scroll_l1geth", "", "", 0, l1StartPort+int(id.Int64()))
startPort := l1StartPort + int(id.Int64())
return NewImgGeth("scroll_l1geth", "", "", startPort, startPort+1)
}
func newTestL2Docker() GethImgInstance {
id, _ := rand.Int(rand.Reader, big.NewInt(2000))
return NewImgGeth("scroll_l2geth", "", "", 0, l2StartPort+int(id.Int64()))
startPort := l2StartPort + int(id.Int64())
return NewImgGeth("scroll_l2geth", "", "", startPort, startPort+1)
}
func newTestDBDocker(driverName string) ImgInstance {
id, _ := rand.Int(rand.Reader, big.NewInt(2000))
return NewImgDB(driverName, "123456", "test_db", dbStartPort+int(id.Int64()))
return NewImgDB(driverName, "postgres", "123456", "test_db", dbStartPort+int(id.Int64()))
}

View File

@@ -18,20 +18,22 @@ type ImgDB struct {
name string
id string
userName string
password string
dbName string
port int
password string
running bool
cmd *cmd.Cmd
}
// NewImgDB return postgres db img instance.
func NewImgDB(image, password, dbName string, port int) ImgInstance {
func NewImgDB(image, userName, password, dbName string, port int) ImgInstance {
img := &ImgDB{
image: image,
name: fmt.Sprintf("%s-%s_%d", image, dbName, port),
userName: userName,
password: password,
name: fmt.Sprintf("%s-%s_%d", image, dbName, port),
dbName: dbName,
port: port,
}
@@ -75,7 +77,7 @@ func (i *ImgDB) Stop() error {
// Endpoint return the dsn.
func (i *ImgDB) Endpoint() string {
return fmt.Sprintf("postgres://postgres:%s@localhost:%d/%s?sslmode=disable", i.password, i.port, i.dbName)
return fmt.Sprintf("postgres://%s:%s@localhost:%d/%s?sslmode=disable", i.userName, i.password, i.port, i.dbName)
}
// IsRunning returns docker container's running status.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -135,7 +135,6 @@ func NewBatchData(parentBatch *BlockBatch, blocks []*WrappedBlock, piCfg *Public
if baseFee == nil {
baseFee = big.NewInt(0)
}
batch.Blocks[i] = abi.IScrollChainBlockContext{
BlockHash: block.Header.Hash(),
ParentHash: block.Header.ParentHash,
@@ -208,13 +207,18 @@ func NewGenesisBatchData(genesisBlockTrace *WrappedBlock) *BatchData {
// PrevStateRoot, WithdrawTrieRoot, ParentBatchHash should all be 0
// L2Transactions should be empty
// set baseFee to 0 when it's nil in the block header
baseFee := header.BaseFee
if baseFee == nil {
baseFee = big.NewInt(0)
}
// fill in block context
batch.Blocks[0] = abi.IScrollChainBlockContext{
BlockHash: header.Hash(),
ParentHash: header.ParentHash,
BlockNumber: header.Number.Uint64(),
Timestamp: header.Time,
BaseFee: header.BaseFee,
BaseFee: baseFee,
GasLimit: header.GasLimit,
NumTransactions: 0,
NumL1Messages: 0,

View File

@@ -2,7 +2,6 @@ package types
import (
"encoding/binary"
"fmt"
"math/big"
"github.com/scroll-tech/go-ethereum/common"
@@ -18,99 +17,46 @@ type BatchHeader struct {
totalL1MessagePopped uint64
dataHash common.Hash
parentBatchHash common.Hash
skippedL1MessageBitmap []byte
skippedL1MessageBitmap []*big.Int // LSB is the first L1 message
}
// NewBatchHeader creates a new BatchHeader
func NewBatchHeader(version uint8, batchIndex, totalL1MessagePoppedBefore uint64, parentBatchHash common.Hash, chunks []*Chunk) (*BatchHeader, error) {
// buffer for storing chunk hashes in order to compute the batch data hash
// TODO calculate `l1MessagePopped`, `totalL1MessagePopped`, and `skippedL1MessageBitmap` based on `chunks`
var dataBytes []byte
// skipped L1 message bitmap, an array of 256-bit bitmaps
var skippedBitmap []*big.Int
// the first queue index that belongs to this batch
baseIndex := totalL1MessagePoppedBefore
// the next queue index that we need to process
nextIndex := totalL1MessagePoppedBefore
for _, chunk := range chunks {
// build data hash
totalL1MessagePoppedBeforeChunk := nextIndex
chunkBytes, err := chunk.Hash(totalL1MessagePoppedBeforeChunk)
// Build dataHash
chunkBytes, err := chunk.Hash()
if err != nil {
return nil, err
}
dataBytes = append(dataBytes, chunkBytes...)
// build skip bitmap
for _, block := range chunk.Blocks {
for _, tx := range block.Transactions {
if tx.Type != 0x7E {
continue
}
currentIndex := tx.Nonce
if currentIndex < nextIndex {
return nil, fmt.Errorf("unexpected batch payload, expected queue index: %d, got: %d", nextIndex, currentIndex)
}
// mark skipped messages
for skippedIndex := nextIndex; skippedIndex < currentIndex; skippedIndex++ {
quo := int((skippedIndex - baseIndex) / 256)
rem := int((skippedIndex - baseIndex) % 256)
for len(skippedBitmap) <= quo {
bitmap := big.NewInt(0)
skippedBitmap = append(skippedBitmap, bitmap)
}
skippedBitmap[quo].SetBit(skippedBitmap[quo], rem, 1)
}
// process included message
quo := int((currentIndex - baseIndex) / 256)
for len(skippedBitmap) <= quo {
bitmap := big.NewInt(0)
skippedBitmap = append(skippedBitmap, bitmap)
}
nextIndex = currentIndex + 1
}
}
}
// compute data hash
dataHash := crypto.Keccak256Hash(dataBytes)
// compute skipped bitmap
bitmapBytes := make([]byte, len(skippedBitmap)*32)
for ii, num := range skippedBitmap {
bytes := num.Bytes()
padding := 32 - len(bytes)
copy(bitmapBytes[32*ii+padding:], bytes)
}
return &BatchHeader{
version: version,
batchIndex: batchIndex,
l1MessagePopped: nextIndex - totalL1MessagePoppedBefore,
totalL1MessagePopped: nextIndex,
l1MessagePopped: 0, // TODO
totalL1MessagePopped: totalL1MessagePoppedBefore, // TODO
dataHash: dataHash,
parentBatchHash: parentBatchHash,
skippedL1MessageBitmap: bitmapBytes,
skippedL1MessageBitmap: nil, // TODO
}, nil
}
// Encode encodes the BatchHeader into RollupV2 BatchHeaderV0Codec Encoding.
func (b *BatchHeader) Encode() []byte {
batchBytes := make([]byte, 89+len(b.skippedL1MessageBitmap))
batchBytes := make([]byte, 89)
batchBytes[0] = b.version
binary.BigEndian.PutUint64(batchBytes[1:], b.batchIndex)
binary.BigEndian.PutUint64(batchBytes[9:], b.l1MessagePopped)
binary.BigEndian.PutUint64(batchBytes[17:], b.totalL1MessagePopped)
copy(batchBytes[25:], b.dataHash[:])
copy(batchBytes[57:], b.parentBatchHash[:])
copy(batchBytes[89:], b.skippedL1MessageBitmap[:])
// TODO: encode skippedL1MessageBitmap
return batchBytes
}

View File

@@ -10,7 +10,6 @@ import (
)
func TestNewBatchHeader(t *testing.T) {
// Without L1 Msg
templateBlockTrace, err := os.ReadFile("../testdata/blockTrace_02.json")
assert.NoError(t, err)
@@ -33,100 +32,9 @@ func TestNewBatchHeader(t *testing.T) {
batchHeader, err := NewBatchHeader(1, 1, 0, parentBatchHeader.Hash(), []*Chunk{chunk})
assert.NoError(t, err)
assert.NotNil(t, batchHeader)
assert.Equal(t, 0, len(batchHeader.skippedL1MessageBitmap))
// 1 L1 Msg in 1 bitmap
templateBlockTrace2, err := os.ReadFile("../testdata/blockTrace_04.json")
assert.NoError(t, err)
wrappedBlock2 := &WrappedBlock{}
assert.NoError(t, json.Unmarshal(templateBlockTrace2, wrappedBlock2))
chunk = &Chunk{
Blocks: []*WrappedBlock{
wrappedBlock2,
},
}
batchHeader, err = NewBatchHeader(1, 1, 0, parentBatchHeader.Hash(), []*Chunk{chunk})
assert.NoError(t, err)
assert.NotNil(t, batchHeader)
assert.Equal(t, 32, len(batchHeader.skippedL1MessageBitmap))
expectedBitmap := "00000000000000000000000000000000000000000000000000000000000003ff" // skip first 10
assert.Equal(t, expectedBitmap, common.Bytes2Hex(batchHeader.skippedL1MessageBitmap))
// many consecutive L1 Msgs in 1 bitmap, no leading skipped msgs
templateBlockTrace3, err := os.ReadFile("../testdata/blockTrace_05.json")
assert.NoError(t, err)
wrappedBlock3 := &WrappedBlock{}
assert.NoError(t, json.Unmarshal(templateBlockTrace3, wrappedBlock3))
chunk = &Chunk{
Blocks: []*WrappedBlock{
wrappedBlock3,
},
}
batchHeader, err = NewBatchHeader(1, 1, 37, parentBatchHeader.Hash(), []*Chunk{chunk})
assert.NoError(t, err)
assert.NotNil(t, batchHeader)
assert.Equal(t, uint64(5), batchHeader.l1MessagePopped)
assert.Equal(t, 32, len(batchHeader.skippedL1MessageBitmap))
expectedBitmap = "0000000000000000000000000000000000000000000000000000000000000000" // all bits are included, so none are skipped
assert.Equal(t, expectedBitmap, common.Bytes2Hex(batchHeader.skippedL1MessageBitmap))
// many consecutive L1 Msgs in 1 bitmap, with leading skipped msgs
chunk = &Chunk{
Blocks: []*WrappedBlock{
wrappedBlock3,
},
}
batchHeader, err = NewBatchHeader(1, 1, 0, parentBatchHeader.Hash(), []*Chunk{chunk})
assert.NoError(t, err)
assert.NotNil(t, batchHeader)
assert.Equal(t, uint64(42), batchHeader.l1MessagePopped)
assert.Equal(t, 32, len(batchHeader.skippedL1MessageBitmap))
expectedBitmap = "0000000000000000000000000000000000000000000000000000001fffffffff" // skipped the first 37 messages
assert.Equal(t, expectedBitmap, common.Bytes2Hex(batchHeader.skippedL1MessageBitmap))
// many sparse L1 Msgs in 1 bitmap
templateBlockTrace4, err := os.ReadFile("../testdata/blockTrace_06.json")
assert.NoError(t, err)
wrappedBlock4 := &WrappedBlock{}
assert.NoError(t, json.Unmarshal(templateBlockTrace4, wrappedBlock4))
chunk = &Chunk{
Blocks: []*WrappedBlock{
wrappedBlock4,
},
}
batchHeader, err = NewBatchHeader(1, 1, 0, parentBatchHeader.Hash(), []*Chunk{chunk})
assert.NoError(t, err)
assert.NotNil(t, batchHeader)
assert.Equal(t, uint64(10), batchHeader.l1MessagePopped)
assert.Equal(t, 32, len(batchHeader.skippedL1MessageBitmap))
expectedBitmap = "00000000000000000000000000000000000000000000000000000000000001dd" // 0111011101
assert.Equal(t, expectedBitmap, common.Bytes2Hex(batchHeader.skippedL1MessageBitmap))
// many L1 Msgs in each of 2 bitmaps
templateBlockTrace5, err := os.ReadFile("../testdata/blockTrace_07.json")
assert.NoError(t, err)
wrappedBlock5 := &WrappedBlock{}
assert.NoError(t, json.Unmarshal(templateBlockTrace5, wrappedBlock5))
chunk = &Chunk{
Blocks: []*WrappedBlock{
wrappedBlock5,
},
}
batchHeader, err = NewBatchHeader(1, 1, 0, parentBatchHeader.Hash(), []*Chunk{chunk})
assert.NoError(t, err)
assert.NotNil(t, batchHeader)
assert.Equal(t, uint64(257), batchHeader.l1MessagePopped)
assert.Equal(t, 64, len(batchHeader.skippedL1MessageBitmap))
expectedBitmap = "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd0000000000000000000000000000000000000000000000000000000000000000"
assert.Equal(t, expectedBitmap, common.Bytes2Hex(batchHeader.skippedL1MessageBitmap))
}
func TestBatchHeaderEncode(t *testing.T) {
// Without L1 Msg
templateBlockTrace, err := os.ReadFile("../testdata/blockTrace_02.json")
assert.NoError(t, err)
@@ -152,28 +60,9 @@ func TestBatchHeaderEncode(t *testing.T) {
bytes := batchHeader.Encode()
assert.Equal(t, 89, len(bytes))
assert.Equal(t, "0100000000000000010000000000000000000000000000000010a64c9bd905f8caf5d668fbda622d6558c5a42cdb4b3895709743d159c22e534136709aabc8a23aa17fbcc833da2f7857d3c2884feec9aae73429c135f94985", common.Bytes2Hex(bytes))
// With L1 Msg
templateBlockTrace2, err := os.ReadFile("../testdata/blockTrace_04.json")
assert.NoError(t, err)
wrappedBlock2 := &WrappedBlock{}
assert.NoError(t, json.Unmarshal(templateBlockTrace2, wrappedBlock2))
chunk = &Chunk{
Blocks: []*WrappedBlock{
wrappedBlock2,
},
}
batchHeader, err = NewBatchHeader(1, 1, 0, parentBatchHeader.Hash(), []*Chunk{chunk})
assert.NoError(t, err)
assert.NotNil(t, batchHeader)
bytes = batchHeader.Encode()
assert.Equal(t, 121, len(bytes))
assert.Equal(t, "010000000000000001000000000000000b000000000000000b457a9e90e8e51ba2de2f66c6b589540b88cf594dac7fa7d04b99cdcfecf24e384136709aabc8a23aa17fbcc833da2f7857d3c2884feec9aae73429c135f9498500000000000000000000000000000000000000000000000000000000000003ff", common.Bytes2Hex(bytes))
}
func TestBatchHeaderHash(t *testing.T) {
// Without L1 Msg
templateBlockTrace, err := os.ReadFile("../testdata/blockTrace_02.json")
assert.NoError(t, err)
@@ -214,21 +103,4 @@ func TestBatchHeaderHash(t *testing.T) {
assert.NotNil(t, batchHeader2)
hash2 := batchHeader2.Hash()
assert.Equal(t, "34de600163aa745d4513113137a5b54960d13f0d3f2849e490c4b875028bf930", common.Bytes2Hex(hash2.Bytes()))
// With L1 Msg
templateBlockTrace3, err := os.ReadFile("../testdata/blockTrace_04.json")
assert.NoError(t, err)
wrappedBlock3 := &WrappedBlock{}
assert.NoError(t, json.Unmarshal(templateBlockTrace3, wrappedBlock3))
chunk = &Chunk{
Blocks: []*WrappedBlock{
wrappedBlock3,
},
}
batchHeader, err = NewBatchHeader(1, 1, 0, parentBatchHeader.Hash(), []*Chunk{chunk})
assert.NoError(t, err)
assert.NotNil(t, batchHeader)
hash = batchHeader.Hash()
assert.Equal(t, "0ec9547c6645d5f0c1254e121f49e93f54525cfda5bfb2236440fb3470f48902", common.Bytes2Hex(hash.Bytes()))
}

View File

@@ -17,25 +17,8 @@ type WrappedBlock struct {
WithdrawTrieRoot common.Hash `json:"withdraw_trie_root,omitempty"`
}
// NumL1Messages returns the number of L1 messages in this block.
// This number is the sum of included and skipped L1 messages.
func (w *WrappedBlock) NumL1Messages(totalL1MessagePoppedBefore uint64) uint64 {
var lastQueueIndex *uint64
for _, txData := range w.Transactions {
if txData.Type == 0x7E {
lastQueueIndex = &txData.Nonce
}
}
if lastQueueIndex == nil {
return 0
}
// note: last queue index included before this block is totalL1MessagePoppedBefore - 1
// TODO: cache results
return *lastQueueIndex - totalL1MessagePoppedBefore + 1
}
// Encode encodes the WrappedBlock into RollupV2 BlockContext Encoding.
func (w *WrappedBlock) Encode(totalL1MessagePoppedBefore uint64) ([]byte, error) {
func (w *WrappedBlock) Encode() ([]byte, error) {
bytes := make([]byte, 60)
if !w.Header.Number.IsUint64() {
@@ -44,10 +27,14 @@ func (w *WrappedBlock) Encode(totalL1MessagePoppedBefore uint64) ([]byte, error)
if len(w.Transactions) > math.MaxUint16 {
return nil, errors.New("number of transactions exceeds max uint16")
}
numL1Messages := w.NumL1Messages(totalL1MessagePoppedBefore)
if numL1Messages > math.MaxUint16 {
return nil, errors.New("number of L1 messages exceeds max uint16")
var numL1Messages uint16
for _, txData := range w.Transactions {
if txData.Type == 0x7E {
if numL1Messages == math.MaxUint16 {
return nil, errors.New("number of L1 messages exceeds max uint16")
}
numL1Messages++
}
}
binary.BigEndian.PutUint64(bytes[0:], w.Header.Number.Uint64())
@@ -55,7 +42,7 @@ func (w *WrappedBlock) Encode(totalL1MessagePoppedBefore uint64) ([]byte, error)
// TODO: [16:47] Currently, baseFee is 0, because we disable EIP-1559.
binary.BigEndian.PutUint64(bytes[48:], w.Header.GasLimit)
binary.BigEndian.PutUint16(bytes[56:], uint16(len(w.Transactions)))
binary.BigEndian.PutUint16(bytes[58:], uint16(numL1Messages))
binary.BigEndian.PutUint16(bytes[58:], numL1Messages)
return bytes, nil
}

View File

@@ -17,21 +17,8 @@ type Chunk struct {
Blocks []*WrappedBlock `json:"blocks"`
}
// NumL1Messages returns the number of L1 messages in this chunk.
// This number is the sum of included and skipped L1 messages.
func (c *Chunk) NumL1Messages(totalL1MessagePoppedBefore uint64) uint64 {
var numL1Messages uint64
for _, block := range c.Blocks {
numL1MessagesInBlock := block.NumL1Messages(totalL1MessagePoppedBefore)
numL1Messages += numL1MessagesInBlock
totalL1MessagePoppedBefore += numL1MessagesInBlock
}
// TODO: cache results
return numL1Messages
}
// Encode encodes the Chunk into RollupV2 Chunk Encoding.
func (c *Chunk) Encode(totalL1MessagePoppedBefore uint64) ([]byte, error) {
func (c *Chunk) Encode() ([]byte, error) {
numBlocks := len(c.Blocks)
if numBlocks > 255 {
@@ -47,11 +34,10 @@ func (c *Chunk) Encode(totalL1MessagePoppedBefore uint64) ([]byte, error) {
var l2TxDataBytes []byte
for _, block := range c.Blocks {
blockBytes, err := block.Encode(totalL1MessagePoppedBefore)
blockBytes, err := block.Encode()
if err != nil {
return nil, fmt.Errorf("failed to encode block: %v", err)
}
totalL1MessagePoppedBefore += block.NumL1Messages(totalL1MessagePoppedBefore)
if len(blockBytes) != 60 {
return nil, fmt.Errorf("block encoding is not 60 bytes long %x", len(blockBytes))
@@ -91,8 +77,8 @@ func (c *Chunk) Encode(totalL1MessagePoppedBefore uint64) ([]byte, error) {
}
// Hash hashes the Chunk into RollupV2 Chunk Hash
func (c *Chunk) Hash(totalL1MessagePoppedBefore uint64) ([]byte, error) {
chunkBytes, err := c.Encode(totalL1MessagePoppedBefore)
func (c *Chunk) Hash() ([]byte, error) {
chunkBytes, err := c.Encode()
if err != nil {
return nil, err
}

View File

@@ -14,7 +14,7 @@ func TestChunkEncode(t *testing.T) {
chunk := &Chunk{
Blocks: []*WrappedBlock{},
}
bytes, err := chunk.Encode(0)
bytes, err := chunk.Encode()
assert.Nil(t, bytes)
assert.Error(t, err)
assert.Contains(t, err.Error(), "number of blocks is 0")
@@ -26,7 +26,7 @@ func TestChunkEncode(t *testing.T) {
for i := 0; i < 256; i++ {
chunk.Blocks = append(chunk.Blocks, &WrappedBlock{})
}
bytes, err = chunk.Encode(0)
bytes, err = chunk.Encode()
assert.Nil(t, bytes)
assert.Error(t, err)
assert.Contains(t, err.Error(), "number of blocks exceeds 1 byte")
@@ -37,13 +37,12 @@ func TestChunkEncode(t *testing.T) {
wrappedBlock := &WrappedBlock{}
assert.NoError(t, json.Unmarshal(templateBlockTrace, wrappedBlock))
assert.Equal(t, uint64(0), wrappedBlock.NumL1Messages(0))
chunk = &Chunk{
Blocks: []*WrappedBlock{
wrappedBlock,
},
}
bytes, err = chunk.Encode(0)
bytes, err = chunk.Encode()
hexString := hex.EncodeToString(bytes)
assert.NoError(t, err)
assert.Equal(t, 299, len(bytes))
@@ -55,17 +54,16 @@ func TestChunkEncode(t *testing.T) {
wrappedBlock2 := &WrappedBlock{}
assert.NoError(t, json.Unmarshal(templateBlockTrace2, wrappedBlock2))
assert.Equal(t, uint64(11), wrappedBlock2.NumL1Messages(0)) // 0..=9 skipped, 10 included
chunk = &Chunk{
Blocks: []*WrappedBlock{
wrappedBlock2,
},
}
bytes, err = chunk.Encode(0)
bytes, err = chunk.Encode()
hexString = hex.EncodeToString(bytes)
assert.NoError(t, err)
assert.Equal(t, 97, len(bytes))
assert.Equal(t, "01000000000000000d00000000646b6e13000000000000000000000000000000000000000000000000000000000000000000000000007a12000002000b00000020df0b80825dc0941a258d17bf244c4df02d40343a7626a9d321e1058080808080", hexString)
assert.Equal(t, "01000000000000000d00000000646b6e13000000000000000000000000000000000000000000000000000000000000000000000000007a12000002000100000020df0b80825dc0941a258d17bf244c4df02d40343a7626a9d321e1058080808080", hexString)
// Test case 5: when the chunk contains two blocks each with 1 L1MsgTx
chunk = &Chunk{
@@ -74,11 +72,11 @@ func TestChunkEncode(t *testing.T) {
wrappedBlock2,
},
}
bytes, err = chunk.Encode(0)
bytes, err = chunk.Encode()
hexString = hex.EncodeToString(bytes)
assert.NoError(t, err)
assert.Equal(t, 193, len(bytes))
assert.Equal(t, "02000000000000000d00000000646b6e13000000000000000000000000000000000000000000000000000000000000000000000000007a12000002000b000000000000000d00000000646b6e13000000000000000000000000000000000000000000000000000000000000000000000000007a12000002000000000020df0b80825dc0941a258d17bf244c4df02d40343a7626a9d321e105808080808000000020df0b80825dc0941a258d17bf244c4df02d40343a7626a9d321e1058080808080", hexString)
assert.Equal(t, "02000000000000000d00000000646b6e13000000000000000000000000000000000000000000000000000000000000000000000000007a120000020001000000000000000d00000000646b6e13000000000000000000000000000000000000000000000000000000000000000000000000007a12000002000100000020df0b80825dc0941a258d17bf244c4df02d40343a7626a9d321e105808080808000000020df0b80825dc0941a258d17bf244c4df02d40343a7626a9d321e1058080808080", hexString)
}
func TestChunkHash(t *testing.T) {
@@ -86,7 +84,7 @@ func TestChunkHash(t *testing.T) {
chunk := &Chunk{
Blocks: []*WrappedBlock{},
}
bytes, err := chunk.Hash(0)
bytes, err := chunk.Hash()
assert.Nil(t, bytes)
assert.Error(t, err)
assert.Contains(t, err.Error(), "number of blocks is 0")
@@ -101,7 +99,7 @@ func TestChunkHash(t *testing.T) {
wrappedBlock,
},
}
bytes, err = chunk.Hash(0)
bytes, err = chunk.Hash()
hexString := hex.EncodeToString(bytes)
assert.NoError(t, err)
assert.Equal(t, "78c839dfc494396c16b40946f32b3f4c3e8c2d4bfd04aefcf235edec474482f8", hexString)
@@ -117,7 +115,7 @@ func TestChunkHash(t *testing.T) {
wrappedBlock1,
},
}
bytes, err = chunk.Hash(0)
bytes, err = chunk.Hash()
hexString = hex.EncodeToString(bytes)
assert.NoError(t, err)
assert.Equal(t, "aa9e494f72bc6965857856f0fae6916f27b2a6591c714a573b2fab46df03b8ae", hexString)
@@ -133,7 +131,7 @@ func TestChunkHash(t *testing.T) {
wrappedBlock2,
},
}
bytes, err = chunk.Hash(0)
bytes, err = chunk.Hash()
hexString = hex.EncodeToString(bytes)
assert.NoError(t, err)
assert.Equal(t, "42967825696a129e7a83f082097aca982747480956dcaa448c9296e795c9a91a", hexString)

View File

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

View File

@@ -63,6 +63,28 @@ Initialize the storage of L1ScrollMessenger.
| _rollup | address | The address of ScrollChain contract. |
| _messageQueue | address | The address of L1MessageQueue contract. |
### isL1MessageRelayed
```solidity
function isL1MessageRelayed(bytes32) external view returns (bool)
```
Mapping from relay id to relay status.
#### Parameters
| Name | Type | Description |
|---|---|---|
| _0 | bytes32 | undefined |
#### Returns
| Name | Type | Description |
|---|---|---|
| _0 | bool | undefined |
### isL1MessageSent
```solidity

View File

@@ -2,16 +2,7 @@
/* eslint-disable node/no-missing-import */
import { expect } from "chai";
import { BigNumber, constants } from "ethers";
import {
concat,
getAddress,
hexlify,
keccak256,
randomBytes,
RLP,
stripZeros,
TransactionTypes,
} from "ethers/lib/utils";
import { concat, getAddress, hexlify, keccak256, randomBytes, RLP } from "ethers/lib/utils";
import { ethers } from "hardhat";
import { L1MessageQueue, L2GasPriceOracle } from "../typechain";
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
@@ -103,8 +94,8 @@ describe("L1MessageQueue", async () => {
context("#computeTransactionHash", async () => {
it("should succeed", async () => {
const sender = "0xb2a70fab1a45b1b9be443b6567849a1702bc1232";
const target = "0xcb18150e4efefb6786130e289a5f61a82a5b86d7";
const sender = hexlify(randomBytes(20));
const target = hexlify(randomBytes(20));
const transactionType = "0x7E";
for (const nonce of [
@@ -132,30 +123,19 @@ describe("L1MessageQueue", async () => {
constants.MaxUint256,
]) {
for (const dataLen of [0, 1, 2, 3, 4, 55, 56, 100]) {
const tests = [randomBytes(dataLen)];
if (dataLen === 1) {
for (const byte of [0, 1, 127, 128]) {
tests.push(Uint8Array.from([byte]));
}
}
for (const data of tests) {
const transactionPayload = RLP.encode([
stripZeros(nonce.toHexString()),
stripZeros(gasLimit.toHexString()),
target,
stripZeros(value.toHexString()),
data,
sender,
]);
const payload = concat([transactionType, transactionPayload]);
const expectedHash = keccak256(payload);
const computedHash = await queue.computeTransactionHash(sender, nonce, value, target, gasLimit, data);
if (computedHash !== expectedHash) {
console.log(hexlify(transactionPayload));
console.log(nonce, gasLimit, target, value, data, sender);
}
expect(expectedHash).to.eq(computedHash);
}
const data = randomBytes(dataLen);
const transactionPayload = RLP.encode([
nonce.toHexString(),
gasLimit.toHexString(),
target,
value.toHexString(),
data,
sender,
]);
const payload = concat([transactionType, transactionPayload]);
const expectedHash = keccak256(payload);
const computedHash = await queue.computeTransactionHash(sender, nonce, value, target, gasLimit, data);
expect(expectedHash).to.eq(computedHash);
}
}
}

View File

@@ -30,6 +30,9 @@ contract L1ScrollMessenger is PausableUpgradeable, ScrollMessengerBase, IL1Scrol
* Variables *
*************/
/// @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;
@@ -42,6 +45,28 @@ contract L1ScrollMessenger is PausableUpgradeable, ScrollMessengerBase, IL1Scrol
/// @notice The address of L1MessageQueue contract.
address public messageQueue;
// @note move to ScrollMessengerBase in next big refactor
/// @dev The status of for non-reentrant check.
uint256 private _lock_status;
/**********************
* Function Modifiers *
**********************/
modifier nonReentrant() {
// On the first call to nonReentrant, _notEntered will be true
require(_lock_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_lock_status = _ENTERED;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_lock_status = _NOT_ENTERED;
}
/***************
* Constructor *
***************/
@@ -137,6 +162,9 @@ contract L1ScrollMessenger is PausableUpgradeable, ScrollMessengerBase, IL1Scrol
} else {
emit FailedRelayedMessage(_xDomainCalldataHash);
}
bytes32 _relayId = keccak256(abi.encodePacked(_xDomainCalldataHash, msg.sender, block.number));
isL1MessageRelayed[_relayId] = true;
}
/// @inheritdoc IL1ScrollMessenger

View File

@@ -139,27 +139,18 @@ contract L1MessageQueue is OwnableUpgradeable, IL1MessageQueue {
}
}
// This is used for both store uint and single byte.
// Integer zero is special handled by geth to encode as `0x80`
function store_uint_or_byte(_ptr, v, is_uint) -> ptr {
function store_uint(_ptr, v) -> ptr {
ptr := _ptr
switch lt(v, 128)
case 1 {
switch and(iszero(v), is_uint)
case 1 {
// integer 0
mstore8(ptr, 0x80)
}
default {
// single byte in the [0x00, 0x7f]
mstore8(ptr, v)
}
// single byte in the [0x00, 0x7f]
mstore(ptr, shl(248, v))
ptr := add(ptr, 1)
}
default {
// 1-32 bytes long
let len := get_uint_bytes(v)
mstore8(ptr, add(len, 0x80))
mstore(ptr, shl(248, add(len, 0x80)))
ptr := add(ptr, 1)
mstore(ptr, shl(mul(8, sub(32, len)), v))
ptr := add(ptr, len)
@@ -169,7 +160,7 @@ contract L1MessageQueue is OwnableUpgradeable, IL1MessageQueue {
function store_address(_ptr, v) -> ptr {
ptr := _ptr
// 20 bytes long
mstore8(ptr, 0x94) // 0x80 + 0x14
mstore(ptr, shl(248, 0x94)) // 0x80 + 0x14
ptr := add(ptr, 1)
mstore(ptr, shl(96, v))
ptr := add(ptr, 0x14)
@@ -179,21 +170,21 @@ contract L1MessageQueue is OwnableUpgradeable, IL1MessageQueue {
// 4 byte for list payload length
let start_ptr := add(mload(0x40), 5)
let ptr := start_ptr
ptr := store_uint_or_byte(ptr, _queueIndex, 1)
ptr := store_uint_or_byte(ptr, _gasLimit, 1)
ptr := store_uint(ptr, _queueIndex)
ptr := store_uint(ptr, _gasLimit)
ptr := store_address(ptr, _target)
ptr := store_uint_or_byte(ptr, _value, 1)
ptr := store_uint(ptr, _value)
switch eq(_data.length, 1)
case 1 {
// single byte
ptr := store_uint_or_byte(ptr, byte(0, calldataload(_data.offset)), 0)
ptr := store_uint(ptr, shr(248, calldataload(_data.offset)))
}
default {
switch lt(_data.length, 56)
case 1 {
// a string is 0-55 bytes long
mstore8(ptr, add(0x80, _data.length))
mstore(ptr, shl(248, add(0x80, _data.length)))
ptr := add(ptr, 1)
calldatacopy(ptr, _data.offset, _data.length)
ptr := add(ptr, _data.length)
@@ -201,7 +192,7 @@ contract L1MessageQueue is OwnableUpgradeable, IL1MessageQueue {
default {
// a string is more than 55 bytes long
let len_bytes := get_uint_bytes(_data.length)
mstore8(ptr, add(0xb7, len_bytes))
mstore(ptr, shl(248, add(0xb7, len_bytes)))
ptr := add(ptr, 1)
mstore(ptr, shl(mul(8, sub(32, len_bytes)), _data.length))
ptr := add(ptr, len_bytes)

View File

@@ -1,118 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IRollupVerifier} from "../../libraries/verifier/IRollupVerifier.sol";
import {IZkEvmVerifier} from "../../libraries/verifier/IZkEvmVerifier.sol";
contract MultipleVersionRollupVerifier is IRollupVerifier, Ownable {
/**********
* Events *
**********/
/// @notice Emitted when the address of verifier is updated.
/// @param startBatchIndex The start batch index when the verifier will be used.
/// @param verifier The address of new verifier.
event UpdateVerifier(uint256 startBatchIndex, address verifier);
/***********
* Structs *
***********/
struct Verifier {
// The start batch index for the verifier.
uint64 startBatchIndex;
// The address of zkevm verifier.
address verifier;
}
/*************
* Variables *
*************/
/// @notice The list of legacy zkevm verifier, sorted by batchIndex in increasing order.
Verifier[] public legacyVerifiers;
/// @notice The lastest used zkevm verifier.
Verifier public latestVerifier;
/***************
* Constructor *
***************/
constructor(address _verifier) {
require(_verifier != address(0), "zero verifier address");
latestVerifier.verifier = _verifier;
}
/*************************
* Public View Functions *
*************************/
/// @notice Return the number of legacy verifiers.
function legacyVerifiersLength() external view returns (uint256) {
return legacyVerifiers.length;
}
/// @notice Compute the verifier should be used for specific batch.
/// @param _batchIndex The batch index to query.
function getVerifier(uint256 _batchIndex) public view returns (address) {
// Normally, we will use the latest verifier.
Verifier memory _verifier = latestVerifier;
if (_verifier.startBatchIndex > _batchIndex) {
uint256 _length = legacyVerifiers.length;
// In most case, only last few verifier will be used by `ScrollChain`.
// So, we use linear search instead of binary search.
unchecked {
for (uint256 i = _length; i > 0; --i) {
_verifier = legacyVerifiers[i - 1];
if (_verifier.startBatchIndex <= _batchIndex) break;
}
}
}
return _verifier.verifier;
}
/*****************************
* Public Mutating Functions *
*****************************/
/// @inheritdoc IRollupVerifier
function verifyAggregateProof(
uint256 _batchIndex,
bytes calldata _aggrProof,
bytes32 _publicInputHash
) external view override {
address _verifier = getVerifier(_batchIndex);
IZkEvmVerifier(_verifier).verify(_aggrProof, _publicInputHash);
}
/************************
* Restricted Functions *
************************/
/// @notice Update the address of zkevm verifier.
/// @param _startBatchIndex The start batch index when the verifier will be used.
/// @param _verifier The address of new verifier.
function updateVerifier(uint64 _startBatchIndex, address _verifier) external onlyOwner {
Verifier memory _latestVerifier = latestVerifier;
require(_startBatchIndex >= _latestVerifier.startBatchIndex, "start batch index too small");
require(_verifier != address(0), "zero verifier address");
if (_latestVerifier.startBatchIndex < _startBatchIndex) {
legacyVerifiers.push(_latestVerifier);
_latestVerifier.startBatchIndex = _startBatchIndex;
}
_latestVerifier.verifier = _verifier;
latestVerifier = _latestVerifier;
emit UpdateVerifier(_startBatchIndex, _verifier);
}
}

View File

@@ -130,7 +130,12 @@ contract ScrollChain is OwnableUpgradeable, IScrollChain {
*****************************/
/// @notice Import layer 2 genesis block
function importGenesisBatch(bytes calldata _batchHeader, bytes32 _stateRoot) external {
/// @dev Although `_withdrawRoot` is always zero, we add this parameter for the convenience of unit testing.
function importGenesisBatch(
bytes calldata _batchHeader,
bytes32 _stateRoot,
bytes32 _withdrawRoot
) external {
// check genesis batch header length
require(_stateRoot != bytes32(0), "zero state root");
@@ -152,9 +157,10 @@ contract ScrollChain is OwnableUpgradeable, IScrollChain {
committedBatches[0] = _batchHash;
finalizedStateRoots[0] = _stateRoot;
withdrawRoots[0] = _withdrawRoot;
emit CommitBatch(_batchHash);
emit FinalizeBatch(_batchHash, _stateRoot, bytes32(0));
emit FinalizeBatch(_batchHash, _stateRoot, _withdrawRoot);
}
/// @inheritdoc IScrollChain
@@ -308,7 +314,7 @@ contract ScrollChain is OwnableUpgradeable, IScrollChain {
);
// verify batch
IRollupVerifier(verifier).verifyAggregateProof(_batchIndex, _aggrProof, _publicInputHash);
IRollupVerifier(verifier).verifyAggregateProof(_aggrProof, _publicInputHash);
// check and update lastFinalizedBatchIndex
unchecked {

View File

@@ -62,6 +62,28 @@ contract L2ScrollMessenger is ScrollMessengerBase, PausableUpgradeable, IL2Scrol
/// @notice The maximum number of times each L1 message can fail on L2.
uint256 public maxFailedExecutionTimes;
// @note move to ScrollMessengerBase in next big refactor
/// @dev The status of for non-reentrant check.
uint256 private _lock_status;
/**********************
* Function Modifiers *
**********************/
modifier nonReentrant() {
// On the first call to nonReentrant, _notEntered will be true
require(_lock_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_lock_status = _ENTERED;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_lock_status = _NOT_ENTERED;
}
/***************
* Constructor *
***************/
@@ -104,16 +126,14 @@ contract L2ScrollMessenger is ScrollMessengerBase, PausableUpgradeable, IL2Scrol
require(_expectedStateRoot != bytes32(0), "Block is not imported");
bytes32 _storageKey;
// `mapping(bytes32 => bool) public isL1MessageSent` is the 155-th slot of contract `L1ScrollMessenger`.
// + 1 from `Initializable`
// `mapping(bytes32 => bool) public isL1MessageSent` is the 105-nd slot of contract `L1ScrollMessenger`.
// + 50 from `OwnableUpgradeable`
// + 50 from `ContextUpgradeable`
// + 4 from `ScrollMessengerBase`
// + 50 from `PausableUpgradeable`
// + 1-st in `L1ScrollMessenger`
// + 2-nd in `L1ScrollMessenger`
assembly {
mstore(0x00, _msgHash)
mstore(0x20, 155)
mstore(0x20, 105)
_storageKey := keccak256(0x00, 0x40)
}
@@ -141,16 +161,14 @@ contract L2ScrollMessenger is ScrollMessengerBase, PausableUpgradeable, IL2Scrol
require(_expectedStateRoot != bytes32(0), "Block not imported");
bytes32 _storageKey;
// `mapping(bytes32 => bool) public isL2MessageExecuted` is the 156-th slot of contract `L1ScrollMessenger`.
// + 1 from `Initializable`
// `mapping(bytes32 => bool) public isL2MessageExecuted` is the 106-th slot of contract `L1ScrollMessenger`.
// + 50 from `OwnableUpgradeable`
// + 50 from `ContextUpgradeable`
// + 4 from `ScrollMessengerBase`
// + 50 from `PausableUpgradeable`
// + 2-nd in `L1ScrollMessenger`
// + 3-rd in `L1ScrollMessenger`
assembly {
mstore(0x00, _msgHash)
mstore(0x20, 156)
mstore(0x20, 106)
_storageKey := keccak256(0x00, 0x40)
}

View File

@@ -51,8 +51,7 @@ contract WETH9 {
balanceOf[msg.sender] -= wad;
}
(bool success, ) = msg.sender.call{value:wad}("");
require(success, "withdraw ETH failed");
payable(msg.sender).transfer(wad);
emit Withdrawal(msg.sender, wad);
}

View File

@@ -38,28 +38,6 @@ abstract contract ScrollMessengerBase is OwnableUpgradeable, IScrollMessenger {
/// @notice The address of fee vault, collecting cross domain messaging fee.
address public feeVault;
// @note move to ScrollMessengerBase in next big refactor
/// @dev The status of for non-reentrant check.
uint256 private _lock_status;
/**********************
* Function Modifiers *
**********************/
modifier nonReentrant() {
// On the first call to nonReentrant, _notEntered will be true
require(_lock_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_lock_status = _ENTERED;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_lock_status = _NOT_ENTERED;
}
/***************
* Constructor *
***************/

View File

@@ -4,12 +4,7 @@ pragma solidity ^0.8.0;
interface IRollupVerifier {
/// @notice Verify aggregate zk proof.
/// @param batchIndex The batch index to verify.
/// @param aggrProof The aggregated proof.
/// @param publicInputHash The public input hash.
function verifyAggregateProof(
uint256 batchIndex,
bytes calldata aggrProof,
bytes32 publicInputHash
) external view;
function verifyAggregateProof(bytes calldata aggrProof, bytes32 publicInputHash) external view;
}

View File

@@ -1,10 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IZkEvmVerifier {
/// @notice Verify aggregate zk proof.
/// @param aggrProof The aggregated proof.
/// @param publicInputHash The public input hash.
function verify(bytes calldata aggrProof, bytes32 publicInputHash) external view;
}

View File

@@ -12,10 +12,6 @@ import {Whitelist} from "../L2/predeploys/Whitelist.sol";
import {L1ScrollMessenger} from "../L1/L1ScrollMessenger.sol";
import {L2ScrollMessenger} from "../L2/L2ScrollMessenger.sol";
import {MockRollupVerifier} from "./mocks/MockRollupVerifier.sol";
// solhint-disable no-inline-assembly
abstract contract L1GatewayTestBase is DSTestPlus {
// from L1MessageQueue
event QueueTransaction(
@@ -48,8 +44,6 @@ abstract contract L1GatewayTestBase is DSTestPlus {
EnforcedTxGateway internal enforcedTxGateway;
ScrollChain internal rollup;
MockRollupVerifier internal verifier;
address internal feeVault;
Whitelist private whitelist;
@@ -65,7 +59,6 @@ abstract contract L1GatewayTestBase is DSTestPlus {
rollup = new ScrollChain(1233);
enforcedTxGateway = new EnforcedTxGateway();
whitelist = new Whitelist(address(this));
verifier = new MockRollupVerifier();
// Deploy L2 contracts
l2Messenger = new L2ScrollMessenger(address(0), address(0), address(0));
@@ -81,7 +74,7 @@ abstract contract L1GatewayTestBase is DSTestPlus {
);
gasOracle.initialize(0, 0, 0, 0);
gasOracle.updateWhitelist(address(whitelist));
rollup.initialize(address(messageQueue), address(verifier), 44);
rollup.initialize(address(messageQueue), address(0), 44);
address[] memory _accounts = new address[](1);
_accounts[0] = address(this);
@@ -89,40 +82,11 @@ abstract contract L1GatewayTestBase is DSTestPlus {
}
function prepareL2MessageRoot(bytes32 messageHash) internal {
rollup.updateSequencer(address(this), true);
rollup.updateProver(address(this), true);
// import genesis batch
bytes memory batchHeader0 = new bytes(89);
bytes memory _batchHeader = new bytes(89);
assembly {
mstore(add(batchHeader0, add(0x20, 25)), 1)
}
rollup.importGenesisBatch(batchHeader0, bytes32(uint256(1)));
bytes32 batchHash0 = rollup.committedBatches(0);
// commit one batch
bytes[] memory chunks = new bytes[](1);
bytes memory chunk0 = new bytes(1 + 60);
chunk0[0] = bytes1(uint8(1)); // one block in this chunk
chunks[0] = chunk0;
rollup.commitBatch(0, batchHeader0, chunks, new bytes(0));
bytes memory batchHeader1 = new bytes(89);
assembly {
mstore(add(batchHeader1, 0x20), 0) // version
mstore(add(batchHeader1, add(0x20, 1)), shl(192, 1)) // batchIndex
mstore(add(batchHeader1, add(0x20, 9)), 0) // l1MessagePopped
mstore(add(batchHeader1, add(0x20, 17)), 0) // totalL1MessagePopped
mstore(add(batchHeader1, add(0x20, 25)), 0x246394445f4fe64ed5598554d55d1682d6fb3fe04bf58eb54ef81d1189fafb51) // dataHash
mstore(add(batchHeader1, add(0x20, 57)), batchHash0) // parentBatchHash
mstore(add(_batchHeader, add(0x20, 25)), 1)
}
rollup.finalizeBatchWithProof(
batchHeader1,
bytes32(uint256(1)),
bytes32(uint256(2)),
messageHash,
new bytes(0)
);
rollup.importGenesisBatch(_batchHeader, bytes32(uint256(1)), messageHash);
}
}

View File

@@ -12,47 +12,81 @@ import {Whitelist} from "../L2/predeploys/Whitelist.sol";
import {IL1ScrollMessenger, L1ScrollMessenger} from "../L1/L1ScrollMessenger.sol";
import {L2ScrollMessenger} from "../L2/L2ScrollMessenger.sol";
import {L1GatewayTestBase} from "./L1GatewayTestBase.t.sol";
contract L1ScrollMessengerTest is DSTestPlus {
L2ScrollMessenger internal l2Messenger;
address internal feeVault;
L1ScrollMessenger internal l1Messenger;
ScrollChain internal scrollChain;
L1MessageQueue internal l1MessageQueue;
L2GasPriceOracle internal gasOracle;
EnforcedTxGateway internal enforcedTxGateway;
Whitelist internal whitelist;
contract L1ScrollMessengerTest is L1GatewayTestBase {
function setUp() public {
L1GatewayTestBase.setUpBase();
// Deploy L2 contracts
l2Messenger = new L2ScrollMessenger(address(0), address(0), address(0));
// Deploy L1 contracts
scrollChain = new ScrollChain(0);
l1MessageQueue = new L1MessageQueue();
l1Messenger = new L1ScrollMessenger();
gasOracle = new L2GasPriceOracle();
enforcedTxGateway = new EnforcedTxGateway();
whitelist = new Whitelist(address(this));
// Initialize L1 contracts
l1Messenger.initialize(address(l2Messenger), feeVault, address(scrollChain), address(l1MessageQueue));
l1MessageQueue.initialize(
address(l1Messenger),
address(scrollChain),
address(enforcedTxGateway),
address(gasOracle),
10000000
);
gasOracle.initialize(0, 0, 0, 0);
scrollChain.initialize(address(l1MessageQueue), address(0), 44);
gasOracle.updateWhitelist(address(whitelist));
address[] memory _accounts = new address[](1);
_accounts[0] = address(this);
whitelist.updateWhitelistStatus(_accounts, true);
}
function testForbidCallMessageQueueFromL2() external {
bytes32 _xDomainCalldataHash = keccak256(
abi.encodeWithSignature(
"relayMessage(address,address,uint256,uint256,bytes)",
address(this),
address(messageQueue),
0,
0,
new bytes(0)
)
// import genesis batch
bytes memory _batchHeader = new bytes(89);
assembly {
mstore(add(_batchHeader, add(0x20, 25)), 1)
}
scrollChain.importGenesisBatch(
_batchHeader,
bytes32(uint256(1)),
bytes32(0x3152134c22e545ab5d345248502b4f04ef5b45f735f939c7fe6ddc0ffefc9c52)
);
prepareL2MessageRoot(_xDomainCalldataHash);
IL1ScrollMessenger.L2MessageProof memory proof;
proof.batchIndex = rollup.lastFinalizedBatchIndex();
proof.batchIndex = scrollChain.lastFinalizedBatchIndex();
hevm.expectRevert("Forbid to call message queue");
l1Messenger.relayMessageWithProof(address(this), address(messageQueue), 0, 0, new bytes(0), proof);
l1Messenger.relayMessageWithProof(address(this), address(l1MessageQueue), 0, 0, new bytes(0), proof);
}
function testForbidCallSelfFromL2() external {
bytes32 _xDomainCalldataHash = keccak256(
abi.encodeWithSignature(
"relayMessage(address,address,uint256,uint256,bytes)",
address(this),
address(l1Messenger),
0,
0,
new bytes(0)
)
// import genesis batch
bytes memory _batchHeader = new bytes(89);
assembly {
mstore(add(_batchHeader, 57), 1)
}
scrollChain.importGenesisBatch(
_batchHeader,
bytes32(uint256(1)),
bytes32(0xf7c03e2b13c88e3fca1410b228b001dd94e3f5ab4b4a4a6981d09a4eb3e5b631)
);
prepareL2MessageRoot(_xDomainCalldataHash);
IL1ScrollMessenger.L2MessageProof memory proof;
proof.batchIndex = rollup.lastFinalizedBatchIndex();
proof.batchIndex = scrollChain.lastFinalizedBatchIndex();
hevm.expectRevert("Forbid to call self");
l1Messenger.relayMessageWithProof(address(this), address(l1Messenger), 0, 0, new bytes(0), proof);
@@ -76,9 +110,7 @@ contract L1ScrollMessengerTest is L1GatewayTestBase {
function testReplayMessage(uint256 exceedValue, address refundAddress) external {
hevm.assume(refundAddress.code.length == 0);
hevm.assume(uint256(uint160(refundAddress)) > uint256(100)); // ignore some precompile contracts
hevm.assume(refundAddress != feeVault);
hevm.assume(refundAddress != address(0x000000000000000000636F6e736F6c652e6c6f67)); // ignore console/console2
hevm.assume(uint256(uint160(refundAddress)) > 100); // ignore some precompile contracts
exceedValue = bound(exceedValue, 1, address(this).balance / 2);
@@ -138,7 +170,7 @@ contract L1ScrollMessengerTest is L1GatewayTestBase {
l1Messenger.sendMessage{value: _fee + value}(address(0), value, hex"0011220033", gasLimit);
// update max gas limit
messageQueue.updateMaxGasLimit(gasLimit);
l1MessageQueue.updateMaxGasLimit(gasLimit);
l1Messenger.sendMessage{value: _fee + value}(address(0), value, hex"0011220033", gasLimit);
}
}

View File

@@ -1,105 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {DSTestPlus} from "solmate/test/utils/DSTestPlus.sol";
import {L1MessageQueue} from "../L1/rollup/L1MessageQueue.sol";
import {MultipleVersionRollupVerifier} from "../L1/rollup/MultipleVersionRollupVerifier.sol";
import {MockZkEvmVerifier} from "./mocks/MockZkEvmVerifier.sol";
contract MultipleVersionRollupVerifierTest is DSTestPlus {
// from MultipleVersionRollupVerifier
event UpdateVerifier(uint256 startBatchIndex, address verifier);
MultipleVersionRollupVerifier private verifier;
MockZkEvmVerifier private v0;
MockZkEvmVerifier private v1;
MockZkEvmVerifier private v2;
function setUp() external {
v0 = new MockZkEvmVerifier();
v1 = new MockZkEvmVerifier();
v2 = new MockZkEvmVerifier();
verifier = new MultipleVersionRollupVerifier(address(v0));
}
function testUpdateVerifier(address _newVerifier) external {
hevm.assume(_newVerifier != address(0));
// set by non-owner, should revert
hevm.startPrank(address(1));
hevm.expectRevert("Ownable: caller is not the owner");
verifier.updateVerifier(0, address(0));
hevm.stopPrank();
// zero verifier address, revert
hevm.expectRevert("zero verifier address");
verifier.updateVerifier(0, address(0));
// change to random operator
assertEq(verifier.legacyVerifiersLength(), 0);
verifier.updateVerifier(uint64(100), _newVerifier);
assertEq(verifier.legacyVerifiersLength(), 1);
(uint64 _startBatchIndex, address _verifier) = verifier.latestVerifier();
assertEq(_startBatchIndex, uint64(100));
assertEq(_verifier, _newVerifier);
(_startBatchIndex, _verifier) = verifier.legacyVerifiers(0);
assertEq(_startBatchIndex, uint64(0));
assertEq(_verifier, address(v0));
// change to same batch index
verifier.updateVerifier(uint64(100), address(v1));
(_startBatchIndex, _verifier) = verifier.latestVerifier();
assertEq(_startBatchIndex, uint64(100));
assertEq(_verifier, address(v1));
(_startBatchIndex, _verifier) = verifier.legacyVerifiers(0);
assertEq(_startBatchIndex, uint64(0));
assertEq(_verifier, address(v0));
// start batch index too small, revert
hevm.expectRevert("start batch index too small");
verifier.updateVerifier(99, _newVerifier);
}
function testGetVerifier() external {
verifier.updateVerifier(100, address(v1));
verifier.updateVerifier(300, address(v2));
assertEq(verifier.getVerifier(0), address(v0));
assertEq(verifier.getVerifier(1), address(v0));
assertEq(verifier.getVerifier(99), address(v0));
assertEq(verifier.getVerifier(100), address(v1));
assertEq(verifier.getVerifier(101), address(v1));
assertEq(verifier.getVerifier(299), address(v1));
assertEq(verifier.getVerifier(300), address(v2));
assertEq(verifier.getVerifier(301), address(v2));
assertEq(verifier.getVerifier(10000), address(v2));
}
function testVerifyAggregateProof() external {
verifier.updateVerifier(100, address(v1));
verifier.updateVerifier(300, address(v2));
hevm.expectRevert(abi.encode(address(v0)));
verifier.verifyAggregateProof(0, new bytes(0), bytes32(0));
hevm.expectRevert(abi.encode(address(v0)));
verifier.verifyAggregateProof(1, new bytes(0), bytes32(0));
hevm.expectRevert(abi.encode(address(v0)));
verifier.verifyAggregateProof(99, new bytes(0), bytes32(0));
hevm.expectRevert(abi.encode(address(v1)));
verifier.verifyAggregateProof(100, new bytes(0), bytes32(0));
hevm.expectRevert(abi.encode(address(v1)));
verifier.verifyAggregateProof(101, new bytes(0), bytes32(0));
hevm.expectRevert(abi.encode(address(v1)));
verifier.verifyAggregateProof(299, new bytes(0), bytes32(0));
hevm.expectRevert(abi.encode(address(v2)));
verifier.verifyAggregateProof(300, new bytes(0), bytes32(0));
hevm.expectRevert(abi.encode(address(v2)));
verifier.verifyAggregateProof(301, new bytes(0), bytes32(0));
hevm.expectRevert(abi.encode(address(v2)));
verifier.verifyAggregateProof(10000, new bytes(0), bytes32(0));
}
}

View File

@@ -53,7 +53,7 @@ contract ScrollChainTest is DSTestPlus {
assembly {
mstore(add(batchHeader0, add(0x20, 25)), 1)
}
rollup.importGenesisBatch(batchHeader0, bytes32(uint256(1)));
rollup.importGenesisBatch(batchHeader0, bytes32(uint256(1)), bytes32(uint256(0)));
// caller not sequencer, revert
hevm.expectRevert("caller not sequencer");
@@ -136,7 +136,7 @@ contract ScrollChainTest is DSTestPlus {
assembly {
mstore(add(batchHeader0, add(0x20, 25)), 1)
}
rollup.importGenesisBatch(batchHeader0, bytes32(uint256(1)));
rollup.importGenesisBatch(batchHeader0, bytes32(uint256(1)), bytes32(uint256(0)));
bytes32 batchHash0 = rollup.committedBatches(0);
bytes[] memory chunks = new bytes[](1);
@@ -228,7 +228,7 @@ contract ScrollChainTest is DSTestPlus {
assembly {
mstore(add(batchHeader0, add(0x20, 25)), 1)
}
rollup.importGenesisBatch(batchHeader0, bytes32(uint256(1)));
rollup.importGenesisBatch(batchHeader0, bytes32(uint256(1)), bytes32(uint256(0)));
bytes32 batchHash0 = rollup.committedBatches(0);
bytes memory bitmap;
@@ -243,28 +243,28 @@ contract ScrollChainTest is DSTestPlus {
// 0000000000000000000000000000000000000000000000000000000000000000
// 0000000000000000
// 0001
// a2277fd30bbbe74323309023b56035b376d7768ad237ae4fc46ead7dc9591ae1
// 50c3caa727394b95dc4885b7d25033ed22ac772b985fb274f2a7c0699a11346d
// => data hash for chunk0
// 9ef1e5694bdb014a1eea42be756a8f63bfd8781d6332e9ef3b5126d90c62f110
// bb88f47194a07d59ed17bc9b2015f83d0afea8f7892d9c5f0b6565563bf06b26
// => data hash for all chunks
// d9cb6bf9264006fcea490d5c261f7453ab95b1b26033a3805996791b8e3a62f3
// 038433daac85a0b03cd443ed50bc85e832c883061651ae2182b2984751e0b340
// => payload for batch header
// 00
// 0000000000000002
// 0000000000000001
// 0000000000000001
// 0000000000000001
// d9cb6bf9264006fcea490d5c261f7453ab95b1b26033a3805996791b8e3a62f3
// 038433daac85a0b03cd443ed50bc85e832c883061651ae2182b2984751e0b340
// 119b828c2a2798d2c957228ebeaff7e10bb099ae0d4e224f3eeb779ff61cba61
// 0000000000000000000000000000000000000000000000000000000000000000
// => hash for batch header
// 00847173b29b238cf319cde79512b7c213e5a8b4138daa7051914c4592b6dfc7
// cef70bf80683c4d9b8b2813e90c314e8c56648e231300b8cfed9d666b0caf14e
bytes memory batchHeader1 = new bytes(89 + 32);
assembly {
mstore(add(batchHeader1, 0x20), 0) // version
mstore(add(batchHeader1, add(0x20, 1)), shl(192, 1)) // batchIndex = 1
mstore(add(batchHeader1, add(0x20, 9)), shl(192, 1)) // l1MessagePopped = 1
mstore(add(batchHeader1, add(0x20, 17)), shl(192, 1)) // totalL1MessagePopped = 1
mstore(add(batchHeader1, add(0x20, 25)), 0xd9cb6bf9264006fcea490d5c261f7453ab95b1b26033a3805996791b8e3a62f3) // dataHash
mstore(add(batchHeader1, add(0x20, 25)), 0x038433daac85a0b03cd443ed50bc85e832c883061651ae2182b2984751e0b340) // dataHash
mstore(add(batchHeader1, add(0x20, 57)), batchHash0) // parentBatchHash
mstore(add(batchHeader1, add(0x20, 89)), 0) // bitmap0
}
@@ -280,7 +280,7 @@ contract ScrollChainTest is DSTestPlus {
rollup.commitBatch(0, batchHeader0, chunks, bitmap);
assertBoolEq(rollup.isBatchFinalized(1), false);
bytes32 batchHash1 = rollup.committedBatches(1);
assertEq(batchHash1, bytes32(0x00847173b29b238cf319cde79512b7c213e5a8b4138daa7051914c4592b6dfc7));
assertEq(batchHash1, bytes32(0xcef70bf80683c4d9b8b2813e90c314e8c56648e231300b8cfed9d666b0caf14e));
// finalize batch1
rollup.finalizeBatchWithProof(
@@ -330,26 +330,26 @@ contract ScrollChainTest is DSTestPlus {
// 012c
// ... (some tx hashes)
// => data hash for chunk2
// 0520f1fbe159af97fdf1d6cfcfe7605f99f7bfe3ed876e87b64250b1810df00b
// 5c91563ee8be18cb94accfc83728f883ff5e3aa600fd0799e0a4e39afc7970b9
// => data hash for all chunks
// f52343299f6379fd15b20b23d51fc61b9b357b124be112686626b6278bcffa83
// bf38f308e0a87ed7bf92fa2da038fa1d59a7b9801eb0f6d487f8eef528632145
// => payload for batch header
// 00
// 0000000000000002
// 0000000000000108
// 0000000000000109
// f52343299f6379fd15b20b23d51fc61b9b357b124be112686626b6278bcffa83
// bf38f308e0a87ed7bf92fa2da038fa1d59a7b9801eb0f6d487f8eef528632145
// cef70bf80683c4d9b8b2813e90c314e8c56648e231300b8cfed9d666b0caf14e
// aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa800000000000000000000000000000000000000000000000000000000000000aa
// => hash for batch header
// 2231cf185a5c07931584f970738b4cd2ae4fb39e2d90853b26746c7616ea71b9
// 17fe6c12739f3a6261ae6db6486f41758dbd5d0508f19a5ca9ac37df67bbfec2
bytes memory batchHeader2 = new bytes(89 + 32 + 32);
assembly {
mstore(add(batchHeader2, 0x20), 0) // version
mstore(add(batchHeader2, add(0x20, 1)), shl(192, 2)) // batchIndex = 2
mstore(add(batchHeader2, add(0x20, 9)), shl(192, 264)) // l1MessagePopped = 264
mstore(add(batchHeader2, add(0x20, 17)), shl(192, 265)) // totalL1MessagePopped = 265
mstore(add(batchHeader2, add(0x20, 25)), 0xf52343299f6379fd15b20b23d51fc61b9b357b124be112686626b6278bcffa83) // dataHash
mstore(add(batchHeader2, add(0x20, 25)), 0xbf38f308e0a87ed7bf92fa2da038fa1d59a7b9801eb0f6d487f8eef528632145) // dataHash
mstore(add(batchHeader2, add(0x20, 57)), batchHash1) // parentBatchHash
mstore(
add(batchHeader2, add(0x20, 89)),
@@ -398,7 +398,7 @@ contract ScrollChainTest is DSTestPlus {
rollup.commitBatch(0, batchHeader1, chunks, bitmap);
assertBoolEq(rollup.isBatchFinalized(2), false);
bytes32 batchHash2 = rollup.committedBatches(2);
assertEq(batchHash2, bytes32(0x2231cf185a5c07931584f970738b4cd2ae4fb39e2d90853b26746c7616ea71b9));
assertEq(batchHash2, bytes32(0x17fe6c12739f3a6261ae6db6486f41758dbd5d0508f19a5ca9ac37df67bbfec2));
// verify committed batch correctly
rollup.finalizeBatchWithProof(
@@ -450,7 +450,7 @@ contract ScrollChainTest is DSTestPlus {
assembly {
mstore(add(batchHeader0, add(0x20, 25)), 1)
}
rollup.importGenesisBatch(batchHeader0, bytes32(uint256(1)));
rollup.importGenesisBatch(batchHeader0, bytes32(uint256(1)), bytes32(uint256(0)));
bytes32 batchHash0 = rollup.committedBatches(0);
bytes[] memory chunks = new bytes[](1);
@@ -577,52 +577,52 @@ contract ScrollChainTest is DSTestPlus {
// zero state root, revert
batchHeader = new bytes(89);
hevm.expectRevert("zero state root");
rollup.importGenesisBatch(batchHeader, bytes32(0));
rollup.importGenesisBatch(batchHeader, bytes32(0), bytes32(0));
// batch header length too small, revert
batchHeader = new bytes(88);
hevm.expectRevert("batch header length too small");
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)));
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)), bytes32(0));
// wrong bitmap length, revert
batchHeader = new bytes(90);
hevm.expectRevert("wrong bitmap length");
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)));
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)), bytes32(0));
// not all fields are zero, revert
batchHeader = new bytes(89);
batchHeader[0] = bytes1(uint8(1)); // version not zero
hevm.expectRevert("not all fields are zero");
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)));
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)), bytes32(0));
batchHeader = new bytes(89);
batchHeader[1] = bytes1(uint8(1)); // batchIndex not zero
hevm.expectRevert("not all fields are zero");
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)));
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)), bytes32(0));
batchHeader = new bytes(89 + 32);
assembly {
mstore(add(batchHeader, add(0x20, 9)), shl(192, 1)) // l1MessagePopped not zero
}
hevm.expectRevert("not all fields are zero");
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)));
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)), bytes32(0));
batchHeader = new bytes(89);
batchHeader[17] = bytes1(uint8(1)); // totalL1MessagePopped not zero
hevm.expectRevert("not all fields are zero");
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)));
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)), bytes32(0));
// zero data hash, revert
batchHeader = new bytes(89);
hevm.expectRevert("zero data hash");
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)));
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)), bytes32(0));
// nonzero parent batch hash, revert
batchHeader = new bytes(89);
batchHeader[25] = bytes1(uint8(1)); // dataHash not zero
batchHeader[57] = bytes1(uint8(1)); // parentBatchHash not zero
hevm.expectRevert("nonzero parent batch hash");
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)));
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)), bytes32(0));
// import correctly
batchHeader = new bytes(89);
@@ -630,13 +630,13 @@ contract ScrollChainTest is DSTestPlus {
assertEq(rollup.finalizedStateRoots(0), bytes32(0));
assertEq(rollup.withdrawRoots(0), bytes32(0));
assertEq(rollup.committedBatches(0), bytes32(0));
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)));
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)), bytes32(uint256(2)));
assertEq(rollup.finalizedStateRoots(0), bytes32(uint256(1)));
assertEq(rollup.withdrawRoots(0), bytes32(0));
assertEq(rollup.withdrawRoots(0), bytes32(uint256(2)));
assertGt(uint256(rollup.committedBatches(0)), 0);
// Genesis batch imported, revert
hevm.expectRevert("Genesis batch imported");
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)));
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)), bytes32(uint256(2)));
}
}

View File

@@ -6,9 +6,5 @@ import {IRollupVerifier} from "../../libraries/verifier/IRollupVerifier.sol";
contract MockRollupVerifier is IRollupVerifier {
/// @inheritdoc IRollupVerifier
function verifyAggregateProof(
uint256,
bytes calldata,
bytes32
) external view {}
function verifyAggregateProof(bytes calldata, bytes32) external view {}
}

View File

@@ -1,14 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IZkEvmVerifier} from "../../libraries/verifier/IZkEvmVerifier.sol";
contract MockZkEvmVerifier is IZkEvmVerifier {
event Called(address);
/// @inheritdoc IZkEvmVerifier
function verify(bytes calldata, bytes32) external view {
revert(string(abi.encode(address(this))));
}
}

View File

@@ -25,13 +25,6 @@ type RollerAPI interface {
SubmitProof(proof *message.ProofMsg) error
}
// AdminAPI for Coordinator in order to manage process.
type AdminAPI interface {
StartSend()
PauseSend()
PauseSendUntil(batchIdx uint64)
}
// RequestToken generates and sends back register token for roller
func (m *Manager) RequestToken(authMsg *message.AuthMsg) (string, error) {
if ok, err := authMsg.Verify(); !ok {
@@ -134,18 +127,3 @@ func (m *Manager) SubmitProof(proof *message.ProofMsg) error {
return nil
}
// StartSend starts to send basic tasks.
func (m *Manager) StartSend() {
m.StartSendTask()
}
// PauseSend pauses to send basic tasks.
func (m *Manager) PauseSend() {
m.PauseSendTask()
}
// PauseSendUntil pause to send basic tasks until batchIdx.
func (m *Manager) PauseSendUntil(batchIdx uint64) {
m.PauseSendTaskUntil(batchIdx)
}

View File

@@ -17,9 +17,7 @@ const (
// RollerManagerConfig loads sequencer configuration items.
type RollerManagerConfig struct {
PauseSendTask bool `json:"pause_send_task"`
PauseSendTaskUntil uint64 `json:"pause_send_task_until"`
CompressionLevel int `json:"compression_level,omitempty"`
CompressionLevel int `json:"compression_level,omitempty"`
// asc or desc (default: asc)
OrderSession string `json:"order_session,omitempty"`
// The amount of rollers to pick per proof generation session.

View File

@@ -9,7 +9,6 @@ require (
github.com/scroll-tech/go-ethereum v1.10.14-0.20230508165858-27a3830afa61
github.com/stretchr/testify v1.8.2
github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa
go.uber.org/atomic v1.11.0
golang.org/x/exp v0.0.0-20230206171751-46f607a40771
golang.org/x/sync v0.1.0
)

View File

@@ -133,8 +133,6 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRT
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=

View File

@@ -15,7 +15,6 @@ import (
"github.com/scroll-tech/go-ethereum/log"
geth_metrics "github.com/scroll-tech/go-ethereum/metrics"
"github.com/scroll-tech/go-ethereum/rpc"
uatomic "go.uber.org/atomic"
"golang.org/x/exp/rand"
"scroll-tech/common/metrics"
@@ -76,9 +75,7 @@ type Manager struct {
cfg *config.RollerManagerConfig
// The indicator whether the backend is running or not.
running int32
sendTaskPaused *uatomic.Bool
pauseUntilBatchIdx *uatomic.Uint64
running int32
// A mutex guarding the boolean below.
mu sync.RWMutex
@@ -120,8 +117,6 @@ func New(ctx context.Context, cfg *config.RollerManagerConfig, orm database.OrmF
return &Manager{
ctx: ctx,
cfg: cfg,
sendTaskPaused: uatomic.NewBool(cfg.PauseSendTask),
pauseUntilBatchIdx: uatomic.NewUint64(cfg.PauseSendTaskUntil),
rollerPool: cmap.New(),
sessions: make(map[string]*session),
failedSessionInfos: make(map[string]*SessionInfo),
@@ -206,13 +201,7 @@ func (m *Manager) Loop() {
}
}
// Select basic type roller and send message
for len(tasks) > 0 {
if m.isSendTaskPaused(tasks[0].Index) {
break
}
if !m.StartBasicProofGenerationSession(tasks[0], nil) {
break
}
for len(tasks) > 0 && m.StartBasicProofGenerationSession(tasks[0], nil) {
tasks = tasks[1:]
}
case <-m.ctx.Done():
@@ -570,11 +559,6 @@ func (m *Manager) APIs() []rpc.API {
Service: RollerAPI(m),
Public: true,
},
{
Namespace: "admin",
Service: AdminAPI(m),
Public: true,
},
{
Namespace: "debug",
Public: true,
@@ -583,26 +567,6 @@ func (m *Manager) APIs() []rpc.API {
}
}
// StartSendTask starts to send basic tasks loop.
func (m *Manager) StartSendTask() {
m.sendTaskPaused.Store(false)
}
// PauseSendTask pauses to send basic tasks loop.
func (m *Manager) PauseSendTask() {
m.sendTaskPaused.Store(true)
}
// PauseSendTaskUntil pauses to send basic tasks loop until batchIdx.
func (m *Manager) PauseSendTaskUntil(batchIdx uint64) {
m.PauseSendTask()
m.pauseUntilBatchIdx.Store(batchIdx)
}
func (m *Manager) isSendTaskPaused(batchIdx uint64) bool {
return m.sendTaskPaused.Load() && m.pauseUntilBatchIdx.Load() > batchIdx
}
// StartBasicProofGenerationSession starts a basic proof generation session
func (m *Manager) StartBasicProofGenerationSession(task *types.BlockBatch, prevSession *session) (success bool) {
var taskID string

View File

@@ -7,15 +7,26 @@ import (
"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/stretchr/testify/assert"
"scroll-tech/database"
"scroll-tech/database/migrate"
"scroll-tech/database/orm"
"scroll-tech/common/bytecode/erc20"
"scroll-tech/common/bytecode/greeter"
l1gateway "scroll-tech/common/bytecode/scroll/L1/gateways"
"scroll-tech/common/bytecode/scroll/L1/rollup"
l2gateway "scroll-tech/common/bytecode/scroll/L2/gateways"
ctypes "scroll-tech/common/types"
"scroll-tech/common/utils"
)
var (
erc20Address = common.HexToAddress("0x7363726f6c6c6c20000000000000000000000014")
greeterAddress = common.HexToAddress("0x7363726f6c6c6c20000000000000000000000015")
// Balance equal to 1e28
amount = new(big.Int).SetBytes(common.FromHex("204fce5e3e25020000000000"))
ether = big.NewInt(1e18)
)
func TestERC20(t *testing.T) {
@@ -23,31 +34,31 @@ func TestERC20(t *testing.T) {
l2Cli, err := base.L2Client()
assert.Nil(t, err)
token, err := erc20.NewERC20Mock(erc20Address, l2Cli)
token, err := erc20.NewERC20Mock(base.ERC20, l2Cli)
assert.NoError(t, err)
auth, err := bind.NewKeyedTransactorWithChainID(bridgeApp.Config.L2Config.RelayerConfig.MessageSenderPrivateKeys[0], base.L2gethImg.ChainID())
auth, err := bind.NewKeyedTransactorWithChainID(bridgeApp.Config.L2Config.RelayerConfig.GasOracleSenderPrivateKeys[0], base.L2gethImg.ChainID())
assert.NoError(t, err)
authBls0, err := token.BalanceOf(nil, auth.From)
assert.NoError(t, err)
tokenBls0, err := token.BalanceOf(nil, erc20Address)
tokenBls0, err := token.BalanceOf(nil, base.ERC20)
assert.NoError(t, err)
// create tx to transfer balance.
value := big.NewInt(1000)
tx, err := token.Transfer(auth, erc20Address, value)
to := common.HexToAddress("0x85fd9d96a42972f8301b886e77838f363e72dff7")
tx, err := token.Transfer(auth, to, value)
assert.NoError(t, err)
bind.WaitMined(context.Background(), l2Cli, tx)
authBls1, err := token.BalanceOf(nil, auth.From)
assert.NoError(t, err)
tokenBls1, err := token.BalanceOf(nil, erc20Address)
_, err = bind.WaitMined(context.Background(), l2Cli, tx)
assert.NoError(t, err)
// check balance.
authBls1, err := token.BalanceOf(nil, auth.From)
assert.NoError(t, err)
tokenBls1, err := token.BalanceOf(nil, to)
assert.NoError(t, err)
assert.Equal(t, authBls0.Int64(), authBls1.Add(authBls1, value).Int64())
assert.Equal(t, tokenBls1.Int64(), tokenBls0.Add(tokenBls0, value).Int64())
}
@@ -57,18 +68,193 @@ func TestGreeter(t *testing.T) {
l2Cli, err := base.L2Client()
assert.Nil(t, err)
auth, err := bind.NewKeyedTransactorWithChainID(bridgeApp.Config.L2Config.RelayerConfig.MessageSenderPrivateKeys[0], base.L2gethImg.ChainID())
chainID, _ := l2Cli.ChainID(context.Background())
auth, err := bind.NewKeyedTransactorWithChainID(bridgeApp.Config.L2Config.RelayerConfig.GasOracleSenderPrivateKeys[0], chainID)
assert.NoError(t, err)
token, err := greeter.NewGreeter(greeterAddress, l2Cli)
token, err := greeter.NewGreeter(base.Greeter, l2Cli)
assert.NoError(t, err)
val := big.NewInt(100)
tx, err := token.SetValue(auth, val)
assert.NoError(t, err)
_, err = bind.WaitMined(context.Background(), l2Cli, tx)
assert.NoError(t, err)
// check result.
res, err := token.Retrieve(nil)
assert.NoError(t, err)
assert.Equal(t, val.String(), res.String())
}
func TestDepositAndWithdraw(t *testing.T) {
base.RunImages(t)
assert.NoError(t, migrate.ResetDB(base.DBClient(t)))
// Run bridge apps.
bridgeApp.RunApp(t, utils.EventWatcherApp)
bridgeApp.RunApp(t, utils.GasOracleApp)
bridgeApp.RunApp(t, utils.MessageRelayerApp)
bridgeApp.RunApp(t, utils.RollupRelayerApp)
// Run coordinator app.
coordinatorApp.RunApp(t)
// Run roller app.
rollerApp.RunApp(t)
t.Run("TestGenesisBatch", testGenesisBatch)
t.Run("TestETHDeposit", testETHDeposit)
t.Run("TestETHWithdraw", testETHWithdraw)
// Free apps.
bridgeApp.WaitExit()
rollerApp.WaitExit()
coordinatorApp.WaitExit()
}
func testGenesisBatch(t *testing.T) {
l1Cli, err := base.L1Client()
assert.NoError(t, err)
l2Cli, err := base.L2Client()
assert.NoError(t, err)
scrollChain, err := rollup.NewScrollChain(base.L1Contracts.L1ScrollChain, l1Cli)
assert.NoError(t, err)
// Create genesis batch.
genesis, err := l2Cli.HeaderByNumber(context.Background(), big.NewInt(0))
assert.NoError(t, err)
batchData0 := ctypes.NewGenesisBatchData(&ctypes.WrappedBlock{Header: genesis, WithdrawTrieRoot: common.Hash{}})
// Check genesis batch is imported or not.
expectBatch, err := scrollChain.Batches(nil, *batchData0.Hash())
assert.NoError(t, err)
if expectBatch.NewStateRoot == batchData0.Batch.NewStateRoot {
return
}
// Import genesis batch.
l1ChainID, _ := l1Cli.ChainID(context.Background())
l1Auth, err := bind.NewKeyedTransactorWithChainID(bridgeApp.Config.L2Config.RelayerConfig.GasOracleSenderPrivateKeys[0], l1ChainID)
assert.NoError(t, err)
tx, err := scrollChain.ImportGenesisBatch(l1Auth, translateBatch(batchData0))
assert.NoError(t, err)
receipt, err := bind.WaitMined(context.Background(), l1Cli, tx)
assert.NoError(t, err)
assert.Equal(t, types.ReceiptStatusSuccessful, receipt.Status)
// Make sure genesis batch is exist.
expectBatch, err = scrollChain.Batches(nil, *batchData0.Hash())
assert.NoError(t, err)
assert.Equal(t, batchData0.Batch.NewStateRoot, common.BytesToHash(expectBatch.NewStateRoot[:]))
}
func testETHDeposit(t *testing.T) {
l1Cli, err := base.L1Client()
assert.NoError(t, err)
l2Cli, err := base.L2Client()
assert.NoError(t, err)
l1ChainID, _ := l1Cli.ChainID(context.Background())
l1Auth, err := bind.NewKeyedTransactorWithChainID(bridgeApp.Config.L2Config.RelayerConfig.GasOracleSenderPrivateKeys[0], l1ChainID)
assert.NoError(t, err)
l1Auth.Value = ether
l1EthGateway, err := l1gateway.NewL1ETHGateway(base.L1Contracts.L1ETHGateway, l1Cli)
assert.NoError(t, err)
value := big.NewInt(100)
to := common.HexToAddress("0x7363726f6c6c6c02000000000000000000000007")
tx, err := l1EthGateway.DepositETH0(l1Auth, to, value, big.NewInt(10000))
assert.NoError(t, err)
receipt, err := bind.WaitMined(context.Background(), l1Cli, tx)
assert.NoError(t, err)
assert.Equal(t, types.ReceiptStatusSuccessful, receipt.Status)
db, err := database.NewOrmFactory(base.DBConfig)
assert.NoError(t, err)
l1MsgOrm := db.(orm.L1MessageOrm)
// l1 message wait result.
ok := utils.TryTimes(60, func() bool {
msgs, err := l1MsgOrm.GetL1MessagesByStatus(ctypes.MsgConfirmed, 1)
return err == nil && len(msgs) == 1
})
assert.True(t, ok)
// Check to address balance in l2 chain.
ok = utils.TryTimes(10, func() bool {
bls, err := l2Cli.BalanceAt(context.Background(), to, nil)
return err == nil && bls.Cmp(value) >= 0
})
assert.True(t, ok)
}
func testETHWithdraw(t *testing.T) {
l1Cli, err := base.L1Client()
assert.NoError(t, err)
l2Cli, err := base.L2Client()
assert.NoError(t, err)
l2EthGateway, err := l2gateway.NewL2ETHGateway(base.L2Contracts.L2ETHGateway, l2Cli)
assert.NoError(t, err)
l2ChainID, _ := l2Cli.ChainID(context.Background())
l2Auth, err := bind.NewKeyedTransactorWithChainID(bridgeApp.Config.L1Config.RelayerConfig.GasOracleSenderPrivateKeys[0], l2ChainID)
assert.NoError(t, err)
l2Auth.Value = ether
bls, err := l2Cli.BalanceAt(context.Background(), common.HexToAddress("0x7363726f6c6c6c02000000000000000000000007"), nil)
assert.NoError(t, err)
t.Log("balance in l2chain: ", bls.String())
to := common.HexToAddress("0x7363726f6c6c6c02000000000000000000000007")
value := big.NewInt(20)
tx, err := l2EthGateway.WithdrawETH(l2Auth, to, value, big.NewInt(1000000))
assert.NoError(t, err)
receipt, err := bind.WaitMined(context.Background(), l2Cli, tx)
assert.NoError(t, err)
assert.Equal(t, types.ReceiptStatusSuccessful, receipt.Status)
db, err := database.NewOrmFactory(base.DBConfig)
assert.NoError(t, err)
l2MsgOrm := db.(orm.L2MessageOrm)
// l1 message wait result.
ok := utils.TryTimes(80, func() bool {
msgs, err := l2MsgOrm.GetL2Messages(map[string]interface{}{"status": ctypes.MsgConfirmed}, "ORDER BY nonce", "LIMIT 1")
return err == nil && len(msgs) == 1
})
assert.True(t, ok)
// Check to address balance in l2 chain.
bls, err = l1Cli.BalanceAt(context.Background(), to, nil)
assert.NoError(t, err)
assert.True(t, bls.Cmp(value) >= 0)
}
func translateBatch(batchData *ctypes.BatchData) rollup.IScrollChainBatch {
batch := batchData.Batch
iBatchData := rollup.IScrollChainBatch{
Blocks: make([]rollup.IScrollChainBlockContext, len(batch.Blocks)),
PrevStateRoot: batch.PrevStateRoot,
NewStateRoot: batch.NewStateRoot,
WithdrawTrieRoot: batch.WithdrawTrieRoot,
BatchIndex: batch.BatchIndex,
ParentBatchHash: batch.ParentBatchHash,
L2Transactions: batch.L2Transactions,
}
for i, block0 := range batch.Blocks {
iBatchData.Blocks[i] = rollup.IScrollChainBlockContext{
BlockHash: block0.BlockHash,
ParentHash: block0.ParentHash,
BlockNumber: block0.BlockNumber,
Timestamp: block0.Timestamp,
BaseFee: block0.BaseFee,
GasLimit: block0.GasLimit,
NumTransactions: block0.NumTransactions,
NumL1Messages: block0.NumL1Messages,
}
}
return iBatchData
}

View File

@@ -34,10 +34,6 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR
github.com/scroll-tech/go-ethereum v1.10.14-0.20230508165858-27a3830afa61 h1:4fslVpNOPLeJPYX3tivrVWgqNvChPs7/y9OqWvQSNCw=
github.com/scroll-tech/zktrie v0.5.3 h1:jjzQchGU6XPL5s1C5bwwivSadefSRuYASE9OL7UKAdE=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
@@ -45,10 +41,6 @@ github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYm
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
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/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=