mirror of
https://github.com/scroll-tech/scroll.git
synced 2026-01-13 07:57:58 -05:00
Compare commits
25 Commits
feat/integ
...
manager_ap
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50305f3039 | ||
|
|
8d667f9353 | ||
|
|
dfc9a44743 | ||
|
|
08c49d9b2c | ||
|
|
ecb3f5a043 | ||
|
|
57a058c516 | ||
|
|
55612a0dbb | ||
|
|
7d9e111e9c | ||
|
|
25e43462c6 | ||
|
|
74e0960dc5 | ||
|
|
a8b2706752 | ||
|
|
76cfb97f99 | ||
|
|
d9ae117548 | ||
|
|
de2669da2b | ||
|
|
6880dd83da | ||
|
|
9d6e53a120 | ||
|
|
0940788143 | ||
|
|
ad46a85a2d | ||
|
|
9d29a95675 | ||
|
|
b1d7654970 | ||
|
|
a6164046e1 | ||
|
|
119e62d4b1 | ||
|
|
16e0cbf542 | ||
|
|
bfaf2fd0e2 | ||
|
|
ea227b5c85 |
8
.github/workflows/bridge.yml
vendored
8
.github/workflows/bridge.yml
vendored
@@ -103,7 +103,13 @@ jobs:
|
||||
- name: Test bridge packages
|
||||
working-directory: 'bridge'
|
||||
run: |
|
||||
go test -v -race -gcflags="-l" -ldflags="-s=false" -covermode=atomic ./...
|
||||
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
|
||||
# docker-build:
|
||||
# if: github.event.pull_request.draft == false
|
||||
# runs-on: ubuntu-latest
|
||||
|
||||
6
.github/workflows/bridge_history_api.yml
vendored
6
.github/workflows/bridge_history_api.yml
vendored
@@ -53,6 +53,12 @@ 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
|
||||
|
||||
14
.github/workflows/common.yml
vendored
14
.github/workflows/common.yml
vendored
@@ -30,12 +30,6 @@ 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:
|
||||
@@ -100,4 +94,10 @@ jobs:
|
||||
- name: Test common packages
|
||||
working-directory: 'common'
|
||||
run: |
|
||||
go test -v -race -gcflags="-l" -ldflags="-s=false" -covermode=atomic ./...
|
||||
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
|
||||
|
||||
9
.github/workflows/coordinator.yml
vendored
9
.github/workflows/coordinator.yml
vendored
@@ -110,4 +110,11 @@ jobs:
|
||||
- name: Test coordinator packages
|
||||
working-directory: 'coordinator'
|
||||
run: |
|
||||
go test -v -race -gcflags="-l" -ldflags="-s=false" -covermode=atomic -tags mock_verifier ./...
|
||||
# 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
|
||||
|
||||
8
.github/workflows/database.yml
vendored
8
.github/workflows/database.yml
vendored
@@ -87,4 +87,10 @@ jobs:
|
||||
- name: Test database packages
|
||||
working-directory: 'database'
|
||||
run: |
|
||||
go test -v -race -gcflags="-l" -ldflags="-s=false" -covermode=atomic ./...
|
||||
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
|
||||
|
||||
2
.github/workflows/integration.yaml
vendored
2
.github/workflows/integration.yaml
vendored
@@ -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 scroll-tech/integration-test/...
|
||||
go test -v -tags="mock_prover mock_verifier" -p 1 -coverprofile=coverage.txt scroll-tech/integration-test/...
|
||||
|
||||
8
.github/workflows/roller.yml
vendored
8
.github/workflows/roller.yml
vendored
@@ -47,7 +47,13 @@ jobs:
|
||||
- name: Test
|
||||
run: |
|
||||
make roller
|
||||
go test -tags="mock_prover" -v ./...
|
||||
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
|
||||
check:
|
||||
if: github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
100
Jenkinsfile
vendored
100
Jenkinsfile
vendored
@@ -1,100 +0,0 @@
|
||||
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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
# Scroll Monorepo
|
||||
|
||||
[](https://codecov.io/gh/scroll-tech/scroll)
|
||||
|
||||
## Prerequisites
|
||||
+ Go 1.19
|
||||
+ Rust (for version, see [rust-toolchain](./common/libzkp/impl/rust-toolchain))
|
||||
|
||||
@@ -325,3 +325,26 @@ 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
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ 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"
|
||||
@@ -60,6 +61,11 @@ 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)
|
||||
@@ -72,6 +78,7 @@ 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)
|
||||
|
||||
@@ -2,28 +2,28 @@
|
||||
"l1": {
|
||||
"confirmation": 64,
|
||||
"endpoint": "https://rpc.ankr.com/eth_goerli",
|
||||
"startHeight": 9890194 ,
|
||||
"startHeight": 9090194 ,
|
||||
"blockTime": 10,
|
||||
"MessengerAddr": "0x5260e38080BFe97e6C4925d9209eCc5f964373b6",
|
||||
"ETHGatewayAddr": "0x429b73A21cF3BF1f3E696a21A95408161daF311f",
|
||||
"WETHGatewayAddr": "0x8be69E499D8848DfFb4cF9bac909f3e2cF2FeFa0",
|
||||
"StandardERC20Gateway": "0xeF37207c1A1efF6D6a9d7BfF3cF4270e406d319b",
|
||||
"CustomERC20GatewayAddr": "0x920f906B814597cF5DC76F95100F09CBAF9c5748",
|
||||
"ERC721GatewayAddr": "0x1C441Dfc5C2eD7A2AA8636748A664E59CB029157",
|
||||
"ERC1155GatewayAddr": "0xd1bE599aaCBC21448fD6373bbc7c1b4c7806f135"
|
||||
"MessengerAddr": "0x326517Eb8eB1Ce5eaB5b513C2e9A24839b402d90",
|
||||
"ETHGatewayAddr": "0x8305cB7B8448677736095965B63d7431017328fe",
|
||||
"WETHGatewayAddr": "0xe3bA3c60d99a2d9a5f817734bC85353470b23931",
|
||||
"StandardERC20Gateway": "0x16c1079B27eD9c363B7D08aC5Ae937A398972A5C",
|
||||
"CustomERC20GatewayAddr": "0x61f08caD3d6F77801167d3bA8669433701586643",
|
||||
"ERC721GatewayAddr": "0x4A73D25A4C99CB912acaf6C5B5e554f2982201c5",
|
||||
"ERC1155GatewayAddr": "0xa3F5DD3033698c2832C53f3C3Fe6E062F58cD808"
|
||||
},
|
||||
"l2": {
|
||||
"confirmation": 1,
|
||||
"endpoint": "https://alpha-rpc.scroll.io/l2",
|
||||
"endpoint": "http://staging-l2geth-rpc0.scroll.tech:8545",
|
||||
"blockTime": 3,
|
||||
"startHeight": 1900068,
|
||||
"CustomERC20GatewayAddr": "0xa07Cb742657294C339fB4d5d6CdF3fdBeE8C1c68",
|
||||
"ERC721GatewayAddr": "0x8Fee20e0C0Ef16f2898a8073531a857D11b9C700",
|
||||
"StandardERC20Gateway": "0xB878F37BB278bf0e4974856fFe86f5e6F66BD725",
|
||||
"MessengerAddr": "0xb75d7e84517e1504C151B270255B087Fd746D34C",
|
||||
"ETHGatewayAddr": "0x32139B5C8838E94fFcD83E60dff95Daa7F0bA14c",
|
||||
"WETHGatewayAddr": "0xBb88bF582F2BBa46702621dae5CB9271057bC85b",
|
||||
"ERC1155GatewayAddr": "0x2946cB860028276b3C4bccE1767841641C2E0828"
|
||||
"startHeight": 0,
|
||||
"CustomERC20GatewayAddr": "0x905db21f836749fEeD12de781afc4A5Ab4Dd0d51",
|
||||
"ERC721GatewayAddr": "0xC53D835514780664BCd7eCfcE7c2E5d9554dc41B",
|
||||
"StandardERC20Gateway": "0x90271634BCB020e06ea4840C3f7aa61b8F860651",
|
||||
"MessengerAddr": "0xE8b0956Ac75c65Aa1669e83888DA13afF2E108f4",
|
||||
"ETHGatewayAddr": "0xD5938590D5dD8ce95812D4D515a219C12C551D67",
|
||||
"WETHGatewayAddr": "0xb0aaA582564fade4232a16fdB1383004A6A7247F",
|
||||
"ERC1155GatewayAddr": "0x4f33B1655619c2C0B7C450128Df760B4365Cb549"
|
||||
},
|
||||
"db": {
|
||||
"dsn": "postgres://postgres:1234@localhost:5444/test?sslmode=disable",
|
||||
|
||||
@@ -16,7 +16,7 @@ type QueryHashController struct {
|
||||
}
|
||||
|
||||
func (c *QueryAddressController) Get(req model.QueryByAddressRequest) (*model.QueryByAddressResponse, error) {
|
||||
message, err := c.Service.GetTxsByAddress(common.HexToAddress(req.Address), int64(req.Offset), int64(req.Limit))
|
||||
message, total, 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: len(message),
|
||||
Total: total,
|
||||
}}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -49,20 +49,20 @@ CREATE TRIGGER update_timestamp BEFORE UPDATE
|
||||
ON cross_message FOR EACH ROW EXECUTE PROCEDURE
|
||||
update_timestamp();
|
||||
|
||||
CREATE OR REPLACE FUNCTION delete_at_trigger()
|
||||
CREATE OR REPLACE FUNCTION deleted_at_trigger()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF NEW.is_deleted AND OLD.is_deleted != NEW.is_deleted THEN
|
||||
UPDATE cross_message SET delete_at = NOW() WHERE id = NEW.id;
|
||||
UPDATE cross_message SET deleted_at = NOW() WHERE id = NEW.id;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER delete_at_trigger
|
||||
CREATE TRIGGER deleted_at_trigger
|
||||
AFTER UPDATE ON cross_message
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION delete_at_trigger();
|
||||
EXECUTE FUNCTION deleted_at_trigger();
|
||||
|
||||
|
||||
-- +goose StatementEnd
|
||||
|
||||
@@ -31,20 +31,20 @@ CREATE TRIGGER update_timestamp BEFORE UPDATE
|
||||
ON relayed_msg FOR EACH ROW EXECUTE PROCEDURE
|
||||
update_timestamp();
|
||||
|
||||
CREATE OR REPLACE FUNCTION delete_at_trigger()
|
||||
CREATE OR REPLACE FUNCTION deleted_at_trigger()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF NEW.is_deleted AND OLD.is_deleted != NEW.is_deleted THEN
|
||||
UPDATE relayed_msg SET delete_at = NOW() WHERE id = NEW.id;
|
||||
UPDATE relayed_msg SET deleted_at = NOW() WHERE id = NEW.id;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER delete_at_trigger
|
||||
CREATE TRIGGER deleted_at_trigger
|
||||
AFTER UPDATE ON relayed_msg
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION delete_at_trigger();
|
||||
EXECUTE FUNCTION deleted_at_trigger();
|
||||
|
||||
|
||||
-- +goose StatementEnd
|
||||
|
||||
@@ -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 l2_cross_message WHERE layer2_hash = $1 AND NOT is_deleted;`, l2Hash.String())
|
||||
row := l.db.QueryRowx(`SELECT * FROM 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
|
||||
|
||||
@@ -16,6 +16,7 @@ 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)
|
||||
@@ -59,6 +60,15 @@ 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
|
||||
|
||||
@@ -4,6 +4,7 @@ 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
|
||||
|
||||
@@ -242,6 +242,8 @@ 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=
|
||||
|
||||
@@ -4,7 +4,7 @@ import "bridge-history-api/service"
|
||||
|
||||
type Data struct {
|
||||
Result []*service.TxHistoryInfo `json:"result"`
|
||||
Total int `json:"total"`
|
||||
Total uint64 `json:"total"`
|
||||
}
|
||||
|
||||
type QueryByAddressResponse struct {
|
||||
|
||||
@@ -32,7 +32,7 @@ type TxHistoryInfo struct {
|
||||
|
||||
// HistoryService example service.
|
||||
type HistoryService interface {
|
||||
GetTxsByAddress(address common.Address, offset int64, limit int64) ([]*TxHistoryInfo, error)
|
||||
GetTxsByAddress(address common.Address, offset int64, limit int64) ([]*TxHistoryInfo, uint64, error)
|
||||
GetTxsByHashes(hashes []string) ([]*TxHistoryInfo, error)
|
||||
}
|
||||
|
||||
@@ -69,15 +69,19 @@ func updateCrossTxHash(msgHash string, txInfo *TxHistoryInfo, db db.OrmFactory)
|
||||
|
||||
}
|
||||
|
||||
func (h *historyBackend) GetTxsByAddress(address common.Address, offset int64, limit int64) ([]*TxHistoryInfo, error) {
|
||||
txHistories := make([]*TxHistoryInfo, 0)
|
||||
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
|
||||
}
|
||||
result, err := h.db.GetCrossMsgsByAddressWithOffset(address.String(), offset, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, 0, err
|
||||
}
|
||||
for _, msg := range result {
|
||||
txHistory := &TxHistoryInfo{
|
||||
Hash: msg.MsgHash,
|
||||
Hash: msg.Layer1Hash + msg.Layer2Hash,
|
||||
Amount: msg.Amount,
|
||||
To: msg.Target,
|
||||
IsL1: msg.MsgType == int(orm.Layer1Msg),
|
||||
@@ -91,7 +95,7 @@ func (h *historyBackend) GetTxsByAddress(address common.Address, offset int64, l
|
||||
updateCrossTxHash(msg.MsgHash, txHistory, h.db)
|
||||
txHistories = append(txHistories, txHistory)
|
||||
}
|
||||
return txHistories, nil
|
||||
return txHistories, total, nil
|
||||
}
|
||||
|
||||
func (h *historyBackend) GetTxsByHashes(hashes []string) ([]*TxHistoryInfo, error) {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,7 +1,9 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
@@ -59,3 +61,48 @@ 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
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package utils_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@@ -18,3 +19,30 @@ 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))
|
||||
}
|
||||
|
||||
@@ -87,39 +87,11 @@ func (b *MockApp) MockConfig(store bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
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.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()
|
||||
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 {
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,85 +0,0 @@
|
||||
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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
38
codecov.yml
Normal file
38
codecov.yml
Normal file
@@ -0,0 +1,38 @@
|
||||
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%
|
||||
2
common/bytecode/.gitignore
vendored
2
common/bytecode/.gitignore
vendored
@@ -1,2 +1,2 @@
|
||||
*.go
|
||||
*.json
|
||||
*.sol
|
||||
@@ -1,54 +1,9 @@
|
||||
.PHONY: all erc20 greeter scroll scroll_sol clean
|
||||
.PHONY: all erc20 greeter
|
||||
|
||||
all: erc20 greeter scroll
|
||||
all: erc20 greeter
|
||||
|
||||
erc20:
|
||||
go run github.com/scroll-tech/go-ethereum/cmd/abigen@develop --sol erc20/ERC20Mock.sol --pkg erc20 --out erc20/ERC20Mock.go --contract ERC20Mock
|
||||
go run github.com/scroll-tech/go-ethereum/cmd/abigen --combined-json ./erc20/ERC20Mock.json --pkg erc20 --out ./erc20/ERC20Mock.go
|
||||
|
||||
greeter:
|
||||
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
|
||||
go run github.com/scroll-tech/go-ethereum/cmd/abigen --combined-json ./greeter/Greeter.json --pkg greeter --out ./greeter/Greeter.go
|
||||
|
||||
387
common/bytecode/erc20/ERC20Mock.json
Normal file
387
common/bytecode/erc20/ERC20Mock.json
Normal file
@@ -0,0 +1,387 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
@@ -1,572 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
72
common/bytecode/greeter/Greeter.json
Normal file
72
common/bytecode/greeter/Greeter.json
Normal file
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
// 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
@@ -1,782 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -1,656 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -1,597 +0,0 @@
|
||||
// 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
@@ -1,768 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -1,518 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -1,334 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
@@ -1,301 +0,0 @@
|
||||
// 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) {}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
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"`
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package docker
|
||||
import (
|
||||
"crypto/rand"
|
||||
"database/sql"
|
||||
_ "embed" //nolint:golint
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
@@ -26,9 +25,6 @@ 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
|
||||
@@ -47,9 +43,6 @@ type App struct {
|
||||
DBConfig *database.DBConfig
|
||||
DBConfigFile string
|
||||
|
||||
// pre deployed contracts' addresses.
|
||||
ContractsList
|
||||
|
||||
// common time stamp.
|
||||
Timestamp int
|
||||
}
|
||||
@@ -64,11 +57,6 @@ 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)
|
||||
}
|
||||
@@ -193,17 +181,15 @@ func (b *App) mockDBConfig() error {
|
||||
|
||||
func newTestL1Docker() GethImgInstance {
|
||||
id, _ := rand.Int(rand.Reader, big.NewInt(2000))
|
||||
startPort := l1StartPort + int(id.Int64())
|
||||
return NewImgGeth("scroll_l1geth", "", "", startPort, startPort+1)
|
||||
return NewImgGeth("scroll_l1geth", "", "", 0, l1StartPort+int(id.Int64()))
|
||||
}
|
||||
|
||||
func newTestL2Docker() GethImgInstance {
|
||||
id, _ := rand.Int(rand.Reader, big.NewInt(2000))
|
||||
startPort := l2StartPort + int(id.Int64())
|
||||
return NewImgGeth("scroll_l2geth", "", "", startPort, startPort+1)
|
||||
return NewImgGeth("scroll_l2geth", "", "", 0, l2StartPort+int(id.Int64()))
|
||||
}
|
||||
|
||||
func newTestDBDocker(driverName string) ImgInstance {
|
||||
id, _ := rand.Int(rand.Reader, big.NewInt(2000))
|
||||
return NewImgDB(driverName, "postgres", "123456", "test_db", dbStartPort+int(id.Int64()))
|
||||
return NewImgDB(driverName, "123456", "test_db", dbStartPort+int(id.Int64()))
|
||||
}
|
||||
|
||||
@@ -18,22 +18,20 @@ 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, userName, password, dbName string, port int) ImgInstance {
|
||||
func NewImgDB(image, password, dbName string, port int) ImgInstance {
|
||||
img := &ImgDB{
|
||||
image: image,
|
||||
userName: userName,
|
||||
password: password,
|
||||
name: fmt.Sprintf("%s-%s_%d", image, dbName, port),
|
||||
password: password,
|
||||
dbName: dbName,
|
||||
port: port,
|
||||
}
|
||||
@@ -77,7 +75,7 @@ func (i *ImgDB) Stop() error {
|
||||
|
||||
// Endpoint return the dsn.
|
||||
func (i *ImgDB) Endpoint() string {
|
||||
return fmt.Sprintf("postgres://%s:%s@localhost:%d/%s?sslmode=disable", i.userName, i.password, i.port, i.dbName)
|
||||
return fmt.Sprintf("postgres://postgres:%s@localhost:%d/%s?sslmode=disable", 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
3710
common/testdata/blockTrace_05.json
vendored
Normal file
3710
common/testdata/blockTrace_05.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3678
common/testdata/blockTrace_06.json
vendored
Normal file
3678
common/testdata/blockTrace_06.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3662
common/testdata/blockTrace_07.json
vendored
Normal file
3662
common/testdata/blockTrace_07.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -135,6 +135,7 @@ 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,
|
||||
@@ -207,18 +208,13 @@ 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: baseFee,
|
||||
BaseFee: header.BaseFee,
|
||||
GasLimit: header.GasLimit,
|
||||
NumTransactions: 0,
|
||||
NumL1Messages: 0,
|
||||
|
||||
@@ -2,6 +2,7 @@ package types
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
@@ -17,46 +18,99 @@ type BatchHeader struct {
|
||||
totalL1MessagePopped uint64
|
||||
dataHash common.Hash
|
||||
parentBatchHash common.Hash
|
||||
skippedL1MessageBitmap []*big.Int // LSB is the first L1 message
|
||||
skippedL1MessageBitmap []byte
|
||||
}
|
||||
|
||||
// NewBatchHeader creates a new BatchHeader
|
||||
func NewBatchHeader(version uint8, batchIndex, totalL1MessagePoppedBefore uint64, parentBatchHash common.Hash, chunks []*Chunk) (*BatchHeader, error) {
|
||||
// TODO calculate `l1MessagePopped`, `totalL1MessagePopped`, and `skippedL1MessageBitmap` based on `chunks`
|
||||
// buffer for storing chunk hashes in order to compute the batch data hash
|
||||
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 dataHash
|
||||
chunkBytes, err := chunk.Hash()
|
||||
// build data hash
|
||||
totalL1MessagePoppedBeforeChunk := nextIndex
|
||||
chunkBytes, err := chunk.Hash(totalL1MessagePoppedBeforeChunk)
|
||||
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: 0, // TODO
|
||||
totalL1MessagePopped: totalL1MessagePoppedBefore, // TODO
|
||||
l1MessagePopped: nextIndex - totalL1MessagePoppedBefore,
|
||||
totalL1MessagePopped: nextIndex,
|
||||
dataHash: dataHash,
|
||||
parentBatchHash: parentBatchHash,
|
||||
skippedL1MessageBitmap: nil, // TODO
|
||||
skippedL1MessageBitmap: bitmapBytes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Encode encodes the BatchHeader into RollupV2 BatchHeaderV0Codec Encoding.
|
||||
func (b *BatchHeader) Encode() []byte {
|
||||
batchBytes := make([]byte, 89)
|
||||
batchBytes := make([]byte, 89+len(b.skippedL1MessageBitmap))
|
||||
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[:])
|
||||
// TODO: encode skippedL1MessageBitmap
|
||||
|
||||
copy(batchBytes[89:], b.skippedL1MessageBitmap[:])
|
||||
return batchBytes
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func TestNewBatchHeader(t *testing.T) {
|
||||
// Without L1 Msg
|
||||
templateBlockTrace, err := os.ReadFile("../testdata/blockTrace_02.json")
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -32,9 +33,100 @@ 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)
|
||||
|
||||
@@ -60,9 +152,28 @@ 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)
|
||||
|
||||
@@ -103,4 +214,21 @@ 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()))
|
||||
}
|
||||
|
||||
@@ -17,8 +17,25 @@ 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() ([]byte, error) {
|
||||
func (w *WrappedBlock) Encode(totalL1MessagePoppedBefore uint64) ([]byte, error) {
|
||||
bytes := make([]byte, 60)
|
||||
|
||||
if !w.Header.Number.IsUint64() {
|
||||
@@ -27,14 +44,10 @@ func (w *WrappedBlock) Encode() ([]byte, error) {
|
||||
if len(w.Transactions) > math.MaxUint16 {
|
||||
return nil, errors.New("number of transactions 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++
|
||||
}
|
||||
|
||||
numL1Messages := w.NumL1Messages(totalL1MessagePoppedBefore)
|
||||
if numL1Messages > math.MaxUint16 {
|
||||
return nil, errors.New("number of L1 messages exceeds max uint16")
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint64(bytes[0:], w.Header.Number.Uint64())
|
||||
@@ -42,7 +55,7 @@ func (w *WrappedBlock) Encode() ([]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:], numL1Messages)
|
||||
binary.BigEndian.PutUint16(bytes[58:], uint16(numL1Messages))
|
||||
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
@@ -17,8 +17,21 @@ 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() ([]byte, error) {
|
||||
func (c *Chunk) Encode(totalL1MessagePoppedBefore uint64) ([]byte, error) {
|
||||
numBlocks := len(c.Blocks)
|
||||
|
||||
if numBlocks > 255 {
|
||||
@@ -34,10 +47,11 @@ func (c *Chunk) Encode() ([]byte, error) {
|
||||
var l2TxDataBytes []byte
|
||||
|
||||
for _, block := range c.Blocks {
|
||||
blockBytes, err := block.Encode()
|
||||
blockBytes, err := block.Encode(totalL1MessagePoppedBefore)
|
||||
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))
|
||||
@@ -77,8 +91,8 @@ func (c *Chunk) Encode() ([]byte, error) {
|
||||
}
|
||||
|
||||
// Hash hashes the Chunk into RollupV2 Chunk Hash
|
||||
func (c *Chunk) Hash() ([]byte, error) {
|
||||
chunkBytes, err := c.Encode()
|
||||
func (c *Chunk) Hash(totalL1MessagePoppedBefore uint64) ([]byte, error) {
|
||||
chunkBytes, err := c.Encode(totalL1MessagePoppedBefore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ func TestChunkEncode(t *testing.T) {
|
||||
chunk := &Chunk{
|
||||
Blocks: []*WrappedBlock{},
|
||||
}
|
||||
bytes, err := chunk.Encode()
|
||||
bytes, err := chunk.Encode(0)
|
||||
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()
|
||||
bytes, err = chunk.Encode(0)
|
||||
assert.Nil(t, bytes)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "number of blocks exceeds 1 byte")
|
||||
@@ -37,12 +37,13 @@ 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()
|
||||
bytes, err = chunk.Encode(0)
|
||||
hexString := hex.EncodeToString(bytes)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 299, len(bytes))
|
||||
@@ -54,16 +55,17 @@ 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()
|
||||
bytes, err = chunk.Encode(0)
|
||||
hexString = hex.EncodeToString(bytes)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 97, len(bytes))
|
||||
assert.Equal(t, "01000000000000000d00000000646b6e13000000000000000000000000000000000000000000000000000000000000000000000000007a12000002000100000020df0b80825dc0941a258d17bf244c4df02d40343a7626a9d321e1058080808080", hexString)
|
||||
assert.Equal(t, "01000000000000000d00000000646b6e13000000000000000000000000000000000000000000000000000000000000000000000000007a12000002000b00000020df0b80825dc0941a258d17bf244c4df02d40343a7626a9d321e1058080808080", hexString)
|
||||
|
||||
// Test case 5: when the chunk contains two blocks each with 1 L1MsgTx
|
||||
chunk = &Chunk{
|
||||
@@ -72,11 +74,11 @@ func TestChunkEncode(t *testing.T) {
|
||||
wrappedBlock2,
|
||||
},
|
||||
}
|
||||
bytes, err = chunk.Encode()
|
||||
bytes, err = chunk.Encode(0)
|
||||
hexString = hex.EncodeToString(bytes)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 193, len(bytes))
|
||||
assert.Equal(t, "02000000000000000d00000000646b6e13000000000000000000000000000000000000000000000000000000000000000000000000007a120000020001000000000000000d00000000646b6e13000000000000000000000000000000000000000000000000000000000000000000000000007a12000002000100000020df0b80825dc0941a258d17bf244c4df02d40343a7626a9d321e105808080808000000020df0b80825dc0941a258d17bf244c4df02d40343a7626a9d321e1058080808080", hexString)
|
||||
assert.Equal(t, "02000000000000000d00000000646b6e13000000000000000000000000000000000000000000000000000000000000000000000000007a12000002000b000000000000000d00000000646b6e13000000000000000000000000000000000000000000000000000000000000000000000000007a12000002000000000020df0b80825dc0941a258d17bf244c4df02d40343a7626a9d321e105808080808000000020df0b80825dc0941a258d17bf244c4df02d40343a7626a9d321e1058080808080", hexString)
|
||||
}
|
||||
|
||||
func TestChunkHash(t *testing.T) {
|
||||
@@ -84,7 +86,7 @@ func TestChunkHash(t *testing.T) {
|
||||
chunk := &Chunk{
|
||||
Blocks: []*WrappedBlock{},
|
||||
}
|
||||
bytes, err := chunk.Hash()
|
||||
bytes, err := chunk.Hash(0)
|
||||
assert.Nil(t, bytes)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "number of blocks is 0")
|
||||
@@ -99,7 +101,7 @@ func TestChunkHash(t *testing.T) {
|
||||
wrappedBlock,
|
||||
},
|
||||
}
|
||||
bytes, err = chunk.Hash()
|
||||
bytes, err = chunk.Hash(0)
|
||||
hexString := hex.EncodeToString(bytes)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "78c839dfc494396c16b40946f32b3f4c3e8c2d4bfd04aefcf235edec474482f8", hexString)
|
||||
@@ -115,7 +117,7 @@ func TestChunkHash(t *testing.T) {
|
||||
wrappedBlock1,
|
||||
},
|
||||
}
|
||||
bytes, err = chunk.Hash()
|
||||
bytes, err = chunk.Hash(0)
|
||||
hexString = hex.EncodeToString(bytes)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "aa9e494f72bc6965857856f0fae6916f27b2a6591c714a573b2fab46df03b8ae", hexString)
|
||||
@@ -131,7 +133,7 @@ func TestChunkHash(t *testing.T) {
|
||||
wrappedBlock2,
|
||||
},
|
||||
}
|
||||
bytes, err = chunk.Hash()
|
||||
bytes, err = chunk.Hash(0)
|
||||
hexString = hex.EncodeToString(bytes)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "42967825696a129e7a83f082097aca982747480956dcaa448c9296e795c9a91a", hexString)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
var tag = "v3.3.4"
|
||||
var tag = "v3.3.7"
|
||||
|
||||
var commit = func() string {
|
||||
if info, ok := debug.ReadBuildInfo(); ok {
|
||||
|
||||
@@ -63,28 +63,6 @@ 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
|
||||
|
||||
@@ -2,7 +2,16 @@
|
||||
/* eslint-disable node/no-missing-import */
|
||||
import { expect } from "chai";
|
||||
import { BigNumber, constants } from "ethers";
|
||||
import { concat, getAddress, hexlify, keccak256, randomBytes, RLP } from "ethers/lib/utils";
|
||||
import {
|
||||
concat,
|
||||
getAddress,
|
||||
hexlify,
|
||||
keccak256,
|
||||
randomBytes,
|
||||
RLP,
|
||||
stripZeros,
|
||||
TransactionTypes,
|
||||
} from "ethers/lib/utils";
|
||||
import { ethers } from "hardhat";
|
||||
import { L1MessageQueue, L2GasPriceOracle } from "../typechain";
|
||||
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
|
||||
@@ -94,8 +103,8 @@ describe("L1MessageQueue", async () => {
|
||||
|
||||
context("#computeTransactionHash", async () => {
|
||||
it("should succeed", async () => {
|
||||
const sender = hexlify(randomBytes(20));
|
||||
const target = hexlify(randomBytes(20));
|
||||
const sender = "0xb2a70fab1a45b1b9be443b6567849a1702bc1232";
|
||||
const target = "0xcb18150e4efefb6786130e289a5f61a82a5b86d7";
|
||||
const transactionType = "0x7E";
|
||||
|
||||
for (const nonce of [
|
||||
@@ -123,19 +132,30 @@ describe("L1MessageQueue", async () => {
|
||||
constants.MaxUint256,
|
||||
]) {
|
||||
for (const dataLen of [0, 1, 2, 3, 4, 55, 56, 100]) {
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,9 +30,6 @@ 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;
|
||||
|
||||
@@ -45,28 +42,6 @@ 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 *
|
||||
***************/
|
||||
@@ -162,9 +137,6 @@ 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
|
||||
|
||||
@@ -139,18 +139,27 @@ contract L1MessageQueue is OwnableUpgradeable, IL1MessageQueue {
|
||||
}
|
||||
}
|
||||
|
||||
function store_uint(_ptr, v) -> ptr {
|
||||
// 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 {
|
||||
ptr := _ptr
|
||||
switch lt(v, 128)
|
||||
case 1 {
|
||||
// single byte in the [0x00, 0x7f]
|
||||
mstore(ptr, shl(248, v))
|
||||
switch and(iszero(v), is_uint)
|
||||
case 1 {
|
||||
// integer 0
|
||||
mstore8(ptr, 0x80)
|
||||
}
|
||||
default {
|
||||
// single byte in the [0x00, 0x7f]
|
||||
mstore8(ptr, v)
|
||||
}
|
||||
ptr := add(ptr, 1)
|
||||
}
|
||||
default {
|
||||
// 1-32 bytes long
|
||||
let len := get_uint_bytes(v)
|
||||
mstore(ptr, shl(248, add(len, 0x80)))
|
||||
mstore8(ptr, add(len, 0x80))
|
||||
ptr := add(ptr, 1)
|
||||
mstore(ptr, shl(mul(8, sub(32, len)), v))
|
||||
ptr := add(ptr, len)
|
||||
@@ -160,7 +169,7 @@ contract L1MessageQueue is OwnableUpgradeable, IL1MessageQueue {
|
||||
function store_address(_ptr, v) -> ptr {
|
||||
ptr := _ptr
|
||||
// 20 bytes long
|
||||
mstore(ptr, shl(248, 0x94)) // 0x80 + 0x14
|
||||
mstore8(ptr, 0x94) // 0x80 + 0x14
|
||||
ptr := add(ptr, 1)
|
||||
mstore(ptr, shl(96, v))
|
||||
ptr := add(ptr, 0x14)
|
||||
@@ -170,21 +179,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(ptr, _queueIndex)
|
||||
ptr := store_uint(ptr, _gasLimit)
|
||||
ptr := store_uint_or_byte(ptr, _queueIndex, 1)
|
||||
ptr := store_uint_or_byte(ptr, _gasLimit, 1)
|
||||
ptr := store_address(ptr, _target)
|
||||
ptr := store_uint(ptr, _value)
|
||||
ptr := store_uint_or_byte(ptr, _value, 1)
|
||||
|
||||
switch eq(_data.length, 1)
|
||||
case 1 {
|
||||
// single byte
|
||||
ptr := store_uint(ptr, shr(248, calldataload(_data.offset)))
|
||||
ptr := store_uint_or_byte(ptr, byte(0, calldataload(_data.offset)), 0)
|
||||
}
|
||||
default {
|
||||
switch lt(_data.length, 56)
|
||||
case 1 {
|
||||
// a string is 0-55 bytes long
|
||||
mstore(ptr, shl(248, add(0x80, _data.length)))
|
||||
mstore8(ptr, add(0x80, _data.length))
|
||||
ptr := add(ptr, 1)
|
||||
calldatacopy(ptr, _data.offset, _data.length)
|
||||
ptr := add(ptr, _data.length)
|
||||
@@ -192,7 +201,7 @@ contract L1MessageQueue is OwnableUpgradeable, IL1MessageQueue {
|
||||
default {
|
||||
// a string is more than 55 bytes long
|
||||
let len_bytes := get_uint_bytes(_data.length)
|
||||
mstore(ptr, shl(248, add(0xb7, len_bytes)))
|
||||
mstore8(ptr, add(0xb7, len_bytes))
|
||||
ptr := add(ptr, 1)
|
||||
mstore(ptr, shl(mul(8, sub(32, len_bytes)), _data.length))
|
||||
ptr := add(ptr, len_bytes)
|
||||
|
||||
118
contracts/src/L1/rollup/MultipleVersionRollupVerifier.sol
Normal file
118
contracts/src/L1/rollup/MultipleVersionRollupVerifier.sol
Normal file
@@ -0,0 +1,118 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -130,12 +130,7 @@ contract ScrollChain is OwnableUpgradeable, IScrollChain {
|
||||
*****************************/
|
||||
|
||||
/// @notice Import layer 2 genesis block
|
||||
/// @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 {
|
||||
function importGenesisBatch(bytes calldata _batchHeader, bytes32 _stateRoot) external {
|
||||
// check genesis batch header length
|
||||
require(_stateRoot != bytes32(0), "zero state root");
|
||||
|
||||
@@ -157,10 +152,9 @@ contract ScrollChain is OwnableUpgradeable, IScrollChain {
|
||||
|
||||
committedBatches[0] = _batchHash;
|
||||
finalizedStateRoots[0] = _stateRoot;
|
||||
withdrawRoots[0] = _withdrawRoot;
|
||||
|
||||
emit CommitBatch(_batchHash);
|
||||
emit FinalizeBatch(_batchHash, _stateRoot, _withdrawRoot);
|
||||
emit FinalizeBatch(_batchHash, _stateRoot, bytes32(0));
|
||||
}
|
||||
|
||||
/// @inheritdoc IScrollChain
|
||||
@@ -314,7 +308,7 @@ contract ScrollChain is OwnableUpgradeable, IScrollChain {
|
||||
);
|
||||
|
||||
// verify batch
|
||||
IRollupVerifier(verifier).verifyAggregateProof(_aggrProof, _publicInputHash);
|
||||
IRollupVerifier(verifier).verifyAggregateProof(_batchIndex, _aggrProof, _publicInputHash);
|
||||
|
||||
// check and update lastFinalizedBatchIndex
|
||||
unchecked {
|
||||
|
||||
@@ -62,28 +62,6 @@ 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 *
|
||||
***************/
|
||||
@@ -126,14 +104,16 @@ contract L2ScrollMessenger is ScrollMessengerBase, PausableUpgradeable, IL2Scrol
|
||||
require(_expectedStateRoot != bytes32(0), "Block is not imported");
|
||||
|
||||
bytes32 _storageKey;
|
||||
// `mapping(bytes32 => bool) public isL1MessageSent` is the 105-nd slot of contract `L1ScrollMessenger`.
|
||||
// `mapping(bytes32 => bool) public isL1MessageSent` is the 155-th slot of contract `L1ScrollMessenger`.
|
||||
// + 1 from `Initializable`
|
||||
// + 50 from `OwnableUpgradeable`
|
||||
// + 50 from `ContextUpgradeable`
|
||||
// + 4 from `ScrollMessengerBase`
|
||||
// + 50 from `PausableUpgradeable`
|
||||
// + 2-nd in `L1ScrollMessenger`
|
||||
// + 1-st in `L1ScrollMessenger`
|
||||
assembly {
|
||||
mstore(0x00, _msgHash)
|
||||
mstore(0x20, 105)
|
||||
mstore(0x20, 155)
|
||||
_storageKey := keccak256(0x00, 0x40)
|
||||
}
|
||||
|
||||
@@ -161,14 +141,16 @@ contract L2ScrollMessenger is ScrollMessengerBase, PausableUpgradeable, IL2Scrol
|
||||
require(_expectedStateRoot != bytes32(0), "Block not imported");
|
||||
|
||||
bytes32 _storageKey;
|
||||
// `mapping(bytes32 => bool) public isL2MessageExecuted` is the 106-th slot of contract `L1ScrollMessenger`.
|
||||
// `mapping(bytes32 => bool) public isL2MessageExecuted` is the 156-th slot of contract `L1ScrollMessenger`.
|
||||
// + 1 from `Initializable`
|
||||
// + 50 from `OwnableUpgradeable`
|
||||
// + 50 from `ContextUpgradeable`
|
||||
// + 4 from `ScrollMessengerBase`
|
||||
// + 50 from `PausableUpgradeable`
|
||||
// + 3-rd in `L1ScrollMessenger`
|
||||
// + 2-nd in `L1ScrollMessenger`
|
||||
assembly {
|
||||
mstore(0x00, _msgHash)
|
||||
mstore(0x20, 106)
|
||||
mstore(0x20, 156)
|
||||
_storageKey := keccak256(0x00, 0x40)
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,8 @@ contract WETH9 {
|
||||
balanceOf[msg.sender] -= wad;
|
||||
}
|
||||
|
||||
payable(msg.sender).transfer(wad);
|
||||
(bool success, ) = msg.sender.call{value:wad}("");
|
||||
require(success, "withdraw ETH failed");
|
||||
|
||||
emit Withdrawal(msg.sender, wad);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,28 @@ 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 *
|
||||
***************/
|
||||
|
||||
@@ -4,7 +4,12 @@ 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(bytes calldata aggrProof, bytes32 publicInputHash) external view;
|
||||
function verifyAggregateProof(
|
||||
uint256 batchIndex,
|
||||
bytes calldata aggrProof,
|
||||
bytes32 publicInputHash
|
||||
) external view;
|
||||
}
|
||||
|
||||
10
contracts/src/libraries/verifier/IZkEvmVerifier.sol
Normal file
10
contracts/src/libraries/verifier/IZkEvmVerifier.sol
Normal file
@@ -0,0 +1,10 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -12,6 +12,10 @@ 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(
|
||||
@@ -44,6 +48,8 @@ abstract contract L1GatewayTestBase is DSTestPlus {
|
||||
EnforcedTxGateway internal enforcedTxGateway;
|
||||
ScrollChain internal rollup;
|
||||
|
||||
MockRollupVerifier internal verifier;
|
||||
|
||||
address internal feeVault;
|
||||
Whitelist private whitelist;
|
||||
|
||||
@@ -59,6 +65,7 @@ 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));
|
||||
@@ -74,7 +81,7 @@ abstract contract L1GatewayTestBase is DSTestPlus {
|
||||
);
|
||||
gasOracle.initialize(0, 0, 0, 0);
|
||||
gasOracle.updateWhitelist(address(whitelist));
|
||||
rollup.initialize(address(messageQueue), address(0), 44);
|
||||
rollup.initialize(address(messageQueue), address(verifier), 44);
|
||||
|
||||
address[] memory _accounts = new address[](1);
|
||||
_accounts[0] = address(this);
|
||||
@@ -82,11 +89,40 @@ abstract contract L1GatewayTestBase is DSTestPlus {
|
||||
}
|
||||
|
||||
function prepareL2MessageRoot(bytes32 messageHash) internal {
|
||||
bytes memory _batchHeader = new bytes(89);
|
||||
rollup.updateSequencer(address(this), true);
|
||||
rollup.updateProver(address(this), true);
|
||||
|
||||
// import genesis batch
|
||||
bytes memory batchHeader0 = new bytes(89);
|
||||
assembly {
|
||||
mstore(add(_batchHeader, add(0x20, 25)), 1)
|
||||
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
|
||||
}
|
||||
|
||||
rollup.importGenesisBatch(_batchHeader, bytes32(uint256(1)), messageHash);
|
||||
rollup.finalizeBatchWithProof(
|
||||
batchHeader1,
|
||||
bytes32(uint256(1)),
|
||||
bytes32(uint256(2)),
|
||||
messageHash,
|
||||
new bytes(0)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,81 +12,47 @@ import {Whitelist} from "../L2/predeploys/Whitelist.sol";
|
||||
import {IL1ScrollMessenger, L1ScrollMessenger} from "../L1/L1ScrollMessenger.sol";
|
||||
import {L2ScrollMessenger} from "../L2/L2ScrollMessenger.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;
|
||||
import {L1GatewayTestBase} from "./L1GatewayTestBase.t.sol";
|
||||
|
||||
contract L1ScrollMessengerTest is L1GatewayTestBase {
|
||||
function setUp() public {
|
||||
// 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);
|
||||
L1GatewayTestBase.setUpBase();
|
||||
}
|
||||
|
||||
function testForbidCallMessageQueueFromL2() external {
|
||||
// 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)
|
||||
bytes32 _xDomainCalldataHash = keccak256(
|
||||
abi.encodeWithSignature(
|
||||
"relayMessage(address,address,uint256,uint256,bytes)",
|
||||
address(this),
|
||||
address(messageQueue),
|
||||
0,
|
||||
0,
|
||||
new bytes(0)
|
||||
)
|
||||
);
|
||||
prepareL2MessageRoot(_xDomainCalldataHash);
|
||||
|
||||
IL1ScrollMessenger.L2MessageProof memory proof;
|
||||
proof.batchIndex = scrollChain.lastFinalizedBatchIndex();
|
||||
proof.batchIndex = rollup.lastFinalizedBatchIndex();
|
||||
|
||||
hevm.expectRevert("Forbid to call message queue");
|
||||
l1Messenger.relayMessageWithProof(address(this), address(l1MessageQueue), 0, 0, new bytes(0), proof);
|
||||
l1Messenger.relayMessageWithProof(address(this), address(messageQueue), 0, 0, new bytes(0), proof);
|
||||
}
|
||||
|
||||
function testForbidCallSelfFromL2() external {
|
||||
// import genesis batch
|
||||
bytes memory _batchHeader = new bytes(89);
|
||||
assembly {
|
||||
mstore(add(_batchHeader, 57), 1)
|
||||
}
|
||||
scrollChain.importGenesisBatch(
|
||||
_batchHeader,
|
||||
bytes32(uint256(1)),
|
||||
bytes32(0xf7c03e2b13c88e3fca1410b228b001dd94e3f5ab4b4a4a6981d09a4eb3e5b631)
|
||||
bytes32 _xDomainCalldataHash = keccak256(
|
||||
abi.encodeWithSignature(
|
||||
"relayMessage(address,address,uint256,uint256,bytes)",
|
||||
address(this),
|
||||
address(l1Messenger),
|
||||
0,
|
||||
0,
|
||||
new bytes(0)
|
||||
)
|
||||
);
|
||||
|
||||
prepareL2MessageRoot(_xDomainCalldataHash);
|
||||
IL1ScrollMessenger.L2MessageProof memory proof;
|
||||
proof.batchIndex = scrollChain.lastFinalizedBatchIndex();
|
||||
proof.batchIndex = rollup.lastFinalizedBatchIndex();
|
||||
|
||||
hevm.expectRevert("Forbid to call self");
|
||||
l1Messenger.relayMessageWithProof(address(this), address(l1Messenger), 0, 0, new bytes(0), proof);
|
||||
@@ -110,7 +76,9 @@ contract L1ScrollMessengerTest is DSTestPlus {
|
||||
|
||||
function testReplayMessage(uint256 exceedValue, address refundAddress) external {
|
||||
hevm.assume(refundAddress.code.length == 0);
|
||||
hevm.assume(uint256(uint160(refundAddress)) > 100); // ignore some precompile contracts
|
||||
hevm.assume(uint256(uint160(refundAddress)) > uint256(100)); // ignore some precompile contracts
|
||||
hevm.assume(refundAddress != feeVault);
|
||||
hevm.assume(refundAddress != address(0x000000000000000000636F6e736F6c652e6c6f67)); // ignore console/console2
|
||||
|
||||
exceedValue = bound(exceedValue, 1, address(this).balance / 2);
|
||||
|
||||
@@ -170,7 +138,7 @@ contract L1ScrollMessengerTest is DSTestPlus {
|
||||
l1Messenger.sendMessage{value: _fee + value}(address(0), value, hex"0011220033", gasLimit);
|
||||
|
||||
// update max gas limit
|
||||
l1MessageQueue.updateMaxGasLimit(gasLimit);
|
||||
messageQueue.updateMaxGasLimit(gasLimit);
|
||||
l1Messenger.sendMessage{value: _fee + value}(address(0), value, hex"0011220033", gasLimit);
|
||||
}
|
||||
}
|
||||
|
||||
105
contracts/src/test/MultipleVersionRollupVerifier.t.sol
Normal file
105
contracts/src/test/MultipleVersionRollupVerifier.t.sol
Normal file
@@ -0,0 +1,105 @@
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ contract ScrollChainTest is DSTestPlus {
|
||||
assembly {
|
||||
mstore(add(batchHeader0, add(0x20, 25)), 1)
|
||||
}
|
||||
rollup.importGenesisBatch(batchHeader0, bytes32(uint256(1)), bytes32(uint256(0)));
|
||||
rollup.importGenesisBatch(batchHeader0, bytes32(uint256(1)));
|
||||
|
||||
// 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)), bytes32(uint256(0)));
|
||||
rollup.importGenesisBatch(batchHeader0, bytes32(uint256(1)));
|
||||
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)), bytes32(uint256(0)));
|
||||
rollup.importGenesisBatch(batchHeader0, bytes32(uint256(1)));
|
||||
bytes32 batchHash0 = rollup.committedBatches(0);
|
||||
|
||||
bytes memory bitmap;
|
||||
@@ -243,28 +243,28 @@ contract ScrollChainTest is DSTestPlus {
|
||||
// 0000000000000000000000000000000000000000000000000000000000000000
|
||||
// 0000000000000000
|
||||
// 0001
|
||||
// 50c3caa727394b95dc4885b7d25033ed22ac772b985fb274f2a7c0699a11346d
|
||||
// a2277fd30bbbe74323309023b56035b376d7768ad237ae4fc46ead7dc9591ae1
|
||||
// => data hash for chunk0
|
||||
// bb88f47194a07d59ed17bc9b2015f83d0afea8f7892d9c5f0b6565563bf06b26
|
||||
// 9ef1e5694bdb014a1eea42be756a8f63bfd8781d6332e9ef3b5126d90c62f110
|
||||
// => data hash for all chunks
|
||||
// 038433daac85a0b03cd443ed50bc85e832c883061651ae2182b2984751e0b340
|
||||
// d9cb6bf9264006fcea490d5c261f7453ab95b1b26033a3805996791b8e3a62f3
|
||||
// => payload for batch header
|
||||
// 00
|
||||
// 0000000000000002
|
||||
// 0000000000000001
|
||||
// 0000000000000001
|
||||
// 038433daac85a0b03cd443ed50bc85e832c883061651ae2182b2984751e0b340
|
||||
// 0000000000000001
|
||||
// d9cb6bf9264006fcea490d5c261f7453ab95b1b26033a3805996791b8e3a62f3
|
||||
// 119b828c2a2798d2c957228ebeaff7e10bb099ae0d4e224f3eeb779ff61cba61
|
||||
// 0000000000000000000000000000000000000000000000000000000000000000
|
||||
// => hash for batch header
|
||||
// cef70bf80683c4d9b8b2813e90c314e8c56648e231300b8cfed9d666b0caf14e
|
||||
// 00847173b29b238cf319cde79512b7c213e5a8b4138daa7051914c4592b6dfc7
|
||||
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)), 0x038433daac85a0b03cd443ed50bc85e832c883061651ae2182b2984751e0b340) // dataHash
|
||||
mstore(add(batchHeader1, add(0x20, 25)), 0xd9cb6bf9264006fcea490d5c261f7453ab95b1b26033a3805996791b8e3a62f3) // 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(0xcef70bf80683c4d9b8b2813e90c314e8c56648e231300b8cfed9d666b0caf14e));
|
||||
assertEq(batchHash1, bytes32(0x00847173b29b238cf319cde79512b7c213e5a8b4138daa7051914c4592b6dfc7));
|
||||
|
||||
// finalize batch1
|
||||
rollup.finalizeBatchWithProof(
|
||||
@@ -330,26 +330,26 @@ contract ScrollChainTest is DSTestPlus {
|
||||
// 012c
|
||||
// ... (some tx hashes)
|
||||
// => data hash for chunk2
|
||||
// 5c91563ee8be18cb94accfc83728f883ff5e3aa600fd0799e0a4e39afc7970b9
|
||||
// 0520f1fbe159af97fdf1d6cfcfe7605f99f7bfe3ed876e87b64250b1810df00b
|
||||
// => data hash for all chunks
|
||||
// bf38f308e0a87ed7bf92fa2da038fa1d59a7b9801eb0f6d487f8eef528632145
|
||||
// f52343299f6379fd15b20b23d51fc61b9b357b124be112686626b6278bcffa83
|
||||
// => payload for batch header
|
||||
// 00
|
||||
// 0000000000000002
|
||||
// 0000000000000108
|
||||
// 0000000000000109
|
||||
// bf38f308e0a87ed7bf92fa2da038fa1d59a7b9801eb0f6d487f8eef528632145
|
||||
// f52343299f6379fd15b20b23d51fc61b9b357b124be112686626b6278bcffa83
|
||||
// cef70bf80683c4d9b8b2813e90c314e8c56648e231300b8cfed9d666b0caf14e
|
||||
// aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa800000000000000000000000000000000000000000000000000000000000000aa
|
||||
// => hash for batch header
|
||||
// 17fe6c12739f3a6261ae6db6486f41758dbd5d0508f19a5ca9ac37df67bbfec2
|
||||
// 2231cf185a5c07931584f970738b4cd2ae4fb39e2d90853b26746c7616ea71b9
|
||||
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)), 0xbf38f308e0a87ed7bf92fa2da038fa1d59a7b9801eb0f6d487f8eef528632145) // dataHash
|
||||
mstore(add(batchHeader2, add(0x20, 25)), 0xf52343299f6379fd15b20b23d51fc61b9b357b124be112686626b6278bcffa83) // 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(0x17fe6c12739f3a6261ae6db6486f41758dbd5d0508f19a5ca9ac37df67bbfec2));
|
||||
assertEq(batchHash2, bytes32(0x2231cf185a5c07931584f970738b4cd2ae4fb39e2d90853b26746c7616ea71b9));
|
||||
|
||||
// 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)), bytes32(uint256(0)));
|
||||
rollup.importGenesisBatch(batchHeader0, bytes32(uint256(1)));
|
||||
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), bytes32(0));
|
||||
rollup.importGenesisBatch(batchHeader, 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)), bytes32(0));
|
||||
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)));
|
||||
|
||||
// wrong bitmap length, revert
|
||||
batchHeader = new bytes(90);
|
||||
hevm.expectRevert("wrong bitmap length");
|
||||
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)), bytes32(0));
|
||||
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)));
|
||||
|
||||
// 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)), bytes32(0));
|
||||
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)));
|
||||
|
||||
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)), bytes32(0));
|
||||
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)));
|
||||
|
||||
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)), bytes32(0));
|
||||
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)));
|
||||
|
||||
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)), bytes32(0));
|
||||
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)));
|
||||
|
||||
// zero data hash, revert
|
||||
batchHeader = new bytes(89);
|
||||
hevm.expectRevert("zero data hash");
|
||||
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)), bytes32(0));
|
||||
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)));
|
||||
|
||||
// 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)), bytes32(0));
|
||||
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)));
|
||||
|
||||
// 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)), bytes32(uint256(2)));
|
||||
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)));
|
||||
assertEq(rollup.finalizedStateRoots(0), bytes32(uint256(1)));
|
||||
assertEq(rollup.withdrawRoots(0), bytes32(uint256(2)));
|
||||
assertEq(rollup.withdrawRoots(0), bytes32(0));
|
||||
assertGt(uint256(rollup.committedBatches(0)), 0);
|
||||
|
||||
// Genesis batch imported, revert
|
||||
hevm.expectRevert("Genesis batch imported");
|
||||
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)), bytes32(uint256(2)));
|
||||
rollup.importGenesisBatch(batchHeader, bytes32(uint256(1)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,5 +6,9 @@ import {IRollupVerifier} from "../../libraries/verifier/IRollupVerifier.sol";
|
||||
|
||||
contract MockRollupVerifier is IRollupVerifier {
|
||||
/// @inheritdoc IRollupVerifier
|
||||
function verifyAggregateProof(bytes calldata, bytes32) external view {}
|
||||
function verifyAggregateProof(
|
||||
uint256,
|
||||
bytes calldata,
|
||||
bytes32
|
||||
) external view {}
|
||||
}
|
||||
|
||||
14
contracts/src/test/mocks/MockZkEvmVerifier.sol
Normal file
14
contracts/src/test/mocks/MockZkEvmVerifier.sol
Normal file
@@ -0,0 +1,14 @@
|
||||
// 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))));
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,13 @@ 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 {
|
||||
@@ -127,3 +134,18 @@ 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)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ const (
|
||||
|
||||
// RollerManagerConfig loads sequencer configuration items.
|
||||
type RollerManagerConfig struct {
|
||||
CompressionLevel int `json:"compression_level,omitempty"`
|
||||
PauseSendTask bool `json:"pause_send_task"`
|
||||
PauseSendTaskUntil uint64 `json:"pause_send_task_until"`
|
||||
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.
|
||||
|
||||
@@ -9,6 +9,7 @@ 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
|
||||
)
|
||||
|
||||
@@ -133,6 +133,8 @@ 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=
|
||||
|
||||
@@ -15,6 +15,7 @@ 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"
|
||||
@@ -75,7 +76,9 @@ type Manager struct {
|
||||
cfg *config.RollerManagerConfig
|
||||
|
||||
// The indicator whether the backend is running or not.
|
||||
running int32
|
||||
running int32
|
||||
sendTaskPaused *uatomic.Bool
|
||||
pauseUntilBatchIdx *uatomic.Uint64
|
||||
|
||||
// A mutex guarding the boolean below.
|
||||
mu sync.RWMutex
|
||||
@@ -117,6 +120,8 @@ 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),
|
||||
@@ -201,7 +206,13 @@ func (m *Manager) Loop() {
|
||||
}
|
||||
}
|
||||
// Select basic type roller and send message
|
||||
for len(tasks) > 0 && m.StartBasicProofGenerationSession(tasks[0], nil) {
|
||||
for len(tasks) > 0 {
|
||||
if m.isSendTaskPaused(tasks[0].Index) {
|
||||
break
|
||||
}
|
||||
if !m.StartBasicProofGenerationSession(tasks[0], nil) {
|
||||
break
|
||||
}
|
||||
tasks = tasks[1:]
|
||||
}
|
||||
case <-m.ctx.Done():
|
||||
@@ -559,6 +570,11 @@ func (m *Manager) APIs() []rpc.API {
|
||||
Service: RollerAPI(m),
|
||||
Public: true,
|
||||
},
|
||||
{
|
||||
Namespace: "admin",
|
||||
Service: AdminAPI(m),
|
||||
Public: true,
|
||||
},
|
||||
{
|
||||
Namespace: "debug",
|
||||
Public: true,
|
||||
@@ -567,6 +583,26 @@ 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
|
||||
|
||||
@@ -7,26 +7,15 @@ 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 (
|
||||
// Balance equal to 1e28
|
||||
amount = new(big.Int).SetBytes(common.FromHex("204fce5e3e25020000000000"))
|
||||
ether = big.NewInt(1e18)
|
||||
erc20Address = common.HexToAddress("0x7363726f6c6c6c20000000000000000000000014")
|
||||
greeterAddress = common.HexToAddress("0x7363726f6c6c6c20000000000000000000000015")
|
||||
)
|
||||
|
||||
func TestERC20(t *testing.T) {
|
||||
@@ -34,31 +23,31 @@ func TestERC20(t *testing.T) {
|
||||
l2Cli, err := base.L2Client()
|
||||
assert.Nil(t, err)
|
||||
|
||||
token, err := erc20.NewERC20Mock(base.ERC20, l2Cli)
|
||||
token, err := erc20.NewERC20Mock(erc20Address, l2Cli)
|
||||
assert.NoError(t, err)
|
||||
|
||||
auth, err := bind.NewKeyedTransactorWithChainID(bridgeApp.Config.L2Config.RelayerConfig.GasOracleSenderPrivateKeys[0], base.L2gethImg.ChainID())
|
||||
auth, err := bind.NewKeyedTransactorWithChainID(bridgeApp.Config.L2Config.RelayerConfig.MessageSenderPrivateKeys[0], base.L2gethImg.ChainID())
|
||||
assert.NoError(t, err)
|
||||
|
||||
authBls0, err := token.BalanceOf(nil, auth.From)
|
||||
assert.NoError(t, err)
|
||||
|
||||
tokenBls0, err := token.BalanceOf(nil, base.ERC20)
|
||||
tokenBls0, err := token.BalanceOf(nil, erc20Address)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// create tx to transfer balance.
|
||||
value := big.NewInt(1000)
|
||||
to := common.HexToAddress("0x85fd9d96a42972f8301b886e77838f363e72dff7")
|
||||
tx, err := token.Transfer(auth, to, value)
|
||||
tx, err := token.Transfer(auth, erc20Address, value)
|
||||
assert.NoError(t, err)
|
||||
_, err = bind.WaitMined(context.Background(), l2Cli, tx)
|
||||
bind.WaitMined(context.Background(), l2Cli, tx)
|
||||
|
||||
authBls1, err := token.BalanceOf(nil, auth.From)
|
||||
assert.NoError(t, err)
|
||||
|
||||
tokenBls1, err := token.BalanceOf(nil, erc20Address)
|
||||
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())
|
||||
}
|
||||
@@ -68,193 +57,18 @@ func TestGreeter(t *testing.T) {
|
||||
l2Cli, err := base.L2Client()
|
||||
assert.Nil(t, err)
|
||||
|
||||
chainID, _ := l2Cli.ChainID(context.Background())
|
||||
auth, err := bind.NewKeyedTransactorWithChainID(bridgeApp.Config.L2Config.RelayerConfig.GasOracleSenderPrivateKeys[0], chainID)
|
||||
auth, err := bind.NewKeyedTransactorWithChainID(bridgeApp.Config.L2Config.RelayerConfig.MessageSenderPrivateKeys[0], base.L2gethImg.ChainID())
|
||||
assert.NoError(t, err)
|
||||
|
||||
token, err := greeter.NewGreeter(base.Greeter, l2Cli)
|
||||
token, err := greeter.NewGreeter(greeterAddress, 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
|
||||
}
|
||||
|
||||
@@ -34,6 +34,10 @@ 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=
|
||||
@@ -41,6 +45,10 @@ 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=
|
||||
|
||||
Reference in New Issue
Block a user