mirror of
https://github.com/scroll-tech/scroll.git
synced 2026-01-14 00:18:03 -05:00
Compare commits
14 Commits
alpha-v2.0
...
alpha-v2.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2962fa4b0e | ||
|
|
5b7ee9e55c | ||
|
|
0b8a737090 | ||
|
|
ceb406b68b | ||
|
|
1a29797ee1 | ||
|
|
19f74075a1 | ||
|
|
c752e3473d | ||
|
|
cb6a609366 | ||
|
|
87cc80e6e3 | ||
|
|
77f1fa7ca7 | ||
|
|
c2445176ec | ||
|
|
3a1cb6a34b | ||
|
|
0a404fe10f | ||
|
|
73b6bd176e |
35
.github/workflows/contracts.yaml
vendored
35
.github/workflows/contracts.yaml
vendored
@@ -2,26 +2,10 @@ name: Contracts
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
- prod
|
||||
- release/*
|
||||
- staging
|
||||
- develop
|
||||
- alpha
|
||||
paths:
|
||||
- 'contracts/**'
|
||||
- '.github/workflows/contracts.yaml'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
- prod
|
||||
- release/*
|
||||
- staging
|
||||
- develop
|
||||
- alpha
|
||||
paths:
|
||||
- 'contracts/**'
|
||||
- '.github/workflows/contracts.yaml'
|
||||
@@ -44,6 +28,9 @@ jobs:
|
||||
uses: foundry-rs/foundry-toolchain@v1
|
||||
with:
|
||||
version: nightly
|
||||
|
||||
- name: Setup LCOV
|
||||
uses: hrishikesh-kadam/setup-lcov@v1
|
||||
|
||||
- name: Install Node.js 14
|
||||
uses: actions/setup-node@v2
|
||||
@@ -80,6 +67,22 @@ jobs:
|
||||
- name: Run foundry tests
|
||||
run: forge test -vvv
|
||||
|
||||
- name: Run foundry coverage
|
||||
run : forge coverage --report lcov
|
||||
|
||||
- name : Prune coverage
|
||||
run : lcov --remove ./lcov.info -o ./lcov.info.pruned 'src/mocks/*' 'src/test/*' 'scripts/*' 'node_modules/*' 'lib/*'
|
||||
|
||||
- name: Report code coverage
|
||||
uses: zgosalvez/github-actions-report-lcov@v3
|
||||
with:
|
||||
coverage-files: contracts/lcov.info.pruned
|
||||
minimum-coverage: 0
|
||||
artifact-name: code-coverage-report
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
working-directory: contracts
|
||||
update-comment: true
|
||||
|
||||
hardhat:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
||||
16
Jenkinsfile
vendored
16
Jenkinsfile
vendored
@@ -63,42 +63,42 @@ pipeline {
|
||||
parallel{
|
||||
stage('Test bridge package') {
|
||||
steps {
|
||||
sh 'go test -v -race -coverprofile=coverage.bridge.txt -covermode=atomic -p 1 scroll-tech/bridge/...'
|
||||
sh 'go test -v -coverprofile=coverage.bridge.txt -covermode=atomic -p 1 scroll-tech/bridge/...'
|
||||
}
|
||||
}
|
||||
stage('Test common package') {
|
||||
steps {
|
||||
sh 'go test -v -race -coverprofile=coverage.common.txt -covermode=atomic -p 1 scroll-tech/common/...'
|
||||
sh 'go test -v -coverprofile=coverage.common.txt -covermode=atomic -p 1 scroll-tech/common/...'
|
||||
}
|
||||
}
|
||||
stage('Test coordinator package') {
|
||||
steps {
|
||||
sh 'go test -v -race -coverprofile=coverage.coordinator.txt -covermode=atomic -p 1 scroll-tech/coordinator/...'
|
||||
sh 'go test -v -coverprofile=coverage.coordinator.txt -covermode=atomic -p 1 scroll-tech/coordinator/...'
|
||||
}
|
||||
}
|
||||
stage('Test database package') {
|
||||
steps {
|
||||
sh 'go test -v -race -coverprofile=coverage.db.txt -covermode=atomic -p 1 scroll-tech/database/...'
|
||||
sh 'go test -v -coverprofile=coverage.db.txt -covermode=atomic -p 1 scroll-tech/database/...'
|
||||
}
|
||||
}
|
||||
stage('Integration test') {
|
||||
steps {
|
||||
sh 'go test -v -race -tags="mock_prover mock_verifier" -coverprofile=coverage.integration.txt -covermode=atomic -p 1 scroll-tech/integration-test/...'
|
||||
sh 'go test -v -tags="mock_prover mock_verifier" -coverprofile=coverage.integration.txt -covermode=atomic -p 1 scroll-tech/integration-test/...'
|
||||
}
|
||||
}
|
||||
stage('Race test bridge package') {
|
||||
steps {
|
||||
sh "cd bridge && go test -v -race -coverprofile=coverage.txt -covermode=atomic \$(go list ./... | grep -v 'database\\|common\\|l1\\|l2\\|coordinator')"
|
||||
sh 'go test -v -race -coverprofile=coverage.txt -covermode=atomic scroll-tech/bridge/...'
|
||||
}
|
||||
}
|
||||
stage('Race test coordinator package') {
|
||||
steps {
|
||||
sh "cd coordinator && go test -v -race -coverprofile=coverage.txt -covermode=atomic \$(go list ./... | grep -v 'database\\|common\\|l1\\|l2\\|coordinator')"
|
||||
sh 'go test -v -race -coverprofile=coverage.txt -covermode=atomic scroll-tech/coordinator/...'
|
||||
}
|
||||
}
|
||||
stage('Race test database package') {
|
||||
steps {
|
||||
sh "cd database && go test -v -race -coverprofile=coverage.txt -covermode=atomic \$(go list ./... | grep -v 'database\\|common\\|l1\\|l2\\|coordinator')"
|
||||
sh 'go test -v -race -coverprofile=coverage.txt -covermode=atomic scroll-tech/database/...'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
10
Makefile
10
Makefile
@@ -16,11 +16,11 @@ lint: ## The code's format and security checks.
|
||||
|
||||
update: ## update dependencies
|
||||
go work sync
|
||||
cd $(PWD)/bridge/ && go get -u github.com/scroll-tech/go-ethereum@staging && go mod tidy
|
||||
cd $(PWD)/common/ && go get -u github.com/scroll-tech/go-ethereum@staging && go mod tidy
|
||||
cd $(PWD)/coordinator/ && go get -u github.com/scroll-tech/go-ethereum@staging && go mod tidy
|
||||
cd $(PWD)/database/ && go get -u github.com/scroll-tech/go-ethereum@staging && go mod tidy
|
||||
cd $(PWD)/roller/ && go get -u github.com/scroll-tech/go-ethereum@staging && go mod tidy
|
||||
cd $(PWD)/bridge/ && go get -u github.com/scroll-tech/go-ethereum@scroll && go mod tidy
|
||||
cd $(PWD)/common/ && go get -u github.com/scroll-tech/go-ethereum@scroll && go mod tidy
|
||||
cd $(PWD)/coordinator/ && go get -u github.com/scroll-tech/go-ethereum@scroll && go mod tidy
|
||||
cd $(PWD)/database/ && go get -u github.com/scroll-tech/go-ethereum@scroll && go mod tidy
|
||||
cd $(PWD)/roller/ && go get -u github.com/scroll-tech/go-ethereum@scroll && go mod tidy
|
||||
goimports -local $(PWD)/bridge/ -w .
|
||||
goimports -local $(PWD)/common/ -w .
|
||||
goimports -local $(PWD)/coordinator/ -w .
|
||||
|
||||
@@ -40,6 +40,8 @@ type BatchProposerConfig struct {
|
||||
BatchBlocksLimit uint64 `json:"batch_blocks_limit"`
|
||||
// Commit tx calldata size limit in bytes, target to cap the gas use of commit tx at 2M gas
|
||||
CommitTxCalldataSizeLimit uint64 `json:"commit_tx_calldata_size_limit"`
|
||||
// Commit tx calldata min size limit in bytes
|
||||
CommitTxCalldataMinSize uint64 `json:"commit_tx_calldata_min_size,omitempty"`
|
||||
// The public input hash config
|
||||
PublicInputConfig *types.PublicInputHashConfig `json:"public_input_config"`
|
||||
}
|
||||
|
||||
@@ -51,6 +51,8 @@ type RelayerConfig struct {
|
||||
GasOracleConfig *GasOracleConfig `json:"gas_oracle_config"`
|
||||
// The interval in which we send finalize batch transactions.
|
||||
FinalizeBatchIntervalSec uint64 `json:"finalize_batch_interval_sec"`
|
||||
// MessageRelayMinGasLimit to avoid OutOfGas error
|
||||
MessageRelayMinGasLimit uint64 `json:"message_relay_min_gas_limit,omitempty"`
|
||||
// The private key of the relayer
|
||||
MessageSenderPrivateKeys []*ecdsa.PrivateKey `json:"-"`
|
||||
GasOracleSenderPrivateKeys []*ecdsa.PrivateKey `json:"-"`
|
||||
|
||||
@@ -4,7 +4,7 @@ go 1.18
|
||||
|
||||
require (
|
||||
github.com/orcaman/concurrent-map v1.0.0
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20230306131930-03b4de32b78b
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20230321020420-127af384ed04
|
||||
github.com/stretchr/testify v1.8.2
|
||||
github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa
|
||||
golang.org/x/sync v0.1.0
|
||||
@@ -28,7 +28,7 @@ require (
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
github.com/rjeczalik/notify v0.9.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/scroll-tech/zktrie v0.5.0 // indirect
|
||||
github.com/scroll-tech/zktrie v0.5.2 // indirect
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.10 // indirect
|
||||
github.com/tklauser/numcpus v0.4.0 // indirect
|
||||
@@ -36,6 +36,7 @@ require (
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/crypto v0.7.0 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -48,6 +48,11 @@ github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7Bd
|
||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
@@ -69,13 +74,14 @@ github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6O
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rjeczalik/notify v0.9.1 h1:CLCKso/QK1snAlnhNR/CNvNiFU2saUtjV0bx3EwNeCE=
|
||||
github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20230306131930-03b4de32b78b h1:shNTzAnD2oDcDCrM4aaVCTzQNVfYxF1An08R2H2DLAg=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20230306131930-03b4de32b78b/go.mod h1:f9ygxrxL7WRCTzuloV+t/UlcxMq3AL+gcNU60liiNNU=
|
||||
github.com/scroll-tech/zktrie v0.5.0 h1:dABDR6lMZq6Hs+fWQSiHbX8s3AOX6hY+5nkhSYm5rmU=
|
||||
github.com/scroll-tech/zktrie v0.5.0/go.mod h1:XvNo7vAk8yxNyTjBDj5WIiFzYW4bx/gJ78+NK6Zn6Uk=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20230321020420-127af384ed04 h1:PpI31kaBVm6+7sZtyK03Ex0QIg3P821Ktae0FHFh7IM=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20230321020420-127af384ed04/go.mod h1:jH8c08L9K8Hieaf0r/ur2P/cpesn4dFhmLm2Mmoi8kI=
|
||||
github.com/scroll-tech/zktrie v0.5.2 h1:U34jPXMLGOlRHfdvYp5VVgOcC0RuPeJmcS3bWotCWiY=
|
||||
github.com/scroll-tech/zktrie v0.5.2/go.mod h1:XvNo7vAk8yxNyTjBDj5WIiFzYW4bx/gJ78+NK6Zn6Uk=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA=
|
||||
@@ -113,8 +119,9 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
|
||||
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
|
||||
|
||||
@@ -35,6 +35,8 @@ const (
|
||||
gasPriceDiffPrecision = 1000000
|
||||
|
||||
defaultGasPriceDiff = 50000 // 5%
|
||||
|
||||
defaultMessageRelayMinGasLimit = 130000 // should be enough for both ERC20 and ETH relay
|
||||
)
|
||||
|
||||
// Layer1Relayer is responsible for
|
||||
@@ -58,6 +60,8 @@ type Layer1Relayer struct {
|
||||
gasOracleCh <-chan *sender.Confirmation
|
||||
l1GasOracleABI *abi.ABI
|
||||
|
||||
minGasLimitForMessageRelay uint64
|
||||
|
||||
lastGasPrice uint64
|
||||
minGasPrice uint64
|
||||
gasPriceDiff uint64
|
||||
@@ -92,6 +96,11 @@ func NewLayer1Relayer(ctx context.Context, db database.OrmFactory, cfg *config.R
|
||||
gasPriceDiff = defaultGasPriceDiff
|
||||
}
|
||||
|
||||
minGasLimitForMessageRelay := uint64(defaultMessageRelayMinGasLimit)
|
||||
if cfg.MessageRelayMinGasLimit != 0 {
|
||||
minGasLimitForMessageRelay = cfg.MessageRelayMinGasLimit
|
||||
}
|
||||
|
||||
return &Layer1Relayer{
|
||||
ctx: ctx,
|
||||
db: db,
|
||||
@@ -104,6 +113,8 @@ func NewLayer1Relayer(ctx context.Context, db database.OrmFactory, cfg *config.R
|
||||
gasOracleCh: gasOracleSender.ConfirmChan(),
|
||||
l1GasOracleABI: bridge_abi.L1GasPriceOracleABI,
|
||||
|
||||
minGasLimitForMessageRelay: minGasLimitForMessageRelay,
|
||||
|
||||
minGasPrice: minGasPrice,
|
||||
gasPriceDiff: gasPriceDiff,
|
||||
|
||||
@@ -138,7 +149,7 @@ func (r *Layer1Relayer) ProcessSavedEvents() {
|
||||
func (r *Layer1Relayer) processSavedEvent(msg *types.L1Message) error {
|
||||
calldata := common.Hex2Bytes(msg.Calldata)
|
||||
|
||||
hash, err := r.messageSender.SendTransaction(msg.MsgHash, &r.cfg.MessengerContractAddress, big.NewInt(0), calldata)
|
||||
hash, err := r.messageSender.SendTransaction(msg.MsgHash, &r.cfg.MessengerContractAddress, big.NewInt(0), calldata, r.minGasLimitForMessageRelay)
|
||||
if err != nil && err.Error() == "execution reverted: Message expired" {
|
||||
return r.db.UpdateLayer1Status(r.ctx, msg.MsgHash, types.MsgExpired)
|
||||
}
|
||||
@@ -190,7 +201,7 @@ func (r *Layer1Relayer) ProcessGasPriceOracle() {
|
||||
return
|
||||
}
|
||||
|
||||
hash, err := r.gasOracleSender.SendTransaction(block.Hash, &r.cfg.GasPriceOracleContractAddress, big.NewInt(0), data)
|
||||
hash, err := r.gasOracleSender.SendTransaction(block.Hash, &r.cfg.GasPriceOracleContractAddress, big.NewInt(0), data, 0)
|
||||
if err != nil {
|
||||
if !errors.Is(err, sender.ErrNoAvailableAccount) {
|
||||
log.Error("Failed to send setL1BaseFee tx to layer2 ", "block.Hash", block.Hash, "block.Height", block.Number, "err", err)
|
||||
|
||||
@@ -80,6 +80,7 @@ type BatchProposer struct {
|
||||
batchCommitTimeSec uint64
|
||||
commitCalldataSizeLimit uint64
|
||||
batchDataBufferSizeLimit uint64
|
||||
commitCalldataMinSize uint64
|
||||
|
||||
proofGenerationFreq uint64
|
||||
batchDataBuffer []*types.BatchData
|
||||
@@ -102,6 +103,7 @@ func NewBatchProposer(ctx context.Context, cfg *config.BatchProposerConfig, rela
|
||||
batchBlocksLimit: cfg.BatchBlocksLimit,
|
||||
batchCommitTimeSec: cfg.BatchCommitTimeSec,
|
||||
commitCalldataSizeLimit: cfg.CommitTxCalldataSizeLimit,
|
||||
commitCalldataMinSize: cfg.CommitTxCalldataMinSize,
|
||||
batchDataBufferSizeLimit: 100*cfg.CommitTxCalldataSizeLimit + 1*1024*1024, // @todo: determine the value.
|
||||
proofGenerationFreq: cfg.ProofGenerationFreq,
|
||||
piCfg: cfg.PublicInputConfig,
|
||||
@@ -217,7 +219,7 @@ func (p *BatchProposer) tryProposeBatch() {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
|
||||
if p.getBatchDataBufferSize() < p.batchDataBufferSizeLimit {
|
||||
for p.getBatchDataBufferSize() < p.batchDataBufferSizeLimit {
|
||||
blocks, err := p.orm.GetUnbatchedL2Blocks(
|
||||
map[string]interface{}{},
|
||||
fmt.Sprintf("order by number ASC LIMIT %d", p.batchBlocksLimit),
|
||||
@@ -227,7 +229,18 @@ func (p *BatchProposer) tryProposeBatch() {
|
||||
return
|
||||
}
|
||||
|
||||
p.proposeBatch(blocks)
|
||||
batchCreated := p.proposeBatch(blocks)
|
||||
|
||||
// while size of batchDataBuffer < commitCalldataMinSize,
|
||||
// proposer keeps fetching and porposing batches.
|
||||
if p.getBatchDataBufferSize() >= p.commitCalldataMinSize {
|
||||
return
|
||||
}
|
||||
|
||||
if !batchCreated {
|
||||
// wait for watcher to insert l2 traces.
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,9 +289,9 @@ func (p *BatchProposer) tryCommitBatches() {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *BatchProposer) proposeBatch(blocks []*types.BlockInfo) {
|
||||
func (p *BatchProposer) proposeBatch(blocks []*types.BlockInfo) bool {
|
||||
if len(blocks) == 0 {
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
if blocks[0].GasUsed > p.batchGasThreshold {
|
||||
@@ -291,7 +304,7 @@ func (p *BatchProposer) proposeBatch(blocks []*types.BlockInfo) {
|
||||
bridgeL2BatchesGasCreatedRateMeter.Mark(int64(blocks[0].GasUsed))
|
||||
bridgeL2BatchesCreatedRateMeter.Mark(1)
|
||||
}
|
||||
return
|
||||
return true
|
||||
}
|
||||
|
||||
if blocks[0].TxNum > p.batchTxNumThreshold {
|
||||
@@ -304,7 +317,7 @@ func (p *BatchProposer) proposeBatch(blocks []*types.BlockInfo) {
|
||||
bridgeL2BatchesGasCreatedRateMeter.Mark(int64(blocks[0].GasUsed))
|
||||
bridgeL2BatchesCreatedRateMeter.Mark(1)
|
||||
}
|
||||
return
|
||||
return true
|
||||
}
|
||||
|
||||
var gasUsed, txNum uint64
|
||||
@@ -324,7 +337,7 @@ func (p *BatchProposer) proposeBatch(blocks []*types.BlockInfo) {
|
||||
// if it's not old enough we will skip proposing the batch,
|
||||
// otherwise we will still propose a batch
|
||||
if !reachThreshold && blocks[0].BlockTimestamp+p.batchTimeSec > uint64(time.Now().Unix()) {
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
if err := p.createBatchForBlocks(blocks); err != nil {
|
||||
@@ -334,6 +347,8 @@ func (p *BatchProposer) proposeBatch(blocks []*types.BlockInfo) {
|
||||
bridgeL2BatchesGasCreatedRateMeter.Mark(int64(gasUsed))
|
||||
bridgeL2BatchesCreatedRateMeter.Mark(int64(len(blocks)))
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *BatchProposer) createBatchForBlocks(blocks []*types.BlockInfo) error {
|
||||
|
||||
@@ -44,6 +44,8 @@ const (
|
||||
gasPriceDiffPrecision = 1000000
|
||||
|
||||
defaultGasPriceDiff = 50000 // 5%
|
||||
|
||||
defaultMessageRelayMinGasLimit = 200000 // should be enough for both ERC20 and ETH relay
|
||||
)
|
||||
|
||||
// Layer2Relayer is responsible for
|
||||
@@ -72,6 +74,8 @@ type Layer2Relayer struct {
|
||||
gasOracleCh <-chan *sender.Confirmation
|
||||
l2GasOracleABI *abi.ABI
|
||||
|
||||
minGasLimitForMessageRelay uint64
|
||||
|
||||
lastGasPrice uint64
|
||||
minGasPrice uint64
|
||||
gasPriceDiff uint64
|
||||
@@ -122,6 +126,11 @@ func NewLayer2Relayer(ctx context.Context, l2Client *ethclient.Client, db databa
|
||||
gasPriceDiff = defaultGasPriceDiff
|
||||
}
|
||||
|
||||
minGasLimitForMessageRelay := uint64(defaultMessageRelayMinGasLimit)
|
||||
if cfg.MessageRelayMinGasLimit != 0 {
|
||||
minGasLimitForMessageRelay = cfg.MessageRelayMinGasLimit
|
||||
}
|
||||
|
||||
return &Layer2Relayer{
|
||||
ctx: ctx,
|
||||
db: db,
|
||||
@@ -140,6 +149,8 @@ func NewLayer2Relayer(ctx context.Context, l2Client *ethclient.Client, db databa
|
||||
gasOracleCh: gasOracleSender.ConfirmChan(),
|
||||
l2GasOracleABI: bridge_abi.L2GasPriceOracleABI,
|
||||
|
||||
minGasLimitForMessageRelay: minGasLimitForMessageRelay,
|
||||
|
||||
minGasPrice: minGasPrice,
|
||||
gasPriceDiff: gasPriceDiff,
|
||||
|
||||
@@ -232,7 +243,7 @@ func (r *Layer2Relayer) processSavedEvent(msg *types.L2Message) error {
|
||||
return err
|
||||
}
|
||||
|
||||
hash, err := r.messageSender.SendTransaction(msg.MsgHash, &r.cfg.MessengerContractAddress, big.NewInt(0), data)
|
||||
hash, err := r.messageSender.SendTransaction(msg.MsgHash, &r.cfg.MessengerContractAddress, big.NewInt(0), data, r.minGasLimitForMessageRelay)
|
||||
if err != nil && err.Error() == "execution reverted: Message expired" {
|
||||
return r.db.UpdateLayer2Status(r.ctx, msg.MsgHash, types.MsgExpired)
|
||||
}
|
||||
@@ -284,7 +295,7 @@ func (r *Layer2Relayer) ProcessGasPriceOracle() {
|
||||
return
|
||||
}
|
||||
|
||||
hash, err := r.gasOracleSender.SendTransaction(batch.Hash, &r.cfg.GasPriceOracleContractAddress, big.NewInt(0), data)
|
||||
hash, err := r.gasOracleSender.SendTransaction(batch.Hash, &r.cfg.GasPriceOracleContractAddress, big.NewInt(0), data, 0)
|
||||
if err != nil {
|
||||
if !errors.Is(err, sender.ErrNoAvailableAccount) {
|
||||
log.Error("Failed to send setL2BaseFee tx to layer2 ", "batch.Hash", batch.Hash, "err", err)
|
||||
@@ -330,7 +341,7 @@ func (r *Layer2Relayer) SendCommitTx(batchData []*types.BatchData) error {
|
||||
bytes = append(bytes, batch.Hash().Bytes()...)
|
||||
}
|
||||
txID := crypto.Keccak256Hash(bytes).String()
|
||||
txHash, err := r.rollupSender.SendTransaction(txID, &r.cfg.RollupContractAddress, big.NewInt(0), calldata)
|
||||
txHash, err := r.rollupSender.SendTransaction(txID, &r.cfg.RollupContractAddress, big.NewInt(0), calldata, 0)
|
||||
if err != nil {
|
||||
if !errors.Is(err, sender.ErrNoAvailableAccount) {
|
||||
log.Error("Failed to send commitBatches tx to layer1 ", "err", err)
|
||||
@@ -479,7 +490,7 @@ func (r *Layer2Relayer) ProcessCommittedBatches() {
|
||||
|
||||
txID := hash + "-finalize"
|
||||
// add suffix `-finalize` to avoid duplication with commit tx in unit tests
|
||||
txHash, err := r.rollupSender.SendTransaction(txID, &r.cfg.RollupContractAddress, big.NewInt(0), data)
|
||||
txHash, err := r.rollupSender.SendTransaction(txID, &r.cfg.RollupContractAddress, big.NewInt(0), data, 0)
|
||||
finalizeTxHash := &txHash
|
||||
if err != nil {
|
||||
if !errors.Is(err, sender.ErrNoAvailableAccount) {
|
||||
|
||||
@@ -45,7 +45,7 @@ func testCreateNewWatcherAndStop(t *testing.T) {
|
||||
numTransactions := 3
|
||||
toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
|
||||
for i := 0; i < numTransactions; i++ {
|
||||
_, err = newSender.SendTransaction(strconv.Itoa(1000+i), &toAddress, big.NewInt(1000000000), nil)
|
||||
_, err = newSender.SendTransaction(strconv.Itoa(1000+i), &toAddress, big.NewInt(1000000000), nil, 0)
|
||||
assert.NoError(t, err)
|
||||
<-newSender.ConfirmChan()
|
||||
}
|
||||
|
||||
@@ -9,12 +9,12 @@ import (
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
)
|
||||
|
||||
func (s *Sender) estimateLegacyGas(auth *bind.TransactOpts, contract *common.Address, value *big.Int, input []byte) (*FeeData, error) {
|
||||
func (s *Sender) estimateLegacyGas(auth *bind.TransactOpts, contract *common.Address, value *big.Int, input []byte, minGasLimit uint64) (*FeeData, error) {
|
||||
gasPrice, err := s.client.SuggestGasPrice(s.ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gasLimit, err := s.estimateGasLimit(auth, contract, input, gasPrice, nil, nil, value)
|
||||
gasLimit, err := s.estimateGasLimit(auth, contract, input, gasPrice, nil, nil, value, minGasLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -24,7 +24,7 @@ func (s *Sender) estimateLegacyGas(auth *bind.TransactOpts, contract *common.Add
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Sender) estimateDynamicGas(auth *bind.TransactOpts, contract *common.Address, value *big.Int, input []byte) (*FeeData, error) {
|
||||
func (s *Sender) estimateDynamicGas(auth *bind.TransactOpts, contract *common.Address, value *big.Int, input []byte, minGasLimit uint64) (*FeeData, error) {
|
||||
gasTipCap, err := s.client.SuggestGasTipCap(s.ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -38,7 +38,7 @@ func (s *Sender) estimateDynamicGas(auth *bind.TransactOpts, contract *common.Ad
|
||||
gasTipCap,
|
||||
new(big.Int).Mul(baseFee, big.NewInt(2)),
|
||||
)
|
||||
gasLimit, err := s.estimateGasLimit(auth, contract, input, nil, gasTipCap, gasFeeCap, value)
|
||||
gasLimit, err := s.estimateGasLimit(auth, contract, input, nil, gasTipCap, gasFeeCap, value, minGasLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -49,7 +49,7 @@ func (s *Sender) estimateDynamicGas(auth *bind.TransactOpts, contract *common.Ad
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Sender) estimateGasLimit(opts *bind.TransactOpts, contract *common.Address, input []byte, gasPrice, gasTipCap, gasFeeCap, value *big.Int) (uint64, error) {
|
||||
func (s *Sender) estimateGasLimit(opts *bind.TransactOpts, contract *common.Address, input []byte, gasPrice, gasTipCap, gasFeeCap, value *big.Int, minGasLimit uint64) (uint64, error) {
|
||||
msg := ethereum.CallMsg{
|
||||
From: opts.From,
|
||||
To: contract,
|
||||
@@ -63,6 +63,10 @@ func (s *Sender) estimateGasLimit(opts *bind.TransactOpts, contract *common.Addr
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if minGasLimit > gasLimit {
|
||||
gasLimit = minGasLimit
|
||||
}
|
||||
|
||||
gasLimit = gasLimit * 15 / 10 // 50% extra gas to void out of gas error
|
||||
|
||||
return gasLimit, nil
|
||||
|
||||
@@ -150,15 +150,15 @@ func (s *Sender) NumberOfAccounts() int {
|
||||
return len(s.auths.accounts)
|
||||
}
|
||||
|
||||
func (s *Sender) getFeeData(auth *bind.TransactOpts, target *common.Address, value *big.Int, data []byte) (*FeeData, error) {
|
||||
func (s *Sender) getFeeData(auth *bind.TransactOpts, target *common.Address, value *big.Int, data []byte, minGasLimit uint64) (*FeeData, error) {
|
||||
if s.config.TxType == DynamicFeeTxType {
|
||||
return s.estimateDynamicGas(auth, target, value, data)
|
||||
return s.estimateDynamicGas(auth, target, value, data, minGasLimit)
|
||||
}
|
||||
return s.estimateLegacyGas(auth, target, value, data)
|
||||
return s.estimateLegacyGas(auth, target, value, data, minGasLimit)
|
||||
}
|
||||
|
||||
// SendTransaction send a signed L2tL1 transaction.
|
||||
func (s *Sender) SendTransaction(ID string, target *common.Address, value *big.Int, data []byte) (hash common.Hash, err error) {
|
||||
func (s *Sender) SendTransaction(ID string, target *common.Address, value *big.Int, data []byte, minGasLimit uint64) (hash common.Hash, err error) {
|
||||
// We occupy the ID, in case some other threads call with the same ID in the same time
|
||||
if _, loaded := s.pendingTxs.LoadOrStore(ID, nil); loaded {
|
||||
return common.Hash{}, fmt.Errorf("has the repeat tx ID, ID: %s", ID)
|
||||
@@ -182,7 +182,7 @@ func (s *Sender) SendTransaction(ID string, target *common.Address, value *big.I
|
||||
tx *types.Transaction
|
||||
)
|
||||
// estimate gas fee
|
||||
if feeData, err = s.getFeeData(auth, target, value, data); err != nil {
|
||||
if feeData, err = s.getFeeData(auth, target, value, data, minGasLimit); err != nil {
|
||||
return
|
||||
}
|
||||
if tx, err = s.createAndSendTx(auth, feeData, target, value, data, nil); err == nil {
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
cmap "github.com/orcaman/concurrent-map"
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/crypto"
|
||||
"github.com/scroll-tech/go-ethereum/ethclient"
|
||||
"github.com/scroll-tech/go-ethereum/rpc"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
@@ -56,11 +57,38 @@ func TestSender(t *testing.T) {
|
||||
// Setup
|
||||
setupEnv(t)
|
||||
|
||||
t.Run("test min gas limit", func(t *testing.T) { testMinGasLimit(t) })
|
||||
|
||||
t.Run("test 1 account sender", func(t *testing.T) { testBatchSender(t, 1) })
|
||||
t.Run("test 3 account sender", func(t *testing.T) { testBatchSender(t, 3) })
|
||||
t.Run("test 8 account sender", func(t *testing.T) { testBatchSender(t, 8) })
|
||||
}
|
||||
|
||||
func testMinGasLimit(t *testing.T) {
|
||||
senderCfg := cfg.L1Config.RelayerConfig.SenderConfig
|
||||
senderCfg.Confirmations = rpc.LatestBlockNumber
|
||||
newSender, err := sender.NewSender(context.Background(), senderCfg, privateKeys)
|
||||
assert.NoError(t, err)
|
||||
defer newSender.Stop()
|
||||
|
||||
client, err := ethclient.Dial(senderCfg.Endpoint)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// MinGasLimit = 0
|
||||
txHash0, err := newSender.SendTransaction("0", &common.Address{}, big.NewInt(1), nil, 0)
|
||||
assert.NoError(t, err)
|
||||
tx0, _, err := client.TransactionByHash(context.Background(), txHash0)
|
||||
assert.NoError(t, err)
|
||||
assert.Greater(t, tx0.Gas(), uint64(0))
|
||||
|
||||
// MinGasLimit = 100000
|
||||
txHash1, err := newSender.SendTransaction("1", &common.Address{}, big.NewInt(1), nil, 100000)
|
||||
assert.NoError(t, err)
|
||||
tx1, _, err := client.TransactionByHash(context.Background(), txHash1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tx1.Gas(), uint64(150000))
|
||||
}
|
||||
|
||||
func testBatchSender(t *testing.T, batchSize int) {
|
||||
for len(privateKeys) < batchSize {
|
||||
priv, err := crypto.GenerateKey()
|
||||
@@ -90,7 +118,7 @@ func testBatchSender(t *testing.T, batchSize int) {
|
||||
for i := 0; i < TXBatch; i++ {
|
||||
toAddr := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
|
||||
id := strconv.Itoa(i + index*1000)
|
||||
_, err := newSender.SendTransaction(id, &toAddr, big.NewInt(1), nil)
|
||||
_, err := newSender.SendTransaction(id, &toAddr, big.NewInt(1), nil, 0)
|
||||
if errors.Is(err, sender.ErrNoAvailableAccount) {
|
||||
<-time.After(time.Second)
|
||||
continue
|
||||
|
||||
@@ -4,11 +4,11 @@ ${GOROOT}/bin/bin/gocover-cobertura < coverage.bridge.txt > coverage.bridge.xml
|
||||
${GOROOT}/bin/bin/gocover-cobertura < coverage.db.txt > coverage.db.xml
|
||||
${GOROOT}/bin/bin/gocover-cobertura < coverage.common.txt > coverage.common.xml
|
||||
${GOROOT}/bin/bin/gocover-cobertura < coverage.coordinator.txt > coverage.coordinator.xml
|
||||
${GOROOT}/bin/bin/gocover-cobertura < coverage.integration.txt > coverage.integration.xml
|
||||
# ${GOROOT}/bin/bin/gocover-cobertura < coverage.integration.txt > coverage.integration.xml
|
||||
|
||||
npx cobertura-merge -o cobertura.xml \
|
||||
package1=coverage.bridge.xml \
|
||||
package2=coverage.db.xml \
|
||||
package3=coverage.common.xml \
|
||||
package4=coverage.coordinator.xml \
|
||||
package5=coverage.integration.xml
|
||||
package4=coverage.coordinator.xml
|
||||
# package5=coverage.integration.xml
|
||||
|
||||
@@ -46,7 +46,10 @@ func (c *Cmd) WaitExit() {
|
||||
// Send interrupt signal.
|
||||
c.mu.Lock()
|
||||
_ = c.cmd.Process.Signal(os.Interrupt)
|
||||
_, _ = c.cmd.Process.Wait()
|
||||
// should use `_ = c.cmd.Process.Wait()` here, but we have some bugs in coordinator's graceful exit,
|
||||
// so we use `Kill` as a temp workaround. And since `WaitExit` is only used in integration tests, so
|
||||
// it won't really affect our functionalities.
|
||||
_ = c.cmd.Process.Kill()
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
|
||||
@@ -184,7 +184,7 @@ func newTestL1Docker(t *testing.T) ImgInstance {
|
||||
assert.NoError(t, imgL1geth.Start())
|
||||
|
||||
// try 3 times to get chainID until is ok.
|
||||
utils.TryTimes(3, func() bool {
|
||||
utils.TryTimes(10, func() bool {
|
||||
client, _ := ethclient.Dial(imgL1geth.Endpoint())
|
||||
if client != nil {
|
||||
if _, err := client.ChainID(context.Background()); err == nil {
|
||||
@@ -203,7 +203,7 @@ func newTestL2Docker(t *testing.T) ImgInstance {
|
||||
assert.NoError(t, imgL2geth.Start())
|
||||
|
||||
// try 3 times to get chainID until is ok.
|
||||
utils.TryTimes(3, func() bool {
|
||||
utils.TryTimes(10, func() bool {
|
||||
client, _ := ethclient.Dial(imgL2geth.Endpoint())
|
||||
if client != nil {
|
||||
if _, err := client.ChainID(context.Background()); err == nil {
|
||||
@@ -222,7 +222,7 @@ func newTestDBDocker(t *testing.T, driverName string) ImgInstance {
|
||||
assert.NoError(t, imgDB.Start())
|
||||
|
||||
// try 5 times until the db is ready.
|
||||
utils.TryTimes(5, func() bool {
|
||||
utils.TryTimes(10, func() bool {
|
||||
db, _ := sqlx.Open(driverName, imgDB.Endpoint())
|
||||
if db != nil {
|
||||
return db.Ping() == nil
|
||||
|
||||
@@ -45,7 +45,6 @@ func (i *ImgDB) Start() error {
|
||||
if id != "" {
|
||||
return fmt.Errorf("container already exist, name: %s", i.name)
|
||||
}
|
||||
i.cmd.RunCmd(true)
|
||||
i.running = i.isOk()
|
||||
if !i.running {
|
||||
_ = i.Stop()
|
||||
@@ -106,10 +105,12 @@ func (i *ImgDB) isOk() bool {
|
||||
}
|
||||
})
|
||||
defer i.cmd.UnRegistFunc(keyword)
|
||||
// Start cmd in parallel.
|
||||
i.cmd.RunCmd(true)
|
||||
|
||||
select {
|
||||
case <-okCh:
|
||||
utils.TryTimes(3, func() bool {
|
||||
utils.TryTimes(20, func() bool {
|
||||
i.id = GetContainerID(i.name)
|
||||
return i.id != ""
|
||||
})
|
||||
|
||||
@@ -48,7 +48,6 @@ func (i *ImgGeth) Start() error {
|
||||
if id != "" {
|
||||
return fmt.Errorf("container already exist, name: %s", i.name)
|
||||
}
|
||||
i.cmd.RunCmd(true)
|
||||
i.running = i.isOk()
|
||||
if !i.running {
|
||||
_ = i.Stop()
|
||||
@@ -85,10 +84,12 @@ func (i *ImgGeth) isOk() bool {
|
||||
}
|
||||
})
|
||||
defer i.cmd.UnRegistFunc(keyword)
|
||||
// Start cmd in parallel.
|
||||
i.cmd.RunCmd(true)
|
||||
|
||||
select {
|
||||
case <-okCh:
|
||||
utils.TryTimes(3, func() bool {
|
||||
utils.TryTimes(20, func() bool {
|
||||
i.id = GetContainerID(i.name)
|
||||
return i.id != ""
|
||||
})
|
||||
|
||||
@@ -8,9 +8,9 @@ require (
|
||||
github.com/lib/pq v1.10.6
|
||||
github.com/mattn/go-colorable v0.1.13
|
||||
github.com/mattn/go-isatty v0.0.16
|
||||
github.com/modern-go/reflect2 v1.0.1
|
||||
github.com/modern-go/reflect2 v1.0.2
|
||||
github.com/orcaman/concurrent-map v1.0.0
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20230306131930-03b4de32b78b
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20230321020420-127af384ed04
|
||||
github.com/stretchr/testify v1.8.2
|
||||
github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa
|
||||
gotest.tools v2.2.0+incompatible
|
||||
@@ -56,7 +56,6 @@ require (
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/pointerstructure v1.2.0 // indirect
|
||||
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
@@ -69,7 +68,7 @@ require (
|
||||
github.com/rjeczalik/notify v0.9.1 // indirect
|
||||
github.com/rs/cors v1.7.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/scroll-tech/zktrie v0.5.0 // indirect
|
||||
github.com/scroll-tech/zktrie v0.5.2 // indirect
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/status-im/keycard-go v0.2.0 // indirect
|
||||
@@ -87,6 +86,7 @@ require (
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
|
||||
gopkg.in/urfave/cli.v1 v1.20.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
|
||||
@@ -234,6 +234,7 @@ github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH6
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
@@ -276,10 +277,10 @@ github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjU
|
||||
github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4=
|
||||
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI=
|
||||
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
|
||||
@@ -342,10 +343,10 @@ github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20230306131930-03b4de32b78b h1:shNTzAnD2oDcDCrM4aaVCTzQNVfYxF1An08R2H2DLAg=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20230306131930-03b4de32b78b/go.mod h1:f9ygxrxL7WRCTzuloV+t/UlcxMq3AL+gcNU60liiNNU=
|
||||
github.com/scroll-tech/zktrie v0.5.0 h1:dABDR6lMZq6Hs+fWQSiHbX8s3AOX6hY+5nkhSYm5rmU=
|
||||
github.com/scroll-tech/zktrie v0.5.0/go.mod h1:XvNo7vAk8yxNyTjBDj5WIiFzYW4bx/gJ78+NK6Zn6Uk=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20230321020420-127af384ed04 h1:PpI31kaBVm6+7sZtyK03Ex0QIg3P821Ktae0FHFh7IM=
|
||||
github.com/scroll-tech/go-ethereum v1.10.14-0.20230321020420-127af384ed04/go.mod h1:jH8c08L9K8Hieaf0r/ur2P/cpesn4dFhmLm2Mmoi8kI=
|
||||
github.com/scroll-tech/zktrie v0.5.2 h1:U34jPXMLGOlRHfdvYp5VVgOcC0RuPeJmcS3bWotCWiY=
|
||||
github.com/scroll-tech/zktrie v0.5.2/go.mod h1:XvNo7vAk8yxNyTjBDj5WIiFzYW4bx/gJ78+NK6Zn6Uk=
|
||||
github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
|
||||
github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
@@ -620,8 +621,9 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
|
||||
|
||||
51
common/utils/workerpool/workerpool.go
Normal file
51
common/utils/workerpool/workerpool.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package workerpool
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// WorkerPool is responsible for creating workers and managing verify proof task between them
|
||||
type WorkerPool struct {
|
||||
maxWorker int
|
||||
taskQueueChan chan func()
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewWorkerPool creates new worker pool with given amount of workers
|
||||
func NewWorkerPool(maxWorker int) *WorkerPool {
|
||||
return &WorkerPool{
|
||||
maxWorker: maxWorker,
|
||||
taskQueueChan: nil,
|
||||
wg: sync.WaitGroup{},
|
||||
}
|
||||
}
|
||||
|
||||
// Run runs WorkerPool
|
||||
func (vwp *WorkerPool) Run() {
|
||||
vwp.taskQueueChan = make(chan func())
|
||||
for i := 0; i < vwp.maxWorker; i++ {
|
||||
go func() {
|
||||
for task := range vwp.taskQueueChan {
|
||||
if task != nil {
|
||||
task()
|
||||
vwp.wg.Done()
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// Stop stop WorkerPool
|
||||
func (vwp *WorkerPool) Stop() {
|
||||
vwp.wg.Wait()
|
||||
// close task queue channel, so that all goruotines listening from it stop
|
||||
close(vwp.taskQueueChan)
|
||||
}
|
||||
|
||||
// AddTask adds a task to WorkerPool
|
||||
func (vwp *WorkerPool) AddTask(task func()) {
|
||||
vwp.wg.Add(1)
|
||||
vwp.taskQueueChan <- task
|
||||
}
|
||||
57
common/utils/workerpool/workerpool_test.go
Normal file
57
common/utils/workerpool/workerpool_test.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package workerpool_test
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"scroll-tech/common/utils/workerpool"
|
||||
)
|
||||
|
||||
func TestWorkerPool(t *testing.T) {
|
||||
as := assert.New(t)
|
||||
|
||||
vwp := workerpool.NewWorkerPool(2)
|
||||
vwp.Run()
|
||||
var cnt int32 = 3
|
||||
|
||||
task := func() {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
atomic.AddInt32(&cnt, -1)
|
||||
}
|
||||
|
||||
go vwp.AddTask(task)
|
||||
go vwp.AddTask(task)
|
||||
go vwp.AddTask(task)
|
||||
|
||||
time.Sleep(600 * time.Millisecond)
|
||||
as.Equal(int32(1), atomic.LoadInt32(&cnt))
|
||||
vwp.Stop()
|
||||
as.Equal(int32(0), atomic.LoadInt32(&cnt))
|
||||
|
||||
}
|
||||
|
||||
func TestWorkerPoolStopAndStart(t *testing.T) {
|
||||
as := assert.New(t)
|
||||
vwp := workerpool.NewWorkerPool(1)
|
||||
var cnt int32 = 3
|
||||
|
||||
task := func() {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
atomic.AddInt32(&cnt, -1)
|
||||
}
|
||||
|
||||
vwp.Run()
|
||||
vwp.AddTask(task)
|
||||
vwp.AddTask(task)
|
||||
vwp.Stop()
|
||||
as.Equal(int32(1), atomic.LoadInt32(&cnt))
|
||||
|
||||
vwp.Run()
|
||||
vwp.AddTask(task)
|
||||
vwp.Stop()
|
||||
as.Equal(int32(0), atomic.LoadInt32(&cnt))
|
||||
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
var tag = "alpha-v2.0"
|
||||
var tag = "alpha-v2.5"
|
||||
|
||||
var commit = func() string {
|
||||
if info, ok := debug.ReadBuildInfo(); ok {
|
||||
|
||||
@@ -3,3 +3,6 @@ artifacts
|
||||
cache
|
||||
coverage*
|
||||
gasReporterOutput.json
|
||||
src/libraries/verifier/ZkTrieVerifier.sol
|
||||
src/libraries/verifier/PatriciaMerkleTrieVerifier.sol
|
||||
src/L2/predeploys/L1BlockContainer.sol
|
||||
|
||||
@@ -2,5 +2,17 @@
|
||||
"printWidth": 120,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"bracketSpacing": true
|
||||
"bracketSpacing": true,
|
||||
"overrides": [
|
||||
{
|
||||
"files": "src/**/*.sol",
|
||||
"options": {
|
||||
"printWidth": 120,
|
||||
"tabWidth": 4,
|
||||
"useTabs": false,
|
||||
"singleQuote": false,
|
||||
"bracketSpacing": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -163,17 +163,6 @@ function owner() external view returns (address)
|
||||
|---|---|---|
|
||||
| _0 | address | undefined |
|
||||
|
||||
### pause
|
||||
|
||||
```solidity
|
||||
function pause() external nonpayable
|
||||
```
|
||||
|
||||
Pause the contract
|
||||
|
||||
*This function can only called by contract owner.*
|
||||
|
||||
|
||||
### paused
|
||||
|
||||
```solidity
|
||||
@@ -264,6 +253,26 @@ The address of Rollup contract.
|
||||
|
||||
### sendMessage
|
||||
|
||||
```solidity
|
||||
function sendMessage(address _to, uint256 _value, bytes _message, uint256 _gasLimit, address _refundAddress) external payable
|
||||
```
|
||||
|
||||
Send cross chain message from L1 to L2 or L2 to L1.
|
||||
|
||||
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
|---|---|---|
|
||||
| _to | address | undefined |
|
||||
| _value | uint256 | undefined |
|
||||
| _message | bytes | undefined |
|
||||
| _gasLimit | uint256 | undefined |
|
||||
| _refundAddress | address | undefined |
|
||||
|
||||
### sendMessage
|
||||
|
||||
```solidity
|
||||
function sendMessage(address _to, uint256 _value, bytes _message, uint256 _gasLimit) external payable
|
||||
```
|
||||
@@ -281,6 +290,22 @@ Send cross chain message from L1 to L2 or L2 to L1.
|
||||
| _message | bytes | undefined |
|
||||
| _gasLimit | uint256 | undefined |
|
||||
|
||||
### setPause
|
||||
|
||||
```solidity
|
||||
function setPause(bool _status) external nonpayable
|
||||
```
|
||||
|
||||
Pause the contract
|
||||
|
||||
*This function can only called by contract owner.*
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
|---|---|---|
|
||||
| _status | bool | The pause status to update. |
|
||||
|
||||
### transferOwnership
|
||||
|
||||
```solidity
|
||||
|
||||
@@ -212,17 +212,6 @@ function owner() external view returns (address)
|
||||
|---|---|---|
|
||||
| _0 | address | undefined |
|
||||
|
||||
### pause
|
||||
|
||||
```solidity
|
||||
function pause() external nonpayable
|
||||
```
|
||||
|
||||
Pause the contract
|
||||
|
||||
*This function can only called by contract owner.*
|
||||
|
||||
|
||||
### paused
|
||||
|
||||
```solidity
|
||||
@@ -294,6 +283,26 @@ function retryMessageWithProof(address _from, address _to, uint256 _value, uint2
|
||||
|
||||
### sendMessage
|
||||
|
||||
```solidity
|
||||
function sendMessage(address _to, uint256 _value, bytes _message, uint256 _gasLimit, address _refundAddress) external payable
|
||||
```
|
||||
|
||||
Send cross chain message from L1 to L2 or L2 to L1.
|
||||
|
||||
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
|---|---|---|
|
||||
| _to | address | undefined |
|
||||
| _value | uint256 | undefined |
|
||||
| _message | bytes | undefined |
|
||||
| _gasLimit | uint256 | undefined |
|
||||
| _refundAddress | address | undefined |
|
||||
|
||||
### sendMessage
|
||||
|
||||
```solidity
|
||||
function sendMessage(address _to, uint256 _value, bytes _message, uint256 _gasLimit) external payable
|
||||
```
|
||||
@@ -311,6 +320,22 @@ Send cross chain message from L1 to L2 or L2 to L1.
|
||||
| _message | bytes | undefined |
|
||||
| _gasLimit | uint256 | undefined |
|
||||
|
||||
### setPause
|
||||
|
||||
```solidity
|
||||
function setPause(bool _status) external nonpayable
|
||||
```
|
||||
|
||||
Pause the contract
|
||||
|
||||
*This function can only called by contract owner.*
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
|---|---|---|
|
||||
| _status | bool | The pause status to update. |
|
||||
|
||||
### transferOwnership
|
||||
|
||||
```solidity
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { ProxyAdmin } from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
|
||||
import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
|
||||
import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
|
||||
import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
|
||||
|
||||
@@ -2,55 +2,55 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { IScrollMessenger } from "../libraries/IScrollMessenger.sol";
|
||||
import {IScrollMessenger} from "../libraries/IScrollMessenger.sol";
|
||||
|
||||
interface IL1ScrollMessenger is IScrollMessenger {
|
||||
/***********
|
||||
* Structs *
|
||||
***********/
|
||||
/***********
|
||||
* Structs *
|
||||
***********/
|
||||
|
||||
struct L2MessageProof {
|
||||
// The hash of the batch where the message belongs to.
|
||||
bytes32 batchHash;
|
||||
// Concatenation of merkle proof for withdraw merkle trie.
|
||||
bytes merkleProof;
|
||||
}
|
||||
struct L2MessageProof {
|
||||
// The hash of the batch where the message belongs to.
|
||||
bytes32 batchHash;
|
||||
// Concatenation of merkle proof for withdraw merkle trie.
|
||||
bytes merkleProof;
|
||||
}
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
/// @notice Relay a L2 => L1 message with message proof.
|
||||
/// @param from The address of the sender of the message.
|
||||
/// @param to The address of the recipient of the message.
|
||||
/// @param value The msg.value passed to the message call.
|
||||
/// @param nonce The nonce of the message to avoid replay attack.
|
||||
/// @param message The content of the message.
|
||||
/// @param proof The proof used to verify the correctness of the transaction.
|
||||
function relayMessageWithProof(
|
||||
address from,
|
||||
address to,
|
||||
uint256 value,
|
||||
uint256 nonce,
|
||||
bytes memory message,
|
||||
L2MessageProof memory proof
|
||||
) external;
|
||||
/// @notice Relay a L2 => L1 message with message proof.
|
||||
/// @param from The address of the sender of the message.
|
||||
/// @param to The address of the recipient of the message.
|
||||
/// @param value The msg.value passed to the message call.
|
||||
/// @param nonce The nonce of the message to avoid replay attack.
|
||||
/// @param message The content of the message.
|
||||
/// @param proof The proof used to verify the correctness of the transaction.
|
||||
function relayMessageWithProof(
|
||||
address from,
|
||||
address to,
|
||||
uint256 value,
|
||||
uint256 nonce,
|
||||
bytes memory message,
|
||||
L2MessageProof memory proof
|
||||
) external;
|
||||
|
||||
/// @notice Replay an exsisting message.
|
||||
/// @param from The address of the sender of the message.
|
||||
/// @param to The address of the recipient of the message.
|
||||
/// @param value The msg.value passed to the message call.
|
||||
/// @param queueIndex The queue index for the message to replay.
|
||||
/// @param message The content of the message.
|
||||
/// @param oldGasLimit Original gas limit used to send the message.
|
||||
/// @param newGasLimit New gas limit to be used for this message.
|
||||
function replayMessage(
|
||||
address from,
|
||||
address to,
|
||||
uint256 value,
|
||||
uint256 queueIndex,
|
||||
bytes memory message,
|
||||
uint32 oldGasLimit,
|
||||
uint32 newGasLimit
|
||||
) external;
|
||||
/// @notice Replay an exsisting message.
|
||||
/// @param from The address of the sender of the message.
|
||||
/// @param to The address of the recipient of the message.
|
||||
/// @param value The msg.value passed to the message call.
|
||||
/// @param queueIndex The queue index for the message to replay.
|
||||
/// @param message The content of the message.
|
||||
/// @param oldGasLimit Original gas limit used to send the message.
|
||||
/// @param newGasLimit New gas limit to be used for this message.
|
||||
function replayMessage(
|
||||
address from,
|
||||
address to,
|
||||
uint256 value,
|
||||
uint256 queueIndex,
|
||||
bytes memory message,
|
||||
uint32 oldGasLimit,
|
||||
uint32 newGasLimit
|
||||
) external;
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
|
||||
import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
|
||||
|
||||
import { IScrollChain } from "./rollup/IScrollChain.sol";
|
||||
import { IL1MessageQueue } from "./rollup/IL1MessageQueue.sol";
|
||||
import { IL1ScrollMessenger } from "./IL1ScrollMessenger.sol";
|
||||
import { ScrollConstants } from "../libraries/constants/ScrollConstants.sol";
|
||||
import { IScrollMessenger } from "../libraries/IScrollMessenger.sol";
|
||||
import { ScrollMessengerBase } from "../libraries/ScrollMessengerBase.sol";
|
||||
import { WithdrawTrieVerifier } from "../libraries/verifier/WithdrawTrieVerifier.sol";
|
||||
import {IScrollChain} from "./rollup/IScrollChain.sol";
|
||||
import {IL1MessageQueue} from "./rollup/IL1MessageQueue.sol";
|
||||
import {IL1ScrollMessenger} from "./IL1ScrollMessenger.sol";
|
||||
import {ScrollConstants} from "../libraries/constants/ScrollConstants.sol";
|
||||
import {IScrollMessenger} from "../libraries/IScrollMessenger.sol";
|
||||
import {ScrollMessengerBase} from "../libraries/ScrollMessengerBase.sol";
|
||||
import {WithdrawTrieVerifier} from "../libraries/verifier/WithdrawTrieVerifier.sol";
|
||||
|
||||
// solhint-disable avoid-low-level-calls
|
||||
|
||||
@@ -25,178 +25,228 @@ import { WithdrawTrieVerifier } from "../libraries/verifier/WithdrawTrieVerifier
|
||||
/// @dev All deposited Ether (including `WETH` deposited throng `L1WETHGateway`) will locked in
|
||||
/// this contract.
|
||||
contract L1ScrollMessenger is PausableUpgradeable, ScrollMessengerBase, IL1ScrollMessenger {
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
/// @notice Mapping from relay id to relay status.
|
||||
mapping(bytes32 => bool) public isL1MessageRelayed;
|
||||
/// @notice Mapping from relay id to relay status.
|
||||
mapping(bytes32 => bool) public isL1MessageRelayed;
|
||||
|
||||
/// @notice Mapping from L1 message hash to sent status.
|
||||
mapping(bytes32 => bool) public isL1MessageSent;
|
||||
/// @notice Mapping from L1 message hash to sent status.
|
||||
mapping(bytes32 => bool) public isL1MessageSent;
|
||||
|
||||
/// @notice Mapping from L2 message hash to a boolean value indicating if the message has been successfully executed.
|
||||
mapping(bytes32 => bool) public isL2MessageExecuted;
|
||||
/// @notice Mapping from L2 message hash to a boolean value indicating if the message has been successfully executed.
|
||||
mapping(bytes32 => bool) public isL2MessageExecuted;
|
||||
|
||||
/// @notice The address of Rollup contract.
|
||||
address public rollup;
|
||||
/// @notice The address of Rollup contract.
|
||||
address public rollup;
|
||||
|
||||
/// @notice The address of L1MessageQueue contract.
|
||||
address public messageQueue;
|
||||
/// @notice The address of L1MessageQueue contract.
|
||||
address public messageQueue;
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
// @note move to ScrollMessengerBase in next big refactor
|
||||
/// @dev The status of for non-reentrant check.
|
||||
uint256 private _lock_status;
|
||||
|
||||
/// @notice Initialize the storage of L1ScrollMessenger.
|
||||
/// @param _counterpart The address of L2ScrollMessenger contract in L2.
|
||||
/// @param _feeVault The address of fee vault, which will be used to collect relayer fee.
|
||||
/// @param _rollup The address of ScrollChain contract.
|
||||
/// @param _messageQueue The address of L1MessageQueue contract.
|
||||
function initialize(
|
||||
address _counterpart,
|
||||
address _feeVault,
|
||||
address _rollup,
|
||||
address _messageQueue
|
||||
) public initializer {
|
||||
PausableUpgradeable.__Pausable_init();
|
||||
ScrollMessengerBase._initialize(_counterpart, _feeVault);
|
||||
/**********************
|
||||
* Function Modifiers *
|
||||
**********************/
|
||||
|
||||
rollup = _rollup;
|
||||
messageQueue = _messageQueue;
|
||||
modifier nonReentrant() {
|
||||
// On the first call to nonReentrant, _notEntered will be true
|
||||
require(_lock_status != _ENTERED, "ReentrancyGuard: reentrant call");
|
||||
|
||||
// initialize to a nonzero value
|
||||
xDomainMessageSender = ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER;
|
||||
}
|
||||
// Any calls to nonReentrant after this point will fail
|
||||
_lock_status = _ENTERED;
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
_;
|
||||
|
||||
/// @inheritdoc IScrollMessenger
|
||||
function sendMessage(
|
||||
address _to,
|
||||
uint256 _value,
|
||||
bytes memory _message,
|
||||
uint256 _gasLimit
|
||||
) external payable override whenNotPaused {
|
||||
address _messageQueue = messageQueue; // gas saving
|
||||
address _counterpart = counterpart; // gas saving
|
||||
|
||||
// compute the actual cross domain message calldata.
|
||||
uint256 _messageNonce = IL1MessageQueue(_messageQueue).nextCrossDomainMessageIndex();
|
||||
bytes memory _xDomainCalldata = _encodeXDomainCalldata(msg.sender, _to, _value, _messageNonce, _message);
|
||||
|
||||
// compute and deduct the messaging fee to fee vault.
|
||||
uint256 _fee = IL1MessageQueue(_messageQueue).estimateCrossDomainMessageFee(
|
||||
address(this),
|
||||
_counterpart,
|
||||
_xDomainCalldata,
|
||||
_gasLimit
|
||||
);
|
||||
require(msg.value >= _fee + _value, "Insufficient msg.value");
|
||||
if (_fee > 0) {
|
||||
(bool _success, ) = feeVault.call{ value: _fee }("");
|
||||
require(_success, "Failed to deduct the fee");
|
||||
// By storing the original value once again, a refund is triggered (see
|
||||
// https://eips.ethereum.org/EIPS/eip-2200)
|
||||
_lock_status = _NOT_ENTERED;
|
||||
}
|
||||
|
||||
// append message to L1MessageQueue
|
||||
IL1MessageQueue(_messageQueue).appendCrossDomainMessage(_counterpart, _gasLimit, _xDomainCalldata);
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
// record the message hash for future use.
|
||||
bytes32 _xDomainCalldataHash = keccak256(_xDomainCalldata);
|
||||
/// @notice Initialize the storage of L1ScrollMessenger.
|
||||
/// @param _counterpart The address of L2ScrollMessenger contract in L2.
|
||||
/// @param _feeVault The address of fee vault, which will be used to collect relayer fee.
|
||||
/// @param _rollup The address of ScrollChain contract.
|
||||
/// @param _messageQueue The address of L1MessageQueue contract.
|
||||
function initialize(
|
||||
address _counterpart,
|
||||
address _feeVault,
|
||||
address _rollup,
|
||||
address _messageQueue
|
||||
) public initializer {
|
||||
PausableUpgradeable.__Pausable_init();
|
||||
ScrollMessengerBase._initialize(_counterpart, _feeVault);
|
||||
|
||||
// normally this won't happen, since each message has different nonce, but just in case.
|
||||
require(!isL1MessageSent[_xDomainCalldataHash], "Duplicated message");
|
||||
isL1MessageSent[_xDomainCalldataHash] = true;
|
||||
rollup = _rollup;
|
||||
messageQueue = _messageQueue;
|
||||
|
||||
emit SentMessage(msg.sender, _to, _value, _messageNonce, _gasLimit, _message);
|
||||
|
||||
// refund fee to tx.origin
|
||||
unchecked {
|
||||
uint256 _refund = msg.value - _fee - _value;
|
||||
if (_refund > 0) {
|
||||
(bool _success, ) = tx.origin.call{ value: _refund }("");
|
||||
require(_success, "Failed to refund the fee");
|
||||
}
|
||||
// initialize to a nonzero value
|
||||
xDomainMessageSender = ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER;
|
||||
}
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ScrollMessenger
|
||||
function relayMessageWithProof(
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _value,
|
||||
uint256 _nonce,
|
||||
bytes memory _message,
|
||||
L2MessageProof memory _proof
|
||||
) external override whenNotPaused onlyWhitelistedSender(msg.sender) {
|
||||
require(xDomainMessageSender == ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER, "Message is already in execution");
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
bytes32 _xDomainCalldataHash = keccak256(_encodeXDomainCalldata(_from, _to, _value, _nonce, _message));
|
||||
require(!isL2MessageExecuted[_xDomainCalldataHash], "Message was already successfully executed");
|
||||
/// @inheritdoc IScrollMessenger
|
||||
function sendMessage(
|
||||
address _to,
|
||||
uint256 _value,
|
||||
bytes memory _message,
|
||||
uint256 _gasLimit
|
||||
) external payable override whenNotPaused {
|
||||
_sendMessage(_to, _value, _message, _gasLimit, tx.origin);
|
||||
}
|
||||
|
||||
{
|
||||
address _rollup = rollup;
|
||||
require(IScrollChain(_rollup).isBatchFinalized(_proof.batchHash), "Batch is not finalized");
|
||||
// @note skip verify for now
|
||||
/*
|
||||
/// @inheritdoc IScrollMessenger
|
||||
function sendMessage(
|
||||
address _to,
|
||||
uint256 _value,
|
||||
bytes calldata _message,
|
||||
uint256 _gasLimit,
|
||||
address _refundAddress
|
||||
) external payable override whenNotPaused {
|
||||
_sendMessage(_to, _value, _message, _gasLimit, _refundAddress);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ScrollMessenger
|
||||
function relayMessageWithProof(
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _value,
|
||||
uint256 _nonce,
|
||||
bytes memory _message,
|
||||
L2MessageProof memory _proof
|
||||
) external override whenNotPaused onlyWhitelistedSender(msg.sender) {
|
||||
require(
|
||||
xDomainMessageSender == ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER,
|
||||
"Message is already in execution"
|
||||
);
|
||||
|
||||
bytes32 _xDomainCalldataHash = keccak256(_encodeXDomainCalldata(_from, _to, _value, _nonce, _message));
|
||||
require(!isL2MessageExecuted[_xDomainCalldataHash], "Message was already successfully executed");
|
||||
|
||||
{
|
||||
address _rollup = rollup;
|
||||
require(IScrollChain(_rollup).isBatchFinalized(_proof.batchHash), "Batch is not finalized");
|
||||
// @note skip verify for now
|
||||
/*
|
||||
bytes32 _messageRoot = IScrollChain(_rollup).getL2MessageRoot(_proof.batchHash);
|
||||
require(
|
||||
WithdrawTrieVerifier.verifyMerkleProof(_messageRoot, _xDomainCalldataHash, _nonce, _proof.merkleProof),
|
||||
"Invalid proof"
|
||||
);
|
||||
*/
|
||||
}
|
||||
|
||||
// @todo check more `_to` address to avoid attack.
|
||||
require(_to != messageQueue, "Forbid to call message queue");
|
||||
require(_to != address(this), "Forbid to call self");
|
||||
|
||||
// @note This usually will never happen, just in case.
|
||||
require(_from != xDomainMessageSender, "Invalid message sender");
|
||||
|
||||
xDomainMessageSender = _from;
|
||||
(bool success, ) = _to.call{value: _value}(_message);
|
||||
// reset value to refund gas.
|
||||
xDomainMessageSender = ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER;
|
||||
|
||||
if (success) {
|
||||
isL2MessageExecuted[_xDomainCalldataHash] = true;
|
||||
emit RelayedMessage(_xDomainCalldataHash);
|
||||
} else {
|
||||
emit FailedRelayedMessage(_xDomainCalldataHash);
|
||||
}
|
||||
|
||||
bytes32 _relayId = keccak256(abi.encodePacked(_xDomainCalldataHash, msg.sender, block.number));
|
||||
isL1MessageRelayed[_relayId] = true;
|
||||
}
|
||||
|
||||
// @todo check more `_to` address to avoid attack.
|
||||
require(_to != messageQueue, "Forbid to call message queue");
|
||||
require(_to != address(this), "Forbid to call self");
|
||||
|
||||
// @note This usually will never happen, just in case.
|
||||
require(_from != xDomainMessageSender, "Invalid message sender");
|
||||
|
||||
xDomainMessageSender = _from;
|
||||
(bool success, ) = _to.call{ value: _value }(_message);
|
||||
// reset value to refund gas.
|
||||
xDomainMessageSender = ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER;
|
||||
|
||||
if (success) {
|
||||
isL2MessageExecuted[_xDomainCalldataHash] = true;
|
||||
emit RelayedMessage(_xDomainCalldataHash);
|
||||
} else {
|
||||
emit FailedRelayedMessage(_xDomainCalldataHash);
|
||||
/// @inheritdoc IL1ScrollMessenger
|
||||
function replayMessage(
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _value,
|
||||
uint256 _queueIndex,
|
||||
bytes memory _message,
|
||||
uint32 _oldGasLimit,
|
||||
uint32 _newGasLimit
|
||||
) external override whenNotPaused {
|
||||
// @todo
|
||||
}
|
||||
|
||||
bytes32 _relayId = keccak256(abi.encodePacked(_xDomainCalldataHash, msg.sender, block.number));
|
||||
isL1MessageRelayed[_relayId] = true;
|
||||
}
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
/// @inheritdoc IL1ScrollMessenger
|
||||
function replayMessage(
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _value,
|
||||
uint256 _queueIndex,
|
||||
bytes memory _message,
|
||||
uint32 _oldGasLimit,
|
||||
uint32 _newGasLimit
|
||||
) external override whenNotPaused {
|
||||
// @todo
|
||||
}
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
/// @notice Pause the contract
|
||||
/// @dev This function can only called by contract owner.
|
||||
/// @param _status The pause status to update.
|
||||
function setPause(bool _status) external onlyOwner {
|
||||
if (_status) {
|
||||
_pause();
|
||||
} else {
|
||||
_unpause();
|
||||
/// @notice Pause the contract
|
||||
/// @dev This function can only called by contract owner.
|
||||
/// @param _status The pause status to update.
|
||||
function setPause(bool _status) external onlyOwner {
|
||||
if (_status) {
|
||||
_pause();
|
||||
} else {
|
||||
_unpause();
|
||||
}
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
function _sendMessage(
|
||||
address _to,
|
||||
uint256 _value,
|
||||
bytes memory _message,
|
||||
uint256 _gasLimit,
|
||||
address _refundAddress
|
||||
) internal nonReentrant {
|
||||
address _messageQueue = messageQueue; // gas saving
|
||||
address _counterpart = counterpart; // gas saving
|
||||
|
||||
// compute the actual cross domain message calldata.
|
||||
uint256 _messageNonce = IL1MessageQueue(_messageQueue).nextCrossDomainMessageIndex();
|
||||
bytes memory _xDomainCalldata = _encodeXDomainCalldata(msg.sender, _to, _value, _messageNonce, _message);
|
||||
|
||||
// compute and deduct the messaging fee to fee vault.
|
||||
uint256 _fee = IL1MessageQueue(_messageQueue).estimateCrossDomainMessageFee(
|
||||
address(this),
|
||||
_counterpart,
|
||||
_xDomainCalldata,
|
||||
_gasLimit
|
||||
);
|
||||
require(msg.value >= _fee + _value, "Insufficient msg.value");
|
||||
if (_fee > 0) {
|
||||
(bool _success, ) = feeVault.call{value: _fee}("");
|
||||
require(_success, "Failed to deduct the fee");
|
||||
}
|
||||
|
||||
// append message to L1MessageQueue
|
||||
IL1MessageQueue(_messageQueue).appendCrossDomainMessage(_counterpart, _gasLimit, _xDomainCalldata);
|
||||
|
||||
// record the message hash for future use.
|
||||
bytes32 _xDomainCalldataHash = keccak256(_xDomainCalldata);
|
||||
|
||||
// normally this won't happen, since each message has different nonce, but just in case.
|
||||
require(!isL1MessageSent[_xDomainCalldataHash], "Duplicated message");
|
||||
isL1MessageSent[_xDomainCalldataHash] = true;
|
||||
|
||||
emit SentMessage(msg.sender, _to, _value, _messageNonce, _gasLimit, _message);
|
||||
|
||||
// refund fee to tx.origin
|
||||
unchecked {
|
||||
uint256 _refund = msg.value - _fee - _value;
|
||||
if (_refund > 0) {
|
||||
(bool _success, ) = _refundAddress.call{value: _refund}("");
|
||||
require(_success, "Failed to refund the fee");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,163 +4,163 @@ pragma solidity ^0.8.0;
|
||||
|
||||
/// @title The interface for the ERC1155 cross chain gateway in layer 1.
|
||||
interface IL1ERC1155Gateway {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
|
||||
/// @notice Emitted when the ERC1155 NFT is transfered to recipient in layer 1.
|
||||
/// @param _l1Token The address of ERC1155 NFT in layer 1.
|
||||
/// @param _l2Token The address of ERC1155 NFT in layer 2.
|
||||
/// @param _from The address of sender in layer 2.
|
||||
/// @param _to The address of recipient in layer 1.
|
||||
/// @param _tokenId The token id of the ERC1155 NFT to withdraw from layer 2.
|
||||
/// @param _amount The number of token to withdraw from layer 2.
|
||||
event FinalizeWithdrawERC1155(
|
||||
address indexed _l1Token,
|
||||
address indexed _l2Token,
|
||||
address indexed _from,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount
|
||||
);
|
||||
/// @notice Emitted when the ERC1155 NFT is transfered to recipient in layer 1.
|
||||
/// @param _l1Token The address of ERC1155 NFT in layer 1.
|
||||
/// @param _l2Token The address of ERC1155 NFT in layer 2.
|
||||
/// @param _from The address of sender in layer 2.
|
||||
/// @param _to The address of recipient in layer 1.
|
||||
/// @param _tokenId The token id of the ERC1155 NFT to withdraw from layer 2.
|
||||
/// @param _amount The number of token to withdraw from layer 2.
|
||||
event FinalizeWithdrawERC1155(
|
||||
address indexed _l1Token,
|
||||
address indexed _l2Token,
|
||||
address indexed _from,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount
|
||||
);
|
||||
|
||||
/// @notice Emitted when the ERC1155 NFT is batch transfered to recipient in layer 1.
|
||||
/// @param _l1Token The address of ERC1155 NFT in layer 1.
|
||||
/// @param _l2Token The address of ERC1155 NFT in layer 2.
|
||||
/// @param _from The address of sender in layer 2.
|
||||
/// @param _to The address of recipient in layer 1.
|
||||
/// @param _tokenIds The list of token ids of the ERC1155 NFT to withdraw from layer 2.
|
||||
/// @param _amounts The list of corresponding number of token to withdraw from layer 2.
|
||||
event FinalizeBatchWithdrawERC1155(
|
||||
address indexed _l1Token,
|
||||
address indexed _l2Token,
|
||||
address indexed _from,
|
||||
address _to,
|
||||
uint256[] _tokenIds,
|
||||
uint256[] _amounts
|
||||
);
|
||||
/// @notice Emitted when the ERC1155 NFT is batch transfered to recipient in layer 1.
|
||||
/// @param _l1Token The address of ERC1155 NFT in layer 1.
|
||||
/// @param _l2Token The address of ERC1155 NFT in layer 2.
|
||||
/// @param _from The address of sender in layer 2.
|
||||
/// @param _to The address of recipient in layer 1.
|
||||
/// @param _tokenIds The list of token ids of the ERC1155 NFT to withdraw from layer 2.
|
||||
/// @param _amounts The list of corresponding number of token to withdraw from layer 2.
|
||||
event FinalizeBatchWithdrawERC1155(
|
||||
address indexed _l1Token,
|
||||
address indexed _l2Token,
|
||||
address indexed _from,
|
||||
address _to,
|
||||
uint256[] _tokenIds,
|
||||
uint256[] _amounts
|
||||
);
|
||||
|
||||
/// @notice Emitted when the ERC1155 NFT is deposited to gateway in layer 1.
|
||||
/// @param _l1Token The address of ERC1155 NFT in layer 1.
|
||||
/// @param _l2Token The address of ERC1155 NFT in layer 2.
|
||||
/// @param _from The address of sender in layer 1.
|
||||
/// @param _to The address of recipient in layer 2.
|
||||
/// @param _tokenId The token id of the ERC1155 NFT to deposit in layer 1.
|
||||
/// @param _amount The number of token to deposit in layer 1.
|
||||
event DepositERC1155(
|
||||
address indexed _l1Token,
|
||||
address indexed _l2Token,
|
||||
address indexed _from,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount
|
||||
);
|
||||
/// @notice Emitted when the ERC1155 NFT is deposited to gateway in layer 1.
|
||||
/// @param _l1Token The address of ERC1155 NFT in layer 1.
|
||||
/// @param _l2Token The address of ERC1155 NFT in layer 2.
|
||||
/// @param _from The address of sender in layer 1.
|
||||
/// @param _to The address of recipient in layer 2.
|
||||
/// @param _tokenId The token id of the ERC1155 NFT to deposit in layer 1.
|
||||
/// @param _amount The number of token to deposit in layer 1.
|
||||
event DepositERC1155(
|
||||
address indexed _l1Token,
|
||||
address indexed _l2Token,
|
||||
address indexed _from,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount
|
||||
);
|
||||
|
||||
/// @notice Emitted when the ERC1155 NFT is batch deposited to gateway in layer 1.
|
||||
/// @param _l1Token The address of ERC1155 NFT in layer 1.
|
||||
/// @param _l2Token The address of ERC1155 NFT in layer 2.
|
||||
/// @param _from The address of sender in layer 1.
|
||||
/// @param _to The address of recipient in layer 2.
|
||||
/// @param _tokenIds The list of token ids of the ERC1155 NFT to deposit in layer 1.
|
||||
/// @param _amounts The list of corresponding number of token to deposit in layer 1.
|
||||
event BatchDepositERC1155(
|
||||
address indexed _l1Token,
|
||||
address indexed _l2Token,
|
||||
address indexed _from,
|
||||
address _to,
|
||||
uint256[] _tokenIds,
|
||||
uint256[] _amounts
|
||||
);
|
||||
/// @notice Emitted when the ERC1155 NFT is batch deposited to gateway in layer 1.
|
||||
/// @param _l1Token The address of ERC1155 NFT in layer 1.
|
||||
/// @param _l2Token The address of ERC1155 NFT in layer 2.
|
||||
/// @param _from The address of sender in layer 1.
|
||||
/// @param _to The address of recipient in layer 2.
|
||||
/// @param _tokenIds The list of token ids of the ERC1155 NFT to deposit in layer 1.
|
||||
/// @param _amounts The list of corresponding number of token to deposit in layer 1.
|
||||
event BatchDepositERC1155(
|
||||
address indexed _l1Token,
|
||||
address indexed _l2Token,
|
||||
address indexed _from,
|
||||
address _to,
|
||||
uint256[] _tokenIds,
|
||||
uint256[] _amounts
|
||||
);
|
||||
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/// @notice Deposit some ERC1155 NFT to caller's account on layer 2.
|
||||
/// @param _token The address of ERC1155 NFT in layer 1.
|
||||
/// @param _tokenId The token id to deposit.
|
||||
/// @param _amount The amount of token to deposit.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
|
||||
function depositERC1155(
|
||||
address _token,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable;
|
||||
/// @notice Deposit some ERC1155 NFT to caller's account on layer 2.
|
||||
/// @param _token The address of ERC1155 NFT in layer 1.
|
||||
/// @param _tokenId The token id to deposit.
|
||||
/// @param _amount The amount of token to deposit.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
|
||||
function depositERC1155(
|
||||
address _token,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable;
|
||||
|
||||
/// @notice Deposit some ERC1155 NFT to a recipient's account on layer 2.
|
||||
/// @param _token The address of ERC1155 NFT in layer 1.
|
||||
/// @param _to The address of recipient in layer 2.
|
||||
/// @param _tokenId The token id to deposit.
|
||||
/// @param _amount The amount of token to deposit.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
|
||||
function depositERC1155(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable;
|
||||
/// @notice Deposit some ERC1155 NFT to a recipient's account on layer 2.
|
||||
/// @param _token The address of ERC1155 NFT in layer 1.
|
||||
/// @param _to The address of recipient in layer 2.
|
||||
/// @param _tokenId The token id to deposit.
|
||||
/// @param _amount The amount of token to deposit.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
|
||||
function depositERC1155(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable;
|
||||
|
||||
/// @notice Deposit a list of some ERC1155 NFT to caller's account on layer 2.
|
||||
/// @param _token The address of ERC1155 NFT in layer 1.
|
||||
/// @param _tokenIds The list of token ids to deposit.
|
||||
/// @param _amounts The list of corresponding number of token to deposit.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
|
||||
function batchDepositERC1155(
|
||||
address _token,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256[] calldata _amounts,
|
||||
uint256 _gasLimit
|
||||
) external payable;
|
||||
/// @notice Deposit a list of some ERC1155 NFT to caller's account on layer 2.
|
||||
/// @param _token The address of ERC1155 NFT in layer 1.
|
||||
/// @param _tokenIds The list of token ids to deposit.
|
||||
/// @param _amounts The list of corresponding number of token to deposit.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
|
||||
function batchDepositERC1155(
|
||||
address _token,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256[] calldata _amounts,
|
||||
uint256 _gasLimit
|
||||
) external payable;
|
||||
|
||||
/// @notice Deposit a list of some ERC1155 NFT to a recipient's account on layer 2.
|
||||
/// @param _token The address of ERC1155 NFT in layer 1.
|
||||
/// @param _to The address of recipient in layer 2.
|
||||
/// @param _tokenIds The list of token ids to deposit.
|
||||
/// @param _amounts The list of corresponding number of token to deposit.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
|
||||
function batchDepositERC1155(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256[] calldata _amounts,
|
||||
uint256 _gasLimit
|
||||
) external payable;
|
||||
/// @notice Deposit a list of some ERC1155 NFT to a recipient's account on layer 2.
|
||||
/// @param _token The address of ERC1155 NFT in layer 1.
|
||||
/// @param _to The address of recipient in layer 2.
|
||||
/// @param _tokenIds The list of token ids to deposit.
|
||||
/// @param _amounts The list of corresponding number of token to deposit.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
|
||||
function batchDepositERC1155(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256[] calldata _amounts,
|
||||
uint256 _gasLimit
|
||||
) external payable;
|
||||
|
||||
/// @notice Complete ERC1155 withdraw from layer 2 to layer 1 and send fund to recipient's account in layer 1.
|
||||
/// The function should only be called by L1ScrollMessenger.
|
||||
/// The function should also only be called by L2ERC1155Gateway in layer 2.
|
||||
/// @param _l1Token The address of corresponding layer 1 token.
|
||||
/// @param _l2Token The address of corresponding layer 2 token.
|
||||
/// @param _from The address of account who withdraw the token in layer 2.
|
||||
/// @param _to The address of recipient in layer 1 to receive the token.
|
||||
/// @param _tokenId The token id to withdraw.
|
||||
/// @param _amount The amount of token to withdraw.
|
||||
function finalizeWithdrawERC1155(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount
|
||||
) external;
|
||||
/// @notice Complete ERC1155 withdraw from layer 2 to layer 1 and send fund to recipient's account in layer 1.
|
||||
/// The function should only be called by L1ScrollMessenger.
|
||||
/// The function should also only be called by L2ERC1155Gateway in layer 2.
|
||||
/// @param _l1Token The address of corresponding layer 1 token.
|
||||
/// @param _l2Token The address of corresponding layer 2 token.
|
||||
/// @param _from The address of account who withdraw the token in layer 2.
|
||||
/// @param _to The address of recipient in layer 1 to receive the token.
|
||||
/// @param _tokenId The token id to withdraw.
|
||||
/// @param _amount The amount of token to withdraw.
|
||||
function finalizeWithdrawERC1155(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount
|
||||
) external;
|
||||
|
||||
/// @notice Complete ERC1155 batch withdraw from layer 2 to layer 1 and send fund to recipient's account in layer 1.
|
||||
/// The function should only be called by L1ScrollMessenger.
|
||||
/// The function should also only be called by L2ERC1155Gateway in layer 2.
|
||||
/// @param _l1Token The address of corresponding layer 1 token.
|
||||
/// @param _l2Token The address of corresponding layer 2 token.
|
||||
/// @param _from The address of account who withdraw the token in layer 2.
|
||||
/// @param _to The address of recipient in layer 1 to receive the token.
|
||||
/// @param _tokenIds The list of token ids to withdraw.
|
||||
/// @param _amounts The list of corresponding number of token to withdraw.
|
||||
function finalizeBatchWithdrawERC1155(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256[] calldata _amounts
|
||||
) external;
|
||||
/// @notice Complete ERC1155 batch withdraw from layer 2 to layer 1 and send fund to recipient's account in layer 1.
|
||||
/// The function should only be called by L1ScrollMessenger.
|
||||
/// The function should also only be called by L2ERC1155Gateway in layer 2.
|
||||
/// @param _l1Token The address of corresponding layer 1 token.
|
||||
/// @param _l2Token The address of corresponding layer 2 token.
|
||||
/// @param _from The address of account who withdraw the token in layer 2.
|
||||
/// @param _to The address of recipient in layer 1 to receive the token.
|
||||
/// @param _tokenIds The list of token ids to withdraw.
|
||||
/// @param _amounts The list of corresponding number of token to withdraw.
|
||||
function finalizeBatchWithdrawERC1155(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256[] calldata _amounts
|
||||
) external;
|
||||
}
|
||||
|
||||
@@ -3,109 +3,109 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
interface IL1ERC20Gateway {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
|
||||
/// @notice Emitted when ERC20 token is withdrawn from L2 to L1 and transfer to recipient.
|
||||
/// @param l1Token The address of the token in L1.
|
||||
/// @param l2Token The address of the token in L2.
|
||||
/// @param from The address of sender in L2.
|
||||
/// @param to The address of recipient in L1.
|
||||
/// @param amount The amount of token withdrawn from L2 to L1.
|
||||
/// @param data The optional calldata passed to recipient in L1.
|
||||
event FinalizeWithdrawERC20(
|
||||
address indexed l1Token,
|
||||
address indexed l2Token,
|
||||
address indexed from,
|
||||
address to,
|
||||
uint256 amount,
|
||||
bytes data
|
||||
);
|
||||
/// @notice Emitted when ERC20 token is withdrawn from L2 to L1 and transfer to recipient.
|
||||
/// @param l1Token The address of the token in L1.
|
||||
/// @param l2Token The address of the token in L2.
|
||||
/// @param from The address of sender in L2.
|
||||
/// @param to The address of recipient in L1.
|
||||
/// @param amount The amount of token withdrawn from L2 to L1.
|
||||
/// @param data The optional calldata passed to recipient in L1.
|
||||
event FinalizeWithdrawERC20(
|
||||
address indexed l1Token,
|
||||
address indexed l2Token,
|
||||
address indexed from,
|
||||
address to,
|
||||
uint256 amount,
|
||||
bytes data
|
||||
);
|
||||
|
||||
/// @notice Emitted when someone deposit ERC20 token from L1 to L2.
|
||||
/// @param l1Token The address of the token in L1.
|
||||
/// @param l2Token The address of the token in L2.
|
||||
/// @param from The address of sender in L1.
|
||||
/// @param to The address of recipient in L2.
|
||||
/// @param amount The amount of token will be deposited from L1 to L2.
|
||||
/// @param data The optional calldata passed to recipient in L2.
|
||||
event DepositERC20(
|
||||
address indexed l1Token,
|
||||
address indexed l2Token,
|
||||
address indexed from,
|
||||
address to,
|
||||
uint256 amount,
|
||||
bytes data
|
||||
);
|
||||
/// @notice Emitted when someone deposit ERC20 token from L1 to L2.
|
||||
/// @param l1Token The address of the token in L1.
|
||||
/// @param l2Token The address of the token in L2.
|
||||
/// @param from The address of sender in L1.
|
||||
/// @param to The address of recipient in L2.
|
||||
/// @param amount The amount of token will be deposited from L1 to L2.
|
||||
/// @param data The optional calldata passed to recipient in L2.
|
||||
event DepositERC20(
|
||||
address indexed l1Token,
|
||||
address indexed l2Token,
|
||||
address indexed from,
|
||||
address to,
|
||||
uint256 amount,
|
||||
bytes data
|
||||
);
|
||||
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/// @notice Return the corresponding l2 token address given l1 token address.
|
||||
/// @param _l1Token The address of l1 token.
|
||||
function getL2ERC20Address(address _l1Token) external view returns (address);
|
||||
/// @notice Return the corresponding l2 token address given l1 token address.
|
||||
/// @param _l1Token The address of l1 token.
|
||||
function getL2ERC20Address(address _l1Token) external view returns (address);
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
/// @notice Deposit some token to a caller's account on L2.
|
||||
/// @dev Make this function payable to send relayer fee in Ether.
|
||||
/// @param _token The address of token in L1.
|
||||
/// @param _amount The amount of token to transfer.
|
||||
/// @param _gasLimit Gas limit required to complete the deposit on L2.
|
||||
function depositERC20(
|
||||
address _token,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable;
|
||||
/// @notice Deposit some token to a caller's account on L2.
|
||||
/// @dev Make this function payable to send relayer fee in Ether.
|
||||
/// @param _token The address of token in L1.
|
||||
/// @param _amount The amount of token to transfer.
|
||||
/// @param _gasLimit Gas limit required to complete the deposit on L2.
|
||||
function depositERC20(
|
||||
address _token,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable;
|
||||
|
||||
/// @notice Deposit some token to a recipient's account on L2.
|
||||
/// @dev Make this function payable to send relayer fee in Ether.
|
||||
/// @param _token The address of token in L1.
|
||||
/// @param _to The address of recipient's account on L2.
|
||||
/// @param _amount The amount of token to transfer.
|
||||
/// @param _gasLimit Gas limit required to complete the deposit on L2.
|
||||
function depositERC20(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable;
|
||||
/// @notice Deposit some token to a recipient's account on L2.
|
||||
/// @dev Make this function payable to send relayer fee in Ether.
|
||||
/// @param _token The address of token in L1.
|
||||
/// @param _to The address of recipient's account on L2.
|
||||
/// @param _amount The amount of token to transfer.
|
||||
/// @param _gasLimit Gas limit required to complete the deposit on L2.
|
||||
function depositERC20(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable;
|
||||
|
||||
/// @notice Deposit some token to a recipient's account on L2 and call.
|
||||
/// @dev Make this function payable to send relayer fee in Ether.
|
||||
/// @param _token The address of token in L1.
|
||||
/// @param _to The address of recipient's account on L2.
|
||||
/// @param _amount The amount of token to transfer.
|
||||
/// @param _data Optional data to forward to recipient's account.
|
||||
/// @param _gasLimit Gas limit required to complete the deposit on L2.
|
||||
function depositERC20AndCall(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) external payable;
|
||||
/// @notice Deposit some token to a recipient's account on L2 and call.
|
||||
/// @dev Make this function payable to send relayer fee in Ether.
|
||||
/// @param _token The address of token in L1.
|
||||
/// @param _to The address of recipient's account on L2.
|
||||
/// @param _amount The amount of token to transfer.
|
||||
/// @param _data Optional data to forward to recipient's account.
|
||||
/// @param _gasLimit Gas limit required to complete the deposit on L2.
|
||||
function depositERC20AndCall(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) external payable;
|
||||
|
||||
/// @notice Complete ERC20 withdraw from L2 to L1 and send fund to recipient's account in L1.
|
||||
/// @dev Make this function payable to handle WETH deposit/withdraw.
|
||||
/// The function should only be called by L1ScrollMessenger.
|
||||
/// The function should also only be called by L2ERC20Gateway in L2.
|
||||
/// @param _l1Token The address of corresponding L1 token.
|
||||
/// @param _l2Token The address of corresponding L2 token.
|
||||
/// @param _from The address of account who withdraw the token in L2.
|
||||
/// @param _to The address of recipient in L1 to receive the token.
|
||||
/// @param _amount The amount of the token to withdraw.
|
||||
/// @param _data Optional data to forward to recipient's account.
|
||||
function finalizeWithdrawERC20(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes calldata _data
|
||||
) external payable;
|
||||
/// @notice Complete ERC20 withdraw from L2 to L1 and send fund to recipient's account in L1.
|
||||
/// @dev Make this function payable to handle WETH deposit/withdraw.
|
||||
/// The function should only be called by L1ScrollMessenger.
|
||||
/// The function should also only be called by L2ERC20Gateway in L2.
|
||||
/// @param _l1Token The address of corresponding L1 token.
|
||||
/// @param _l2Token The address of corresponding L2 token.
|
||||
/// @param _from The address of account who withdraw the token in L2.
|
||||
/// @param _to The address of recipient in L1 to receive the token.
|
||||
/// @param _amount The amount of the token to withdraw.
|
||||
/// @param _data Optional data to forward to recipient's account.
|
||||
function finalizeWithdrawERC20(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes calldata _data
|
||||
) external payable;
|
||||
}
|
||||
|
||||
@@ -4,145 +4,145 @@ pragma solidity ^0.8.0;
|
||||
|
||||
/// @title The interface for the ERC721 cross chain gateway in layer 1.
|
||||
interface IL1ERC721Gateway {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
|
||||
/// @notice Emitted when the ERC721 NFT is transfered to recipient in layer 1.
|
||||
/// @param _l1Token The address of ERC721 NFT in layer 1.
|
||||
/// @param _l2Token The address of ERC721 NFT in layer 2.
|
||||
/// @param _from The address of sender in layer 2.
|
||||
/// @param _to The address of recipient in layer 1.
|
||||
/// @param _tokenId The token id of the ERC721 NFT to withdraw from layer 2.
|
||||
event FinalizeWithdrawERC721(
|
||||
address indexed _l1Token,
|
||||
address indexed _l2Token,
|
||||
address indexed _from,
|
||||
address _to,
|
||||
uint256 _tokenId
|
||||
);
|
||||
/// @notice Emitted when the ERC721 NFT is transfered to recipient in layer 1.
|
||||
/// @param _l1Token The address of ERC721 NFT in layer 1.
|
||||
/// @param _l2Token The address of ERC721 NFT in layer 2.
|
||||
/// @param _from The address of sender in layer 2.
|
||||
/// @param _to The address of recipient in layer 1.
|
||||
/// @param _tokenId The token id of the ERC721 NFT to withdraw from layer 2.
|
||||
event FinalizeWithdrawERC721(
|
||||
address indexed _l1Token,
|
||||
address indexed _l2Token,
|
||||
address indexed _from,
|
||||
address _to,
|
||||
uint256 _tokenId
|
||||
);
|
||||
|
||||
/// @notice Emitted when the ERC721 NFT is batch transfered to recipient in layer 1.
|
||||
/// @param _l1Token The address of ERC721 NFT in layer 1.
|
||||
/// @param _l2Token The address of ERC721 NFT in layer 2.
|
||||
/// @param _from The address of sender in layer 2.
|
||||
/// @param _to The address of recipient in layer 1.
|
||||
/// @param _tokenIds The list of token ids of the ERC721 NFT to withdraw from layer 2.
|
||||
event FinalizeBatchWithdrawERC721(
|
||||
address indexed _l1Token,
|
||||
address indexed _l2Token,
|
||||
address indexed _from,
|
||||
address _to,
|
||||
uint256[] _tokenIds
|
||||
);
|
||||
/// @notice Emitted when the ERC721 NFT is batch transfered to recipient in layer 1.
|
||||
/// @param _l1Token The address of ERC721 NFT in layer 1.
|
||||
/// @param _l2Token The address of ERC721 NFT in layer 2.
|
||||
/// @param _from The address of sender in layer 2.
|
||||
/// @param _to The address of recipient in layer 1.
|
||||
/// @param _tokenIds The list of token ids of the ERC721 NFT to withdraw from layer 2.
|
||||
event FinalizeBatchWithdrawERC721(
|
||||
address indexed _l1Token,
|
||||
address indexed _l2Token,
|
||||
address indexed _from,
|
||||
address _to,
|
||||
uint256[] _tokenIds
|
||||
);
|
||||
|
||||
/// @notice Emitted when the ERC721 NFT is deposited to gateway in layer 1.
|
||||
/// @param _l1Token The address of ERC721 NFT in layer 1.
|
||||
/// @param _l2Token The address of ERC721 NFT in layer 2.
|
||||
/// @param _from The address of sender in layer 1.
|
||||
/// @param _to The address of recipient in layer 2.
|
||||
/// @param _tokenId The token id of the ERC721 NFT to deposit in layer 1.
|
||||
event DepositERC721(
|
||||
address indexed _l1Token,
|
||||
address indexed _l2Token,
|
||||
address indexed _from,
|
||||
address _to,
|
||||
uint256 _tokenId
|
||||
);
|
||||
/// @notice Emitted when the ERC721 NFT is deposited to gateway in layer 1.
|
||||
/// @param _l1Token The address of ERC721 NFT in layer 1.
|
||||
/// @param _l2Token The address of ERC721 NFT in layer 2.
|
||||
/// @param _from The address of sender in layer 1.
|
||||
/// @param _to The address of recipient in layer 2.
|
||||
/// @param _tokenId The token id of the ERC721 NFT to deposit in layer 1.
|
||||
event DepositERC721(
|
||||
address indexed _l1Token,
|
||||
address indexed _l2Token,
|
||||
address indexed _from,
|
||||
address _to,
|
||||
uint256 _tokenId
|
||||
);
|
||||
|
||||
/// @notice Emitted when the ERC721 NFT is batch deposited to gateway in layer 1.
|
||||
/// @param _l1Token The address of ERC721 NFT in layer 1.
|
||||
/// @param _l2Token The address of ERC721 NFT in layer 2.
|
||||
/// @param _from The address of sender in layer 1.
|
||||
/// @param _to The address of recipient in layer 2.
|
||||
/// @param _tokenIds The list of token ids of the ERC721 NFT to deposit in layer 1.
|
||||
event BatchDepositERC721(
|
||||
address indexed _l1Token,
|
||||
address indexed _l2Token,
|
||||
address indexed _from,
|
||||
address _to,
|
||||
uint256[] _tokenIds
|
||||
);
|
||||
/// @notice Emitted when the ERC721 NFT is batch deposited to gateway in layer 1.
|
||||
/// @param _l1Token The address of ERC721 NFT in layer 1.
|
||||
/// @param _l2Token The address of ERC721 NFT in layer 2.
|
||||
/// @param _from The address of sender in layer 1.
|
||||
/// @param _to The address of recipient in layer 2.
|
||||
/// @param _tokenIds The list of token ids of the ERC721 NFT to deposit in layer 1.
|
||||
event BatchDepositERC721(
|
||||
address indexed _l1Token,
|
||||
address indexed _l2Token,
|
||||
address indexed _from,
|
||||
address _to,
|
||||
uint256[] _tokenIds
|
||||
);
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
/// @notice Deposit some ERC721 NFT to caller's account on layer 2.
|
||||
/// @param _token The address of ERC721 NFT in layer 1.
|
||||
/// @param _tokenId The token id to deposit.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
|
||||
function depositERC721(
|
||||
address _token,
|
||||
uint256 _tokenId,
|
||||
uint256 _gasLimit
|
||||
) external payable;
|
||||
/// @notice Deposit some ERC721 NFT to caller's account on layer 2.
|
||||
/// @param _token The address of ERC721 NFT in layer 1.
|
||||
/// @param _tokenId The token id to deposit.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
|
||||
function depositERC721(
|
||||
address _token,
|
||||
uint256 _tokenId,
|
||||
uint256 _gasLimit
|
||||
) external payable;
|
||||
|
||||
/// @notice Deposit some ERC721 NFT to a recipient's account on layer 2.
|
||||
/// @param _token The address of ERC721 NFT in layer 1.
|
||||
/// @param _to The address of recipient in layer 2.
|
||||
/// @param _tokenId The token id to deposit.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
|
||||
function depositERC721(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _gasLimit
|
||||
) external payable;
|
||||
/// @notice Deposit some ERC721 NFT to a recipient's account on layer 2.
|
||||
/// @param _token The address of ERC721 NFT in layer 1.
|
||||
/// @param _to The address of recipient in layer 2.
|
||||
/// @param _tokenId The token id to deposit.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
|
||||
function depositERC721(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _gasLimit
|
||||
) external payable;
|
||||
|
||||
/// @notice Deposit a list of some ERC721 NFT to caller's account on layer 2.
|
||||
/// @param _token The address of ERC721 NFT in layer 1.
|
||||
/// @param _tokenIds The list of token ids to deposit.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
|
||||
function batchDepositERC721(
|
||||
address _token,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256 _gasLimit
|
||||
) external payable;
|
||||
/// @notice Deposit a list of some ERC721 NFT to caller's account on layer 2.
|
||||
/// @param _token The address of ERC721 NFT in layer 1.
|
||||
/// @param _tokenIds The list of token ids to deposit.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
|
||||
function batchDepositERC721(
|
||||
address _token,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256 _gasLimit
|
||||
) external payable;
|
||||
|
||||
/// @notice Deposit a list of some ERC721 NFT to a recipient's account on layer 2.
|
||||
/// @param _token The address of ERC721 NFT in layer 1.
|
||||
/// @param _to The address of recipient in layer 2.
|
||||
/// @param _tokenIds The list of token ids to deposit.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
|
||||
function batchDepositERC721(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256 _gasLimit
|
||||
) external payable;
|
||||
/// @notice Deposit a list of some ERC721 NFT to a recipient's account on layer 2.
|
||||
/// @param _token The address of ERC721 NFT in layer 1.
|
||||
/// @param _to The address of recipient in layer 2.
|
||||
/// @param _tokenIds The list of token ids to deposit.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
|
||||
function batchDepositERC721(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256 _gasLimit
|
||||
) external payable;
|
||||
|
||||
/// @notice Complete ERC721 withdraw from layer 2 to layer 1 and send NFT to recipient's account in layer 1.
|
||||
/// @dev Requirements:
|
||||
/// - The function should only be called by L1ScrollMessenger.
|
||||
/// - The function should also only be called by L2ERC721Gateway in layer 2.
|
||||
/// @param _l1Token The address of corresponding layer 1 token.
|
||||
/// @param _l2Token The address of corresponding layer 2 token.
|
||||
/// @param _from The address of account who withdraw the token in layer 2.
|
||||
/// @param _to The address of recipient in layer 1 to receive the token.
|
||||
/// @param _tokenId The token id to withdraw.
|
||||
function finalizeWithdrawERC721(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _tokenId
|
||||
) external;
|
||||
/// @notice Complete ERC721 withdraw from layer 2 to layer 1 and send NFT to recipient's account in layer 1.
|
||||
/// @dev Requirements:
|
||||
/// - The function should only be called by L1ScrollMessenger.
|
||||
/// - The function should also only be called by L2ERC721Gateway in layer 2.
|
||||
/// @param _l1Token The address of corresponding layer 1 token.
|
||||
/// @param _l2Token The address of corresponding layer 2 token.
|
||||
/// @param _from The address of account who withdraw the token in layer 2.
|
||||
/// @param _to The address of recipient in layer 1 to receive the token.
|
||||
/// @param _tokenId The token id to withdraw.
|
||||
function finalizeWithdrawERC721(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _tokenId
|
||||
) external;
|
||||
|
||||
/// @notice Complete ERC721 batch withdraw from layer 2 to layer 1 and send NFT to recipient's account in layer 1.
|
||||
/// @dev Requirements:
|
||||
/// - The function should only be called by L1ScrollMessenger.
|
||||
/// - The function should also only be called by L2ERC721Gateway in layer 2.
|
||||
/// @param _l1Token The address of corresponding layer 1 token.
|
||||
/// @param _l2Token The address of corresponding layer 2 token.
|
||||
/// @param _from The address of account who withdraw the token in layer 2.
|
||||
/// @param _to The address of recipient in layer 1 to receive the token.
|
||||
/// @param _tokenIds The list of token ids to withdraw.
|
||||
function finalizeBatchWithdrawERC721(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds
|
||||
) external;
|
||||
/// @notice Complete ERC721 batch withdraw from layer 2 to layer 1 and send NFT to recipient's account in layer 1.
|
||||
/// @dev Requirements:
|
||||
/// - The function should only be called by L1ScrollMessenger.
|
||||
/// - The function should also only be called by L2ERC721Gateway in layer 2.
|
||||
/// @param _l1Token The address of corresponding layer 1 token.
|
||||
/// @param _l2Token The address of corresponding layer 2 token.
|
||||
/// @param _from The address of account who withdraw the token in layer 2.
|
||||
/// @param _to The address of recipient in layer 1 to receive the token.
|
||||
/// @param _tokenIds The list of token ids to withdraw.
|
||||
function finalizeBatchWithdrawERC721(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds
|
||||
) external;
|
||||
}
|
||||
|
||||
@@ -3,66 +3,66 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
interface IL1ETHGateway {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
|
||||
/// @notice Emitted when ETH is withdrawn from L2 to L1 and transfer to recipient.
|
||||
/// @param from The address of sender in L2.
|
||||
/// @param to The address of recipient in L1.
|
||||
/// @param amount The amount of ETH withdrawn from L2 to L1.
|
||||
/// @param data The optional calldata passed to recipient in L1.
|
||||
event FinalizeWithdrawETH(address indexed from, address indexed to, uint256 amount, bytes data);
|
||||
/// @notice Emitted when ETH is withdrawn from L2 to L1 and transfer to recipient.
|
||||
/// @param from The address of sender in L2.
|
||||
/// @param to The address of recipient in L1.
|
||||
/// @param amount The amount of ETH withdrawn from L2 to L1.
|
||||
/// @param data The optional calldata passed to recipient in L1.
|
||||
event FinalizeWithdrawETH(address indexed from, address indexed to, uint256 amount, bytes data);
|
||||
|
||||
/// @notice Emitted when someone deposit ETH from L1 to L2.
|
||||
/// @param from The address of sender in L1.
|
||||
/// @param to The address of recipient in L2.
|
||||
/// @param amount The amount of ETH will be deposited from L1 to L2.
|
||||
/// @param data The optional calldata passed to recipient in L2.
|
||||
event DepositETH(address indexed from, address indexed to, uint256 amount, bytes data);
|
||||
/// @notice Emitted when someone deposit ETH from L1 to L2.
|
||||
/// @param from The address of sender in L1.
|
||||
/// @param to The address of recipient in L2.
|
||||
/// @param amount The amount of ETH will be deposited from L1 to L2.
|
||||
/// @param data The optional calldata passed to recipient in L2.
|
||||
event DepositETH(address indexed from, address indexed to, uint256 amount, bytes data);
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
/// @notice Deposit ETH to caller's account in L2.
|
||||
/// @param amount The amount of ETH to be deposited.
|
||||
/// @param gasLimit Gas limit required to complete the deposit on L2.
|
||||
function depositETH(uint256 amount, uint256 gasLimit) external payable;
|
||||
/// @notice Deposit ETH to caller's account in L2.
|
||||
/// @param amount The amount of ETH to be deposited.
|
||||
/// @param gasLimit Gas limit required to complete the deposit on L2.
|
||||
function depositETH(uint256 amount, uint256 gasLimit) external payable;
|
||||
|
||||
/// @notice Deposit ETH to some recipient's account in L2.
|
||||
/// @param to The address of recipient's account on L2.
|
||||
/// @param amount The amount of ETH to be deposited.
|
||||
/// @param gasLimit Gas limit required to complete the deposit on L2.
|
||||
function depositETH(
|
||||
address to,
|
||||
uint256 amount,
|
||||
uint256 gasLimit
|
||||
) external payable;
|
||||
/// @notice Deposit ETH to some recipient's account in L2.
|
||||
/// @param to The address of recipient's account on L2.
|
||||
/// @param amount The amount of ETH to be deposited.
|
||||
/// @param gasLimit Gas limit required to complete the deposit on L2.
|
||||
function depositETH(
|
||||
address to,
|
||||
uint256 amount,
|
||||
uint256 gasLimit
|
||||
) external payable;
|
||||
|
||||
/// @notice Deposit ETH to some recipient's account in L2 and call the target contract.
|
||||
/// @param to The address of recipient's account on L2.
|
||||
/// @param amount The amount of ETH to be deposited.
|
||||
/// @param data Optional data to forward to recipient's account.
|
||||
/// @param gasLimit Gas limit required to complete the deposit on L2.
|
||||
function depositETHAndCall(
|
||||
address to,
|
||||
uint256 amount,
|
||||
bytes calldata data,
|
||||
uint256 gasLimit
|
||||
) external payable;
|
||||
/// @notice Deposit ETH to some recipient's account in L2 and call the target contract.
|
||||
/// @param to The address of recipient's account on L2.
|
||||
/// @param amount The amount of ETH to be deposited.
|
||||
/// @param data Optional data to forward to recipient's account.
|
||||
/// @param gasLimit Gas limit required to complete the deposit on L2.
|
||||
function depositETHAndCall(
|
||||
address to,
|
||||
uint256 amount,
|
||||
bytes calldata data,
|
||||
uint256 gasLimit
|
||||
) external payable;
|
||||
|
||||
/// @notice Complete ETH withdraw from L2 to L1 and send fund to recipient's account in L1.
|
||||
/// @dev This function should only be called by L1ScrollMessenger.
|
||||
/// This function should also only be called by L1ETHGateway in L2.
|
||||
/// @param from The address of account who withdraw ETH in L2.
|
||||
/// @param to The address of recipient in L1 to receive ETH.
|
||||
/// @param amount The amount of ETH to withdraw.
|
||||
/// @param data Optional data to forward to recipient's account.
|
||||
function finalizeWithdrawETH(
|
||||
address from,
|
||||
address to,
|
||||
uint256 amount,
|
||||
bytes calldata data
|
||||
) external payable;
|
||||
/// @notice Complete ETH withdraw from L2 to L1 and send fund to recipient's account in L1.
|
||||
/// @dev This function should only be called by L1ScrollMessenger.
|
||||
/// This function should also only be called by L1ETHGateway in L2.
|
||||
/// @param from The address of account who withdraw ETH in L2.
|
||||
/// @param to The address of recipient in L1 to receive ETH.
|
||||
/// @param amount The amount of ETH to withdraw.
|
||||
/// @param data Optional data to forward to recipient's account.
|
||||
function finalizeWithdrawETH(
|
||||
address from,
|
||||
address to,
|
||||
uint256 amount,
|
||||
bytes calldata data
|
||||
) external payable;
|
||||
}
|
||||
|
||||
@@ -2,24 +2,24 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { IL1ETHGateway } from "./IL1ETHGateway.sol";
|
||||
import { IL1ERC20Gateway } from "./IL1ERC20Gateway.sol";
|
||||
import {IL1ETHGateway} from "./IL1ETHGateway.sol";
|
||||
import {IL1ERC20Gateway} from "./IL1ERC20Gateway.sol";
|
||||
|
||||
interface IL1GatewayRouter is IL1ETHGateway, IL1ERC20Gateway {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
|
||||
/// @notice Emitted when the address of ETH Gateway is updated.
|
||||
/// @param ethGateway The address of new ETH Gateway.
|
||||
event SetETHGateway(address indexed ethGateway);
|
||||
/// @notice Emitted when the address of ETH Gateway is updated.
|
||||
/// @param ethGateway The address of new ETH Gateway.
|
||||
event SetETHGateway(address indexed ethGateway);
|
||||
|
||||
/// @notice Emitted when the address of default ERC20 Gateway is updated.
|
||||
/// @param defaultERC20Gateway The address of new default ERC20 Gateway.
|
||||
event SetDefaultERC20Gateway(address indexed defaultERC20Gateway);
|
||||
/// @notice Emitted when the address of default ERC20 Gateway is updated.
|
||||
/// @param defaultERC20Gateway The address of new default ERC20 Gateway.
|
||||
event SetDefaultERC20Gateway(address indexed defaultERC20Gateway);
|
||||
|
||||
/// @notice Emitted when the `gateway` for `token` is updated.
|
||||
/// @param token The address of token updated.
|
||||
/// @param gateway The corresponding address of gateway updated.
|
||||
event SetERC20Gateway(address indexed token, address indexed gateway);
|
||||
/// @notice Emitted when the `gateway` for `token` is updated.
|
||||
/// @param token The address of token updated.
|
||||
/// @param gateway The corresponding address of gateway updated.
|
||||
event SetERC20Gateway(address indexed token, address indexed gateway);
|
||||
}
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
|
||||
import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
|
||||
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
|
||||
import {SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
|
||||
|
||||
import { IL2ERC20Gateway } from "../../L2/gateways/IL2ERC20Gateway.sol";
|
||||
import { IL1ScrollMessenger } from "../IL1ScrollMessenger.sol";
|
||||
import { IL1ERC20Gateway } from "./IL1ERC20Gateway.sol";
|
||||
import {IL2ERC20Gateway} from "../../L2/gateways/IL2ERC20Gateway.sol";
|
||||
import {IL1ScrollMessenger} from "../IL1ScrollMessenger.sol";
|
||||
import {IL1ERC20Gateway} from "./IL1ERC20Gateway.sol";
|
||||
|
||||
import { ScrollGatewayBase } from "../../libraries/gateway/ScrollGatewayBase.sol";
|
||||
import { L1ERC20Gateway } from "./L1ERC20Gateway.sol";
|
||||
import {ScrollGatewayBase} from "../../libraries/gateway/ScrollGatewayBase.sol";
|
||||
import {L1ERC20Gateway} from "./L1ERC20Gateway.sol";
|
||||
|
||||
/// @title L1CustomERC20Gateway
|
||||
/// @notice The `L1CustomERC20Gateway` is used to deposit custom ERC20 compatible tokens in layer 1 and
|
||||
@@ -19,139 +19,139 @@ import { L1ERC20Gateway } from "./L1ERC20Gateway.sol";
|
||||
/// @dev The deposited tokens are held in this gateway. On finalizing withdraw, the corresponding
|
||||
/// tokens will be transfer to the recipient directly.
|
||||
contract L1CustomERC20Gateway is OwnableUpgradeable, ScrollGatewayBase, L1ERC20Gateway {
|
||||
using SafeERC20Upgradeable for IERC20Upgradeable;
|
||||
using SafeERC20Upgradeable for IERC20Upgradeable;
|
||||
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
|
||||
/// @notice Emitted when token mapping for ERC20 token is updated.
|
||||
/// @param _l1Token The address of ERC20 token in layer 1.
|
||||
/// @param _l2Token The address of corresponding ERC20 token in layer 2.
|
||||
event UpdateTokenMapping(address _l1Token, address _l2Token);
|
||||
/// @notice Emitted when token mapping for ERC20 token is updated.
|
||||
/// @param _l1Token The address of ERC20 token in layer 1.
|
||||
/// @param _l2Token The address of corresponding ERC20 token in layer 2.
|
||||
event UpdateTokenMapping(address _l1Token, address _l2Token);
|
||||
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
/// @notice Mapping from l1 token address to l2 token address for ERC20 token.
|
||||
mapping(address => address) public tokenMapping;
|
||||
/// @notice Mapping from l1 token address to l2 token address for ERC20 token.
|
||||
mapping(address => address) public tokenMapping;
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
/// @notice Initialize the storage of L1CustomERC20Gateway.
|
||||
/// @param _counterpart The address of L2CustomERC20Gateway in L2.
|
||||
/// @param _router The address of L1GatewayRouter.
|
||||
/// @param _messenger The address of L1ScrollMessenger.
|
||||
function initialize(
|
||||
address _counterpart,
|
||||
address _router,
|
||||
address _messenger
|
||||
) external initializer {
|
||||
require(_router != address(0), "zero router address");
|
||||
/// @notice Initialize the storage of L1CustomERC20Gateway.
|
||||
/// @param _counterpart The address of L2CustomERC20Gateway in L2.
|
||||
/// @param _router The address of L1GatewayRouter.
|
||||
/// @param _messenger The address of L1ScrollMessenger.
|
||||
function initialize(
|
||||
address _counterpart,
|
||||
address _router,
|
||||
address _messenger
|
||||
) external initializer {
|
||||
require(_router != address(0), "zero router address");
|
||||
|
||||
OwnableUpgradeable.__Ownable_init();
|
||||
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
|
||||
}
|
||||
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function getL2ERC20Address(address _l1Token) public view override returns (address) {
|
||||
return tokenMapping[_l1Token];
|
||||
}
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function finalizeWithdrawERC20(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes calldata _data
|
||||
) external payable override onlyCallByCounterpart {
|
||||
require(msg.value == 0, "nonzero msg.value");
|
||||
require(_l2Token == tokenMapping[_l1Token], "l2 token mismatch");
|
||||
|
||||
// @note can possible trigger reentrant call to this contract or messenger,
|
||||
// but it seems not a big problem.
|
||||
IERC20Upgradeable(_l1Token).safeTransfer(_to, _amount);
|
||||
|
||||
// @todo forward `_data` to `_to` in the near future
|
||||
|
||||
emit FinalizeWithdrawERC20(_l1Token, _l2Token, _from, _to, _amount, _data);
|
||||
}
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
/// @notice Update layer 1 to layer 2 token mapping.
|
||||
/// @param _l1Token The address of ERC20 token in layer 1.
|
||||
/// @param _l2Token The address of corresponding ERC20 token in layer 2.
|
||||
function updateTokenMapping(address _l1Token, address _l2Token) external onlyOwner {
|
||||
require(_l2Token != address(0), "map to zero address");
|
||||
|
||||
tokenMapping[_l1Token] = _l2Token;
|
||||
|
||||
emit UpdateTokenMapping(_l1Token, _l2Token);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
/// @inheritdoc L1ERC20Gateway
|
||||
function _deposit(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) internal virtual override nonReentrant {
|
||||
address _l2Token = tokenMapping[_token];
|
||||
require(_l2Token != address(0), "no corresponding l2 token");
|
||||
|
||||
// 1. Extract real sender if this call is from L1GatewayRouter.
|
||||
address _from = msg.sender;
|
||||
if (router == msg.sender) {
|
||||
(_from, _data) = abi.decode(_data, (address, bytes));
|
||||
OwnableUpgradeable.__Ownable_init();
|
||||
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
|
||||
}
|
||||
|
||||
// 2. Transfer token into this contract.
|
||||
{
|
||||
// common practice to handle fee on transfer token.
|
||||
uint256 _before = IERC20Upgradeable(_token).balanceOf(address(this));
|
||||
IERC20Upgradeable(_token).safeTransferFrom(_from, address(this), _amount);
|
||||
uint256 _after = IERC20Upgradeable(_token).balanceOf(address(this));
|
||||
// no unchecked here, since some weird token may return arbitrary balance.
|
||||
_amount = _after - _before;
|
||||
// ignore weird fee on transfer token
|
||||
require(_amount > 0, "deposit zero amount");
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function getL2ERC20Address(address _l1Token) public view override returns (address) {
|
||||
return tokenMapping[_l1Token];
|
||||
}
|
||||
|
||||
// 3. Generate message passed to L2StandardERC20Gateway.
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL2ERC20Gateway.finalizeDepositERC20.selector,
|
||||
_token,
|
||||
_l2Token,
|
||||
_from,
|
||||
_to,
|
||||
_amount,
|
||||
_data
|
||||
);
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
// 4. Send message to L1ScrollMessenger.
|
||||
IL1ScrollMessenger(messenger).sendMessage{ value: msg.value }(counterpart, 0, _message, _gasLimit);
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function finalizeWithdrawERC20(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes calldata _data
|
||||
) external payable override onlyCallByCounterpart {
|
||||
require(msg.value == 0, "nonzero msg.value");
|
||||
require(_l2Token == tokenMapping[_l1Token], "l2 token mismatch");
|
||||
|
||||
emit DepositERC20(_token, _l2Token, _from, _to, _amount, _data);
|
||||
}
|
||||
// @note can possible trigger reentrant call to this contract or messenger,
|
||||
// but it seems not a big problem.
|
||||
IERC20Upgradeable(_l1Token).safeTransfer(_to, _amount);
|
||||
|
||||
// @todo forward `_data` to `_to` in the near future
|
||||
|
||||
emit FinalizeWithdrawERC20(_l1Token, _l2Token, _from, _to, _amount, _data);
|
||||
}
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
/// @notice Update layer 1 to layer 2 token mapping.
|
||||
/// @param _l1Token The address of ERC20 token in layer 1.
|
||||
/// @param _l2Token The address of corresponding ERC20 token in layer 2.
|
||||
function updateTokenMapping(address _l1Token, address _l2Token) external onlyOwner {
|
||||
require(_l2Token != address(0), "map to zero address");
|
||||
|
||||
tokenMapping[_l1Token] = _l2Token;
|
||||
|
||||
emit UpdateTokenMapping(_l1Token, _l2Token);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
/// @inheritdoc L1ERC20Gateway
|
||||
function _deposit(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) internal virtual override nonReentrant {
|
||||
address _l2Token = tokenMapping[_token];
|
||||
require(_l2Token != address(0), "no corresponding l2 token");
|
||||
|
||||
// 1. Extract real sender if this call is from L1GatewayRouter.
|
||||
address _from = msg.sender;
|
||||
if (router == msg.sender) {
|
||||
(_from, _data) = abi.decode(_data, (address, bytes));
|
||||
}
|
||||
|
||||
// 2. Transfer token into this contract.
|
||||
{
|
||||
// common practice to handle fee on transfer token.
|
||||
uint256 _before = IERC20Upgradeable(_token).balanceOf(address(this));
|
||||
IERC20Upgradeable(_token).safeTransferFrom(_from, address(this), _amount);
|
||||
uint256 _after = IERC20Upgradeable(_token).balanceOf(address(this));
|
||||
// no unchecked here, since some weird token may return arbitrary balance.
|
||||
_amount = _after - _before;
|
||||
// ignore weird fee on transfer token
|
||||
require(_amount > 0, "deposit zero amount");
|
||||
}
|
||||
|
||||
// 3. Generate message passed to L2StandardERC20Gateway.
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL2ERC20Gateway.finalizeDepositERC20.selector,
|
||||
_token,
|
||||
_l2Token,
|
||||
_from,
|
||||
_to,
|
||||
_amount,
|
||||
_data
|
||||
);
|
||||
|
||||
// 4. Send message to L1ScrollMessenger.
|
||||
IL1ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, _gasLimit);
|
||||
|
||||
emit DepositERC20(_token, _l2Token, _from, _to, _amount, _data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
import { IERC1155Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC1155/IERC1155Upgradeable.sol";
|
||||
import { ERC1155HolderUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC1155/utils/ERC1155HolderUpgradeable.sol";
|
||||
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
import {IERC1155Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC1155/IERC1155Upgradeable.sol";
|
||||
import {ERC1155HolderUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC1155/utils/ERC1155HolderUpgradeable.sol";
|
||||
|
||||
import { IL2ERC1155Gateway } from "../../L2/gateways/IL2ERC1155Gateway.sol";
|
||||
import { IL1ScrollMessenger } from "../IL1ScrollMessenger.sol";
|
||||
import { IL1ERC1155Gateway } from "./IL1ERC1155Gateway.sol";
|
||||
import {IL2ERC1155Gateway} from "../../L2/gateways/IL2ERC1155Gateway.sol";
|
||||
import {IL1ScrollMessenger} from "../IL1ScrollMessenger.sol";
|
||||
import {IL1ERC1155Gateway} from "./IL1ERC1155Gateway.sol";
|
||||
|
||||
import { ScrollGatewayBase } from "../../libraries/gateway/ScrollGatewayBase.sol";
|
||||
import {ScrollGatewayBase} from "../../libraries/gateway/ScrollGatewayBase.sol";
|
||||
|
||||
/// @title L1ERC1155Gateway
|
||||
/// @notice The `L1ERC1155Gateway` is used to deposit ERC1155 compatible NFT in layer 1 and
|
||||
@@ -21,209 +21,209 @@ import { ScrollGatewayBase } from "../../libraries/gateway/ScrollGatewayBase.sol
|
||||
/// This will be changed if we have more specific scenarios.
|
||||
// @todo Current implementation doesn't support calling from `L1GatewayRouter`.
|
||||
contract L1ERC1155Gateway is OwnableUpgradeable, ERC1155HolderUpgradeable, ScrollGatewayBase, IL1ERC1155Gateway {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
|
||||
/// @notice Emitted when token mapping for ERC1155 token is updated.
|
||||
/// @param _l1Token The address of ERC1155 token in layer 1.
|
||||
/// @param _l1Token The address of corresponding ERC1155 token in layer 2.
|
||||
event UpdateTokenMapping(address _l1Token, address _l2Token);
|
||||
/// @notice Emitted when token mapping for ERC1155 token is updated.
|
||||
/// @param _l1Token The address of ERC1155 token in layer 1.
|
||||
/// @param _l1Token The address of corresponding ERC1155 token in layer 2.
|
||||
event UpdateTokenMapping(address _l1Token, address _l2Token);
|
||||
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
/// @notice Mapping from l1 token address to l2 token address for ERC1155 NFT.
|
||||
mapping(address => address) public tokenMapping;
|
||||
/// @notice Mapping from l1 token address to l2 token address for ERC1155 NFT.
|
||||
mapping(address => address) public tokenMapping;
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
/// @notice Initialize the storage of L1ERC1155Gateway.
|
||||
/// @param _counterpart The address of L2ERC1155Gateway in L2.
|
||||
/// @param _messenger The address of L1ScrollMessenger.
|
||||
function initialize(address _counterpart, address _messenger) external initializer {
|
||||
OwnableUpgradeable.__Ownable_init();
|
||||
ScrollGatewayBase._initialize(_counterpart, address(0), _messenger);
|
||||
}
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
|
||||
/// @inheritdoc IL1ERC1155Gateway
|
||||
function depositERC1155(
|
||||
address _token,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_depositERC1155(_token, msg.sender, _tokenId, _amount, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ERC1155Gateway
|
||||
function depositERC1155(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_depositERC1155(_token, _to, _tokenId, _amount, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ERC1155Gateway
|
||||
function batchDepositERC1155(
|
||||
address _token,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256[] calldata _amounts,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_batchDepositERC1155(_token, msg.sender, _tokenIds, _amounts, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ERC1155Gateway
|
||||
function batchDepositERC1155(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256[] calldata _amounts,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_batchDepositERC1155(_token, _to, _tokenIds, _amounts, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ERC1155Gateway
|
||||
function finalizeWithdrawERC1155(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount
|
||||
) external override nonReentrant onlyCallByCounterpart {
|
||||
require(_l2Token == tokenMapping[_l1Token], "l2 token mismatch");
|
||||
|
||||
IERC1155Upgradeable(_l1Token).safeTransferFrom(address(this), _to, _tokenId, _amount, "");
|
||||
|
||||
emit FinalizeWithdrawERC1155(_l1Token, _l2Token, _from, _to, _tokenId, _amount);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ERC1155Gateway
|
||||
function finalizeBatchWithdrawERC1155(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256[] calldata _amounts
|
||||
) external override nonReentrant onlyCallByCounterpart {
|
||||
require(_l2Token == tokenMapping[_l1Token], "l2 token mismatch");
|
||||
|
||||
IERC1155Upgradeable(_l1Token).safeBatchTransferFrom(address(this), _to, _tokenIds, _amounts, "");
|
||||
|
||||
emit FinalizeBatchWithdrawERC1155(_l1Token, _l2Token, _from, _to, _tokenIds, _amounts);
|
||||
}
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
/// @notice Update layer 2 to layer 2 token mapping.
|
||||
/// @param _l1Token The address of ERC1155 token in layer 1.
|
||||
/// @param _l1Token The address of corresponding ERC1155 token in layer 2.
|
||||
function updateTokenMapping(address _l1Token, address _l2Token) external onlyOwner {
|
||||
require(_l2Token != address(0), "map to zero address");
|
||||
|
||||
tokenMapping[_l1Token] = _l2Token;
|
||||
|
||||
emit UpdateTokenMapping(_l1Token, _l2Token);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
/// @dev Internal function to deposit ERC1155 NFT to layer 2.
|
||||
/// @param _token The address of ERC1155 NFT in layer 1.
|
||||
/// @param _to The address of recipient in layer 2.
|
||||
/// @param _tokenId The token id to deposit.
|
||||
/// @param _amount The amount of token to deposit.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
|
||||
function _depositERC1155(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) internal nonReentrant {
|
||||
require(_amount > 0, "deposit zero amount");
|
||||
|
||||
address _l2Token = tokenMapping[_token];
|
||||
require(_l2Token != address(0), "token not supported");
|
||||
|
||||
// 1. transfer token to this contract
|
||||
IERC1155Upgradeable(_token).safeTransferFrom(msg.sender, address(this), _tokenId, _amount, "");
|
||||
|
||||
// 2. Generate message passed to L2ERC1155Gateway.
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL2ERC1155Gateway.finalizeDepositERC1155.selector,
|
||||
_token,
|
||||
_l2Token,
|
||||
msg.sender,
|
||||
_to,
|
||||
_tokenId,
|
||||
_amount
|
||||
);
|
||||
|
||||
// 3. Send message to L1ScrollMessenger.
|
||||
IL1ScrollMessenger(messenger).sendMessage{ value: msg.value }(counterpart, 0, _message, _gasLimit);
|
||||
|
||||
emit DepositERC1155(_token, _l2Token, msg.sender, _to, _tokenId, _amount);
|
||||
}
|
||||
|
||||
/// @dev Internal function to batch deposit ERC1155 NFT to layer 2.
|
||||
/// @param _token The address of ERC1155 NFT in layer 1.
|
||||
/// @param _to The address of recipient in layer 2.
|
||||
/// @param _tokenIds The list of token ids to deposit.
|
||||
/// @param _amounts The list of corresponding number of token to deposit.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
|
||||
function _batchDepositERC1155(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256[] calldata _amounts,
|
||||
uint256 _gasLimit
|
||||
) internal nonReentrant {
|
||||
require(_tokenIds.length > 0, "no token to deposit");
|
||||
require(_tokenIds.length == _amounts.length, "length mismatch");
|
||||
|
||||
for (uint256 i = 0; i < _amounts.length; i++) {
|
||||
require(_amounts[i] > 0, "deposit zero amount");
|
||||
/// @notice Initialize the storage of L1ERC1155Gateway.
|
||||
/// @param _counterpart The address of L2ERC1155Gateway in L2.
|
||||
/// @param _messenger The address of L1ScrollMessenger.
|
||||
function initialize(address _counterpart, address _messenger) external initializer {
|
||||
OwnableUpgradeable.__Ownable_init();
|
||||
ScrollGatewayBase._initialize(_counterpart, address(0), _messenger);
|
||||
}
|
||||
|
||||
address _l2Token = tokenMapping[_token];
|
||||
require(_l2Token != address(0), "token not supported");
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
// 1. transfer token to this contract
|
||||
IERC1155Upgradeable(_token).safeBatchTransferFrom(msg.sender, address(this), _tokenIds, _amounts, "");
|
||||
/// @inheritdoc IL1ERC1155Gateway
|
||||
function depositERC1155(
|
||||
address _token,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_depositERC1155(_token, msg.sender, _tokenId, _amount, _gasLimit);
|
||||
}
|
||||
|
||||
// 2. Generate message passed to L2ERC1155Gateway.
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL2ERC1155Gateway.finalizeBatchDepositERC1155.selector,
|
||||
_token,
|
||||
_l2Token,
|
||||
msg.sender,
|
||||
_to,
|
||||
_tokenIds,
|
||||
_amounts
|
||||
);
|
||||
/// @inheritdoc IL1ERC1155Gateway
|
||||
function depositERC1155(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_depositERC1155(_token, _to, _tokenId, _amount, _gasLimit);
|
||||
}
|
||||
|
||||
// 3. Send message to L1ScrollMessenger.
|
||||
IL1ScrollMessenger(messenger).sendMessage{ value: msg.value }(counterpart, 0, _message, _gasLimit);
|
||||
/// @inheritdoc IL1ERC1155Gateway
|
||||
function batchDepositERC1155(
|
||||
address _token,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256[] calldata _amounts,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_batchDepositERC1155(_token, msg.sender, _tokenIds, _amounts, _gasLimit);
|
||||
}
|
||||
|
||||
emit BatchDepositERC1155(_token, _l2Token, msg.sender, _to, _tokenIds, _amounts);
|
||||
}
|
||||
/// @inheritdoc IL1ERC1155Gateway
|
||||
function batchDepositERC1155(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256[] calldata _amounts,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_batchDepositERC1155(_token, _to, _tokenIds, _amounts, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ERC1155Gateway
|
||||
function finalizeWithdrawERC1155(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount
|
||||
) external override nonReentrant onlyCallByCounterpart {
|
||||
require(_l2Token == tokenMapping[_l1Token], "l2 token mismatch");
|
||||
|
||||
IERC1155Upgradeable(_l1Token).safeTransferFrom(address(this), _to, _tokenId, _amount, "");
|
||||
|
||||
emit FinalizeWithdrawERC1155(_l1Token, _l2Token, _from, _to, _tokenId, _amount);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ERC1155Gateway
|
||||
function finalizeBatchWithdrawERC1155(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256[] calldata _amounts
|
||||
) external override nonReentrant onlyCallByCounterpart {
|
||||
require(_l2Token == tokenMapping[_l1Token], "l2 token mismatch");
|
||||
|
||||
IERC1155Upgradeable(_l1Token).safeBatchTransferFrom(address(this), _to, _tokenIds, _amounts, "");
|
||||
|
||||
emit FinalizeBatchWithdrawERC1155(_l1Token, _l2Token, _from, _to, _tokenIds, _amounts);
|
||||
}
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
/// @notice Update layer 2 to layer 2 token mapping.
|
||||
/// @param _l1Token The address of ERC1155 token in layer 1.
|
||||
/// @param _l1Token The address of corresponding ERC1155 token in layer 2.
|
||||
function updateTokenMapping(address _l1Token, address _l2Token) external onlyOwner {
|
||||
require(_l2Token != address(0), "map to zero address");
|
||||
|
||||
tokenMapping[_l1Token] = _l2Token;
|
||||
|
||||
emit UpdateTokenMapping(_l1Token, _l2Token);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
/// @dev Internal function to deposit ERC1155 NFT to layer 2.
|
||||
/// @param _token The address of ERC1155 NFT in layer 1.
|
||||
/// @param _to The address of recipient in layer 2.
|
||||
/// @param _tokenId The token id to deposit.
|
||||
/// @param _amount The amount of token to deposit.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
|
||||
function _depositERC1155(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) internal nonReentrant {
|
||||
require(_amount > 0, "deposit zero amount");
|
||||
|
||||
address _l2Token = tokenMapping[_token];
|
||||
require(_l2Token != address(0), "token not supported");
|
||||
|
||||
// 1. transfer token to this contract
|
||||
IERC1155Upgradeable(_token).safeTransferFrom(msg.sender, address(this), _tokenId, _amount, "");
|
||||
|
||||
// 2. Generate message passed to L2ERC1155Gateway.
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL2ERC1155Gateway.finalizeDepositERC1155.selector,
|
||||
_token,
|
||||
_l2Token,
|
||||
msg.sender,
|
||||
_to,
|
||||
_tokenId,
|
||||
_amount
|
||||
);
|
||||
|
||||
// 3. Send message to L1ScrollMessenger.
|
||||
IL1ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, _gasLimit);
|
||||
|
||||
emit DepositERC1155(_token, _l2Token, msg.sender, _to, _tokenId, _amount);
|
||||
}
|
||||
|
||||
/// @dev Internal function to batch deposit ERC1155 NFT to layer 2.
|
||||
/// @param _token The address of ERC1155 NFT in layer 1.
|
||||
/// @param _to The address of recipient in layer 2.
|
||||
/// @param _tokenIds The list of token ids to deposit.
|
||||
/// @param _amounts The list of corresponding number of token to deposit.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
|
||||
function _batchDepositERC1155(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256[] calldata _amounts,
|
||||
uint256 _gasLimit
|
||||
) internal nonReentrant {
|
||||
require(_tokenIds.length > 0, "no token to deposit");
|
||||
require(_tokenIds.length == _amounts.length, "length mismatch");
|
||||
|
||||
for (uint256 i = 0; i < _amounts.length; i++) {
|
||||
require(_amounts[i] > 0, "deposit zero amount");
|
||||
}
|
||||
|
||||
address _l2Token = tokenMapping[_token];
|
||||
require(_l2Token != address(0), "token not supported");
|
||||
|
||||
// 1. transfer token to this contract
|
||||
IERC1155Upgradeable(_token).safeBatchTransferFrom(msg.sender, address(this), _tokenIds, _amounts, "");
|
||||
|
||||
// 2. Generate message passed to L2ERC1155Gateway.
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL2ERC1155Gateway.finalizeBatchDepositERC1155.selector,
|
||||
_token,
|
||||
_l2Token,
|
||||
msg.sender,
|
||||
_to,
|
||||
_tokenIds,
|
||||
_amounts
|
||||
);
|
||||
|
||||
// 3. Send message to L1ScrollMessenger.
|
||||
IL1ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, _gasLimit);
|
||||
|
||||
emit BatchDepositERC1155(_token, _l2Token, msg.sender, _to, _tokenIds, _amounts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,61 +2,61 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { IL1ERC20Gateway } from "./IL1ERC20Gateway.sol";
|
||||
import {IL1ERC20Gateway} from "./IL1ERC20Gateway.sol";
|
||||
|
||||
// solhint-disable no-empty-blocks
|
||||
|
||||
abstract contract L1ERC20Gateway is IL1ERC20Gateway {
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function depositERC20(
|
||||
address _token,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_deposit(_token, msg.sender, _amount, new bytes(0), _gasLimit);
|
||||
}
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function depositERC20(
|
||||
address _token,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_deposit(_token, msg.sender, _amount, new bytes(0), _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function depositERC20(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_deposit(_token, _to, _amount, new bytes(0), _gasLimit);
|
||||
}
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function depositERC20(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_deposit(_token, _to, _amount, new bytes(0), _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function depositERC20AndCall(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_deposit(_token, _to, _amount, _data, _gasLimit);
|
||||
}
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function depositERC20AndCall(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_deposit(_token, _to, _amount, _data, _gasLimit);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
/// @dev Internal function to do all the deposit operations.
|
||||
///
|
||||
/// @param _token The token to deposit.
|
||||
/// @param _to The recipient address to recieve the token in L2.
|
||||
/// @param _amount The amount of token to deposit.
|
||||
/// @param _data Optional data to forward to recipient's account.
|
||||
/// @param _gasLimit Gas limit required to complete the deposit on L2.
|
||||
function _deposit(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) internal virtual;
|
||||
/// @dev Internal function to do all the deposit operations.
|
||||
///
|
||||
/// @param _token The token to deposit.
|
||||
/// @param _to The recipient address to recieve the token in L2.
|
||||
/// @param _amount The amount of token to deposit.
|
||||
/// @param _data Optional data to forward to recipient's account.
|
||||
/// @param _gasLimit Gas limit required to complete the deposit on L2.
|
||||
function _deposit(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) internal virtual;
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
import { IERC721Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol";
|
||||
import { ERC721HolderUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/utils/ERC721HolderUpgradeable.sol";
|
||||
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
import {IERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol";
|
||||
import {ERC721HolderUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/utils/ERC721HolderUpgradeable.sol";
|
||||
|
||||
import { IL2ERC721Gateway } from "../../L2/gateways/IL2ERC721Gateway.sol";
|
||||
import { IL1ScrollMessenger } from "../IL1ScrollMessenger.sol";
|
||||
import { IL1ERC721Gateway } from "./IL1ERC721Gateway.sol";
|
||||
import {IL2ERC721Gateway} from "../../L2/gateways/IL2ERC721Gateway.sol";
|
||||
import {IL1ScrollMessenger} from "../IL1ScrollMessenger.sol";
|
||||
import {IL1ERC721Gateway} from "./IL1ERC721Gateway.sol";
|
||||
|
||||
import { ScrollGatewayBase } from "../../libraries/gateway/ScrollGatewayBase.sol";
|
||||
import {ScrollGatewayBase} from "../../libraries/gateway/ScrollGatewayBase.sol";
|
||||
|
||||
/// @title L1ERC721Gateway
|
||||
/// @notice The `L1ERC721Gateway` is used to deposit ERC721 compatible NFT in layer 1 and
|
||||
@@ -21,194 +21,194 @@ import { ScrollGatewayBase } from "../../libraries/gateway/ScrollGatewayBase.sol
|
||||
/// This will be changed if we have more specific scenarios.
|
||||
// @todo Current implementation doesn't support calling from `L1GatewayRouter`.
|
||||
contract L1ERC721Gateway is OwnableUpgradeable, ERC721HolderUpgradeable, ScrollGatewayBase, IL1ERC721Gateway {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
|
||||
/// @notice Emitted when token mapping for ERC721 token is updated.
|
||||
/// @param _l1Token The address of ERC721 token in layer 1.
|
||||
/// @param _l1Token The address of corresponding ERC721 token in layer 2.
|
||||
event UpdateTokenMapping(address _l1Token, address _l2Token);
|
||||
/// @notice Emitted when token mapping for ERC721 token is updated.
|
||||
/// @param _l1Token The address of ERC721 token in layer 1.
|
||||
/// @param _l1Token The address of corresponding ERC721 token in layer 2.
|
||||
event UpdateTokenMapping(address _l1Token, address _l2Token);
|
||||
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
/// @notice Mapping from l1 token address to l2 token address for ERC721 NFT.
|
||||
mapping(address => address) public tokenMapping;
|
||||
/// @notice Mapping from l1 token address to l2 token address for ERC721 NFT.
|
||||
mapping(address => address) public tokenMapping;
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
/// @notice Initialize the storage of L1ERC721Gateway.
|
||||
/// @param _counterpart The address of L2ERC721Gateway in L2.
|
||||
/// @param _messenger The address of L1ScrollMessenger.
|
||||
function initialize(address _counterpart, address _messenger) external initializer {
|
||||
OwnableUpgradeable.__Ownable_init();
|
||||
ScrollGatewayBase._initialize(_counterpart, address(0), _messenger);
|
||||
}
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
|
||||
/// @inheritdoc IL1ERC721Gateway
|
||||
function depositERC721(
|
||||
address _token,
|
||||
uint256 _tokenId,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_depositERC721(_token, msg.sender, _tokenId, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ERC721Gateway
|
||||
function depositERC721(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_depositERC721(_token, _to, _tokenId, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ERC721Gateway
|
||||
function batchDepositERC721(
|
||||
address _token,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_batchDepositERC721(_token, msg.sender, _tokenIds, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ERC721Gateway
|
||||
function batchDepositERC721(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_batchDepositERC721(_token, _to, _tokenIds, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ERC721Gateway
|
||||
function finalizeWithdrawERC721(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _tokenId
|
||||
) external override nonReentrant onlyCallByCounterpart {
|
||||
require(_l2Token == tokenMapping[_l1Token], "l2 token mismatch");
|
||||
|
||||
IERC721Upgradeable(_l1Token).safeTransferFrom(address(this), _to, _tokenId);
|
||||
|
||||
emit FinalizeWithdrawERC721(_l1Token, _l2Token, _from, _to, _tokenId);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ERC721Gateway
|
||||
function finalizeBatchWithdrawERC721(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds
|
||||
) external override nonReentrant onlyCallByCounterpart {
|
||||
require(_l2Token == tokenMapping[_l1Token], "l2 token mismatch");
|
||||
|
||||
for (uint256 i = 0; i < _tokenIds.length; i++) {
|
||||
IERC721Upgradeable(_l1Token).safeTransferFrom(address(this), _to, _tokenIds[i]);
|
||||
/// @notice Initialize the storage of L1ERC721Gateway.
|
||||
/// @param _counterpart The address of L2ERC721Gateway in L2.
|
||||
/// @param _messenger The address of L1ScrollMessenger.
|
||||
function initialize(address _counterpart, address _messenger) external initializer {
|
||||
OwnableUpgradeable.__Ownable_init();
|
||||
ScrollGatewayBase._initialize(_counterpart, address(0), _messenger);
|
||||
}
|
||||
|
||||
emit FinalizeBatchWithdrawERC721(_l1Token, _l2Token, _from, _to, _tokenIds);
|
||||
}
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
/// @notice Update layer 2 to layer 2 token mapping.
|
||||
/// @param _l1Token The address of ERC721 token in layer 1.
|
||||
/// @param _l1Token The address of corresponding ERC721 token in layer 2.
|
||||
function updateTokenMapping(address _l1Token, address _l2Token) external onlyOwner {
|
||||
require(_l2Token != address(0), "map to zero address");
|
||||
|
||||
tokenMapping[_l1Token] = _l2Token;
|
||||
|
||||
emit UpdateTokenMapping(_l1Token, _l2Token);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
/// @dev Internal function to deposit ERC721 NFT to layer 2.
|
||||
/// @param _token The address of ERC721 NFT in layer 1.
|
||||
/// @param _to The address of recipient in layer 2.
|
||||
/// @param _tokenId The token id to deposit.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
|
||||
function _depositERC721(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _gasLimit
|
||||
) internal nonReentrant {
|
||||
address _l2Token = tokenMapping[_token];
|
||||
require(_l2Token != address(0), "token not supported");
|
||||
|
||||
// 1. transfer token to this contract
|
||||
IERC721Upgradeable(_token).safeTransferFrom(msg.sender, address(this), _tokenId);
|
||||
|
||||
// 2. Generate message passed to L2ERC721Gateway.
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL2ERC721Gateway.finalizeDepositERC721.selector,
|
||||
_token,
|
||||
_l2Token,
|
||||
msg.sender,
|
||||
_to,
|
||||
_tokenId
|
||||
);
|
||||
|
||||
// 3. Send message to L1ScrollMessenger.
|
||||
IL1ScrollMessenger(messenger).sendMessage{ value: msg.value }(counterpart, 0, _message, _gasLimit);
|
||||
|
||||
emit DepositERC721(_token, _l2Token, msg.sender, _to, _tokenId);
|
||||
}
|
||||
|
||||
/// @dev Internal function to batch deposit ERC721 NFT to layer 2.
|
||||
/// @param _token The address of ERC721 NFT in layer 1.
|
||||
/// @param _to The address of recipient in layer 2.
|
||||
/// @param _tokenIds The list of token ids to deposit.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
|
||||
function _batchDepositERC721(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256 _gasLimit
|
||||
) internal nonReentrant {
|
||||
require(_tokenIds.length > 0, "no token to deposit");
|
||||
|
||||
address _l2Token = tokenMapping[_token];
|
||||
require(_l2Token != address(0), "token not supported");
|
||||
|
||||
// 1. transfer token to this contract
|
||||
for (uint256 i = 0; i < _tokenIds.length; i++) {
|
||||
IERC721Upgradeable(_token).safeTransferFrom(msg.sender, address(this), _tokenIds[i]);
|
||||
/// @inheritdoc IL1ERC721Gateway
|
||||
function depositERC721(
|
||||
address _token,
|
||||
uint256 _tokenId,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_depositERC721(_token, msg.sender, _tokenId, _gasLimit);
|
||||
}
|
||||
|
||||
// 2. Generate message passed to L2ERC721Gateway.
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL2ERC721Gateway.finalizeBatchDepositERC721.selector,
|
||||
_token,
|
||||
_l2Token,
|
||||
msg.sender,
|
||||
_to,
|
||||
_tokenIds
|
||||
);
|
||||
/// @inheritdoc IL1ERC721Gateway
|
||||
function depositERC721(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_depositERC721(_token, _to, _tokenId, _gasLimit);
|
||||
}
|
||||
|
||||
// 3. Send message to L1ScrollMessenger.
|
||||
IL1ScrollMessenger(messenger).sendMessage{ value: msg.value }(counterpart, 0, _message, _gasLimit);
|
||||
/// @inheritdoc IL1ERC721Gateway
|
||||
function batchDepositERC721(
|
||||
address _token,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_batchDepositERC721(_token, msg.sender, _tokenIds, _gasLimit);
|
||||
}
|
||||
|
||||
emit BatchDepositERC721(_token, _l2Token, msg.sender, _to, _tokenIds);
|
||||
}
|
||||
/// @inheritdoc IL1ERC721Gateway
|
||||
function batchDepositERC721(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_batchDepositERC721(_token, _to, _tokenIds, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ERC721Gateway
|
||||
function finalizeWithdrawERC721(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _tokenId
|
||||
) external override nonReentrant onlyCallByCounterpart {
|
||||
require(_l2Token == tokenMapping[_l1Token], "l2 token mismatch");
|
||||
|
||||
IERC721Upgradeable(_l1Token).safeTransferFrom(address(this), _to, _tokenId);
|
||||
|
||||
emit FinalizeWithdrawERC721(_l1Token, _l2Token, _from, _to, _tokenId);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ERC721Gateway
|
||||
function finalizeBatchWithdrawERC721(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds
|
||||
) external override nonReentrant onlyCallByCounterpart {
|
||||
require(_l2Token == tokenMapping[_l1Token], "l2 token mismatch");
|
||||
|
||||
for (uint256 i = 0; i < _tokenIds.length; i++) {
|
||||
IERC721Upgradeable(_l1Token).safeTransferFrom(address(this), _to, _tokenIds[i]);
|
||||
}
|
||||
|
||||
emit FinalizeBatchWithdrawERC721(_l1Token, _l2Token, _from, _to, _tokenIds);
|
||||
}
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
/// @notice Update layer 2 to layer 2 token mapping.
|
||||
/// @param _l1Token The address of ERC721 token in layer 1.
|
||||
/// @param _l1Token The address of corresponding ERC721 token in layer 2.
|
||||
function updateTokenMapping(address _l1Token, address _l2Token) external onlyOwner {
|
||||
require(_l2Token != address(0), "map to zero address");
|
||||
|
||||
tokenMapping[_l1Token] = _l2Token;
|
||||
|
||||
emit UpdateTokenMapping(_l1Token, _l2Token);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
/// @dev Internal function to deposit ERC721 NFT to layer 2.
|
||||
/// @param _token The address of ERC721 NFT in layer 1.
|
||||
/// @param _to The address of recipient in layer 2.
|
||||
/// @param _tokenId The token id to deposit.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
|
||||
function _depositERC721(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _gasLimit
|
||||
) internal nonReentrant {
|
||||
address _l2Token = tokenMapping[_token];
|
||||
require(_l2Token != address(0), "token not supported");
|
||||
|
||||
// 1. transfer token to this contract
|
||||
IERC721Upgradeable(_token).safeTransferFrom(msg.sender, address(this), _tokenId);
|
||||
|
||||
// 2. Generate message passed to L2ERC721Gateway.
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL2ERC721Gateway.finalizeDepositERC721.selector,
|
||||
_token,
|
||||
_l2Token,
|
||||
msg.sender,
|
||||
_to,
|
||||
_tokenId
|
||||
);
|
||||
|
||||
// 3. Send message to L1ScrollMessenger.
|
||||
IL1ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, _gasLimit);
|
||||
|
||||
emit DepositERC721(_token, _l2Token, msg.sender, _to, _tokenId);
|
||||
}
|
||||
|
||||
/// @dev Internal function to batch deposit ERC721 NFT to layer 2.
|
||||
/// @param _token The address of ERC721 NFT in layer 1.
|
||||
/// @param _to The address of recipient in layer 2.
|
||||
/// @param _tokenIds The list of token ids to deposit.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the deposit on layer 2.
|
||||
function _batchDepositERC721(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256 _gasLimit
|
||||
) internal nonReentrant {
|
||||
require(_tokenIds.length > 0, "no token to deposit");
|
||||
|
||||
address _l2Token = tokenMapping[_token];
|
||||
require(_l2Token != address(0), "token not supported");
|
||||
|
||||
// 1. transfer token to this contract
|
||||
for (uint256 i = 0; i < _tokenIds.length; i++) {
|
||||
IERC721Upgradeable(_token).safeTransferFrom(msg.sender, address(this), _tokenIds[i]);
|
||||
}
|
||||
|
||||
// 2. Generate message passed to L2ERC721Gateway.
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL2ERC721Gateway.finalizeBatchDepositERC721.selector,
|
||||
_token,
|
||||
_l2Token,
|
||||
msg.sender,
|
||||
_to,
|
||||
_tokenIds
|
||||
);
|
||||
|
||||
// 3. Send message to L1ScrollMessenger.
|
||||
IL1ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, _gasLimit);
|
||||
|
||||
emit BatchDepositERC721(_token, _l2Token, msg.sender, _to, _tokenIds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
|
||||
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
|
||||
|
||||
import { IL2ETHGateway } from "../../L2/gateways/IL2ETHGateway.sol";
|
||||
import { IL1ScrollMessenger } from "../IL1ScrollMessenger.sol";
|
||||
import { IL1ETHGateway } from "./IL1ETHGateway.sol";
|
||||
import {IL2ETHGateway} from "../../L2/gateways/IL2ETHGateway.sol";
|
||||
import {IL1ScrollMessenger} from "../IL1ScrollMessenger.sol";
|
||||
import {IL1ETHGateway} from "./IL1ETHGateway.sol";
|
||||
|
||||
import { ScrollGatewayBase } from "../../libraries/gateway/ScrollGatewayBase.sol";
|
||||
import {ScrollGatewayBase} from "../../libraries/gateway/ScrollGatewayBase.sol";
|
||||
|
||||
/// @title L1ETHGateway
|
||||
/// @notice The `L1ETHGateway` is used to deposit ETH in layer 1 and
|
||||
@@ -16,103 +16,103 @@ import { ScrollGatewayBase } from "../../libraries/gateway/ScrollGatewayBase.sol
|
||||
/// @dev The deposited ETH tokens are held in this gateway. On finalizing withdraw, the corresponding
|
||||
/// ETH will be transfer to the recipient directly.
|
||||
contract L1ETHGateway is Initializable, ScrollGatewayBase, IL1ETHGateway {
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
/// @notice Initialize the storage of L1ETHGateway.
|
||||
/// @param _counterpart The address of L2ETHGateway in L2.
|
||||
/// @param _router The address of L1GatewayRouter.
|
||||
/// @param _messenger The address of L1ScrollMessenger.
|
||||
function initialize(
|
||||
address _counterpart,
|
||||
address _router,
|
||||
address _messenger
|
||||
) external initializer {
|
||||
require(_router != address(0), "zero router address");
|
||||
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
|
||||
}
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
|
||||
/// @inheritdoc IL1ETHGateway
|
||||
function depositETH(uint256 _amount, uint256 _gasLimit) external payable override {
|
||||
_deposit(msg.sender, _amount, new bytes(0), _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ETHGateway
|
||||
function depositETH(
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) public payable override {
|
||||
_deposit(_to, _amount, new bytes(0), _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ETHGateway
|
||||
function depositETHAndCall(
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes calldata _data,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_deposit(_to, _amount, _data, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ETHGateway
|
||||
function finalizeWithdrawETH(
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes calldata _data
|
||||
) external payable override onlyCallByCounterpart {
|
||||
// @note can possible trigger reentrant call to this contract or messenger,
|
||||
// but it seems not a big problem.
|
||||
// solhint-disable-next-line avoid-low-level-calls
|
||||
(bool _success, ) = _to.call{ value: _amount }("");
|
||||
require(_success, "ETH transfer failed");
|
||||
|
||||
// @todo farward _data to `_to` in near future.
|
||||
|
||||
emit FinalizeWithdrawETH(_from, _to, _amount, _data);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
/// @dev The internal ETH deposit implementation.
|
||||
/// @param _to The address of recipient's account on L2.
|
||||
/// @param _amount The amount of ETH to be deposited.
|
||||
/// @param _data Optional data to forward to recipient's account.
|
||||
/// @param _gasLimit Gas limit required to complete the deposit on L2.
|
||||
function _deposit(
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) internal nonReentrant {
|
||||
require(_amount > 0, "deposit zero eth");
|
||||
|
||||
// 1. Extract real sender if this call is from L1GatewayRouter.
|
||||
address _from = msg.sender;
|
||||
if (router == msg.sender) {
|
||||
(_from, _data) = abi.decode(_data, (address, bytes));
|
||||
/// @notice Initialize the storage of L1ETHGateway.
|
||||
/// @param _counterpart The address of L2ETHGateway in L2.
|
||||
/// @param _router The address of L1GatewayRouter.
|
||||
/// @param _messenger The address of L1ScrollMessenger.
|
||||
function initialize(
|
||||
address _counterpart,
|
||||
address _router,
|
||||
address _messenger
|
||||
) external initializer {
|
||||
require(_router != address(0), "zero router address");
|
||||
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
|
||||
}
|
||||
|
||||
// 2. Generate message passed to L1ScrollMessenger.
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL2ETHGateway.finalizeDepositETH.selector,
|
||||
_from,
|
||||
_to,
|
||||
_amount,
|
||||
_data
|
||||
);
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
IL1ScrollMessenger(messenger).sendMessage{ value: msg.value }(counterpart, _amount, _message, _gasLimit);
|
||||
/// @inheritdoc IL1ETHGateway
|
||||
function depositETH(uint256 _amount, uint256 _gasLimit) external payable override {
|
||||
_deposit(msg.sender, _amount, new bytes(0), _gasLimit);
|
||||
}
|
||||
|
||||
emit DepositETH(_from, _to, _amount, _data);
|
||||
}
|
||||
/// @inheritdoc IL1ETHGateway
|
||||
function depositETH(
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) public payable override {
|
||||
_deposit(_to, _amount, new bytes(0), _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ETHGateway
|
||||
function depositETHAndCall(
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes calldata _data,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_deposit(_to, _amount, _data, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ETHGateway
|
||||
function finalizeWithdrawETH(
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes calldata _data
|
||||
) external payable override onlyCallByCounterpart {
|
||||
// @note can possible trigger reentrant call to this contract or messenger,
|
||||
// but it seems not a big problem.
|
||||
// solhint-disable-next-line avoid-low-level-calls
|
||||
(bool _success, ) = _to.call{value: _amount}("");
|
||||
require(_success, "ETH transfer failed");
|
||||
|
||||
// @todo farward _data to `_to` in near future.
|
||||
|
||||
emit FinalizeWithdrawETH(_from, _to, _amount, _data);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
/// @dev The internal ETH deposit implementation.
|
||||
/// @param _to The address of recipient's account on L2.
|
||||
/// @param _amount The amount of ETH to be deposited.
|
||||
/// @param _data Optional data to forward to recipient's account.
|
||||
/// @param _gasLimit Gas limit required to complete the deposit on L2.
|
||||
function _deposit(
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) internal nonReentrant {
|
||||
require(_amount > 0, "deposit zero eth");
|
||||
|
||||
// 1. Extract real sender if this call is from L1GatewayRouter.
|
||||
address _from = msg.sender;
|
||||
if (router == msg.sender) {
|
||||
(_from, _data) = abi.decode(_data, (address, bytes));
|
||||
}
|
||||
|
||||
// 2. Generate message passed to L1ScrollMessenger.
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL2ETHGateway.finalizeDepositETH.selector,
|
||||
_from,
|
||||
_to,
|
||||
_amount,
|
||||
_data
|
||||
);
|
||||
|
||||
IL1ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, _amount, _message, _gasLimit);
|
||||
|
||||
emit DepositETH(_from, _to, _amount, _data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
|
||||
import { IL2GatewayRouter } from "../../L2/gateways/IL2GatewayRouter.sol";
|
||||
import { IScrollGateway } from "../../libraries/gateway/IScrollGateway.sol";
|
||||
import { IL1ScrollMessenger } from "../IL1ScrollMessenger.sol";
|
||||
import { IL1ETHGateway } from "./IL1ETHGateway.sol";
|
||||
import { IL1ERC20Gateway } from "./IL1ERC20Gateway.sol";
|
||||
import { IL1GatewayRouter } from "./IL1GatewayRouter.sol";
|
||||
import {IL2GatewayRouter} from "../../L2/gateways/IL2GatewayRouter.sol";
|
||||
import {IScrollGateway} from "../../libraries/gateway/IScrollGateway.sol";
|
||||
import {IL1ScrollMessenger} from "../IL1ScrollMessenger.sol";
|
||||
import {IL1ETHGateway} from "./IL1ETHGateway.sol";
|
||||
import {IL1ERC20Gateway} from "./IL1ERC20Gateway.sol";
|
||||
import {IL1GatewayRouter} from "./IL1GatewayRouter.sol";
|
||||
|
||||
/// @title L1GatewayRouter
|
||||
/// @notice The `L1GatewayRouter` is the main entry for depositing Ether and ERC20 tokens.
|
||||
@@ -17,198 +17,198 @@ import { IL1GatewayRouter } from "./IL1GatewayRouter.sol";
|
||||
/// @dev One can also use this contract to query L1/L2 token address mapping.
|
||||
/// In the future, ERC-721 and ERC-1155 tokens will be added to the router too.
|
||||
contract L1GatewayRouter is OwnableUpgradeable, IL1GatewayRouter {
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
/// @notice The address of L1ETHGateway.
|
||||
address public ethGateway;
|
||||
/// @notice The address of L1ETHGateway.
|
||||
address public ethGateway;
|
||||
|
||||
/// @notice The addess of default ERC20 gateway, normally the L1StandardERC20Gateway contract.
|
||||
address public defaultERC20Gateway;
|
||||
/// @notice The addess of default ERC20 gateway, normally the L1StandardERC20Gateway contract.
|
||||
address public defaultERC20Gateway;
|
||||
|
||||
/// @notice Mapping from ERC20 token address to corresponding L1ERC20Gateway.
|
||||
// solhint-disable-next-line var-name-mixedcase
|
||||
mapping(address => address) public ERC20Gateway;
|
||||
/// @notice Mapping from ERC20 token address to corresponding L1ERC20Gateway.
|
||||
// solhint-disable-next-line var-name-mixedcase
|
||||
mapping(address => address) public ERC20Gateway;
|
||||
|
||||
// @todo: add ERC721/ERC1155 Gateway mapping.
|
||||
// @todo: add ERC721/ERC1155 Gateway mapping.
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
/// @notice Initialize the storage of L1GatewayRouter.
|
||||
/// @param _ethGateway The address of L1ETHGateway contract.
|
||||
/// @param _defaultERC20Gateway The address of default ERC20 Gateway contract.
|
||||
function initialize(address _ethGateway, address _defaultERC20Gateway) external initializer {
|
||||
OwnableUpgradeable.__Ownable_init();
|
||||
/// @notice Initialize the storage of L1GatewayRouter.
|
||||
/// @param _ethGateway The address of L1ETHGateway contract.
|
||||
/// @param _defaultERC20Gateway The address of default ERC20 Gateway contract.
|
||||
function initialize(address _ethGateway, address _defaultERC20Gateway) external initializer {
|
||||
OwnableUpgradeable.__Ownable_init();
|
||||
|
||||
// it can be zero during initialization
|
||||
if (_defaultERC20Gateway != address(0)) {
|
||||
defaultERC20Gateway = _defaultERC20Gateway;
|
||||
emit SetDefaultERC20Gateway(_defaultERC20Gateway);
|
||||
// it can be zero during initialization
|
||||
if (_defaultERC20Gateway != address(0)) {
|
||||
defaultERC20Gateway = _defaultERC20Gateway;
|
||||
emit SetDefaultERC20Gateway(_defaultERC20Gateway);
|
||||
}
|
||||
|
||||
// it can be zero during initialization
|
||||
if (_ethGateway != address(0)) {
|
||||
ethGateway = _ethGateway;
|
||||
emit SetETHGateway(_ethGateway);
|
||||
}
|
||||
}
|
||||
|
||||
// it can be zero during initialization
|
||||
if (_ethGateway != address(0)) {
|
||||
ethGateway = _ethGateway;
|
||||
emit SetETHGateway(_ethGateway);
|
||||
}
|
||||
}
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function getL2ERC20Address(address _l1Address) external view override returns (address) {
|
||||
address _gateway = getERC20Gateway(_l1Address);
|
||||
if (_gateway == address(0)) {
|
||||
return address(0);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function getL2ERC20Address(address _l1Address) external view override returns (address) {
|
||||
address _gateway = getERC20Gateway(_l1Address);
|
||||
if (_gateway == address(0)) {
|
||||
return address(0);
|
||||
return IL1ERC20Gateway(_gateway).getL2ERC20Address(_l1Address);
|
||||
}
|
||||
|
||||
return IL1ERC20Gateway(_gateway).getL2ERC20Address(_l1Address);
|
||||
}
|
||||
|
||||
/// @notice Return the corresponding gateway address for given token address.
|
||||
/// @param _token The address of token to query.
|
||||
function getERC20Gateway(address _token) public view returns (address) {
|
||||
address _gateway = ERC20Gateway[_token];
|
||||
if (_gateway == address(0)) {
|
||||
_gateway = defaultERC20Gateway;
|
||||
/// @notice Return the corresponding gateway address for given token address.
|
||||
/// @param _token The address of token to query.
|
||||
function getERC20Gateway(address _token) public view returns (address) {
|
||||
address _gateway = ERC20Gateway[_token];
|
||||
if (_gateway == address(0)) {
|
||||
_gateway = defaultERC20Gateway;
|
||||
}
|
||||
return _gateway;
|
||||
}
|
||||
return _gateway;
|
||||
}
|
||||
|
||||
/************************************************
|
||||
* Public Mutated Functions from L1ERC20Gateway *
|
||||
************************************************/
|
||||
/*************************************************
|
||||
* Public Mutating Functions from L1ERC20Gateway *
|
||||
*************************************************/
|
||||
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function depositERC20(
|
||||
address _token,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
depositERC20AndCall(_token, msg.sender, _amount, new bytes(0), _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function depositERC20(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
depositERC20AndCall(_token, _to, _amount, new bytes(0), _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function depositERC20AndCall(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) public payable override {
|
||||
address _gateway = getERC20Gateway(_token);
|
||||
require(_gateway != address(0), "no gateway available");
|
||||
|
||||
// encode msg.sender with _data
|
||||
bytes memory _routerData = abi.encode(msg.sender, _data);
|
||||
|
||||
IL1ERC20Gateway(_gateway).depositERC20AndCall{ value: msg.value }(_token, _to, _amount, _routerData, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function finalizeWithdrawERC20(
|
||||
address,
|
||||
address,
|
||||
address,
|
||||
address,
|
||||
uint256,
|
||||
bytes calldata
|
||||
) external payable virtual override {
|
||||
revert("should never be called");
|
||||
}
|
||||
|
||||
/**********************************************
|
||||
* Public Mutated Functions from L1ETHGateway *
|
||||
**********************************************/
|
||||
|
||||
/// @inheritdoc IL1ETHGateway
|
||||
function depositETH(uint256 _amount, uint256 _gasLimit) external payable override {
|
||||
depositETHAndCall(msg.sender, _amount, new bytes(0), _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ETHGateway
|
||||
function depositETH(
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
depositETHAndCall(_to, _amount, new bytes(0), _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ETHGateway
|
||||
function depositETHAndCall(
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) public payable override {
|
||||
address _gateway = ethGateway;
|
||||
require(_gateway != address(0), "eth gateway available");
|
||||
|
||||
// encode msg.sender with _data
|
||||
bytes memory _routerData = abi.encode(msg.sender, _data);
|
||||
|
||||
IL1ETHGateway(_gateway).depositETHAndCall{ value: msg.value }(_to, _amount, _routerData, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ETHGateway
|
||||
function finalizeWithdrawETH(
|
||||
address,
|
||||
address,
|
||||
uint256,
|
||||
bytes calldata
|
||||
) external payable virtual override {
|
||||
revert("should never be called");
|
||||
}
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
/// @notice Update the address of ETH gateway contract.
|
||||
/// @dev This function should only be called by contract owner.
|
||||
/// @param _ethGateway The address to update.
|
||||
function setETHGateway(address _ethGateway) external onlyOwner {
|
||||
ethGateway = _ethGateway;
|
||||
|
||||
emit SetETHGateway(_ethGateway);
|
||||
}
|
||||
|
||||
/// @notice Update the address of default ERC20 gateway contract.
|
||||
/// @dev This function should only be called by contract owner.
|
||||
/// @param _defaultERC20Gateway The address to update.
|
||||
function setDefaultERC20Gateway(address _defaultERC20Gateway) external onlyOwner {
|
||||
defaultERC20Gateway = _defaultERC20Gateway;
|
||||
|
||||
emit SetDefaultERC20Gateway(_defaultERC20Gateway);
|
||||
}
|
||||
|
||||
/// @notice Update the mapping from token address to gateway address.
|
||||
/// @dev This function should only be called by contract owner.
|
||||
/// @param _tokens The list of addresses of tokens to update.
|
||||
/// @param _gateways The list of addresses of gateways to update.
|
||||
function setERC20Gateway(address[] memory _tokens, address[] memory _gateways) external onlyOwner {
|
||||
require(_tokens.length == _gateways.length, "length mismatch");
|
||||
|
||||
for (uint256 i = 0; i < _tokens.length; i++) {
|
||||
ERC20Gateway[_tokens[i]] = _gateways[i];
|
||||
|
||||
emit SetERC20Gateway(_tokens[i], _gateways[i]);
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function depositERC20(
|
||||
address _token,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
depositERC20AndCall(_token, msg.sender, _amount, new bytes(0), _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function depositERC20(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
depositERC20AndCall(_token, _to, _amount, new bytes(0), _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function depositERC20AndCall(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) public payable override {
|
||||
address _gateway = getERC20Gateway(_token);
|
||||
require(_gateway != address(0), "no gateway available");
|
||||
|
||||
// encode msg.sender with _data
|
||||
bytes memory _routerData = abi.encode(msg.sender, _data);
|
||||
|
||||
IL1ERC20Gateway(_gateway).depositERC20AndCall{value: msg.value}(_token, _to, _amount, _routerData, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function finalizeWithdrawERC20(
|
||||
address,
|
||||
address,
|
||||
address,
|
||||
address,
|
||||
uint256,
|
||||
bytes calldata
|
||||
) external payable virtual override {
|
||||
revert("should never be called");
|
||||
}
|
||||
|
||||
/***********************************************
|
||||
* Public Mutating Functions from L1ETHGateway *
|
||||
***********************************************/
|
||||
|
||||
/// @inheritdoc IL1ETHGateway
|
||||
function depositETH(uint256 _amount, uint256 _gasLimit) external payable override {
|
||||
depositETHAndCall(msg.sender, _amount, new bytes(0), _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ETHGateway
|
||||
function depositETH(
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
depositETHAndCall(_to, _amount, new bytes(0), _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ETHGateway
|
||||
function depositETHAndCall(
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) public payable override {
|
||||
address _gateway = ethGateway;
|
||||
require(_gateway != address(0), "eth gateway available");
|
||||
|
||||
// encode msg.sender with _data
|
||||
bytes memory _routerData = abi.encode(msg.sender, _data);
|
||||
|
||||
IL1ETHGateway(_gateway).depositETHAndCall{value: msg.value}(_to, _amount, _routerData, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1ETHGateway
|
||||
function finalizeWithdrawETH(
|
||||
address,
|
||||
address,
|
||||
uint256,
|
||||
bytes calldata
|
||||
) external payable virtual override {
|
||||
revert("should never be called");
|
||||
}
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
/// @notice Update the address of ETH gateway contract.
|
||||
/// @dev This function should only be called by contract owner.
|
||||
/// @param _ethGateway The address to update.
|
||||
function setETHGateway(address _ethGateway) external onlyOwner {
|
||||
ethGateway = _ethGateway;
|
||||
|
||||
emit SetETHGateway(_ethGateway);
|
||||
}
|
||||
|
||||
/// @notice Update the address of default ERC20 gateway contract.
|
||||
/// @dev This function should only be called by contract owner.
|
||||
/// @param _defaultERC20Gateway The address to update.
|
||||
function setDefaultERC20Gateway(address _defaultERC20Gateway) external onlyOwner {
|
||||
defaultERC20Gateway = _defaultERC20Gateway;
|
||||
|
||||
emit SetDefaultERC20Gateway(_defaultERC20Gateway);
|
||||
}
|
||||
|
||||
/// @notice Update the mapping from token address to gateway address.
|
||||
/// @dev This function should only be called by contract owner.
|
||||
/// @param _tokens The list of addresses of tokens to update.
|
||||
/// @param _gateways The list of addresses of gateways to update.
|
||||
function setERC20Gateway(address[] memory _tokens, address[] memory _gateways) external onlyOwner {
|
||||
require(_tokens.length == _gateways.length, "length mismatch");
|
||||
|
||||
for (uint256 i = 0; i < _tokens.length; i++) {
|
||||
ERC20Gateway[_tokens[i]] = _gateways[i];
|
||||
|
||||
emit SetERC20Gateway(_tokens[i], _gateways[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
|
||||
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import { Clones } from "@openzeppelin/contracts/proxy/Clones.sol";
|
||||
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
|
||||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
|
||||
|
||||
import { IERC20Metadata } from "../../interfaces/IERC20Metadata.sol";
|
||||
import { IL2ERC20Gateway } from "../../L2/gateways/IL2ERC20Gateway.sol";
|
||||
import { IL1ScrollMessenger } from "../IL1ScrollMessenger.sol";
|
||||
import { IL1ERC20Gateway } from "./IL1ERC20Gateway.sol";
|
||||
import {IERC20Metadata} from "../../interfaces/IERC20Metadata.sol";
|
||||
import {IL2ERC20Gateway} from "../../L2/gateways/IL2ERC20Gateway.sol";
|
||||
import {IL1ScrollMessenger} from "../IL1ScrollMessenger.sol";
|
||||
import {IL1ERC20Gateway} from "./IL1ERC20Gateway.sol";
|
||||
|
||||
import { ScrollGatewayBase } from "../../libraries/gateway/ScrollGatewayBase.sol";
|
||||
import { L1ERC20Gateway } from "./L1ERC20Gateway.sol";
|
||||
import {ScrollGatewayBase} from "../../libraries/gateway/ScrollGatewayBase.sol";
|
||||
import {L1ERC20Gateway} from "./L1ERC20Gateway.sol";
|
||||
|
||||
/// @title L1StandardERC20Gateway
|
||||
/// @notice The `L1StandardERC20Gateway` is used to deposit standard ERC20 tokens in layer 1 and
|
||||
@@ -22,147 +22,147 @@ import { L1ERC20Gateway } from "./L1ERC20Gateway.sol";
|
||||
/// token will be transfer to the recipient directly. Any ERC20 that requires non-standard functionality
|
||||
/// should use a separate gateway.
|
||||
contract L1StandardERC20Gateway is Initializable, ScrollGatewayBase, L1ERC20Gateway {
|
||||
using SafeERC20 for IERC20;
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
/// @notice The address of ScrollStandardERC20 implementation in L2.
|
||||
address public l2TokenImplementation;
|
||||
/// @notice The address of ScrollStandardERC20 implementation in L2.
|
||||
address public l2TokenImplementation;
|
||||
|
||||
/// @notice The address of ScrollStandardERC20Factory contract in L2.
|
||||
address public l2TokenFactory;
|
||||
/// @notice The address of ScrollStandardERC20Factory contract in L2.
|
||||
address public l2TokenFactory;
|
||||
|
||||
/// @notice Mapping from l1 token address to l2 token address.
|
||||
/// @dev This is not necessary, since we can compute the address directly. But, we use this mapping
|
||||
/// to keep track on whether we have deployed the token in L2 using the L2ScrollStandardERC20Factory and
|
||||
/// pass deploy data on first call to the token.
|
||||
mapping(address => address) private tokenMapping;
|
||||
/// @notice Mapping from l1 token address to l2 token address.
|
||||
/// @dev This is not necessary, since we can compute the address directly. But, we use this mapping
|
||||
/// to keep track on whether we have deployed the token in L2 using the L2ScrollStandardERC20Factory and
|
||||
/// pass deploy data on first call to the token.
|
||||
mapping(address => address) private tokenMapping;
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
/// @notice Initialize the storage of L1StandardERC20Gateway.
|
||||
/// @param _counterpart The address of L2StandardERC20Gateway in L2.
|
||||
/// @param _router The address of L1GatewayRouter.
|
||||
/// @param _messenger The address of L1ScrollMessenger.
|
||||
/// @param _l2TokenImplementation The address of ScrollStandardERC20 implementation in L2.
|
||||
/// @param _l2TokenFactory The address of ScrollStandardERC20Factory contract in L2.
|
||||
function initialize(
|
||||
address _counterpart,
|
||||
address _router,
|
||||
address _messenger,
|
||||
address _l2TokenImplementation,
|
||||
address _l2TokenFactory
|
||||
) external initializer {
|
||||
require(_router != address(0), "zero router address");
|
||||
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
|
||||
/// @notice Initialize the storage of L1StandardERC20Gateway.
|
||||
/// @param _counterpart The address of L2StandardERC20Gateway in L2.
|
||||
/// @param _router The address of L1GatewayRouter.
|
||||
/// @param _messenger The address of L1ScrollMessenger.
|
||||
/// @param _l2TokenImplementation The address of ScrollStandardERC20 implementation in L2.
|
||||
/// @param _l2TokenFactory The address of ScrollStandardERC20Factory contract in L2.
|
||||
function initialize(
|
||||
address _counterpart,
|
||||
address _router,
|
||||
address _messenger,
|
||||
address _l2TokenImplementation,
|
||||
address _l2TokenFactory
|
||||
) external initializer {
|
||||
require(_router != address(0), "zero router address");
|
||||
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
|
||||
|
||||
require(_l2TokenImplementation != address(0), "zero implementation hash");
|
||||
require(_l2TokenFactory != address(0), "zero factory address");
|
||||
require(_l2TokenImplementation != address(0), "zero implementation hash");
|
||||
require(_l2TokenFactory != address(0), "zero factory address");
|
||||
|
||||
l2TokenImplementation = _l2TokenImplementation;
|
||||
l2TokenFactory = _l2TokenFactory;
|
||||
}
|
||||
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function getL2ERC20Address(address _l1Token) public view override returns (address) {
|
||||
// In StandardERC20Gateway, all corresponding l2 tokens are depoyed by Create2 with salt,
|
||||
// we can calculate the l2 address directly.
|
||||
bytes32 _salt = keccak256(abi.encodePacked(counterpart, keccak256(abi.encodePacked(_l1Token))));
|
||||
|
||||
return Clones.predictDeterministicAddress(l2TokenImplementation, _salt, l2TokenFactory);
|
||||
}
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function finalizeWithdrawERC20(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes calldata _data
|
||||
) external payable override onlyCallByCounterpart {
|
||||
require(msg.value == 0, "nonzero msg.value");
|
||||
|
||||
// @note can possible trigger reentrant call to this contract or messenger,
|
||||
// but it seems not a big problem.
|
||||
IERC20(_l1Token).safeTransfer(_to, _amount);
|
||||
|
||||
// @todo forward `_data` to `_to` in the near future
|
||||
|
||||
emit FinalizeWithdrawERC20(_l1Token, _l2Token, _from, _to, _amount, _data);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
/// @inheritdoc L1ERC20Gateway
|
||||
function _deposit(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) internal virtual override nonReentrant {
|
||||
require(_amount > 0, "deposit zero amount");
|
||||
|
||||
// 1. Extract real sender if this call is from L1GatewayRouter.
|
||||
address _from = msg.sender;
|
||||
if (router == msg.sender) {
|
||||
(_from, _data) = abi.decode(_data, (address, bytes));
|
||||
l2TokenImplementation = _l2TokenImplementation;
|
||||
l2TokenFactory = _l2TokenFactory;
|
||||
}
|
||||
|
||||
// 2. Transfer token into this contract.
|
||||
{
|
||||
// common practice to handle fee on transfer token.
|
||||
uint256 _before = IERC20(_token).balanceOf(address(this));
|
||||
IERC20(_token).safeTransferFrom(_from, address(this), _amount);
|
||||
uint256 _after = IERC20(_token).balanceOf(address(this));
|
||||
// no unchecked here, since some weird token may return arbitrary balance.
|
||||
_amount = _after - _before;
|
||||
// ignore weird fee on transfer token
|
||||
require(_amount > 0, "deposit zero amount");
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function getL2ERC20Address(address _l1Token) public view override returns (address) {
|
||||
// In StandardERC20Gateway, all corresponding l2 tokens are depoyed by Create2 with salt,
|
||||
// we can calculate the l2 address directly.
|
||||
bytes32 _salt = keccak256(abi.encodePacked(counterpart, keccak256(abi.encodePacked(_l1Token))));
|
||||
|
||||
return Clones.predictDeterministicAddress(l2TokenImplementation, _salt, l2TokenFactory);
|
||||
}
|
||||
|
||||
// 3. Generate message passed to L2StandardERC20Gateway.
|
||||
address _l2Token = tokenMapping[_token];
|
||||
bytes memory _l2Data = _data;
|
||||
if (_l2Token == address(0)) {
|
||||
// It is a new token, compute and store mapping in storage.
|
||||
_l2Token = getL2ERC20Address(_token);
|
||||
tokenMapping[_token] = _l2Token;
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
// passing symbol/name/decimal in order to deploy in L2.
|
||||
string memory _symbol = IERC20Metadata(_token).symbol();
|
||||
string memory _name = IERC20Metadata(_token).name();
|
||||
uint8 _decimals = IERC20Metadata(_token).decimals();
|
||||
_l2Data = abi.encode(_data, abi.encode(_symbol, _name, _decimals));
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function finalizeWithdrawERC20(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes calldata _data
|
||||
) external payable override onlyCallByCounterpart {
|
||||
require(msg.value == 0, "nonzero msg.value");
|
||||
|
||||
// @note can possible trigger reentrant call to this contract or messenger,
|
||||
// but it seems not a big problem.
|
||||
IERC20(_l1Token).safeTransfer(_to, _amount);
|
||||
|
||||
// @todo forward `_data` to `_to` in the near future
|
||||
|
||||
emit FinalizeWithdrawERC20(_l1Token, _l2Token, _from, _to, _amount, _data);
|
||||
}
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL2ERC20Gateway.finalizeDepositERC20.selector,
|
||||
_token,
|
||||
_l2Token,
|
||||
_from,
|
||||
_to,
|
||||
_amount,
|
||||
_l2Data
|
||||
);
|
||||
|
||||
// 4. Send message to L1ScrollMessenger.
|
||||
IL1ScrollMessenger(messenger).sendMessage{ value: msg.value }(counterpart, 0, _message, _gasLimit);
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
emit DepositERC20(_token, _l2Token, _from, _to, _amount, _data);
|
||||
}
|
||||
/// @inheritdoc L1ERC20Gateway
|
||||
function _deposit(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) internal virtual override nonReentrant {
|
||||
require(_amount > 0, "deposit zero amount");
|
||||
|
||||
// 1. Extract real sender if this call is from L1GatewayRouter.
|
||||
address _from = msg.sender;
|
||||
if (router == msg.sender) {
|
||||
(_from, _data) = abi.decode(_data, (address, bytes));
|
||||
}
|
||||
|
||||
// 2. Transfer token into this contract.
|
||||
{
|
||||
// common practice to handle fee on transfer token.
|
||||
uint256 _before = IERC20(_token).balanceOf(address(this));
|
||||
IERC20(_token).safeTransferFrom(_from, address(this), _amount);
|
||||
uint256 _after = IERC20(_token).balanceOf(address(this));
|
||||
// no unchecked here, since some weird token may return arbitrary balance.
|
||||
_amount = _after - _before;
|
||||
// ignore weird fee on transfer token
|
||||
require(_amount > 0, "deposit zero amount");
|
||||
}
|
||||
|
||||
// 3. Generate message passed to L2StandardERC20Gateway.
|
||||
address _l2Token = tokenMapping[_token];
|
||||
bytes memory _l2Data = _data;
|
||||
if (_l2Token == address(0)) {
|
||||
// It is a new token, compute and store mapping in storage.
|
||||
_l2Token = getL2ERC20Address(_token);
|
||||
tokenMapping[_token] = _l2Token;
|
||||
|
||||
// passing symbol/name/decimal in order to deploy in L2.
|
||||
string memory _symbol = IERC20Metadata(_token).symbol();
|
||||
string memory _name = IERC20Metadata(_token).name();
|
||||
uint8 _decimals = IERC20Metadata(_token).decimals();
|
||||
_l2Data = abi.encode(_data, abi.encode(_symbol, _name, _decimals));
|
||||
}
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL2ERC20Gateway.finalizeDepositERC20.selector,
|
||||
_token,
|
||||
_l2Token,
|
||||
_from,
|
||||
_to,
|
||||
_amount,
|
||||
_l2Data
|
||||
);
|
||||
|
||||
// 4. Send message to L1ScrollMessenger.
|
||||
IL1ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, _gasLimit);
|
||||
|
||||
emit DepositERC20(_token, _l2Token, _from, _to, _amount, _data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
|
||||
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
|
||||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
|
||||
import { IWETH } from "../../interfaces/IWETH.sol";
|
||||
import { IL2ERC20Gateway } from "../../L2/gateways/IL2ERC20Gateway.sol";
|
||||
import { IL1ScrollMessenger } from "../IL1ScrollMessenger.sol";
|
||||
import { IL1ERC20Gateway } from "./IL1ERC20Gateway.sol";
|
||||
import {IWETH} from "../../interfaces/IWETH.sol";
|
||||
import {IL2ERC20Gateway} from "../../L2/gateways/IL2ERC20Gateway.sol";
|
||||
import {IL1ScrollMessenger} from "../IL1ScrollMessenger.sol";
|
||||
import {IL1ERC20Gateway} from "./IL1ERC20Gateway.sol";
|
||||
|
||||
import { ScrollGatewayBase } from "../../libraries/gateway/ScrollGatewayBase.sol";
|
||||
import { L1ERC20Gateway } from "./L1ERC20Gateway.sol";
|
||||
import {ScrollGatewayBase} from "../../libraries/gateway/ScrollGatewayBase.sol";
|
||||
import {L1ERC20Gateway} from "./L1ERC20Gateway.sol";
|
||||
|
||||
/// @title L1WETHGateway
|
||||
/// @notice The `L1WETHGateway` contract is used to deposit `WETH` token in layer 1 and
|
||||
@@ -22,118 +22,123 @@ import { L1ERC20Gateway } from "./L1ERC20Gateway.sol";
|
||||
/// On finalizing withdraw, the Ether will be transfered from `L1ScrollMessenger`, then
|
||||
/// wrapped as WETH and finally transfer to recipient.
|
||||
contract L1WETHGateway is Initializable, ScrollGatewayBase, L1ERC20Gateway {
|
||||
using SafeERC20 for IERC20;
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
/*************
|
||||
* Constants *
|
||||
*************/
|
||||
/*************
|
||||
* Constants *
|
||||
*************/
|
||||
|
||||
/// @notice The address of L2 WETH address.
|
||||
address public immutable l2WETH;
|
||||
/// @notice The address of L2 WETH address.
|
||||
address public immutable l2WETH;
|
||||
|
||||
/// @notice The address of L1 WETH address.
|
||||
// solhint-disable-next-line var-name-mixedcase
|
||||
address public immutable WETH;
|
||||
/// @notice The address of L1 WETH address.
|
||||
// solhint-disable-next-line var-name-mixedcase
|
||||
address public immutable WETH;
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
constructor(address _WETH, address _l2WETH) {
|
||||
WETH = _WETH;
|
||||
l2WETH = _l2WETH;
|
||||
}
|
||||
|
||||
/// @notice Initialize the storage of L1WETHGateway.
|
||||
/// @param _counterpart The address of L2ETHGateway in L2.
|
||||
/// @param _router The address of L1GatewayRouter.
|
||||
/// @param _messenger The address of L1ScrollMessenger.
|
||||
function initialize(
|
||||
address _counterpart,
|
||||
address _router,
|
||||
address _messenger
|
||||
) external initializer {
|
||||
require(_router != address(0), "zero router address");
|
||||
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
|
||||
}
|
||||
|
||||
receive() external payable {
|
||||
require(msg.sender == WETH, "only WETH");
|
||||
}
|
||||
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function getL2ERC20Address(address) public view override returns (address) {
|
||||
return l2WETH;
|
||||
}
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function finalizeWithdrawERC20(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes calldata _data
|
||||
) external payable override onlyCallByCounterpart {
|
||||
require(_l1Token == WETH, "l1 token not WETH");
|
||||
require(_l2Token == l2WETH, "l2 token not WETH");
|
||||
require(_amount == msg.value, "msg.value mismatch");
|
||||
|
||||
IWETH(_l1Token).deposit{ value: _amount }();
|
||||
IERC20(_l1Token).safeTransfer(_to, _amount);
|
||||
|
||||
// @todo forward `_data` to `_to`.
|
||||
|
||||
emit FinalizeWithdrawERC20(_l1Token, _l2Token, _from, _to, _amount, _data);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
/// @inheritdoc L1ERC20Gateway
|
||||
function _deposit(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) internal virtual override nonReentrant {
|
||||
require(_amount > 0, "deposit zero amount");
|
||||
require(_token == WETH, "only WETH is allowed");
|
||||
|
||||
// 1. Extract real sender if this call is from L1GatewayRouter.
|
||||
address _from = msg.sender;
|
||||
if (router == msg.sender) {
|
||||
(_from, _data) = abi.decode(_data, (address, bytes));
|
||||
constructor(address _WETH, address _l2WETH) {
|
||||
WETH = _WETH;
|
||||
l2WETH = _l2WETH;
|
||||
}
|
||||
|
||||
// 2. Transfer token into this contract.
|
||||
IERC20(_token).safeTransferFrom(_from, address(this), _amount);
|
||||
IWETH(_token).withdraw(_amount);
|
||||
/// @notice Initialize the storage of L1WETHGateway.
|
||||
/// @param _counterpart The address of L2ETHGateway in L2.
|
||||
/// @param _router The address of L1GatewayRouter.
|
||||
/// @param _messenger The address of L1ScrollMessenger.
|
||||
function initialize(
|
||||
address _counterpart,
|
||||
address _router,
|
||||
address _messenger
|
||||
) external initializer {
|
||||
require(_router != address(0), "zero router address");
|
||||
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
|
||||
}
|
||||
|
||||
// 3. Generate message passed to L2StandardERC20Gateway.
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL2ERC20Gateway.finalizeDepositERC20.selector,
|
||||
_token,
|
||||
l2WETH,
|
||||
_from,
|
||||
_to,
|
||||
_amount,
|
||||
_data
|
||||
);
|
||||
receive() external payable {
|
||||
require(msg.sender == WETH, "only WETH");
|
||||
}
|
||||
|
||||
// 4. Send message to L1ScrollMessenger.
|
||||
IL1ScrollMessenger(messenger).sendMessage{ value: _amount + msg.value }(counterpart, _amount, _message, _gasLimit);
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
emit DepositERC20(_token, l2WETH, _from, _to, _amount, _data);
|
||||
}
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function getL2ERC20Address(address) public view override returns (address) {
|
||||
return l2WETH;
|
||||
}
|
||||
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
/// @inheritdoc IL1ERC20Gateway
|
||||
function finalizeWithdrawERC20(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes calldata _data
|
||||
) external payable override onlyCallByCounterpart {
|
||||
require(_l1Token == WETH, "l1 token not WETH");
|
||||
require(_l2Token == l2WETH, "l2 token not WETH");
|
||||
require(_amount == msg.value, "msg.value mismatch");
|
||||
|
||||
IWETH(_l1Token).deposit{value: _amount}();
|
||||
IERC20(_l1Token).safeTransfer(_to, _amount);
|
||||
|
||||
// @todo forward `_data` to `_to`.
|
||||
|
||||
emit FinalizeWithdrawERC20(_l1Token, _l2Token, _from, _to, _amount, _data);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
/// @inheritdoc L1ERC20Gateway
|
||||
function _deposit(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) internal virtual override nonReentrant {
|
||||
require(_amount > 0, "deposit zero amount");
|
||||
require(_token == WETH, "only WETH is allowed");
|
||||
|
||||
// 1. Extract real sender if this call is from L1GatewayRouter.
|
||||
address _from = msg.sender;
|
||||
if (router == msg.sender) {
|
||||
(_from, _data) = abi.decode(_data, (address, bytes));
|
||||
}
|
||||
|
||||
// 2. Transfer token into this contract.
|
||||
IERC20(_token).safeTransferFrom(_from, address(this), _amount);
|
||||
IWETH(_token).withdraw(_amount);
|
||||
|
||||
// 3. Generate message passed to L2StandardERC20Gateway.
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL2ERC20Gateway.finalizeDepositERC20.selector,
|
||||
_token,
|
||||
l2WETH,
|
||||
_from,
|
||||
_to,
|
||||
_amount,
|
||||
_data
|
||||
);
|
||||
|
||||
// 4. Send message to L1ScrollMessenger.
|
||||
IL1ScrollMessenger(messenger).sendMessage{value: _amount + msg.value}(
|
||||
counterpart,
|
||||
_amount,
|
||||
_message,
|
||||
_gasLimit
|
||||
);
|
||||
|
||||
emit DepositERC20(_token, l2WETH, _from, _to, _amount, _data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,76 +3,76 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
interface IL1MessageQueue {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
|
||||
/// @notice Emitted when a new L1 => L2 transaction is appended to the queue.
|
||||
/// @param sender The address of account who initiates the transaction.
|
||||
/// @param target The address of account who will recieve the transaction.
|
||||
/// @param value The value passed with the transaction.
|
||||
/// @param queueIndex The index of this transaction in the queue.
|
||||
/// @param gasLimit Gas limit required to complete the message relay on L2.
|
||||
/// @param data The calldata of the transaction.
|
||||
event QueueTransaction(
|
||||
address indexed sender,
|
||||
address indexed target,
|
||||
uint256 value,
|
||||
uint256 queueIndex,
|
||||
uint256 gasLimit,
|
||||
bytes data
|
||||
);
|
||||
/// @notice Emitted when a new L1 => L2 transaction is appended to the queue.
|
||||
/// @param sender The address of account who initiates the transaction.
|
||||
/// @param target The address of account who will recieve the transaction.
|
||||
/// @param value The value passed with the transaction.
|
||||
/// @param queueIndex The index of this transaction in the queue.
|
||||
/// @param gasLimit Gas limit required to complete the message relay on L2.
|
||||
/// @param data The calldata of the transaction.
|
||||
event QueueTransaction(
|
||||
address indexed sender,
|
||||
address indexed target,
|
||||
uint256 value,
|
||||
uint256 queueIndex,
|
||||
uint256 gasLimit,
|
||||
bytes data
|
||||
);
|
||||
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/// @notice Return the index of next appended message.
|
||||
/// @dev Also the total number of appended messages.
|
||||
function nextCrossDomainMessageIndex() external view returns (uint256);
|
||||
/// @notice Return the index of next appended message.
|
||||
/// @dev Also the total number of appended messages.
|
||||
function nextCrossDomainMessageIndex() external view returns (uint256);
|
||||
|
||||
/// @notice Return the message of in `queueIndex`.
|
||||
/// @param queueIndex The index to query.
|
||||
function getCrossDomainMessage(uint256 queueIndex) external view returns (bytes32);
|
||||
/// @notice Return the message of in `queueIndex`.
|
||||
/// @param queueIndex The index to query.
|
||||
function getCrossDomainMessage(uint256 queueIndex) external view returns (bytes32);
|
||||
|
||||
/// @notice Return the amount of ETH should pay for cross domain message.
|
||||
/// @param sender The address of account who initiates the message in L1.
|
||||
/// @param target The address of account who will recieve the message in L2.
|
||||
/// @param message The content of the message.
|
||||
/// @param gasLimit Gas limit required to complete the message relay on L2.
|
||||
function estimateCrossDomainMessageFee(
|
||||
address sender,
|
||||
address target,
|
||||
bytes memory message,
|
||||
uint256 gasLimit
|
||||
) external view returns (uint256);
|
||||
/// @notice Return the amount of ETH should pay for cross domain message.
|
||||
/// @param sender The address of account who initiates the message in L1.
|
||||
/// @param target The address of account who will recieve the message in L2.
|
||||
/// @param message The content of the message.
|
||||
/// @param gasLimit Gas limit required to complete the message relay on L2.
|
||||
function estimateCrossDomainMessageFee(
|
||||
address sender,
|
||||
address target,
|
||||
bytes memory message,
|
||||
uint256 gasLimit
|
||||
) external view returns (uint256);
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
/// @notice Append a L1 to L2 message into this contract.
|
||||
/// @param target The address of target contract to call in L2.
|
||||
/// @param gasLimit The maximum gas should be used for relay this message in L2.
|
||||
/// @param data The calldata passed to target contract.
|
||||
function appendCrossDomainMessage(
|
||||
address target,
|
||||
uint256 gasLimit,
|
||||
bytes calldata data
|
||||
) external;
|
||||
/// @notice Append a L1 to L2 message into this contract.
|
||||
/// @param target The address of target contract to call in L2.
|
||||
/// @param gasLimit The maximum gas should be used for relay this message in L2.
|
||||
/// @param data The calldata passed to target contract.
|
||||
function appendCrossDomainMessage(
|
||||
address target,
|
||||
uint256 gasLimit,
|
||||
bytes calldata data
|
||||
) external;
|
||||
|
||||
/// @notice Append an enforced transaction to this contract.
|
||||
/// @dev The address of sender should be an EOA.
|
||||
/// @param sender The address of sender who will initiate this transaction in L2.
|
||||
/// @param target The address of target contract to call in L2.
|
||||
/// @param value The value passed
|
||||
/// @param gasLimit The maximum gas should be used for this transaction in L2.
|
||||
/// @param data The calldata passed to target contract.
|
||||
function appendEnforcedTransaction(
|
||||
address sender,
|
||||
address target,
|
||||
uint256 value,
|
||||
uint256 gasLimit,
|
||||
bytes calldata data
|
||||
) external;
|
||||
/// @notice Append an enforced transaction to this contract.
|
||||
/// @dev The address of sender should be an EOA.
|
||||
/// @param sender The address of sender who will initiate this transaction in L2.
|
||||
/// @param target The address of target contract to call in L2.
|
||||
/// @param value The value passed
|
||||
/// @param gasLimit The maximum gas should be used for this transaction in L2.
|
||||
/// @param data The calldata passed to target contract.
|
||||
function appendEnforcedTransaction(
|
||||
address sender,
|
||||
address target,
|
||||
uint256 value,
|
||||
uint256 gasLimit,
|
||||
bytes calldata data
|
||||
) external;
|
||||
}
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
interface IL2GasPriceOracle {
|
||||
/// @notice Estimate fee for cross chain message call.
|
||||
/// @param _sender The address of sender who invoke the call.
|
||||
/// @param _to The target address to receive the call.
|
||||
/// @param _message The message will be passed to the target address.
|
||||
function estimateCrossDomainMessageFee(
|
||||
address _sender,
|
||||
address _to,
|
||||
bytes memory _message,
|
||||
uint256 _gasLimit
|
||||
) external view returns (uint256);
|
||||
/// @notice Estimate fee for cross chain message call.
|
||||
/// @param _sender The address of sender who invoke the call.
|
||||
/// @param _to The target address to receive the call.
|
||||
/// @param _message The message will be passed to the target address.
|
||||
function estimateCrossDomainMessageFee(
|
||||
address _sender,
|
||||
address _to,
|
||||
bytes memory _message,
|
||||
uint256 _gasLimit
|
||||
) external view returns (uint256);
|
||||
}
|
||||
|
||||
@@ -3,102 +3,102 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
interface IScrollChain {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
|
||||
/// @notice Emitted when a new batch is commited.
|
||||
/// @param batchHash The hash of the batch
|
||||
event CommitBatch(bytes32 indexed batchHash);
|
||||
/// @notice Emitted when a new batch is commited.
|
||||
/// @param batchHash The hash of the batch
|
||||
event CommitBatch(bytes32 indexed batchHash);
|
||||
|
||||
/// @notice Emitted when a batch is reverted.
|
||||
/// @param batchHash The identification of the batch.
|
||||
event RevertBatch(bytes32 indexed batchHash);
|
||||
/// @notice Emitted when a batch is reverted.
|
||||
/// @param batchHash The identification of the batch.
|
||||
event RevertBatch(bytes32 indexed batchHash);
|
||||
|
||||
/// @notice Emitted when a batch is finalized.
|
||||
/// @param batchHash The hash of the batch
|
||||
event FinalizeBatch(bytes32 indexed batchHash);
|
||||
/// @notice Emitted when a batch is finalized.
|
||||
/// @param batchHash The hash of the batch
|
||||
event FinalizeBatch(bytes32 indexed batchHash);
|
||||
|
||||
/***********
|
||||
* Structs *
|
||||
***********/
|
||||
/***********
|
||||
* Structs *
|
||||
***********/
|
||||
|
||||
struct BlockContext {
|
||||
// The hash of this block.
|
||||
bytes32 blockHash;
|
||||
// The parent hash of this block.
|
||||
bytes32 parentHash;
|
||||
// The height of this block.
|
||||
uint64 blockNumber;
|
||||
// The timestamp of this block.
|
||||
uint64 timestamp;
|
||||
// The base fee of this block.
|
||||
// Currently, it is not used, because we disable EIP-1559.
|
||||
// We keep it for future proof.
|
||||
uint256 baseFee;
|
||||
// The gas limit of this block.
|
||||
uint64 gasLimit;
|
||||
// The number of transactions in this block, both L1 & L2 txs.
|
||||
uint16 numTransactions;
|
||||
// The number of l1 messages in this block.
|
||||
uint16 numL1Messages;
|
||||
}
|
||||
struct BlockContext {
|
||||
// The hash of this block.
|
||||
bytes32 blockHash;
|
||||
// The parent hash of this block.
|
||||
bytes32 parentHash;
|
||||
// The height of this block.
|
||||
uint64 blockNumber;
|
||||
// The timestamp of this block.
|
||||
uint64 timestamp;
|
||||
// The base fee of this block.
|
||||
// Currently, it is not used, because we disable EIP-1559.
|
||||
// We keep it for future proof.
|
||||
uint256 baseFee;
|
||||
// The gas limit of this block.
|
||||
uint64 gasLimit;
|
||||
// The number of transactions in this block, both L1 & L2 txs.
|
||||
uint16 numTransactions;
|
||||
// The number of l1 messages in this block.
|
||||
uint16 numL1Messages;
|
||||
}
|
||||
|
||||
struct Batch {
|
||||
// The list of blocks in this batch
|
||||
BlockContext[] blocks; // MAX_NUM_BLOCKS = 100, about 5 min
|
||||
// The state root of previous batch.
|
||||
// The first batch will use 0x0 for prevStateRoot
|
||||
bytes32 prevStateRoot;
|
||||
// The state root of the last block in this batch.
|
||||
bytes32 newStateRoot;
|
||||
// The withdraw trie root of the last block in this batch.
|
||||
bytes32 withdrawTrieRoot;
|
||||
// The index of the batch.
|
||||
uint64 batchIndex;
|
||||
// The parent batch hash.
|
||||
bytes32 parentBatchHash;
|
||||
// Concatenated raw data of RLP encoded L2 txs
|
||||
bytes l2Transactions;
|
||||
}
|
||||
struct Batch {
|
||||
// The list of blocks in this batch
|
||||
BlockContext[] blocks; // MAX_NUM_BLOCKS = 100, about 5 min
|
||||
// The state root of previous batch.
|
||||
// The first batch will use 0x0 for prevStateRoot
|
||||
bytes32 prevStateRoot;
|
||||
// The state root of the last block in this batch.
|
||||
bytes32 newStateRoot;
|
||||
// The withdraw trie root of the last block in this batch.
|
||||
bytes32 withdrawTrieRoot;
|
||||
// The index of the batch.
|
||||
uint64 batchIndex;
|
||||
// The parent batch hash.
|
||||
bytes32 parentBatchHash;
|
||||
// Concatenated raw data of RLP encoded L2 txs
|
||||
bytes l2Transactions;
|
||||
}
|
||||
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/// @notice Return whether the batch is finalized by batch hash.
|
||||
/// @param batchHash The hash of the batch to query.
|
||||
function isBatchFinalized(bytes32 batchHash) external view returns (bool);
|
||||
/// @notice Return whether the batch is finalized by batch hash.
|
||||
/// @param batchHash The hash of the batch to query.
|
||||
function isBatchFinalized(bytes32 batchHash) external view returns (bool);
|
||||
|
||||
/// @notice Return the merkle root of L2 message tree.
|
||||
/// @param batchHash The hash of the batch to query.
|
||||
function getL2MessageRoot(bytes32 batchHash) external view returns (bytes32);
|
||||
/// @notice Return the merkle root of L2 message tree.
|
||||
/// @param batchHash The hash of the batch to query.
|
||||
function getL2MessageRoot(bytes32 batchHash) external view returns (bytes32);
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
/// @notice commit a batch in layer 1
|
||||
/// @param batch The layer2 batch to commit.
|
||||
function commitBatch(Batch memory batch) external;
|
||||
/// @notice commit a batch in layer 1
|
||||
/// @param batch The layer2 batch to commit.
|
||||
function commitBatch(Batch memory batch) external;
|
||||
|
||||
/// @notice commit a list of batches in layer 1
|
||||
/// @param batches The list of layer2 batches to commit.
|
||||
function commitBatches(Batch[] memory batches) external;
|
||||
/// @notice commit a list of batches in layer 1
|
||||
/// @param batches The list of layer2 batches to commit.
|
||||
function commitBatches(Batch[] memory batches) external;
|
||||
|
||||
/// @notice revert a pending batch.
|
||||
/// @dev one can only revert unfinalized batches.
|
||||
/// @param batchId The identification of the batch.
|
||||
function revertBatch(bytes32 batchId) external;
|
||||
/// @notice revert a pending batch.
|
||||
/// @dev one can only revert unfinalized batches.
|
||||
/// @param batchId The identification of the batch.
|
||||
function revertBatch(bytes32 batchId) external;
|
||||
|
||||
/// @notice finalize commited batch in layer 1
|
||||
/// @dev will add more parameters if needed.
|
||||
/// @param batchId The identification of the commited batch.
|
||||
/// @param proof The corresponding proof of the commited batch.
|
||||
/// @param instances Instance used to verify, generated from batch.
|
||||
function finalizeBatchWithProof(
|
||||
bytes32 batchId,
|
||||
uint256[] memory proof,
|
||||
uint256[] memory instances
|
||||
) external;
|
||||
/// @notice finalize commited batch in layer 1
|
||||
/// @dev will add more parameters if needed.
|
||||
/// @param batchId The identification of the commited batch.
|
||||
/// @param proof The corresponding proof of the commited batch.
|
||||
/// @param instances Instance used to verify, generated from batch.
|
||||
function finalizeBatchWithProof(
|
||||
bytes32 batchId,
|
||||
uint256[] memory proof,
|
||||
uint256[] memory instances
|
||||
) external;
|
||||
}
|
||||
|
||||
@@ -2,122 +2,122 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
|
||||
import { IL2GasPriceOracle } from "./IL2GasPriceOracle.sol";
|
||||
import { IL1MessageQueue } from "./IL1MessageQueue.sol";
|
||||
import {IL2GasPriceOracle} from "./IL2GasPriceOracle.sol";
|
||||
import {IL1MessageQueue} from "./IL1MessageQueue.sol";
|
||||
|
||||
import { AddressAliasHelper } from "../../libraries/common/AddressAliasHelper.sol";
|
||||
import {AddressAliasHelper} from "../../libraries/common/AddressAliasHelper.sol";
|
||||
|
||||
/// @title L1MessageQueue
|
||||
/// @notice This contract will hold all L1 to L2 messages.
|
||||
/// Each appended message is assigned with a unique and increasing `uint256` index denoting the message nonce.
|
||||
contract L1MessageQueue is OwnableUpgradeable, IL1MessageQueue {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
|
||||
/// @notice Emitted when owner updates gas oracle contract.
|
||||
/// @param _oldGasOracle The address of old gas oracle contract.
|
||||
/// @param _newGasOracle The address of new gas oracle contract.
|
||||
event UpdateGasOracle(address _oldGasOracle, address _newGasOracle);
|
||||
/// @notice Emitted when owner updates gas oracle contract.
|
||||
/// @param _oldGasOracle The address of old gas oracle contract.
|
||||
/// @param _newGasOracle The address of new gas oracle contract.
|
||||
event UpdateGasOracle(address _oldGasOracle, address _newGasOracle);
|
||||
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
/// @notice The address of L1ScrollMessenger contract.
|
||||
address public messenger;
|
||||
/// @notice The address of L1ScrollMessenger contract.
|
||||
address public messenger;
|
||||
|
||||
/// @notice The address of GasOracle contract.
|
||||
address public gasOracle;
|
||||
/// @notice The address of GasOracle contract.
|
||||
address public gasOracle;
|
||||
|
||||
/// @notice The list of queued cross domain messages.
|
||||
bytes32[] public messageQueue;
|
||||
/// @notice The list of queued cross domain messages.
|
||||
bytes32[] public messageQueue;
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
function initialize(address _messenger, address _gasOracle) external initializer {
|
||||
OwnableUpgradeable.__Ownable_init();
|
||||
function initialize(address _messenger, address _gasOracle) external initializer {
|
||||
OwnableUpgradeable.__Ownable_init();
|
||||
|
||||
messenger = _messenger;
|
||||
gasOracle = _gasOracle;
|
||||
}
|
||||
messenger = _messenger;
|
||||
gasOracle = _gasOracle;
|
||||
}
|
||||
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/// @inheritdoc IL1MessageQueue
|
||||
function nextCrossDomainMessageIndex() external view returns (uint256) {
|
||||
return messageQueue.length;
|
||||
}
|
||||
/// @inheritdoc IL1MessageQueue
|
||||
function nextCrossDomainMessageIndex() external view returns (uint256) {
|
||||
return messageQueue.length;
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1MessageQueue
|
||||
function getCrossDomainMessage(uint256 _queueIndex) external view returns (bytes32) {
|
||||
return messageQueue[_queueIndex];
|
||||
}
|
||||
/// @inheritdoc IL1MessageQueue
|
||||
function getCrossDomainMessage(uint256 _queueIndex) external view returns (bytes32) {
|
||||
return messageQueue[_queueIndex];
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1MessageQueue
|
||||
function estimateCrossDomainMessageFee(
|
||||
address _sender,
|
||||
address _target,
|
||||
bytes memory _message,
|
||||
uint256 _gasLimit
|
||||
) external view override returns (uint256) {
|
||||
address _oracle = gasOracle;
|
||||
if (_oracle == address(0)) return 0;
|
||||
return IL2GasPriceOracle(_oracle).estimateCrossDomainMessageFee(_sender, _target, _message, _gasLimit);
|
||||
}
|
||||
/// @inheritdoc IL1MessageQueue
|
||||
function estimateCrossDomainMessageFee(
|
||||
address _sender,
|
||||
address _target,
|
||||
bytes memory _message,
|
||||
uint256 _gasLimit
|
||||
) external view override returns (uint256) {
|
||||
address _oracle = gasOracle;
|
||||
if (_oracle == address(0)) return 0;
|
||||
return IL2GasPriceOracle(_oracle).estimateCrossDomainMessageFee(_sender, _target, _message, _gasLimit);
|
||||
}
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
/// @inheritdoc IL1MessageQueue
|
||||
function appendCrossDomainMessage(
|
||||
address _target,
|
||||
uint256 _gasLimit,
|
||||
bytes calldata _data
|
||||
) external override {
|
||||
require(msg.sender == messenger, "Only callable by the L1ScrollMessenger");
|
||||
/// @inheritdoc IL1MessageQueue
|
||||
function appendCrossDomainMessage(
|
||||
address _target,
|
||||
uint256 _gasLimit,
|
||||
bytes calldata _data
|
||||
) external override {
|
||||
require(msg.sender == messenger, "Only callable by the L1ScrollMessenger");
|
||||
|
||||
// do address alias to avoid replay attack in L2.
|
||||
address _sender = AddressAliasHelper.applyL1ToL2Alias(msg.sender);
|
||||
// do address alias to avoid replay attack in L2.
|
||||
address _sender = AddressAliasHelper.applyL1ToL2Alias(msg.sender);
|
||||
|
||||
// @todo Change it to rlp encoding later.
|
||||
bytes32 _hash = keccak256(abi.encode(_sender, _target, 0, _gasLimit, _data));
|
||||
// @todo Change it to rlp encoding later.
|
||||
bytes32 _hash = keccak256(abi.encode(_sender, _target, 0, _gasLimit, _data));
|
||||
|
||||
uint256 _queueIndex = messageQueue.length;
|
||||
emit QueueTransaction(_sender, _target, 0, _queueIndex, _gasLimit, _data);
|
||||
uint256 _queueIndex = messageQueue.length;
|
||||
emit QueueTransaction(_sender, _target, 0, _queueIndex, _gasLimit, _data);
|
||||
|
||||
messageQueue.push(_hash);
|
||||
}
|
||||
messageQueue.push(_hash);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL1MessageQueue
|
||||
function appendEnforcedTransaction(
|
||||
address,
|
||||
address,
|
||||
uint256,
|
||||
uint256,
|
||||
bytes calldata
|
||||
) external override {
|
||||
// @todo
|
||||
}
|
||||
/// @inheritdoc IL1MessageQueue
|
||||
function appendEnforcedTransaction(
|
||||
address,
|
||||
address,
|
||||
uint256,
|
||||
uint256,
|
||||
bytes calldata
|
||||
) external override {
|
||||
// @todo
|
||||
}
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
/// @notice Update the address of gas oracle.
|
||||
/// @dev This function can only called by contract owner.
|
||||
/// @param _newGasOracle The address to update.
|
||||
function updateGasOracle(address _newGasOracle) external onlyOwner {
|
||||
address _oldGasOracle = gasOracle;
|
||||
gasOracle = _newGasOracle;
|
||||
/// @notice Update the address of gas oracle.
|
||||
/// @dev This function can only called by contract owner.
|
||||
/// @param _newGasOracle The address to update.
|
||||
function updateGasOracle(address _newGasOracle) external onlyOwner {
|
||||
address _oldGasOracle = gasOracle;
|
||||
gasOracle = _newGasOracle;
|
||||
|
||||
emit UpdateGasOracle(_oldGasOracle, _newGasOracle);
|
||||
}
|
||||
emit UpdateGasOracle(_oldGasOracle, _newGasOracle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,162 +2,162 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
|
||||
import { IWhitelist } from "../../libraries/common/IWhitelist.sol";
|
||||
import {IWhitelist} from "../../libraries/common/IWhitelist.sol";
|
||||
|
||||
import { IL2GasPriceOracle } from "./IL2GasPriceOracle.sol";
|
||||
import {IL2GasPriceOracle} from "./IL2GasPriceOracle.sol";
|
||||
|
||||
contract L2GasPriceOracle is OwnableUpgradeable, IL2GasPriceOracle {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* 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 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 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 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);
|
||||
/// @notice Emitted when current l2 base fee is updated.
|
||||
/// @param l2BaseFee The current l2 base fee updated.
|
||||
event L2BaseFeeUpdated(uint256 l2BaseFee);
|
||||
|
||||
/*************
|
||||
* Constants *
|
||||
*************/
|
||||
/*************
|
||||
* Constants *
|
||||
*************/
|
||||
|
||||
/// @dev The precision used in the scalar.
|
||||
uint256 private constant PRECISION = 1e9;
|
||||
/// @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 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;
|
||||
/// @dev The maximum possible l1 fee scale.
|
||||
/// x1000 should be enough.
|
||||
uint256 private constant MAX_SCALE = 1000 * PRECISION;
|
||||
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
/// @notice The current l1 fee overhead.
|
||||
uint256 public overhead;
|
||||
/// @notice The current l1 fee overhead.
|
||||
uint256 public overhead;
|
||||
|
||||
/// @notice The current l1 fee scalar.
|
||||
uint256 public scalar;
|
||||
/// @notice The current l1 fee scalar.
|
||||
uint256 public scalar;
|
||||
|
||||
/// @notice The latest known l2 base fee.
|
||||
uint256 public l2BaseFee;
|
||||
/// @notice The latest known l2 base fee.
|
||||
uint256 public l2BaseFee;
|
||||
|
||||
/// @notice The address of whitelist contract.
|
||||
IWhitelist public whitelist;
|
||||
/// @notice The address of whitelist contract.
|
||||
IWhitelist public whitelist;
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
/***************
|
||||
* 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;
|
||||
function initialize() external initializer {
|
||||
OwnableUpgradeable.__Ownable_init();
|
||||
}
|
||||
}
|
||||
|
||||
/// @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;
|
||||
/*************************
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
uint256 _unsigned = _total + overhead;
|
||||
return _unsigned + (68 * 16);
|
||||
}
|
||||
}
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
/// @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);
|
||||
}
|
||||
}
|
||||
|
||||
/// @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");
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
l2BaseFee = _l2BaseFee;
|
||||
/// @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");
|
||||
|
||||
emit L2BaseFeeUpdated(_l2BaseFee);
|
||||
}
|
||||
l2BaseFee = _l2BaseFee;
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
emit L2BaseFeeUpdated(_l2BaseFee);
|
||||
}
|
||||
|
||||
/// @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");
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
overhead = _overhead;
|
||||
emit OverheadUpdated(_overhead);
|
||||
}
|
||||
/// @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");
|
||||
|
||||
/// 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");
|
||||
overhead = _overhead;
|
||||
emit OverheadUpdated(_overhead);
|
||||
}
|
||||
|
||||
scalar = _scalar;
|
||||
emit ScalarUpdated(_scalar);
|
||||
}
|
||||
/// 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");
|
||||
|
||||
/// @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);
|
||||
scalar = _scalar;
|
||||
emit ScalarUpdated(_scalar);
|
||||
}
|
||||
|
||||
whitelist = IWhitelist(_newWhitelist);
|
||||
emit UpdateWhitelist(_oldWhitelist, _newWhitelist);
|
||||
}
|
||||
/// @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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
|
||||
import { IL1MessageQueue } from "./IL1MessageQueue.sol";
|
||||
import { IScrollChain } from "./IScrollChain.sol";
|
||||
import { RollupVerifier } from "../../libraries/verifier/RollupVerifier.sol";
|
||||
import {IL1MessageQueue} from "./IL1MessageQueue.sol";
|
||||
import {IScrollChain} from "./IScrollChain.sol";
|
||||
import {RollupVerifier} from "../../libraries/verifier/RollupVerifier.sol";
|
||||
|
||||
// solhint-disable reason-string
|
||||
|
||||
@@ -18,402 +18,405 @@ import { RollupVerifier } from "../../libraries/verifier/RollupVerifier.sol";
|
||||
///
|
||||
/// @dev the message queue is not used yet, the offline relayer only use events in `L1ScrollMessenger`.
|
||||
contract ScrollChain is OwnableUpgradeable, IScrollChain {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
|
||||
/// @notice Emitted when owner updates the status of sequencer.
|
||||
/// @param account The address of account updated.
|
||||
/// @param status The status of the account updated.
|
||||
event UpdateSequencer(address indexed account, bool status);
|
||||
/// @notice Emitted when owner updates the status of sequencer.
|
||||
/// @param account The address of account updated.
|
||||
/// @param status The status of the account updated.
|
||||
event UpdateSequencer(address indexed account, bool status);
|
||||
|
||||
/*************
|
||||
* Constants *
|
||||
*************/
|
||||
/*************
|
||||
* Constants *
|
||||
*************/
|
||||
|
||||
/// @dev The maximum number of transaction in on batch.
|
||||
uint256 public immutable maxNumTxInBatch;
|
||||
/// @dev The maximum number of transaction in on batch.
|
||||
uint256 public immutable maxNumTxInBatch;
|
||||
|
||||
/// @dev The hash used for padding public inputs.
|
||||
bytes32 public immutable paddingTxHash;
|
||||
/// @dev The hash used for padding public inputs.
|
||||
bytes32 public immutable paddingTxHash;
|
||||
|
||||
/// @notice The chain id of the corresponding layer 2 chain.
|
||||
uint256 public immutable layer2ChainId;
|
||||
/// @notice The chain id of the corresponding layer 2 chain.
|
||||
uint256 public immutable layer2ChainId;
|
||||
|
||||
/***********
|
||||
* Structs *
|
||||
***********/
|
||||
/***********
|
||||
* Structs *
|
||||
***********/
|
||||
|
||||
// subject to change
|
||||
struct BatchStored {
|
||||
// The state root of the last block in this batch.
|
||||
bytes32 newStateRoot;
|
||||
// The withdraw trie root of the last block in this batch.
|
||||
bytes32 withdrawTrieRoot;
|
||||
// The parent batch hash.
|
||||
bytes32 parentBatchHash;
|
||||
// The index of the batch.
|
||||
uint64 batchIndex;
|
||||
// The timestamp of the last block in this batch.
|
||||
uint64 timestamp;
|
||||
// The number of transactions in this batch, both L1 & L2 txs.
|
||||
uint64 numTransactions;
|
||||
// The total number of L1 messages included after this batch.
|
||||
uint64 totalL1Messages;
|
||||
// Whether the batch is finalized.
|
||||
bool finalized;
|
||||
}
|
||||
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
/// @notice The address of L1MessageQueue.
|
||||
address public messageQueue;
|
||||
|
||||
/// @notice Whether an account is a sequencer.
|
||||
mapping(address => bool) public isSequencer;
|
||||
|
||||
/// @notice The latest finalized batch hash.
|
||||
bytes32 public lastFinalizedBatchHash;
|
||||
|
||||
/// @notice Mapping from batch id to batch struct.
|
||||
mapping(bytes32 => BatchStored) public batches;
|
||||
|
||||
/// @notice Mapping from batch index to finalized batch hash.
|
||||
mapping(uint256 => bytes32) public finalizedBatches;
|
||||
|
||||
/**********************
|
||||
* Function Modifiers *
|
||||
**********************/
|
||||
|
||||
modifier OnlySequencer() {
|
||||
// @todo In the decentralize mode, it should be only called by a list of validator.
|
||||
require(isSequencer[msg.sender], "caller not sequencer");
|
||||
_;
|
||||
}
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
constructor(
|
||||
uint256 _chainId,
|
||||
uint256 _maxNumTxInBatch,
|
||||
bytes32 _paddingTxHash
|
||||
) {
|
||||
layer2ChainId = _chainId;
|
||||
maxNumTxInBatch = _maxNumTxInBatch;
|
||||
paddingTxHash = _paddingTxHash;
|
||||
}
|
||||
|
||||
function initialize(address _messageQueue) public initializer {
|
||||
OwnableUpgradeable.__Ownable_init();
|
||||
|
||||
messageQueue = _messageQueue;
|
||||
}
|
||||
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/// @inheritdoc IScrollChain
|
||||
function isBatchFinalized(bytes32 _batchHash) external view override returns (bool) {
|
||||
BatchStored storage _batch = batches[_batchHash];
|
||||
if (_batch.newStateRoot == bytes32(0)) {
|
||||
return false;
|
||||
}
|
||||
return batches[lastFinalizedBatchHash].batchIndex >= _batch.batchIndex;
|
||||
}
|
||||
|
||||
/// @inheritdoc IScrollChain
|
||||
function getL2MessageRoot(bytes32 _batchHash) external view override returns (bytes32) {
|
||||
return batches[_batchHash].withdrawTrieRoot;
|
||||
}
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
|
||||
/// @notice Import layer 2 genesis block
|
||||
function importGenesisBatch(Batch memory _genesisBatch) external {
|
||||
require(lastFinalizedBatchHash == bytes32(0), "Genesis batch imported");
|
||||
require(_genesisBatch.blocks.length == 1, "Not exact one block in genesis");
|
||||
require(_genesisBatch.prevStateRoot == bytes32(0), "Nonzero prevStateRoot");
|
||||
|
||||
BlockContext memory _genesisBlock = _genesisBatch.blocks[0];
|
||||
|
||||
require(_genesisBlock.blockHash != bytes32(0), "Block hash is zero");
|
||||
require(_genesisBlock.blockNumber == 0, "Block is not genesis");
|
||||
require(_genesisBlock.parentHash == bytes32(0), "Parent hash not empty");
|
||||
|
||||
bytes32 _batchHash = _commitBatch(_genesisBatch);
|
||||
|
||||
lastFinalizedBatchHash = _batchHash;
|
||||
finalizedBatches[0] = _batchHash;
|
||||
batches[_batchHash].finalized = true;
|
||||
|
||||
emit FinalizeBatch(_batchHash);
|
||||
}
|
||||
|
||||
/// @inheritdoc IScrollChain
|
||||
function commitBatch(Batch memory _batch) public override OnlySequencer {
|
||||
_commitBatch(_batch);
|
||||
}
|
||||
|
||||
/// @inheritdoc IScrollChain
|
||||
function commitBatches(Batch[] memory _batches) public override OnlySequencer {
|
||||
for (uint256 i = 0; i < _batches.length; i++) {
|
||||
_commitBatch(_batches[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// @inheritdoc IScrollChain
|
||||
function revertBatch(bytes32 _batchHash) external override OnlySequencer {
|
||||
BatchStored storage _batch = batches[_batchHash];
|
||||
|
||||
require(_batch.newStateRoot != bytes32(0), "No such batch");
|
||||
require(!_batch.finalized, "Unable to revert finalized batch");
|
||||
|
||||
// delete committed batch
|
||||
delete batches[_batchHash];
|
||||
|
||||
emit RevertBatch(_batchHash);
|
||||
}
|
||||
|
||||
/// @inheritdoc IScrollChain
|
||||
function finalizeBatchWithProof(
|
||||
bytes32 _batchHash,
|
||||
uint256[] memory _proof,
|
||||
uint256[] memory _instances
|
||||
) external override OnlySequencer {
|
||||
BatchStored storage _batch = batches[_batchHash];
|
||||
require(_batch.newStateRoot != bytes32(0), "No such batch");
|
||||
require(!_batch.finalized, "Batch is already finalized");
|
||||
|
||||
// @note skip parent check for now, since we may not prove blocks in order.
|
||||
// bytes32 _parentHash = _block.header.parentHash;
|
||||
// require(lastFinalizedBlockHash == _parentHash, "parent not latest finalized");
|
||||
// this check below is not needed, just incase
|
||||
// require(blocks[_parentHash].verified, "parent not verified");
|
||||
|
||||
// @todo add verification logic
|
||||
RollupVerifier.verify(_proof, _instances);
|
||||
|
||||
uint256 _batchIndex = _batch.batchIndex;
|
||||
finalizedBatches[_batchIndex] = _batchHash;
|
||||
_batch.finalized = true;
|
||||
|
||||
BatchStored storage _finalizedBatch = batches[lastFinalizedBatchHash];
|
||||
if (_batchIndex > _finalizedBatch.batchIndex) {
|
||||
lastFinalizedBatchHash = _batchHash;
|
||||
// subject to change
|
||||
struct BatchStored {
|
||||
// The state root of the last block in this batch.
|
||||
bytes32 newStateRoot;
|
||||
// The withdraw trie root of the last block in this batch.
|
||||
bytes32 withdrawTrieRoot;
|
||||
// The parent batch hash.
|
||||
bytes32 parentBatchHash;
|
||||
// The index of the batch.
|
||||
uint64 batchIndex;
|
||||
// The timestamp of the last block in this batch.
|
||||
uint64 timestamp;
|
||||
// The number of transactions in this batch, both L1 & L2 txs.
|
||||
uint64 numTransactions;
|
||||
// The total number of L1 messages included after this batch.
|
||||
uint64 totalL1Messages;
|
||||
// Whether the batch is finalized.
|
||||
bool finalized;
|
||||
}
|
||||
|
||||
emit FinalizeBatch(_batchHash);
|
||||
}
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
/// @notice The address of L1MessageQueue.
|
||||
address public messageQueue;
|
||||
|
||||
/// @notice Update the status of sequencer.
|
||||
/// @dev This function can only called by contract owner.
|
||||
/// @param _account The address of account to update.
|
||||
/// @param _status The status of the account to update.
|
||||
function updateSequencer(address _account, bool _status) external onlyOwner {
|
||||
isSequencer[_account] = _status;
|
||||
/// @notice Whether an account is a sequencer.
|
||||
mapping(address => bool) public isSequencer;
|
||||
|
||||
emit UpdateSequencer(_account, _status);
|
||||
}
|
||||
/// @notice The latest finalized batch hash.
|
||||
bytes32 public lastFinalizedBatchHash;
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
/// @notice Mapping from batch id to batch struct.
|
||||
mapping(bytes32 => BatchStored) public batches;
|
||||
|
||||
/// @dev Internal function to commit a batch.
|
||||
/// @param _batch The batch to commit.
|
||||
function _commitBatch(Batch memory _batch) internal returns (bytes32) {
|
||||
// check whether the batch is empty
|
||||
require(_batch.blocks.length > 0, "Batch is empty");
|
||||
/// @notice Mapping from batch index to finalized batch hash.
|
||||
mapping(uint256 => bytes32) public finalizedBatches;
|
||||
|
||||
BatchStored storage _parentBatch = batches[_batch.parentBatchHash];
|
||||
require(_parentBatch.newStateRoot == _batch.prevStateRoot, "prevStateRoot is different from newStateRoot in the parent batch");
|
||||
uint64 accTotalL1Messages = _parentBatch.totalL1Messages;
|
||||
/**********************
|
||||
* Function Modifiers *
|
||||
**********************/
|
||||
|
||||
bytes32 publicInputHash;
|
||||
uint64 numTransactionsInBatch;
|
||||
uint64 lastBlockTimestamp;
|
||||
(publicInputHash, numTransactionsInBatch, accTotalL1Messages, lastBlockTimestamp) = _computePublicInputHash(
|
||||
accTotalL1Messages,
|
||||
_batch
|
||||
);
|
||||
modifier OnlySequencer() {
|
||||
// @todo In the decentralize mode, it should be only called by a list of validator.
|
||||
require(isSequencer[msg.sender], "caller not sequencer");
|
||||
_;
|
||||
}
|
||||
|
||||
BatchStored storage _batchInStorage = batches[publicInputHash];
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
require(_batchInStorage.newStateRoot == bytes32(0), "Batch already commited");
|
||||
_batchInStorage.newStateRoot = _batch.newStateRoot;
|
||||
_batchInStorage.withdrawTrieRoot = _batch.withdrawTrieRoot;
|
||||
_batchInStorage.batchIndex = _batch.batchIndex;
|
||||
_batchInStorage.parentBatchHash = _batch.parentBatchHash;
|
||||
_batchInStorage.timestamp = lastBlockTimestamp;
|
||||
_batchInStorage.numTransactions = numTransactionsInBatch;
|
||||
_batchInStorage.totalL1Messages = accTotalL1Messages;
|
||||
constructor(
|
||||
uint256 _chainId,
|
||||
uint256 _maxNumTxInBatch,
|
||||
bytes32 _paddingTxHash
|
||||
) {
|
||||
layer2ChainId = _chainId;
|
||||
maxNumTxInBatch = _maxNumTxInBatch;
|
||||
paddingTxHash = _paddingTxHash;
|
||||
}
|
||||
|
||||
emit CommitBatch(publicInputHash);
|
||||
function initialize(address _messageQueue) public initializer {
|
||||
OwnableUpgradeable.__Ownable_init();
|
||||
|
||||
return publicInputHash;
|
||||
}
|
||||
messageQueue = _messageQueue;
|
||||
}
|
||||
|
||||
/// @dev Internal function to compute the public input hash.
|
||||
/// @param accTotalL1Messages The number of total L1 messages in previous batch.
|
||||
/// @param batch The batch to compute.
|
||||
function _computePublicInputHash(uint64 accTotalL1Messages, Batch memory batch)
|
||||
internal
|
||||
view
|
||||
returns (
|
||||
bytes32,
|
||||
uint64,
|
||||
uint64,
|
||||
uint64
|
||||
)
|
||||
{
|
||||
uint256 publicInputsPtr;
|
||||
// 1. append prevStateRoot, newStateRoot and withdrawTrieRoot to public inputs
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/// @inheritdoc IScrollChain
|
||||
function isBatchFinalized(bytes32 _batchHash) external view override returns (bool) {
|
||||
BatchStored storage _batch = batches[_batchHash];
|
||||
if (_batch.newStateRoot == bytes32(0)) {
|
||||
return false;
|
||||
}
|
||||
return batches[lastFinalizedBatchHash].batchIndex >= _batch.batchIndex;
|
||||
}
|
||||
|
||||
/// @inheritdoc IScrollChain
|
||||
function getL2MessageRoot(bytes32 _batchHash) external view override returns (bytes32) {
|
||||
return batches[_batchHash].withdrawTrieRoot;
|
||||
}
|
||||
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
/// @notice Import layer 2 genesis block
|
||||
function importGenesisBatch(Batch memory _genesisBatch) external {
|
||||
require(lastFinalizedBatchHash == bytes32(0), "Genesis batch imported");
|
||||
require(_genesisBatch.blocks.length == 1, "Not exact one block in genesis");
|
||||
require(_genesisBatch.prevStateRoot == bytes32(0), "Nonzero prevStateRoot");
|
||||
|
||||
BlockContext memory _genesisBlock = _genesisBatch.blocks[0];
|
||||
|
||||
require(_genesisBlock.blockHash != bytes32(0), "Block hash is zero");
|
||||
require(_genesisBlock.blockNumber == 0, "Block is not genesis");
|
||||
require(_genesisBlock.parentHash == bytes32(0), "Parent hash not empty");
|
||||
|
||||
bytes32 _batchHash = _commitBatch(_genesisBatch);
|
||||
|
||||
lastFinalizedBatchHash = _batchHash;
|
||||
finalizedBatches[0] = _batchHash;
|
||||
batches[_batchHash].finalized = true;
|
||||
|
||||
emit FinalizeBatch(_batchHash);
|
||||
}
|
||||
|
||||
/// @inheritdoc IScrollChain
|
||||
function commitBatch(Batch memory _batch) public override OnlySequencer {
|
||||
_commitBatch(_batch);
|
||||
}
|
||||
|
||||
/// @inheritdoc IScrollChain
|
||||
function commitBatches(Batch[] memory _batches) public override OnlySequencer {
|
||||
for (uint256 i = 0; i < _batches.length; i++) {
|
||||
_commitBatch(_batches[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// @inheritdoc IScrollChain
|
||||
function revertBatch(bytes32 _batchHash) external override OnlySequencer {
|
||||
BatchStored storage _batch = batches[_batchHash];
|
||||
|
||||
require(_batch.newStateRoot != bytes32(0), "No such batch");
|
||||
require(!_batch.finalized, "Unable to revert finalized batch");
|
||||
|
||||
// delete committed batch
|
||||
delete batches[_batchHash];
|
||||
|
||||
emit RevertBatch(_batchHash);
|
||||
}
|
||||
|
||||
/// @inheritdoc IScrollChain
|
||||
function finalizeBatchWithProof(
|
||||
bytes32 _batchHash,
|
||||
uint256[] memory _proof,
|
||||
uint256[] memory _instances
|
||||
) external override OnlySequencer {
|
||||
BatchStored storage _batch = batches[_batchHash];
|
||||
require(_batch.newStateRoot != bytes32(0), "No such batch");
|
||||
require(!_batch.finalized, "Batch is already finalized");
|
||||
|
||||
// @note skip parent check for now, since we may not prove blocks in order.
|
||||
// bytes32 _parentHash = _block.header.parentHash;
|
||||
// require(lastFinalizedBlockHash == _parentHash, "parent not latest finalized");
|
||||
// this check below is not needed, just incase
|
||||
// require(blocks[_parentHash].verified, "parent not verified");
|
||||
|
||||
// @todo add verification logic
|
||||
RollupVerifier.verify(_proof, _instances);
|
||||
|
||||
uint256 _batchIndex = _batch.batchIndex;
|
||||
finalizedBatches[_batchIndex] = _batchHash;
|
||||
_batch.finalized = true;
|
||||
|
||||
BatchStored storage _finalizedBatch = batches[lastFinalizedBatchHash];
|
||||
if (_batchIndex > _finalizedBatch.batchIndex) {
|
||||
lastFinalizedBatchHash = _batchHash;
|
||||
}
|
||||
|
||||
emit FinalizeBatch(_batchHash);
|
||||
}
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
/// @notice Update the status of sequencer.
|
||||
/// @dev This function can only called by contract owner.
|
||||
/// @param _account The address of account to update.
|
||||
/// @param _status The status of the account to update.
|
||||
function updateSequencer(address _account, bool _status) external onlyOwner {
|
||||
isSequencer[_account] = _status;
|
||||
|
||||
emit UpdateSequencer(_account, _status);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
/// @dev Internal function to commit a batch.
|
||||
/// @param _batch The batch to commit.
|
||||
function _commitBatch(Batch memory _batch) internal returns (bytes32) {
|
||||
// check whether the batch is empty
|
||||
require(_batch.blocks.length > 0, "Batch is empty");
|
||||
|
||||
BatchStored storage _parentBatch = batches[_batch.parentBatchHash];
|
||||
require(
|
||||
_parentBatch.newStateRoot == _batch.prevStateRoot,
|
||||
"prevStateRoot is different from newStateRoot in the parent batch"
|
||||
);
|
||||
uint64 accTotalL1Messages = _parentBatch.totalL1Messages;
|
||||
|
||||
bytes32 publicInputHash;
|
||||
uint64 numTransactionsInBatch;
|
||||
uint64 lastBlockTimestamp;
|
||||
(publicInputHash, numTransactionsInBatch, accTotalL1Messages, lastBlockTimestamp) = _computePublicInputHash(
|
||||
accTotalL1Messages,
|
||||
_batch
|
||||
);
|
||||
|
||||
BatchStored storage _batchInStorage = batches[publicInputHash];
|
||||
|
||||
require(_batchInStorage.newStateRoot == bytes32(0), "Batch already commited");
|
||||
_batchInStorage.newStateRoot = _batch.newStateRoot;
|
||||
_batchInStorage.withdrawTrieRoot = _batch.withdrawTrieRoot;
|
||||
_batchInStorage.batchIndex = _batch.batchIndex;
|
||||
_batchInStorage.parentBatchHash = _batch.parentBatchHash;
|
||||
_batchInStorage.timestamp = lastBlockTimestamp;
|
||||
_batchInStorage.numTransactions = numTransactionsInBatch;
|
||||
_batchInStorage.totalL1Messages = accTotalL1Messages;
|
||||
|
||||
emit CommitBatch(publicInputHash);
|
||||
|
||||
return publicInputHash;
|
||||
}
|
||||
|
||||
/// @dev Internal function to compute the public input hash.
|
||||
/// @param accTotalL1Messages The number of total L1 messages in previous batch.
|
||||
/// @param batch The batch to compute.
|
||||
function _computePublicInputHash(uint64 accTotalL1Messages, Batch memory batch)
|
||||
internal
|
||||
view
|
||||
returns (
|
||||
bytes32,
|
||||
uint64,
|
||||
uint64,
|
||||
uint64
|
||||
)
|
||||
{
|
||||
bytes32 prevStateRoot = batch.prevStateRoot;
|
||||
bytes32 newStateRoot = batch.newStateRoot;
|
||||
bytes32 withdrawTrieRoot = batch.withdrawTrieRoot;
|
||||
// number of bytes in public inputs: 32 * 3 + 124 * blocks + 32 * MAX_NUM_TXS
|
||||
uint256 publicInputsSize = 32 * 3 + batch.blocks.length * 124 + 32 * maxNumTxInBatch;
|
||||
assembly {
|
||||
publicInputsPtr := mload(0x40)
|
||||
mstore(0x40, add(publicInputsPtr, publicInputsSize))
|
||||
mstore(publicInputsPtr, prevStateRoot)
|
||||
publicInputsPtr := add(publicInputsPtr, 0x20)
|
||||
mstore(publicInputsPtr, newStateRoot)
|
||||
publicInputsPtr := add(publicInputsPtr, 0x20)
|
||||
mstore(publicInputsPtr, withdrawTrieRoot)
|
||||
publicInputsPtr := add(publicInputsPtr, 0x20)
|
||||
}
|
||||
}
|
||||
uint256 publicInputsPtr;
|
||||
// 1. append prevStateRoot, newStateRoot and withdrawTrieRoot to public inputs
|
||||
{
|
||||
bytes32 prevStateRoot = batch.prevStateRoot;
|
||||
bytes32 newStateRoot = batch.newStateRoot;
|
||||
bytes32 withdrawTrieRoot = batch.withdrawTrieRoot;
|
||||
// number of bytes in public inputs: 32 * 3 + 124 * blocks + 32 * MAX_NUM_TXS
|
||||
uint256 publicInputsSize = 32 * 3 + batch.blocks.length * 124 + 32 * maxNumTxInBatch;
|
||||
assembly {
|
||||
publicInputsPtr := mload(0x40)
|
||||
mstore(0x40, add(publicInputsPtr, publicInputsSize))
|
||||
mstore(publicInputsPtr, prevStateRoot)
|
||||
publicInputsPtr := add(publicInputsPtr, 0x20)
|
||||
mstore(publicInputsPtr, newStateRoot)
|
||||
publicInputsPtr := add(publicInputsPtr, 0x20)
|
||||
mstore(publicInputsPtr, withdrawTrieRoot)
|
||||
publicInputsPtr := add(publicInputsPtr, 0x20)
|
||||
}
|
||||
}
|
||||
|
||||
uint64 numTransactionsInBatch;
|
||||
BlockContext memory _block;
|
||||
// 2. append block information to public inputs.
|
||||
for (uint256 i = 0; i < batch.blocks.length; i++) {
|
||||
// validate blocks, we won't check first block against previous batch.
|
||||
{
|
||||
BlockContext memory _currentBlock = batch.blocks[i];
|
||||
if (i > 0) {
|
||||
require(_block.blockHash == _currentBlock.parentHash, "Parent hash mismatch");
|
||||
require(_block.blockNumber + 1 == _currentBlock.blockNumber, "Block number mismatch");
|
||||
}
|
||||
_block = _currentBlock;
|
||||
}
|
||||
uint64 numTransactionsInBatch;
|
||||
BlockContext memory _block;
|
||||
// 2. append block information to public inputs.
|
||||
for (uint256 i = 0; i < batch.blocks.length; i++) {
|
||||
// validate blocks, we won't check first block against previous batch.
|
||||
{
|
||||
BlockContext memory _currentBlock = batch.blocks[i];
|
||||
if (i > 0) {
|
||||
require(_block.blockHash == _currentBlock.parentHash, "Parent hash mismatch");
|
||||
require(_block.blockNumber + 1 == _currentBlock.blockNumber, "Block number mismatch");
|
||||
}
|
||||
_block = _currentBlock;
|
||||
}
|
||||
|
||||
// append blockHash and parentHash to public inputs
|
||||
{
|
||||
bytes32 blockHash = _block.blockHash;
|
||||
bytes32 parentHash = _block.parentHash;
|
||||
assembly {
|
||||
mstore(publicInputsPtr, blockHash)
|
||||
publicInputsPtr := add(publicInputsPtr, 0x20)
|
||||
mstore(publicInputsPtr, parentHash)
|
||||
publicInputsPtr := add(publicInputsPtr, 0x20)
|
||||
// append blockHash and parentHash to public inputs
|
||||
{
|
||||
bytes32 blockHash = _block.blockHash;
|
||||
bytes32 parentHash = _block.parentHash;
|
||||
assembly {
|
||||
mstore(publicInputsPtr, blockHash)
|
||||
publicInputsPtr := add(publicInputsPtr, 0x20)
|
||||
mstore(publicInputsPtr, parentHash)
|
||||
publicInputsPtr := add(publicInputsPtr, 0x20)
|
||||
}
|
||||
}
|
||||
// append blockNumber and blockTimestamp to public inputs
|
||||
{
|
||||
uint256 blockNumber = _block.blockNumber;
|
||||
uint256 blockTimestamp = _block.timestamp;
|
||||
assembly {
|
||||
mstore(publicInputsPtr, shl(192, blockNumber))
|
||||
publicInputsPtr := add(publicInputsPtr, 0x8)
|
||||
mstore(publicInputsPtr, shl(192, blockTimestamp))
|
||||
publicInputsPtr := add(publicInputsPtr, 0x8)
|
||||
}
|
||||
}
|
||||
// append baseFee to public inputs
|
||||
{
|
||||
uint256 baseFee = _block.baseFee;
|
||||
assembly {
|
||||
mstore(publicInputsPtr, baseFee)
|
||||
publicInputsPtr := add(publicInputsPtr, 0x20)
|
||||
}
|
||||
}
|
||||
uint64 numTransactionsInBlock = _block.numTransactions;
|
||||
// gasLimit, numTransactions and numL1Messages to public inputs
|
||||
{
|
||||
uint256 gasLimit = _block.gasLimit;
|
||||
uint256 numL1MessagesInBlock = _block.numL1Messages;
|
||||
assembly {
|
||||
mstore(publicInputsPtr, shl(192, gasLimit))
|
||||
publicInputsPtr := add(publicInputsPtr, 0x8)
|
||||
mstore(publicInputsPtr, shl(240, numTransactionsInBlock))
|
||||
publicInputsPtr := add(publicInputsPtr, 0x2)
|
||||
mstore(publicInputsPtr, shl(240, numL1MessagesInBlock))
|
||||
publicInputsPtr := add(publicInputsPtr, 0x2)
|
||||
}
|
||||
}
|
||||
numTransactionsInBatch += numTransactionsInBlock;
|
||||
}
|
||||
}
|
||||
// append blockNumber and blockTimestamp to public inputs
|
||||
{
|
||||
uint256 blockNumber = _block.blockNumber;
|
||||
uint256 blockTimestamp = _block.timestamp;
|
||||
assembly {
|
||||
mstore(publicInputsPtr, shl(192, blockNumber))
|
||||
publicInputsPtr := add(publicInputsPtr, 0x8)
|
||||
mstore(publicInputsPtr, shl(192, blockTimestamp))
|
||||
publicInputsPtr := add(publicInputsPtr, 0x8)
|
||||
}
|
||||
}
|
||||
// append baseFee to public inputs
|
||||
{
|
||||
uint256 baseFee = _block.baseFee;
|
||||
assembly {
|
||||
mstore(publicInputsPtr, baseFee)
|
||||
publicInputsPtr := add(publicInputsPtr, 0x20)
|
||||
}
|
||||
}
|
||||
uint64 numTransactionsInBlock = _block.numTransactions;
|
||||
// gasLimit, numTransactions and numL1Messages to public inputs
|
||||
{
|
||||
uint256 gasLimit = _block.gasLimit;
|
||||
uint256 numL1MessagesInBlock = _block.numL1Messages;
|
||||
assembly {
|
||||
mstore(publicInputsPtr, shl(192, gasLimit))
|
||||
publicInputsPtr := add(publicInputsPtr, 0x8)
|
||||
mstore(publicInputsPtr, shl(240, numTransactionsInBlock))
|
||||
publicInputsPtr := add(publicInputsPtr, 0x2)
|
||||
mstore(publicInputsPtr, shl(240, numL1MessagesInBlock))
|
||||
publicInputsPtr := add(publicInputsPtr, 0x2)
|
||||
}
|
||||
}
|
||||
numTransactionsInBatch += numTransactionsInBlock;
|
||||
}
|
||||
require(numTransactionsInBatch <= maxNumTxInBatch, "Too many transactions in batch");
|
||||
require(numTransactionsInBatch <= maxNumTxInBatch, "Too many transactions in batch");
|
||||
|
||||
// 3. append transaction hash to public inputs.
|
||||
address _messageQueue = messageQueue;
|
||||
uint256 _l2TxnPtr;
|
||||
{
|
||||
bytes memory l2Transactions = batch.l2Transactions;
|
||||
assembly {
|
||||
_l2TxnPtr := add(l2Transactions, 0x20)
|
||||
}
|
||||
}
|
||||
for (uint256 i = 0; i < batch.blocks.length; i++) {
|
||||
uint256 numL1MessagesInBlock = batch.blocks[i].numL1Messages;
|
||||
while (numL1MessagesInBlock > 0) {
|
||||
bytes32 hash = IL1MessageQueue(_messageQueue).getCrossDomainMessage(uint64(accTotalL1Messages));
|
||||
assembly {
|
||||
mstore(publicInputsPtr, hash)
|
||||
publicInputsPtr := add(publicInputsPtr, 0x20)
|
||||
// 3. append transaction hash to public inputs.
|
||||
address _messageQueue = messageQueue;
|
||||
uint256 _l2TxnPtr;
|
||||
{
|
||||
bytes memory l2Transactions = batch.l2Transactions;
|
||||
assembly {
|
||||
_l2TxnPtr := add(l2Transactions, 0x20)
|
||||
}
|
||||
}
|
||||
unchecked {
|
||||
accTotalL1Messages += 1;
|
||||
numL1MessagesInBlock -= 1;
|
||||
for (uint256 i = 0; i < batch.blocks.length; i++) {
|
||||
uint256 numL1MessagesInBlock = batch.blocks[i].numL1Messages;
|
||||
while (numL1MessagesInBlock > 0) {
|
||||
bytes32 hash = IL1MessageQueue(_messageQueue).getCrossDomainMessage(uint64(accTotalL1Messages));
|
||||
assembly {
|
||||
mstore(publicInputsPtr, hash)
|
||||
publicInputsPtr := add(publicInputsPtr, 0x20)
|
||||
}
|
||||
unchecked {
|
||||
accTotalL1Messages += 1;
|
||||
numL1MessagesInBlock -= 1;
|
||||
}
|
||||
}
|
||||
numL1MessagesInBlock = batch.blocks[i].numL1Messages;
|
||||
uint256 numTransactionsInBlock = batch.blocks[i].numTransactions;
|
||||
for (uint256 j = numL1MessagesInBlock; j < numTransactionsInBlock; ++j) {
|
||||
bytes32 hash;
|
||||
assembly {
|
||||
let txPayloadLength := shr(224, mload(_l2TxnPtr))
|
||||
_l2TxnPtr := add(_l2TxnPtr, 4)
|
||||
_l2TxnPtr := add(_l2TxnPtr, txPayloadLength)
|
||||
hash := keccak256(sub(_l2TxnPtr, txPayloadLength), txPayloadLength)
|
||||
mstore(publicInputsPtr, hash)
|
||||
publicInputsPtr := add(publicInputsPtr, 0x20)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
numL1MessagesInBlock = batch.blocks[i].numL1Messages;
|
||||
uint256 numTransactionsInBlock = batch.blocks[i].numTransactions;
|
||||
for (uint256 j = numL1MessagesInBlock; j < numTransactionsInBlock; ++j) {
|
||||
bytes32 hash;
|
||||
assembly {
|
||||
let txPayloadLength := shr(224, mload(_l2TxnPtr))
|
||||
_l2TxnPtr := add(_l2TxnPtr, 4)
|
||||
_l2TxnPtr := add(_l2TxnPtr, txPayloadLength)
|
||||
hash := keccak256(sub(_l2TxnPtr, txPayloadLength), txPayloadLength)
|
||||
mstore(publicInputsPtr, hash)
|
||||
publicInputsPtr := add(publicInputsPtr, 0x20)
|
||||
|
||||
// 4. append padding transaction to public inputs.
|
||||
bytes32 txHashPadding = paddingTxHash;
|
||||
for (uint256 i = numTransactionsInBatch; i < maxNumTxInBatch; i++) {
|
||||
assembly {
|
||||
mstore(publicInputsPtr, txHashPadding)
|
||||
publicInputsPtr := add(publicInputsPtr, 0x20)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. append padding transaction to public inputs.
|
||||
bytes32 txHashPadding = paddingTxHash;
|
||||
for (uint256 i = numTransactionsInBatch; i < maxNumTxInBatch; i++) {
|
||||
assembly {
|
||||
mstore(publicInputsPtr, txHashPadding)
|
||||
publicInputsPtr := add(publicInputsPtr, 0x20)
|
||||
}
|
||||
}
|
||||
// 5. compute public input hash
|
||||
bytes32 publicInputHash;
|
||||
{
|
||||
uint256 publicInputsSize = 32 * 3 + batch.blocks.length * 124 + 32 * maxNumTxInBatch;
|
||||
assembly {
|
||||
publicInputHash := keccak256(sub(publicInputsPtr, publicInputsSize), publicInputsSize)
|
||||
}
|
||||
}
|
||||
|
||||
// 5. compute public input hash
|
||||
bytes32 publicInputHash;
|
||||
{
|
||||
uint256 publicInputsSize = 32 * 3 + batch.blocks.length * 124 + 32 * maxNumTxInBatch;
|
||||
assembly {
|
||||
publicInputHash := keccak256(sub(publicInputsPtr, publicInputsSize), publicInputsSize)
|
||||
}
|
||||
return (publicInputHash, numTransactionsInBatch, accTotalL1Messages, _block.timestamp);
|
||||
}
|
||||
|
||||
return (publicInputHash, numTransactionsInBatch, accTotalL1Messages, _block.timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,58 +2,58 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { ScrollChain } from "./ScrollChain.sol";
|
||||
import { ZkTrieVerifier } from "../../libraries/verifier/ZkTrieVerifier.sol";
|
||||
import {ScrollChain} from "./ScrollChain.sol";
|
||||
import {ZkTrieVerifier} from "../../libraries/verifier/ZkTrieVerifier.sol";
|
||||
|
||||
contract ScrollChainCommitmentVerifier {
|
||||
/// @notice The address of poseidon hash contract
|
||||
address public immutable poseidon;
|
||||
/// @notice The address of poseidon hash contract
|
||||
address public immutable poseidon;
|
||||
|
||||
/// @notice The address of ScrollChain contract.
|
||||
address public immutable rollup;
|
||||
/// @notice The address of ScrollChain contract.
|
||||
address public immutable rollup;
|
||||
|
||||
constructor(address _poseidon, address _rollup) {
|
||||
poseidon = _poseidon;
|
||||
rollup = _rollup;
|
||||
}
|
||||
constructor(address _poseidon, address _rollup) {
|
||||
poseidon = _poseidon;
|
||||
rollup = _rollup;
|
||||
}
|
||||
|
||||
/// @notice Validates a proof from eth_getProof in l2geth.
|
||||
/// @param account The address of the contract.
|
||||
/// @param storageKey The storage slot to verify.
|
||||
/// @param proof The rlp encoding result of eth_getProof.
|
||||
/// @return stateRoot The computed state root. Must be checked by the caller.
|
||||
/// @return storageValue The value of `storageKey`.
|
||||
///
|
||||
/// The encoding order of `proof` is
|
||||
/// ```text
|
||||
/// | 1 byte | ... | 1 byte | ... |
|
||||
/// | account proof length | account proof | storage proof length | storage proof |
|
||||
/// ```
|
||||
function verifyZkTrieProof(
|
||||
address account,
|
||||
bytes32 storageKey,
|
||||
bytes calldata proof
|
||||
) public view returns (bytes32 stateRoot, bytes32 storageValue) {
|
||||
return ZkTrieVerifier.verifyZkTrieProof(poseidon, account, storageKey, proof);
|
||||
}
|
||||
/// @notice Validates a proof from eth_getProof in l2geth.
|
||||
/// @param account The address of the contract.
|
||||
/// @param storageKey The storage slot to verify.
|
||||
/// @param proof The rlp encoding result of eth_getProof.
|
||||
/// @return stateRoot The computed state root. Must be checked by the caller.
|
||||
/// @return storageValue The value of `storageKey`.
|
||||
///
|
||||
/// The encoding order of `proof` is
|
||||
/// ```text
|
||||
/// | 1 byte | ... | 1 byte | ... |
|
||||
/// | account proof length | account proof | storage proof length | storage proof |
|
||||
/// ```
|
||||
function verifyZkTrieProof(
|
||||
address account,
|
||||
bytes32 storageKey,
|
||||
bytes calldata proof
|
||||
) public view returns (bytes32 stateRoot, bytes32 storageValue) {
|
||||
return ZkTrieVerifier.verifyZkTrieProof(poseidon, account, storageKey, proof);
|
||||
}
|
||||
|
||||
/// @notice Verifies a batch inclusion proof.
|
||||
/// @param batchHash The hash of the batch.
|
||||
/// @param account The address of the contract in L2.
|
||||
/// @param storageKey The storage key inside the contract in L2.
|
||||
/// @param proof The rlp encoding result of eth_getProof.
|
||||
/// @return storageValue The value of `storageKey`.
|
||||
function verifyStateCommitment(
|
||||
bytes32 batchHash,
|
||||
address account,
|
||||
bytes32 storageKey,
|
||||
bytes calldata proof
|
||||
) external view returns (bytes32 storageValue) {
|
||||
require(ScrollChain(rollup).isBatchFinalized(batchHash), "Batch not finalized");
|
||||
/// @notice Verifies a batch inclusion proof.
|
||||
/// @param batchHash The hash of the batch.
|
||||
/// @param account The address of the contract in L2.
|
||||
/// @param storageKey The storage key inside the contract in L2.
|
||||
/// @param proof The rlp encoding result of eth_getProof.
|
||||
/// @return storageValue The value of `storageKey`.
|
||||
function verifyStateCommitment(
|
||||
bytes32 batchHash,
|
||||
address account,
|
||||
bytes32 storageKey,
|
||||
bytes calldata proof
|
||||
) external view returns (bytes32 storageValue) {
|
||||
require(ScrollChain(rollup).isBatchFinalized(batchHash), "Batch not finalized");
|
||||
|
||||
bytes32 computedStateRoot;
|
||||
(computedStateRoot, storageValue) = ZkTrieVerifier.verifyZkTrieProof(poseidon, account, storageKey, proof);
|
||||
(bytes32 expectedStateRoot, , , , , , , ) = ScrollChain(rollup).batches(batchHash);
|
||||
require(computedStateRoot == expectedStateRoot, "Invalid inclusion proof");
|
||||
}
|
||||
bytes32 computedStateRoot;
|
||||
(computedStateRoot, storageValue) = ZkTrieVerifier.verifyZkTrieProof(poseidon, account, storageKey, proof);
|
||||
(bytes32 expectedStateRoot, , , , , , , ) = ScrollChain(rollup).batches(batchHash);
|
||||
require(computedStateRoot == expectedStateRoot, "Invalid inclusion proof");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,50 +2,50 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { IScrollMessenger } from "../libraries/IScrollMessenger.sol";
|
||||
import {IScrollMessenger} from "../libraries/IScrollMessenger.sol";
|
||||
|
||||
interface IL2ScrollMessenger is IScrollMessenger {
|
||||
/***********
|
||||
* Structs *
|
||||
***********/
|
||||
/***********
|
||||
* Structs *
|
||||
***********/
|
||||
|
||||
struct L1MessageProof {
|
||||
bytes32 blockHash;
|
||||
bytes stateRootProof;
|
||||
}
|
||||
struct L1MessageProof {
|
||||
bytes32 blockHash;
|
||||
bytes stateRootProof;
|
||||
}
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
/*****************************
|
||||
* Public Mutating 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
|
||||
/// @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;
|
||||
/// @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;
|
||||
}
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
|
||||
import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
|
||||
|
||||
import { IL2ScrollMessenger } from "./IL2ScrollMessenger.sol";
|
||||
import { L2MessageQueue } from "./predeploys/L2MessageQueue.sol";
|
||||
import { IL1BlockContainer } from "./predeploys/IL1BlockContainer.sol";
|
||||
import { IL1GasPriceOracle } from "./predeploys/IL1GasPriceOracle.sol";
|
||||
import {IL2ScrollMessenger} from "./IL2ScrollMessenger.sol";
|
||||
import {L2MessageQueue} from "./predeploys/L2MessageQueue.sol";
|
||||
import {IL1BlockContainer} from "./predeploys/IL1BlockContainer.sol";
|
||||
import {IL1GasPriceOracle} from "./predeploys/IL1GasPriceOracle.sol";
|
||||
|
||||
import { PatriciaMerkleTrieVerifier } from "../libraries/verifier/PatriciaMerkleTrieVerifier.sol";
|
||||
import { ScrollConstants } from "../libraries/constants/ScrollConstants.sol";
|
||||
import { IScrollMessenger } from "../libraries/IScrollMessenger.sol";
|
||||
import { ScrollMessengerBase } from "../libraries/ScrollMessengerBase.sol";
|
||||
import {PatriciaMerkleTrieVerifier} from "../libraries/verifier/PatriciaMerkleTrieVerifier.sol";
|
||||
import {ScrollConstants} from "../libraries/constants/ScrollConstants.sol";
|
||||
import {IScrollMessenger} from "../libraries/IScrollMessenger.sol";
|
||||
import {ScrollMessengerBase} from "../libraries/ScrollMessengerBase.sol";
|
||||
|
||||
/// @title L2ScrollMessenger
|
||||
/// @notice The `L2ScrollMessenger` contract can:
|
||||
@@ -24,282 +24,327 @@ import { ScrollMessengerBase } from "../libraries/ScrollMessengerBase.sol";
|
||||
/// @dev It should be a predeployed contract in layer 2 and should hold infinite amount
|
||||
/// of Ether (Specifically, `uint256(-1)`), which can be initialized in Genesis Block.
|
||||
contract L2ScrollMessenger is ScrollMessengerBase, PausableUpgradeable, IL2ScrollMessenger {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
|
||||
/// @notice Emitted when the maximum number of times each message can fail in L2 is updated.
|
||||
/// @param maxFailedExecutionTimes The new maximum number of times each message can fail in L2.
|
||||
event UpdateMaxFailedExecutionTimes(uint256 maxFailedExecutionTimes);
|
||||
/// @notice Emitted when the maximum number of times each message can fail in L2 is updated.
|
||||
/// @param maxFailedExecutionTimes The new maximum number of times each message can fail in L2.
|
||||
event UpdateMaxFailedExecutionTimes(uint256 maxFailedExecutionTimes);
|
||||
|
||||
/*************
|
||||
* Constants *
|
||||
*************/
|
||||
/*************
|
||||
* Constants *
|
||||
*************/
|
||||
|
||||
uint256 private constant MIN_GAS_LIMIT = 21000;
|
||||
uint256 private constant MIN_GAS_LIMIT = 21000;
|
||||
|
||||
/// @notice The contract contains the list of L1 blocks.
|
||||
address public immutable blockContainer;
|
||||
/// @notice The contract contains the list of L1 blocks.
|
||||
address public immutable blockContainer;
|
||||
|
||||
/// @notice The address of L2MessageQueue.
|
||||
address public immutable gasOracle;
|
||||
/// @notice The address of L2MessageQueue.
|
||||
address public immutable gasOracle;
|
||||
|
||||
/// @notice The address of L2MessageQueue.
|
||||
address public immutable messageQueue;
|
||||
/// @notice The address of L2MessageQueue.
|
||||
address public immutable messageQueue;
|
||||
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
/// @notice Mapping from L2 message hash to sent status.
|
||||
mapping(bytes32 => bool) public isL2MessageSent;
|
||||
/// @notice Mapping from L2 message hash to sent status.
|
||||
mapping(bytes32 => bool) public isL2MessageSent;
|
||||
|
||||
/// @notice Mapping from L1 message hash to a boolean value indicating if the message has been successfully executed.
|
||||
mapping(bytes32 => bool) public isL1MessageExecuted;
|
||||
/// @notice Mapping from L1 message hash to a boolean value indicating if the message has been successfully executed.
|
||||
mapping(bytes32 => bool) public isL1MessageExecuted;
|
||||
|
||||
/// @notice Mapping from L1 message hash to the number of failure times.
|
||||
mapping(bytes32 => uint256) public l1MessageFailedTimes;
|
||||
/// @notice Mapping from L1 message hash to the number of failure times.
|
||||
mapping(bytes32 => uint256) public l1MessageFailedTimes;
|
||||
|
||||
/// @notice The maximum number of times each L1 message can fail on L2.
|
||||
uint256 public maxFailedExecutionTimes;
|
||||
/// @notice The maximum number of times each L1 message can fail on L2.
|
||||
uint256 public maxFailedExecutionTimes;
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
// @note move to ScrollMessengerBase in next big refactor
|
||||
/// @dev The status of for non-reentrant check.
|
||||
uint256 private _lock_status;
|
||||
|
||||
constructor(
|
||||
address _blockContainer,
|
||||
address _gasOracle,
|
||||
address _messageQueue
|
||||
) {
|
||||
blockContainer = _blockContainer;
|
||||
gasOracle = _gasOracle;
|
||||
messageQueue = _messageQueue;
|
||||
}
|
||||
/**********************
|
||||
* Function Modifiers *
|
||||
**********************/
|
||||
|
||||
function initialize(address _counterpart, address _feeVault) external initializer {
|
||||
PausableUpgradeable.__Pausable_init();
|
||||
ScrollMessengerBase._initialize(_counterpart, _feeVault);
|
||||
modifier nonReentrant() {
|
||||
// On the first call to nonReentrant, _notEntered will be true
|
||||
require(_lock_status != _ENTERED, "ReentrancyGuard: reentrant call");
|
||||
|
||||
maxFailedExecutionTimes = 3;
|
||||
// Any calls to nonReentrant after this point will fail
|
||||
_lock_status = _ENTERED;
|
||||
|
||||
// initialize to a nonzero value
|
||||
xDomainMessageSender = ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER;
|
||||
}
|
||||
_;
|
||||
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/// @notice Check whether the l1 message is included in the corresponding L1 block.
|
||||
/// @param _blockHash The block hash where the message should in.
|
||||
/// @param _msgHash The hash of the message to check.
|
||||
/// @param _proof The encoded storage proof from eth_getProof.
|
||||
/// @return bool Return true is the message is included in L1, otherwise return false.
|
||||
function verifyMessageInclusionStatus(
|
||||
bytes32 _blockHash,
|
||||
bytes32 _msgHash,
|
||||
bytes calldata _proof
|
||||
) public view returns (bool) {
|
||||
bytes32 _expectedStateRoot = IL1BlockContainer(blockContainer).getStateRoot(_blockHash);
|
||||
require(_expectedStateRoot != bytes32(0), "Block is not imported");
|
||||
|
||||
// @todo fix the actual slot later.
|
||||
bytes32 _storageKey;
|
||||
// `mapping(bytes32 => bool) public isL1MessageSent` is the 105-nd slot of contract `L1ScrollMessenger`.
|
||||
assembly {
|
||||
mstore(0x00, _msgHash)
|
||||
mstore(0x20, 105)
|
||||
_storageKey := keccak256(0x00, 0x40)
|
||||
// By storing the original value once again, a refund is triggered (see
|
||||
// https://eips.ethereum.org/EIPS/eip-2200)
|
||||
_lock_status = _NOT_ENTERED;
|
||||
}
|
||||
|
||||
(bytes32 _computedStateRoot, bytes32 _storageValue) = PatriciaMerkleTrieVerifier.verifyPatriciaProof(
|
||||
counterpart,
|
||||
_storageKey,
|
||||
_proof
|
||||
);
|
||||
require(_computedStateRoot == _expectedStateRoot, "State roots mismatch");
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
return uint256(_storageValue) == 1;
|
||||
}
|
||||
|
||||
/// @notice Check whether the message is executed in the corresponding L1 block.
|
||||
/// @param _blockHash The block hash where the message should in.
|
||||
/// @param _msgHash The hash of the message to check.
|
||||
/// @param _proof The encoded storage proof from eth_getProof.
|
||||
/// @return bool Return true is the message is executed in L1, otherwise return false.
|
||||
function verifyMessageExecutionStatus(
|
||||
bytes32 _blockHash,
|
||||
bytes32 _msgHash,
|
||||
bytes calldata _proof
|
||||
) external view returns (bool) {
|
||||
bytes32 _expectedStateRoot = IL1BlockContainer(blockContainer).getStateRoot(_blockHash);
|
||||
require(_expectedStateRoot != bytes32(0), "Block not imported");
|
||||
|
||||
// @todo fix the actual slot later.
|
||||
bytes32 _storageKey;
|
||||
// `mapping(bytes32 => bool) public isL2MessageExecuted` is the 106-th slot of contract `L1ScrollMessenger`.
|
||||
assembly {
|
||||
mstore(0x00, _msgHash)
|
||||
mstore(0x20, 106)
|
||||
_storageKey := keccak256(0x00, 0x40)
|
||||
constructor(
|
||||
address _blockContainer,
|
||||
address _gasOracle,
|
||||
address _messageQueue
|
||||
) {
|
||||
blockContainer = _blockContainer;
|
||||
gasOracle = _gasOracle;
|
||||
messageQueue = _messageQueue;
|
||||
}
|
||||
|
||||
(bytes32 _computedStateRoot, bytes32 _storageValue) = PatriciaMerkleTrieVerifier.verifyPatriciaProof(
|
||||
counterpart,
|
||||
_storageKey,
|
||||
_proof
|
||||
);
|
||||
require(_computedStateRoot == _expectedStateRoot, "State root mismatch");
|
||||
function initialize(address _counterpart, address _feeVault) external initializer {
|
||||
PausableUpgradeable.__Pausable_init();
|
||||
ScrollMessengerBase._initialize(_counterpart, _feeVault);
|
||||
|
||||
return uint256(_storageValue) == 1;
|
||||
}
|
||||
maxFailedExecutionTimes = 3;
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
|
||||
/// @inheritdoc IScrollMessenger
|
||||
function sendMessage(
|
||||
address _to,
|
||||
uint256 _value,
|
||||
bytes memory _message,
|
||||
uint256 _gasLimit
|
||||
) external payable override whenNotPaused {
|
||||
// by pass fee vault relay
|
||||
if (feeVault != msg.sender) {
|
||||
require(_gasLimit >= MIN_GAS_LIMIT, "gas limit too small");
|
||||
// initialize to a nonzero value
|
||||
xDomainMessageSender = ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER;
|
||||
}
|
||||
|
||||
// compute and deduct the messaging fee to fee vault.
|
||||
uint256 _fee = _gasLimit * IL1GasPriceOracle(gasOracle).l1BaseFee();
|
||||
require(msg.value >= _value + _fee, "Insufficient msg.value");
|
||||
if (_fee > 0) {
|
||||
(bool _success, ) = feeVault.call{ value: _fee }("");
|
||||
require(_success, "Failed to deduct the fee");
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/// @notice Check whether the l1 message is included in the corresponding L1 block.
|
||||
/// @param _blockHash The block hash where the message should in.
|
||||
/// @param _msgHash The hash of the message to check.
|
||||
/// @param _proof The encoded storage proof from eth_getProof.
|
||||
/// @return bool Return true is the message is included in L1, otherwise return false.
|
||||
function verifyMessageInclusionStatus(
|
||||
bytes32 _blockHash,
|
||||
bytes32 _msgHash,
|
||||
bytes calldata _proof
|
||||
) public view returns (bool) {
|
||||
bytes32 _expectedStateRoot = IL1BlockContainer(blockContainer).getStateRoot(_blockHash);
|
||||
require(_expectedStateRoot != bytes32(0), "Block is not imported");
|
||||
|
||||
// @todo fix the actual slot later.
|
||||
bytes32 _storageKey;
|
||||
// `mapping(bytes32 => bool) public isL1MessageSent` is the 105-nd slot of contract `L1ScrollMessenger`.
|
||||
assembly {
|
||||
mstore(0x00, _msgHash)
|
||||
mstore(0x20, 105)
|
||||
_storageKey := keccak256(0x00, 0x40)
|
||||
}
|
||||
|
||||
(bytes32 _computedStateRoot, bytes32 _storageValue) = PatriciaMerkleTrieVerifier.verifyPatriciaProof(
|
||||
counterpart,
|
||||
_storageKey,
|
||||
_proof
|
||||
);
|
||||
require(_computedStateRoot == _expectedStateRoot, "State roots mismatch");
|
||||
|
||||
return uint256(_storageValue) == 1;
|
||||
}
|
||||
|
||||
uint256 _nonce = L2MessageQueue(messageQueue).nextMessageIndex();
|
||||
bytes32 _xDomainCalldataHash = keccak256(_encodeXDomainCalldata(msg.sender, _to, _value, _nonce, _message));
|
||||
/// @notice Check whether the message is executed in the corresponding L1 block.
|
||||
/// @param _blockHash The block hash where the message should in.
|
||||
/// @param _msgHash The hash of the message to check.
|
||||
/// @param _proof The encoded storage proof from eth_getProof.
|
||||
/// @return bool Return true is the message is executed in L1, otherwise return false.
|
||||
function verifyMessageExecutionStatus(
|
||||
bytes32 _blockHash,
|
||||
bytes32 _msgHash,
|
||||
bytes calldata _proof
|
||||
) external view returns (bool) {
|
||||
bytes32 _expectedStateRoot = IL1BlockContainer(blockContainer).getStateRoot(_blockHash);
|
||||
require(_expectedStateRoot != bytes32(0), "Block not imported");
|
||||
|
||||
// normally this won't happen, since each message has different nonce, but just in case.
|
||||
require(!isL2MessageSent[_xDomainCalldataHash], "Duplicated message");
|
||||
isL2MessageSent[_xDomainCalldataHash] = true;
|
||||
// @todo fix the actual slot later.
|
||||
bytes32 _storageKey;
|
||||
// `mapping(bytes32 => bool) public isL2MessageExecuted` is the 106-th slot of contract `L1ScrollMessenger`.
|
||||
assembly {
|
||||
mstore(0x00, _msgHash)
|
||||
mstore(0x20, 106)
|
||||
_storageKey := keccak256(0x00, 0x40)
|
||||
}
|
||||
|
||||
L2MessageQueue(messageQueue).appendMessage(_xDomainCalldataHash);
|
||||
(bytes32 _computedStateRoot, bytes32 _storageValue) = PatriciaMerkleTrieVerifier.verifyPatriciaProof(
|
||||
counterpart,
|
||||
_storageKey,
|
||||
_proof
|
||||
);
|
||||
require(_computedStateRoot == _expectedStateRoot, "State root mismatch");
|
||||
|
||||
emit SentMessage(msg.sender, _to, _value, _nonce, _gasLimit, _message);
|
||||
|
||||
// refund fee to tx.origin
|
||||
unchecked {
|
||||
uint256 _refund = msg.value - _fee - _value;
|
||||
if (_refund > 0) {
|
||||
(bool _success, ) = tx.origin.call{ value: _refund }("");
|
||||
require(_success, "Failed to refund the fee");
|
||||
}
|
||||
return uint256(_storageValue) == 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ScrollMessenger
|
||||
function relayMessage(
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _value,
|
||||
uint256 _nonce,
|
||||
bytes memory _message
|
||||
) external override whenNotPaused onlyWhitelistedSender(msg.sender) {
|
||||
// anti reentrance
|
||||
require(xDomainMessageSender == ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER, "Message is already in execution");
|
||||
|
||||
// @todo address unalis to check sender is L1ScrollMessenger
|
||||
|
||||
bytes32 _xDomainCalldataHash = keccak256(_encodeXDomainCalldata(_from, _to, _value, _nonce, _message));
|
||||
|
||||
require(!isL1MessageExecuted[_xDomainCalldataHash], "Message was already successfully executed");
|
||||
|
||||
_executeMessage(_from, _to, _value, _message, _xDomainCalldataHash);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ScrollMessenger
|
||||
function retryMessageWithProof(
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _value,
|
||||
uint256 _nonce,
|
||||
bytes memory _message,
|
||||
L1MessageProof calldata _proof
|
||||
) external override whenNotPaused {
|
||||
// anti reentrance
|
||||
require(xDomainMessageSender == ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER, "Already in execution");
|
||||
|
||||
// check message status
|
||||
bytes32 _xDomainCalldataHash = keccak256(_encodeXDomainCalldata(_from, _to, _value, _nonce, _message));
|
||||
require(!isL1MessageExecuted[_xDomainCalldataHash], "Message successfully executed");
|
||||
require(l1MessageFailedTimes[_xDomainCalldataHash] > 0, "Message not relayed before");
|
||||
|
||||
require(
|
||||
verifyMessageInclusionStatus(_proof.blockHash, _xDomainCalldataHash, _proof.stateRootProof),
|
||||
"Message not included"
|
||||
);
|
||||
|
||||
_executeMessage(_from, _to, _value, _message, _xDomainCalldataHash);
|
||||
}
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
/// @notice Pause the contract
|
||||
/// @dev This function can only called by contract owner.
|
||||
/// @param _status The pause status to update.
|
||||
function setPause(bool _status) external onlyOwner {
|
||||
if (_status) {
|
||||
_pause();
|
||||
} else {
|
||||
_unpause();
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
/// @inheritdoc IScrollMessenger
|
||||
function sendMessage(
|
||||
address _to,
|
||||
uint256 _value,
|
||||
bytes memory _message,
|
||||
uint256 _gasLimit
|
||||
) external payable override whenNotPaused {
|
||||
_sendMessage(_to, _value, _message, _gasLimit, tx.origin);
|
||||
}
|
||||
}
|
||||
|
||||
function updateMaxFailedExecutionTimes(uint256 _maxFailedExecutionTimes) external onlyOwner {
|
||||
maxFailedExecutionTimes = _maxFailedExecutionTimes;
|
||||
|
||||
emit UpdateMaxFailedExecutionTimes(_maxFailedExecutionTimes);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
function _executeMessage(
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _value,
|
||||
bytes memory _message,
|
||||
bytes32 _xDomainCalldataHash
|
||||
) internal {
|
||||
// @todo check more `_to` address to avoid attack.
|
||||
require(_to != messageQueue, "Forbid to call message queue");
|
||||
require(_to != address(this), "Forbid to call self");
|
||||
|
||||
// @note This usually will never happen, just in case.
|
||||
require(_from != xDomainMessageSender, "Invalid message sender");
|
||||
|
||||
xDomainMessageSender = _from;
|
||||
// solhint-disable-next-line avoid-low-level-calls
|
||||
(bool success, ) = _to.call{ value: _value }(_message);
|
||||
// reset value to refund gas.
|
||||
xDomainMessageSender = ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER;
|
||||
|
||||
if (success) {
|
||||
isL1MessageExecuted[_xDomainCalldataHash] = true;
|
||||
emit RelayedMessage(_xDomainCalldataHash);
|
||||
} else {
|
||||
unchecked {
|
||||
uint256 _failedTimes = l1MessageFailedTimes[_xDomainCalldataHash] + 1;
|
||||
require(_failedTimes <= maxFailedExecutionTimes, "Exceed maximum failure times");
|
||||
l1MessageFailedTimes[_xDomainCalldataHash] = _failedTimes;
|
||||
}
|
||||
emit FailedRelayedMessage(_xDomainCalldataHash);
|
||||
/// @inheritdoc IScrollMessenger
|
||||
function sendMessage(
|
||||
address _to,
|
||||
uint256 _value,
|
||||
bytes calldata _message,
|
||||
uint256 _gasLimit,
|
||||
address _refundAddress
|
||||
) external payable override whenNotPaused {
|
||||
_sendMessage(_to, _value, _message, _gasLimit, _refundAddress);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ScrollMessenger
|
||||
function relayMessage(
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _value,
|
||||
uint256 _nonce,
|
||||
bytes memory _message
|
||||
) external override whenNotPaused onlyWhitelistedSender(msg.sender) {
|
||||
// anti reentrance
|
||||
require(
|
||||
xDomainMessageSender == ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER,
|
||||
"Message is already in execution"
|
||||
);
|
||||
|
||||
// @todo address unalis to check sender is L1ScrollMessenger
|
||||
|
||||
bytes32 _xDomainCalldataHash = keccak256(_encodeXDomainCalldata(_from, _to, _value, _nonce, _message));
|
||||
|
||||
require(!isL1MessageExecuted[_xDomainCalldataHash], "Message was already successfully executed");
|
||||
|
||||
_executeMessage(_from, _to, _value, _message, _xDomainCalldataHash);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ScrollMessenger
|
||||
function retryMessageWithProof(
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _value,
|
||||
uint256 _nonce,
|
||||
bytes memory _message,
|
||||
L1MessageProof calldata _proof
|
||||
) external override whenNotPaused {
|
||||
// anti reentrance
|
||||
require(xDomainMessageSender == ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER, "Already in execution");
|
||||
|
||||
// check message status
|
||||
bytes32 _xDomainCalldataHash = keccak256(_encodeXDomainCalldata(_from, _to, _value, _nonce, _message));
|
||||
require(!isL1MessageExecuted[_xDomainCalldataHash], "Message successfully executed");
|
||||
require(l1MessageFailedTimes[_xDomainCalldataHash] > 0, "Message not relayed before");
|
||||
|
||||
require(
|
||||
verifyMessageInclusionStatus(_proof.blockHash, _xDomainCalldataHash, _proof.stateRootProof),
|
||||
"Message not included"
|
||||
);
|
||||
|
||||
_executeMessage(_from, _to, _value, _message, _xDomainCalldataHash);
|
||||
}
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
/// @notice Pause the contract
|
||||
/// @dev This function can only called by contract owner.
|
||||
/// @param _status The pause status to update.
|
||||
function setPause(bool _status) external onlyOwner {
|
||||
if (_status) {
|
||||
_pause();
|
||||
} else {
|
||||
_unpause();
|
||||
}
|
||||
}
|
||||
|
||||
function updateMaxFailedExecutionTimes(uint256 _maxFailedExecutionTimes) external onlyOwner {
|
||||
maxFailedExecutionTimes = _maxFailedExecutionTimes;
|
||||
|
||||
emit UpdateMaxFailedExecutionTimes(_maxFailedExecutionTimes);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
function _sendMessage(
|
||||
address _to,
|
||||
uint256 _value,
|
||||
bytes memory _message,
|
||||
uint256 _gasLimit,
|
||||
address _refundAddress
|
||||
) internal nonReentrant {
|
||||
// by pass fee vault relay
|
||||
if (feeVault != msg.sender) {
|
||||
require(_gasLimit >= MIN_GAS_LIMIT, "gas limit too small");
|
||||
}
|
||||
|
||||
// compute and deduct the messaging fee to fee vault.
|
||||
uint256 _fee = _gasLimit * IL1GasPriceOracle(gasOracle).l1BaseFee();
|
||||
require(msg.value >= _value + _fee, "Insufficient msg.value");
|
||||
if (_fee > 0) {
|
||||
(bool _success, ) = feeVault.call{value: _fee}("");
|
||||
require(_success, "Failed to deduct the fee");
|
||||
}
|
||||
|
||||
uint256 _nonce = L2MessageQueue(messageQueue).nextMessageIndex();
|
||||
bytes32 _xDomainCalldataHash = keccak256(_encodeXDomainCalldata(msg.sender, _to, _value, _nonce, _message));
|
||||
|
||||
// normally this won't happen, since each message has different nonce, but just in case.
|
||||
require(!isL2MessageSent[_xDomainCalldataHash], "Duplicated message");
|
||||
isL2MessageSent[_xDomainCalldataHash] = true;
|
||||
|
||||
L2MessageQueue(messageQueue).appendMessage(_xDomainCalldataHash);
|
||||
|
||||
emit SentMessage(msg.sender, _to, _value, _nonce, _gasLimit, _message);
|
||||
|
||||
// refund fee to tx.origin
|
||||
unchecked {
|
||||
uint256 _refund = msg.value - _fee - _value;
|
||||
if (_refund > 0) {
|
||||
(bool _success, ) = _refundAddress.call{value: _refund}("");
|
||||
require(_success, "Failed to refund the fee");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _executeMessage(
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _value,
|
||||
bytes memory _message,
|
||||
bytes32 _xDomainCalldataHash
|
||||
) internal {
|
||||
// @todo check more `_to` address to avoid attack.
|
||||
require(_to != messageQueue, "Forbid to call message queue");
|
||||
require(_to != address(this), "Forbid to call self");
|
||||
|
||||
// @note This usually will never happen, just in case.
|
||||
require(_from != xDomainMessageSender, "Invalid message sender");
|
||||
|
||||
xDomainMessageSender = _from;
|
||||
// solhint-disable-next-line avoid-low-level-calls
|
||||
(bool success, ) = _to.call{value: _value}(_message);
|
||||
// reset value to refund gas.
|
||||
xDomainMessageSender = ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER;
|
||||
|
||||
if (success) {
|
||||
isL1MessageExecuted[_xDomainCalldataHash] = true;
|
||||
emit RelayedMessage(_xDomainCalldataHash);
|
||||
} else {
|
||||
unchecked {
|
||||
uint256 _failedTimes = l1MessageFailedTimes[_xDomainCalldataHash] + 1;
|
||||
require(_failedTimes <= maxFailedExecutionTimes, "Exceed maximum failure times");
|
||||
l1MessageFailedTimes[_xDomainCalldataHash] = _failedTimes;
|
||||
}
|
||||
emit FailedRelayedMessage(_xDomainCalldataHash);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,165 +4,165 @@ pragma solidity ^0.8.0;
|
||||
|
||||
/// @title The interface for the ERC1155 cross chain gateway in layer 2.
|
||||
interface IL2ERC1155Gateway {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
|
||||
/// @notice Emitted when the ERC1155 NFT is transfered to recipient in layer 2.
|
||||
/// @param l1Token The address of ERC1155 NFT in layer 1.
|
||||
/// @param l2Token The address of ERC1155 NFT in layer 2.
|
||||
/// @param from The address of sender in layer 1.
|
||||
/// @param to The address of recipient in layer 2.
|
||||
/// @param tokenId The token id of the ERC1155 NFT deposited in layer 1.
|
||||
/// @param amount The amount of token deposited.
|
||||
event FinalizeDepositERC1155(
|
||||
address indexed l1Token,
|
||||
address indexed l2Token,
|
||||
address indexed from,
|
||||
address to,
|
||||
uint256 tokenId,
|
||||
uint256 amount
|
||||
);
|
||||
/// @notice Emitted when the ERC1155 NFT is transfered to recipient in layer 2.
|
||||
/// @param l1Token The address of ERC1155 NFT in layer 1.
|
||||
/// @param l2Token The address of ERC1155 NFT in layer 2.
|
||||
/// @param from The address of sender in layer 1.
|
||||
/// @param to The address of recipient in layer 2.
|
||||
/// @param tokenId The token id of the ERC1155 NFT deposited in layer 1.
|
||||
/// @param amount The amount of token deposited.
|
||||
event FinalizeDepositERC1155(
|
||||
address indexed l1Token,
|
||||
address indexed l2Token,
|
||||
address indexed from,
|
||||
address to,
|
||||
uint256 tokenId,
|
||||
uint256 amount
|
||||
);
|
||||
|
||||
/// @notice Emitted when the ERC1155 NFT is batch transfered to recipient in layer 2.
|
||||
/// @param l1Token The address of ERC1155 NFT in layer 1.
|
||||
/// @param l2Token The address of ERC1155 NFT in layer 2.
|
||||
/// @param from The address of sender in layer 1.
|
||||
/// @param to The address of recipient in layer 2.
|
||||
/// @param tokenIds The list of token ids of the ERC1155 NFT deposited in layer 1.
|
||||
/// @param amounts The list of corresponding amounts deposited.
|
||||
event FinalizeBatchDepositERC1155(
|
||||
address indexed l1Token,
|
||||
address indexed l2Token,
|
||||
address indexed from,
|
||||
address to,
|
||||
uint256[] tokenIds,
|
||||
uint256[] amounts
|
||||
);
|
||||
/// @notice Emitted when the ERC1155 NFT is batch transfered to recipient in layer 2.
|
||||
/// @param l1Token The address of ERC1155 NFT in layer 1.
|
||||
/// @param l2Token The address of ERC1155 NFT in layer 2.
|
||||
/// @param from The address of sender in layer 1.
|
||||
/// @param to The address of recipient in layer 2.
|
||||
/// @param tokenIds The list of token ids of the ERC1155 NFT deposited in layer 1.
|
||||
/// @param amounts The list of corresponding amounts deposited.
|
||||
event FinalizeBatchDepositERC1155(
|
||||
address indexed l1Token,
|
||||
address indexed l2Token,
|
||||
address indexed from,
|
||||
address to,
|
||||
uint256[] tokenIds,
|
||||
uint256[] amounts
|
||||
);
|
||||
|
||||
/// @notice Emitted when the ERC1155 NFT is transfered to gateway in layer 2.
|
||||
/// @param l1Token The address of ERC1155 NFT in layer 1.
|
||||
/// @param l2Token The address of ERC1155 NFT in layer 2.
|
||||
/// @param from The address of sender in layer 2.
|
||||
/// @param to The address of recipient in layer 1.
|
||||
/// @param tokenId The token id of the ERC1155 NFT to withdraw in layer 2.
|
||||
/// @param amount The amount of token to withdraw.
|
||||
event WithdrawERC1155(
|
||||
address indexed l1Token,
|
||||
address indexed l2Token,
|
||||
address indexed from,
|
||||
address to,
|
||||
uint256 tokenId,
|
||||
uint256 amount
|
||||
);
|
||||
/// @notice Emitted when the ERC1155 NFT is transfered to gateway in layer 2.
|
||||
/// @param l1Token The address of ERC1155 NFT in layer 1.
|
||||
/// @param l2Token The address of ERC1155 NFT in layer 2.
|
||||
/// @param from The address of sender in layer 2.
|
||||
/// @param to The address of recipient in layer 1.
|
||||
/// @param tokenId The token id of the ERC1155 NFT to withdraw in layer 2.
|
||||
/// @param amount The amount of token to withdraw.
|
||||
event WithdrawERC1155(
|
||||
address indexed l1Token,
|
||||
address indexed l2Token,
|
||||
address indexed from,
|
||||
address to,
|
||||
uint256 tokenId,
|
||||
uint256 amount
|
||||
);
|
||||
|
||||
/// @notice Emitted when the ERC1155 NFT is batch transfered to gateway in layer 2.
|
||||
/// @param l1Token The address of ERC1155 NFT in layer 1.
|
||||
/// @param l2Token The address of ERC1155 NFT in layer 2.
|
||||
/// @param from The address of sender in layer 2.
|
||||
/// @param to The address of recipient in layer 1.
|
||||
/// @param tokenIds The list of token ids of the ERC1155 NFT to withdraw in layer 2.
|
||||
/// @param amounts The list of corresponding amounts to withdraw.
|
||||
event BatchWithdrawERC1155(
|
||||
address indexed l1Token,
|
||||
address indexed l2Token,
|
||||
address indexed from,
|
||||
address to,
|
||||
uint256[] tokenIds,
|
||||
uint256[] amounts
|
||||
);
|
||||
/// @notice Emitted when the ERC1155 NFT is batch transfered to gateway in layer 2.
|
||||
/// @param l1Token The address of ERC1155 NFT in layer 1.
|
||||
/// @param l2Token The address of ERC1155 NFT in layer 2.
|
||||
/// @param from The address of sender in layer 2.
|
||||
/// @param to The address of recipient in layer 1.
|
||||
/// @param tokenIds The list of token ids of the ERC1155 NFT to withdraw in layer 2.
|
||||
/// @param amounts The list of corresponding amounts to withdraw.
|
||||
event BatchWithdrawERC1155(
|
||||
address indexed l1Token,
|
||||
address indexed l2Token,
|
||||
address indexed from,
|
||||
address to,
|
||||
uint256[] tokenIds,
|
||||
uint256[] amounts
|
||||
);
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
/// @notice Withdraw some ERC1155 NFT to caller's account on layer 1.
|
||||
/// @param token The address of ERC1155 NFT in layer 2.
|
||||
/// @param tokenId The token id to withdraw.
|
||||
/// @param amount The amount of token to withdraw.
|
||||
/// @param gasLimit Unused, but included for potential forward compatibility considerations.
|
||||
function withdrawERC1155(
|
||||
address token,
|
||||
uint256 tokenId,
|
||||
uint256 amount,
|
||||
uint256 gasLimit
|
||||
) external;
|
||||
/// @notice Withdraw some ERC1155 NFT to caller's account on layer 1.
|
||||
/// @param token The address of ERC1155 NFT in layer 2.
|
||||
/// @param tokenId The token id to withdraw.
|
||||
/// @param amount The amount of token to withdraw.
|
||||
/// @param gasLimit Unused, but included for potential forward compatibility considerations.
|
||||
function withdrawERC1155(
|
||||
address token,
|
||||
uint256 tokenId,
|
||||
uint256 amount,
|
||||
uint256 gasLimit
|
||||
) external payable;
|
||||
|
||||
/// @notice Withdraw some ERC1155 NFT to caller's account on layer 1.
|
||||
/// @param token The address of ERC1155 NFT in layer 2.
|
||||
/// @param to The address of recipient in layer 1.
|
||||
/// @param tokenId The token id to withdraw.
|
||||
/// @param amount The amount of token to withdraw.
|
||||
/// @param gasLimit Unused, but included for potential forward compatibility considerations.
|
||||
function withdrawERC1155(
|
||||
address token,
|
||||
address to,
|
||||
uint256 tokenId,
|
||||
uint256 amount,
|
||||
uint256 gasLimit
|
||||
) external;
|
||||
/// @notice Withdraw some ERC1155 NFT to caller's account on layer 1.
|
||||
/// @param token The address of ERC1155 NFT in layer 2.
|
||||
/// @param to The address of recipient in layer 1.
|
||||
/// @param tokenId The token id to withdraw.
|
||||
/// @param amount The amount of token to withdraw.
|
||||
/// @param gasLimit Unused, but included for potential forward compatibility considerations.
|
||||
function withdrawERC1155(
|
||||
address token,
|
||||
address to,
|
||||
uint256 tokenId,
|
||||
uint256 amount,
|
||||
uint256 gasLimit
|
||||
) external payable;
|
||||
|
||||
/// @notice Batch withdraw a list of ERC1155 NFT to caller's account on layer 1.
|
||||
/// @param token The address of ERC1155 NFT in layer 2.
|
||||
/// @param tokenIds The list of token ids to withdraw.
|
||||
/// @param amounts The list of corresponding amounts to withdraw.
|
||||
/// @param gasLimit Unused, but included for potential forward compatibility considerations.
|
||||
function batchWithdrawERC1155(
|
||||
address token,
|
||||
uint256[] memory tokenIds,
|
||||
uint256[] memory amounts,
|
||||
uint256 gasLimit
|
||||
) external;
|
||||
/// @notice Batch withdraw a list of ERC1155 NFT to caller's account on layer 1.
|
||||
/// @param token The address of ERC1155 NFT in layer 2.
|
||||
/// @param tokenIds The list of token ids to withdraw.
|
||||
/// @param amounts The list of corresponding amounts to withdraw.
|
||||
/// @param gasLimit Unused, but included for potential forward compatibility considerations.
|
||||
function batchWithdrawERC1155(
|
||||
address token,
|
||||
uint256[] memory tokenIds,
|
||||
uint256[] memory amounts,
|
||||
uint256 gasLimit
|
||||
) external payable;
|
||||
|
||||
/// @notice Batch withdraw a list of ERC1155 NFT to caller's account on layer 1.
|
||||
/// @param token The address of ERC1155 NFT in layer 2.
|
||||
/// @param to The address of recipient in layer 1.
|
||||
/// @param tokenIds The list of token ids to withdraw.
|
||||
/// @param amounts The list of corresponding amounts to withdraw.
|
||||
/// @param gasLimit Unused, but included for potential forward compatibility considerations.
|
||||
function batchWithdrawERC1155(
|
||||
address token,
|
||||
address to,
|
||||
uint256[] memory tokenIds,
|
||||
uint256[] memory amounts,
|
||||
uint256 gasLimit
|
||||
) external;
|
||||
/// @notice Batch withdraw a list of ERC1155 NFT to caller's account on layer 1.
|
||||
/// @param token The address of ERC1155 NFT in layer 2.
|
||||
/// @param to The address of recipient in layer 1.
|
||||
/// @param tokenIds The list of token ids to withdraw.
|
||||
/// @param amounts The list of corresponding amounts to withdraw.
|
||||
/// @param gasLimit Unused, but included for potential forward compatibility considerations.
|
||||
function batchWithdrawERC1155(
|
||||
address token,
|
||||
address to,
|
||||
uint256[] memory tokenIds,
|
||||
uint256[] memory amounts,
|
||||
uint256 gasLimit
|
||||
) external payable;
|
||||
|
||||
/// @notice Complete ERC1155 deposit from layer 1 to layer 2 and send NFT to recipient's account in layer 2.
|
||||
/// @dev Requirements:
|
||||
/// - The function should only be called by L2ScrollMessenger.
|
||||
/// - The function should also only be called by L1ERC1155Gateway in layer 1.
|
||||
/// @param l1Token The address of corresponding layer 1 token.
|
||||
/// @param l2Token The address of corresponding layer 2 token.
|
||||
/// @param from The address of account who deposits the token in layer 1.
|
||||
/// @param to The address of recipient in layer 2 to receive the token.
|
||||
/// @param tokenId The token id to deposit.
|
||||
/// @param amount The amount of token to deposit.
|
||||
function finalizeDepositERC1155(
|
||||
address l1Token,
|
||||
address l2Token,
|
||||
address from,
|
||||
address to,
|
||||
uint256 tokenId,
|
||||
uint256 amount
|
||||
) external;
|
||||
/// @notice Complete ERC1155 deposit from layer 1 to layer 2 and send NFT to recipient's account in layer 2.
|
||||
/// @dev Requirements:
|
||||
/// - The function should only be called by L2ScrollMessenger.
|
||||
/// - The function should also only be called by L1ERC1155Gateway in layer 1.
|
||||
/// @param l1Token The address of corresponding layer 1 token.
|
||||
/// @param l2Token The address of corresponding layer 2 token.
|
||||
/// @param from The address of account who deposits the token in layer 1.
|
||||
/// @param to The address of recipient in layer 2 to receive the token.
|
||||
/// @param tokenId The token id to deposit.
|
||||
/// @param amount The amount of token to deposit.
|
||||
function finalizeDepositERC1155(
|
||||
address l1Token,
|
||||
address l2Token,
|
||||
address from,
|
||||
address to,
|
||||
uint256 tokenId,
|
||||
uint256 amount
|
||||
) external;
|
||||
|
||||
/// @notice Complete ERC1155 deposit from layer 1 to layer 2 and send NFT to recipient's account in layer 2.
|
||||
/// @dev Requirements:
|
||||
/// - The function should only be called by L2ScrollMessenger.
|
||||
/// - The function should also only be called by L1ERC1155Gateway in layer 1.
|
||||
/// @param l1Token The address of corresponding layer 1 token.
|
||||
/// @param l2Token The address of corresponding layer 2 token.
|
||||
/// @param from The address of account who deposits the token in layer 1.
|
||||
/// @param to The address of recipient in layer 2 to receive the token.
|
||||
/// @param tokenIds The list of token ids to deposit.
|
||||
/// @param amounts The list of corresponding amounts to deposit.
|
||||
function finalizeBatchDepositERC1155(
|
||||
address l1Token,
|
||||
address l2Token,
|
||||
address from,
|
||||
address to,
|
||||
uint256[] calldata tokenIds,
|
||||
uint256[] calldata amounts
|
||||
) external;
|
||||
/// @notice Complete ERC1155 deposit from layer 1 to layer 2 and send NFT to recipient's account in layer 2.
|
||||
/// @dev Requirements:
|
||||
/// - The function should only be called by L2ScrollMessenger.
|
||||
/// - The function should also only be called by L1ERC1155Gateway in layer 1.
|
||||
/// @param l1Token The address of corresponding layer 1 token.
|
||||
/// @param l2Token The address of corresponding layer 2 token.
|
||||
/// @param from The address of account who deposits the token in layer 1.
|
||||
/// @param to The address of recipient in layer 2 to receive the token.
|
||||
/// @param tokenIds The list of token ids to deposit.
|
||||
/// @param amounts The list of corresponding amounts to deposit.
|
||||
function finalizeBatchDepositERC1155(
|
||||
address l1Token,
|
||||
address l2Token,
|
||||
address from,
|
||||
address to,
|
||||
uint256[] calldata tokenIds,
|
||||
uint256[] calldata amounts
|
||||
) external;
|
||||
}
|
||||
|
||||
@@ -3,113 +3,113 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
interface IL2ERC20Gateway {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
|
||||
/// @notice Emitted when ERC20 token is deposited from L1 to L2 and transfer to recipient.
|
||||
/// @param l1Token The address of the token in L1.
|
||||
/// @param l2Token The address of the token in L2.
|
||||
/// @param from The address of sender in L1.
|
||||
/// @param to The address of recipient in L2.
|
||||
/// @param amount The amount of token withdrawn from L1 to L2.
|
||||
/// @param data The optional calldata passed to recipient in L2.
|
||||
event FinalizeDepositERC20(
|
||||
address indexed l1Token,
|
||||
address indexed l2Token,
|
||||
address indexed from,
|
||||
address to,
|
||||
uint256 amount,
|
||||
bytes data
|
||||
);
|
||||
/// @notice Emitted when ERC20 token is deposited from L1 to L2 and transfer to recipient.
|
||||
/// @param l1Token The address of the token in L1.
|
||||
/// @param l2Token The address of the token in L2.
|
||||
/// @param from The address of sender in L1.
|
||||
/// @param to The address of recipient in L2.
|
||||
/// @param amount The amount of token withdrawn from L1 to L2.
|
||||
/// @param data The optional calldata passed to recipient in L2.
|
||||
event FinalizeDepositERC20(
|
||||
address indexed l1Token,
|
||||
address indexed l2Token,
|
||||
address indexed from,
|
||||
address to,
|
||||
uint256 amount,
|
||||
bytes data
|
||||
);
|
||||
|
||||
/// @notice Emitted when someone withdraw ERC20 token from L2 to L1.
|
||||
/// @param l1Token The address of the token in L1.
|
||||
/// @param l2Token The address of the token in L2.
|
||||
/// @param from The address of sender in L2.
|
||||
/// @param to The address of recipient in L1.
|
||||
/// @param amount The amount of token will be deposited from L2 to L1.
|
||||
/// @param data The optional calldata passed to recipient in L1.
|
||||
event WithdrawERC20(
|
||||
address indexed l1Token,
|
||||
address indexed l2Token,
|
||||
address indexed from,
|
||||
address to,
|
||||
uint256 amount,
|
||||
bytes data
|
||||
);
|
||||
/// @notice Emitted when someone withdraw ERC20 token from L2 to L1.
|
||||
/// @param l1Token The address of the token in L1.
|
||||
/// @param l2Token The address of the token in L2.
|
||||
/// @param from The address of sender in L2.
|
||||
/// @param to The address of recipient in L1.
|
||||
/// @param amount The amount of token will be deposited from L2 to L1.
|
||||
/// @param data The optional calldata passed to recipient in L1.
|
||||
event WithdrawERC20(
|
||||
address indexed l1Token,
|
||||
address indexed l2Token,
|
||||
address indexed from,
|
||||
address to,
|
||||
uint256 amount,
|
||||
bytes data
|
||||
);
|
||||
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/// @notice Return the corresponding l1 token address given l2 token address.
|
||||
/// @param l2Token The address of l2 token.
|
||||
function getL1ERC20Address(address l2Token) external view returns (address);
|
||||
/// @notice Return the corresponding l1 token address given l2 token address.
|
||||
/// @param l2Token The address of l2 token.
|
||||
function getL1ERC20Address(address l2Token) external view returns (address);
|
||||
|
||||
/// @notice Return the corresponding l2 token address given l1 token address.
|
||||
/// @param l1Token The address of l1 token.
|
||||
function getL2ERC20Address(address l1Token) external view returns (address);
|
||||
/// @notice Return the corresponding l2 token address given l1 token address.
|
||||
/// @param l1Token The address of l1 token.
|
||||
function getL2ERC20Address(address l1Token) external view returns (address);
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
/// @notice Withdraw of some token to a caller's account on L1.
|
||||
/// @dev Make this function payable to send relayer fee in Ether.
|
||||
/// @param token The address of token in L2.
|
||||
/// @param amount The amount of token to transfer.
|
||||
/// @param gasLimit Unused, but included for potential forward compatibility considerations.
|
||||
function withdrawERC20(
|
||||
address token,
|
||||
uint256 amount,
|
||||
uint256 gasLimit
|
||||
) external payable;
|
||||
/// @notice Withdraw of some token to a caller's account on L1.
|
||||
/// @dev Make this function payable to send relayer fee in Ether.
|
||||
/// @param token The address of token in L2.
|
||||
/// @param amount The amount of token to transfer.
|
||||
/// @param gasLimit Unused, but included for potential forward compatibility considerations.
|
||||
function withdrawERC20(
|
||||
address token,
|
||||
uint256 amount,
|
||||
uint256 gasLimit
|
||||
) external payable;
|
||||
|
||||
/// @notice Withdraw of some token to a recipient's account on L1.
|
||||
/// @dev Make this function payable to send relayer fee in Ether.
|
||||
/// @param token The address of token in L2.
|
||||
/// @param to The address of recipient's account on L1.
|
||||
/// @param amount The amount of token to transfer.
|
||||
/// @param gasLimit Unused, but included for potential forward compatibility considerations.
|
||||
function withdrawERC20(
|
||||
address token,
|
||||
address to,
|
||||
uint256 amount,
|
||||
uint256 gasLimit
|
||||
) external payable;
|
||||
/// @notice Withdraw of some token to a recipient's account on L1.
|
||||
/// @dev Make this function payable to send relayer fee in Ether.
|
||||
/// @param token The address of token in L2.
|
||||
/// @param to The address of recipient's account on L1.
|
||||
/// @param amount The amount of token to transfer.
|
||||
/// @param gasLimit Unused, but included for potential forward compatibility considerations.
|
||||
function withdrawERC20(
|
||||
address token,
|
||||
address to,
|
||||
uint256 amount,
|
||||
uint256 gasLimit
|
||||
) external payable;
|
||||
|
||||
/// @notice Withdraw of some token to a recipient's account on L1 and call.
|
||||
/// @dev Make this function payable to send relayer fee in Ether.
|
||||
/// @param token The address of token in L2.
|
||||
/// @param to The address of recipient's account on L1.
|
||||
/// @param amount The amount of token to transfer.
|
||||
/// @param data Optional data to forward to recipient's account.
|
||||
/// @param gasLimit Unused, but included for potential forward compatibility considerations.
|
||||
function withdrawERC20AndCall(
|
||||
address token,
|
||||
address to,
|
||||
uint256 amount,
|
||||
bytes calldata data,
|
||||
uint256 gasLimit
|
||||
) external payable;
|
||||
/// @notice Withdraw of some token to a recipient's account on L1 and call.
|
||||
/// @dev Make this function payable to send relayer fee in Ether.
|
||||
/// @param token The address of token in L2.
|
||||
/// @param to The address of recipient's account on L1.
|
||||
/// @param amount The amount of token to transfer.
|
||||
/// @param data Optional data to forward to recipient's account.
|
||||
/// @param gasLimit Unused, but included for potential forward compatibility considerations.
|
||||
function withdrawERC20AndCall(
|
||||
address token,
|
||||
address to,
|
||||
uint256 amount,
|
||||
bytes calldata data,
|
||||
uint256 gasLimit
|
||||
) external payable;
|
||||
|
||||
/// @notice Complete a deposit from L1 to L2 and send fund to recipient's account in L2.
|
||||
/// @dev Make this function payable to handle WETH deposit/withdraw.
|
||||
/// The function should only be called by L2ScrollMessenger.
|
||||
/// The function should also only be called by L1ERC20Gateway in L1.
|
||||
/// @param l1Token The address of corresponding L1 token.
|
||||
/// @param l2Token The address of corresponding L2 token.
|
||||
/// @param from The address of account who deposits the token in L1.
|
||||
/// @param to The address of recipient in L2 to receive the token.
|
||||
/// @param amount The amount of the token to deposit.
|
||||
/// @param data Optional data to forward to recipient's account.
|
||||
function finalizeDepositERC20(
|
||||
address l1Token,
|
||||
address l2Token,
|
||||
address from,
|
||||
address to,
|
||||
uint256 amount,
|
||||
bytes calldata data
|
||||
) external payable;
|
||||
/// @notice Complete a deposit from L1 to L2 and send fund to recipient's account in L2.
|
||||
/// @dev Make this function payable to handle WETH deposit/withdraw.
|
||||
/// The function should only be called by L2ScrollMessenger.
|
||||
/// The function should also only be called by L1ERC20Gateway in L1.
|
||||
/// @param l1Token The address of corresponding L1 token.
|
||||
/// @param l2Token The address of corresponding L2 token.
|
||||
/// @param from The address of account who deposits the token in L1.
|
||||
/// @param to The address of recipient in L2 to receive the token.
|
||||
/// @param amount The amount of the token to deposit.
|
||||
/// @param data Optional data to forward to recipient's account.
|
||||
function finalizeDepositERC20(
|
||||
address l1Token,
|
||||
address l2Token,
|
||||
address from,
|
||||
address to,
|
||||
uint256 amount,
|
||||
bytes calldata data
|
||||
) external payable;
|
||||
}
|
||||
|
||||
@@ -4,145 +4,145 @@ pragma solidity ^0.8.0;
|
||||
|
||||
/// @title The interface for the ERC721 cross chain gateway in layer 2.
|
||||
interface IL2ERC721Gateway {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
|
||||
/// @notice Emitted when the ERC721 NFT is transfered to recipient in layer 2.
|
||||
/// @param l1Token The address of ERC721 NFT in layer 1.
|
||||
/// @param l2Token The address of ERC721 NFT in layer 2.
|
||||
/// @param from The address of sender in layer 1.
|
||||
/// @param to The address of recipient in layer 2.
|
||||
/// @param tokenId The token id of the ERC721 NFT deposited in layer 1.
|
||||
event FinalizeDepositERC721(
|
||||
address indexed l1Token,
|
||||
address indexed l2Token,
|
||||
address indexed from,
|
||||
address to,
|
||||
uint256 tokenId
|
||||
);
|
||||
/// @notice Emitted when the ERC721 NFT is transfered to recipient in layer 2.
|
||||
/// @param l1Token The address of ERC721 NFT in layer 1.
|
||||
/// @param l2Token The address of ERC721 NFT in layer 2.
|
||||
/// @param from The address of sender in layer 1.
|
||||
/// @param to The address of recipient in layer 2.
|
||||
/// @param tokenId The token id of the ERC721 NFT deposited in layer 1.
|
||||
event FinalizeDepositERC721(
|
||||
address indexed l1Token,
|
||||
address indexed l2Token,
|
||||
address indexed from,
|
||||
address to,
|
||||
uint256 tokenId
|
||||
);
|
||||
|
||||
/// @notice Emitted when the ERC721 NFT is batch transfered to recipient in layer 2.
|
||||
/// @param l1Token The address of ERC721 NFT in layer 1.
|
||||
/// @param l2Token The address of ERC721 NFT in layer 2.
|
||||
/// @param from The address of sender in layer 1.
|
||||
/// @param to The address of recipient in layer 2.
|
||||
/// @param tokenIds The list of token ids of the ERC721 NFT deposited in layer 1.
|
||||
event FinalizeBatchDepositERC721(
|
||||
address indexed l1Token,
|
||||
address indexed l2Token,
|
||||
address indexed from,
|
||||
address to,
|
||||
uint256[] tokenIds
|
||||
);
|
||||
/// @notice Emitted when the ERC721 NFT is batch transfered to recipient in layer 2.
|
||||
/// @param l1Token The address of ERC721 NFT in layer 1.
|
||||
/// @param l2Token The address of ERC721 NFT in layer 2.
|
||||
/// @param from The address of sender in layer 1.
|
||||
/// @param to The address of recipient in layer 2.
|
||||
/// @param tokenIds The list of token ids of the ERC721 NFT deposited in layer 1.
|
||||
event FinalizeBatchDepositERC721(
|
||||
address indexed l1Token,
|
||||
address indexed l2Token,
|
||||
address indexed from,
|
||||
address to,
|
||||
uint256[] tokenIds
|
||||
);
|
||||
|
||||
/// @notice Emitted when the ERC721 NFT is transfered to gateway in layer 2.
|
||||
/// @param l1Token The address of ERC721 NFT in layer 1.
|
||||
/// @param l2Token The address of ERC721 NFT in layer 2.
|
||||
/// @param from The address of sender in layer 2.
|
||||
/// @param to The address of recipient in layer 1.
|
||||
/// @param tokenId The token id of the ERC721 NFT to withdraw in layer 2.
|
||||
event WithdrawERC721(
|
||||
address indexed l1Token,
|
||||
address indexed l2Token,
|
||||
address indexed from,
|
||||
address to,
|
||||
uint256 tokenId
|
||||
);
|
||||
/// @notice Emitted when the ERC721 NFT is transfered to gateway in layer 2.
|
||||
/// @param l1Token The address of ERC721 NFT in layer 1.
|
||||
/// @param l2Token The address of ERC721 NFT in layer 2.
|
||||
/// @param from The address of sender in layer 2.
|
||||
/// @param to The address of recipient in layer 1.
|
||||
/// @param tokenId The token id of the ERC721 NFT to withdraw in layer 2.
|
||||
event WithdrawERC721(
|
||||
address indexed l1Token,
|
||||
address indexed l2Token,
|
||||
address indexed from,
|
||||
address to,
|
||||
uint256 tokenId
|
||||
);
|
||||
|
||||
/// @notice Emitted when the ERC721 NFT is batch transfered to gateway in layer 2.
|
||||
/// @param l1Token The address of ERC721 NFT in layer 1.
|
||||
/// @param l2Token The address of ERC721 NFT in layer 2.
|
||||
/// @param from The address of sender in layer 2.
|
||||
/// @param to The address of recipient in layer 1.
|
||||
/// @param tokenIds The list of token ids of the ERC721 NFT to withdraw in layer 2.
|
||||
event BatchWithdrawERC721(
|
||||
address indexed l1Token,
|
||||
address indexed l2Token,
|
||||
address indexed from,
|
||||
address to,
|
||||
uint256[] tokenIds
|
||||
);
|
||||
/// @notice Emitted when the ERC721 NFT is batch transfered to gateway in layer 2.
|
||||
/// @param l1Token The address of ERC721 NFT in layer 1.
|
||||
/// @param l2Token The address of ERC721 NFT in layer 2.
|
||||
/// @param from The address of sender in layer 2.
|
||||
/// @param to The address of recipient in layer 1.
|
||||
/// @param tokenIds The list of token ids of the ERC721 NFT to withdraw in layer 2.
|
||||
event BatchWithdrawERC721(
|
||||
address indexed l1Token,
|
||||
address indexed l2Token,
|
||||
address indexed from,
|
||||
address to,
|
||||
uint256[] tokenIds
|
||||
);
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
/// @notice Withdraw some ERC721 NFT to caller's account on layer 1.
|
||||
/// @param token The address of ERC721 NFT in layer 2.
|
||||
/// @param tokenId The token id to withdraw.
|
||||
/// @param gasLimit Unused, but included for potential forward compatibility considerations.
|
||||
function withdrawERC721(
|
||||
address token,
|
||||
uint256 tokenId,
|
||||
uint256 gasLimit
|
||||
) external;
|
||||
/// @notice Withdraw some ERC721 NFT to caller's account on layer 1.
|
||||
/// @param token The address of ERC721 NFT in layer 2.
|
||||
/// @param tokenId The token id to withdraw.
|
||||
/// @param gasLimit Unused, but included for potential forward compatibility considerations.
|
||||
function withdrawERC721(
|
||||
address token,
|
||||
uint256 tokenId,
|
||||
uint256 gasLimit
|
||||
) external payable;
|
||||
|
||||
/// @notice Withdraw some ERC721 NFT to caller's account on layer 1.
|
||||
/// @param token The address of ERC721 NFT in layer 2.
|
||||
/// @param to The address of recipient in layer 1.
|
||||
/// @param tokenId The token id to withdraw.
|
||||
/// @param gasLimit Unused, but included for potential forward compatibility considerations.
|
||||
function withdrawERC721(
|
||||
address token,
|
||||
address to,
|
||||
uint256 tokenId,
|
||||
uint256 gasLimit
|
||||
) external;
|
||||
/// @notice Withdraw some ERC721 NFT to caller's account on layer 1.
|
||||
/// @param token The address of ERC721 NFT in layer 2.
|
||||
/// @param to The address of recipient in layer 1.
|
||||
/// @param tokenId The token id to withdraw.
|
||||
/// @param gasLimit Unused, but included for potential forward compatibility considerations.
|
||||
function withdrawERC721(
|
||||
address token,
|
||||
address to,
|
||||
uint256 tokenId,
|
||||
uint256 gasLimit
|
||||
) external payable;
|
||||
|
||||
/// @notice Batch withdraw a list of ERC721 NFT to caller's account on layer 1.
|
||||
/// @param token The address of ERC721 NFT in layer 2.
|
||||
/// @param tokenIds The list of token ids to withdraw.
|
||||
/// @param gasLimit Unused, but included for potential forward compatibility considerations.
|
||||
function batchWithdrawERC721(
|
||||
address token,
|
||||
uint256[] memory tokenIds,
|
||||
uint256 gasLimit
|
||||
) external;
|
||||
/// @notice Batch withdraw a list of ERC721 NFT to caller's account on layer 1.
|
||||
/// @param token The address of ERC721 NFT in layer 2.
|
||||
/// @param tokenIds The list of token ids to withdraw.
|
||||
/// @param gasLimit Unused, but included for potential forward compatibility considerations.
|
||||
function batchWithdrawERC721(
|
||||
address token,
|
||||
uint256[] memory tokenIds,
|
||||
uint256 gasLimit
|
||||
) external payable;
|
||||
|
||||
/// @notice Batch withdraw a list of ERC721 NFT to caller's account on layer 1.
|
||||
/// @param token The address of ERC721 NFT in layer 2.
|
||||
/// @param to The address of recipient in layer 1.
|
||||
/// @param tokenIds The list of token ids to withdraw.
|
||||
/// @param gasLimit Unused, but included for potential forward compatibility considerations.
|
||||
function batchWithdrawERC721(
|
||||
address token,
|
||||
address to,
|
||||
uint256[] memory tokenIds,
|
||||
uint256 gasLimit
|
||||
) external;
|
||||
/// @notice Batch withdraw a list of ERC721 NFT to caller's account on layer 1.
|
||||
/// @param token The address of ERC721 NFT in layer 2.
|
||||
/// @param to The address of recipient in layer 1.
|
||||
/// @param tokenIds The list of token ids to withdraw.
|
||||
/// @param gasLimit Unused, but included for potential forward compatibility considerations.
|
||||
function batchWithdrawERC721(
|
||||
address token,
|
||||
address to,
|
||||
uint256[] memory tokenIds,
|
||||
uint256 gasLimit
|
||||
) external payable;
|
||||
|
||||
/// @notice Complete ERC721 deposit from layer 1 to layer 2 and send NFT to recipient's account in layer 2.
|
||||
/// @dev Requirements:
|
||||
/// - The function should only be called by L2ScrollMessenger.
|
||||
/// - The function should also only be called by L1ERC721Gateway in layer 1.
|
||||
/// @param l1Token The address of corresponding layer 1 token.
|
||||
/// @param l2Token The address of corresponding layer 2 token.
|
||||
/// @param from The address of account who withdraw the token in layer 1.
|
||||
/// @param to The address of recipient in layer 2 to receive the token.
|
||||
/// @param tokenId The token id to withdraw.
|
||||
function finalizeDepositERC721(
|
||||
address l1Token,
|
||||
address l2Token,
|
||||
address from,
|
||||
address to,
|
||||
uint256 tokenId
|
||||
) external;
|
||||
/// @notice Complete ERC721 deposit from layer 1 to layer 2 and send NFT to recipient's account in layer 2.
|
||||
/// @dev Requirements:
|
||||
/// - The function should only be called by L2ScrollMessenger.
|
||||
/// - The function should also only be called by L1ERC721Gateway in layer 1.
|
||||
/// @param l1Token The address of corresponding layer 1 token.
|
||||
/// @param l2Token The address of corresponding layer 2 token.
|
||||
/// @param from The address of account who withdraw the token in layer 1.
|
||||
/// @param to The address of recipient in layer 2 to receive the token.
|
||||
/// @param tokenId The token id to withdraw.
|
||||
function finalizeDepositERC721(
|
||||
address l1Token,
|
||||
address l2Token,
|
||||
address from,
|
||||
address to,
|
||||
uint256 tokenId
|
||||
) external;
|
||||
|
||||
/// @notice Complete ERC721 deposit from layer 1 to layer 2 and send NFT to recipient's account in layer 2.
|
||||
/// @dev Requirements:
|
||||
/// - The function should only be called by L2ScrollMessenger.
|
||||
/// - The function should also only be called by L1ERC721Gateway in layer 1.
|
||||
/// @param l1Token The address of corresponding layer 1 token.
|
||||
/// @param l2Token The address of corresponding layer 2 token.
|
||||
/// @param from The address of account who withdraw the token in layer 1.
|
||||
/// @param to The address of recipient in layer 2 to receive the token.
|
||||
/// @param tokenIds The list of token ids to withdraw.
|
||||
function finalizeBatchDepositERC721(
|
||||
address l1Token,
|
||||
address l2Token,
|
||||
address from,
|
||||
address to,
|
||||
uint256[] calldata tokenIds
|
||||
) external;
|
||||
/// @notice Complete ERC721 deposit from layer 1 to layer 2 and send NFT to recipient's account in layer 2.
|
||||
/// @dev Requirements:
|
||||
/// - The function should only be called by L2ScrollMessenger.
|
||||
/// - The function should also only be called by L1ERC721Gateway in layer 1.
|
||||
/// @param l1Token The address of corresponding layer 1 token.
|
||||
/// @param l2Token The address of corresponding layer 2 token.
|
||||
/// @param from The address of account who withdraw the token in layer 1.
|
||||
/// @param to The address of recipient in layer 2 to receive the token.
|
||||
/// @param tokenIds The list of token ids to withdraw.
|
||||
function finalizeBatchDepositERC721(
|
||||
address l1Token,
|
||||
address l2Token,
|
||||
address from,
|
||||
address to,
|
||||
uint256[] calldata tokenIds
|
||||
) external;
|
||||
}
|
||||
|
||||
@@ -3,66 +3,66 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
interface IL2ETHGateway {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* 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 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);
|
||||
/// @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 *
|
||||
****************************/
|
||||
/*****************************
|
||||
* Public Mutating 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 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 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 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;
|
||||
/// @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;
|
||||
}
|
||||
|
||||
@@ -2,24 +2,24 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { IL2ETHGateway } from "./IL2ETHGateway.sol";
|
||||
import { IL2ERC20Gateway } from "./IL2ERC20Gateway.sol";
|
||||
import {IL2ETHGateway} from "./IL2ETHGateway.sol";
|
||||
import {IL2ERC20Gateway} from "./IL2ERC20Gateway.sol";
|
||||
|
||||
interface IL2GatewayRouter is IL2ETHGateway, IL2ERC20Gateway {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
|
||||
/// @notice Emitted when the address of ETH Gateway is updated.
|
||||
/// @param ethGateway The address of new ETH Gateway.
|
||||
event SetETHGateway(address indexed ethGateway);
|
||||
/// @notice Emitted when the address of ETH Gateway is updated.
|
||||
/// @param ethGateway The address of new ETH Gateway.
|
||||
event SetETHGateway(address indexed ethGateway);
|
||||
|
||||
/// @notice Emitted when the address of default ERC20 Gateway is updated.
|
||||
/// @param defaultERC20Gateway The address of new default ERC20 Gateway.
|
||||
event SetDefaultERC20Gateway(address indexed defaultERC20Gateway);
|
||||
/// @notice Emitted when the address of default ERC20 Gateway is updated.
|
||||
/// @param defaultERC20Gateway The address of new default ERC20 Gateway.
|
||||
event SetDefaultERC20Gateway(address indexed defaultERC20Gateway);
|
||||
|
||||
/// @notice Emitted when the `gateway` for `token` is updated.
|
||||
/// @param token The address of token updated.
|
||||
/// @param gateway The corresponding address of gateway updated.
|
||||
event SetERC20Gateway(address indexed token, address indexed gateway);
|
||||
/// @notice Emitted when the `gateway` for `token` is updated.
|
||||
/// @param token The address of token updated.
|
||||
/// @param gateway The corresponding address of gateway updated.
|
||||
event SetERC20Gateway(address indexed token, address indexed gateway);
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
|
||||
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
|
||||
|
||||
import { IL2ERC20Gateway, L2ERC20Gateway } from "./L2ERC20Gateway.sol";
|
||||
import { IL2ScrollMessenger } from "../IL2ScrollMessenger.sol";
|
||||
import { IL1ERC20Gateway } from "../../L1/gateways/IL1ERC20Gateway.sol";
|
||||
import { ScrollGatewayBase, IScrollGateway } from "../../libraries/gateway/ScrollGatewayBase.sol";
|
||||
import { IScrollStandardERC20 } from "../../libraries/token/IScrollStandardERC20.sol";
|
||||
import {IL2ERC20Gateway, L2ERC20Gateway} from "./L2ERC20Gateway.sol";
|
||||
import {IL2ScrollMessenger} from "../IL2ScrollMessenger.sol";
|
||||
import {IL1ERC20Gateway} from "../../L1/gateways/IL1ERC20Gateway.sol";
|
||||
import {ScrollGatewayBase, IScrollGateway} from "../../libraries/gateway/ScrollGatewayBase.sol";
|
||||
import {IScrollStandardERC20} from "../../libraries/token/IScrollStandardERC20.sol";
|
||||
|
||||
/// @title L2ERC20Gateway
|
||||
/// @notice The `L2ERC20Gateway` is used to withdraw custom ERC20 compatible tokens in layer 2 and
|
||||
@@ -17,130 +17,130 @@ import { IScrollStandardERC20 } from "../../libraries/token/IScrollStandardERC20
|
||||
/// @dev The withdrawn tokens tokens will be burned directly. On finalizing deposit, the corresponding
|
||||
/// tokens will be minted and transfered to the recipient.
|
||||
contract L2CustomERC20Gateway is OwnableUpgradeable, ScrollGatewayBase, L2ERC20Gateway {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
|
||||
/// @notice Emitted when token mapping for ERC20 token is updated.
|
||||
/// @param _l2Token The address of corresponding ERC20 token in layer 2.
|
||||
/// @param _l1Token The address of ERC20 token in layer 1.
|
||||
event UpdateTokenMapping(address _l2Token, address _l1Token);
|
||||
/// @notice Emitted when token mapping for ERC20 token is updated.
|
||||
/// @param _l2Token The address of corresponding ERC20 token in layer 2.
|
||||
/// @param _l1Token The address of ERC20 token in layer 1.
|
||||
event UpdateTokenMapping(address _l2Token, address _l1Token);
|
||||
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
/// @notice Mapping from layer 2 token address to layer 1 token address for ERC20 token.
|
||||
// solhint-disable-next-line var-name-mixedcase
|
||||
mapping(address => address) public tokenMapping;
|
||||
/// @notice Mapping from layer 2 token address to layer 1 token address for ERC20 token.
|
||||
// solhint-disable-next-line var-name-mixedcase
|
||||
mapping(address => address) public tokenMapping;
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
function initialize(
|
||||
address _counterpart,
|
||||
address _router,
|
||||
address _messenger
|
||||
) external initializer {
|
||||
require(_router != address(0), "zero router address");
|
||||
OwnableUpgradeable.__Ownable_init();
|
||||
function initialize(
|
||||
address _counterpart,
|
||||
address _router,
|
||||
address _messenger
|
||||
) external initializer {
|
||||
require(_router != address(0), "zero router address");
|
||||
OwnableUpgradeable.__Ownable_init();
|
||||
|
||||
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
|
||||
}
|
||||
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function getL1ERC20Address(address _l2Token) external view override returns (address) {
|
||||
return tokenMapping[_l2Token];
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function getL2ERC20Address(address) public pure override returns (address) {
|
||||
revert("unimplemented");
|
||||
}
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function finalizeDepositERC20(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes calldata _data
|
||||
) external payable override onlyCallByCounterpart {
|
||||
require(msg.value == 0, "nonzero msg.value");
|
||||
require(_l1Token == tokenMapping[_l2Token], "l1 token mismatch");
|
||||
|
||||
// @todo forward `_callData` to `_to` using transferAndCall in the near future
|
||||
|
||||
IScrollStandardERC20(_l2Token).mint(_to, _amount);
|
||||
|
||||
emit FinalizeDepositERC20(_l1Token, _l2Token, _from, _to, _amount, _data);
|
||||
}
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
/// @notice Update layer 2 to layer 1 token mapping.
|
||||
/// @param _l2Token The address of corresponding ERC20 token in layer 2.
|
||||
/// @param _l1Token The address of ERC20 token in layer 1.
|
||||
function updateTokenMapping(address _l2Token, address _l1Token) external onlyOwner {
|
||||
require(_l1Token != address(0), "map to zero address");
|
||||
|
||||
tokenMapping[_l2Token] = _l1Token;
|
||||
|
||||
emit UpdateTokenMapping(_l2Token, _l1Token);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
/// @inheritdoc L2ERC20Gateway
|
||||
function _withdraw(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) internal virtual override {
|
||||
address _l1Token = tokenMapping[_token];
|
||||
require(_l1Token != address(0), "no corresponding l1 token");
|
||||
|
||||
require(_amount > 0, "withdraw zero amount");
|
||||
|
||||
// 1. Extract real sender if this call is from L2GatewayRouter.
|
||||
address _from = msg.sender;
|
||||
if (router == msg.sender) {
|
||||
(_from, _data) = abi.decode(_data, (address, bytes));
|
||||
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
|
||||
}
|
||||
|
||||
// 2. Burn token.
|
||||
IScrollStandardERC20(_token).burn(_from, _amount);
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
// 3. Generate message passed to L1StandardERC20Gateway.
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL1ERC20Gateway.finalizeWithdrawERC20.selector,
|
||||
_l1Token,
|
||||
_token,
|
||||
_from,
|
||||
_to,
|
||||
_amount,
|
||||
_data
|
||||
);
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function getL1ERC20Address(address _l2Token) external view override returns (address) {
|
||||
return tokenMapping[_l2Token];
|
||||
}
|
||||
|
||||
// 4. send message to L2ScrollMessenger
|
||||
IL2ScrollMessenger(messenger).sendMessage{ value: msg.value }(counterpart, 0, _message, _gasLimit);
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function getL2ERC20Address(address) public pure override returns (address) {
|
||||
revert("unimplemented");
|
||||
}
|
||||
|
||||
emit WithdrawERC20(_l1Token, _token, _from, _to, _amount, _data);
|
||||
}
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function finalizeDepositERC20(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes calldata _data
|
||||
) external payable override onlyCallByCounterpart {
|
||||
require(msg.value == 0, "nonzero msg.value");
|
||||
require(_l1Token == tokenMapping[_l2Token], "l1 token mismatch");
|
||||
|
||||
// @todo forward `_callData` to `_to` using transferAndCall in the near future
|
||||
|
||||
IScrollStandardERC20(_l2Token).mint(_to, _amount);
|
||||
|
||||
emit FinalizeDepositERC20(_l1Token, _l2Token, _from, _to, _amount, _data);
|
||||
}
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
/// @notice Update layer 2 to layer 1 token mapping.
|
||||
/// @param _l2Token The address of corresponding ERC20 token in layer 2.
|
||||
/// @param _l1Token The address of ERC20 token in layer 1.
|
||||
function updateTokenMapping(address _l2Token, address _l1Token) external onlyOwner {
|
||||
require(_l1Token != address(0), "map to zero address");
|
||||
|
||||
tokenMapping[_l2Token] = _l1Token;
|
||||
|
||||
emit UpdateTokenMapping(_l2Token, _l1Token);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
/// @inheritdoc L2ERC20Gateway
|
||||
function _withdraw(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) internal virtual override {
|
||||
address _l1Token = tokenMapping[_token];
|
||||
require(_l1Token != address(0), "no corresponding l1 token");
|
||||
|
||||
require(_amount > 0, "withdraw zero amount");
|
||||
|
||||
// 1. Extract real sender if this call is from L2GatewayRouter.
|
||||
address _from = msg.sender;
|
||||
if (router == msg.sender) {
|
||||
(_from, _data) = abi.decode(_data, (address, bytes));
|
||||
}
|
||||
|
||||
// 2. Burn token.
|
||||
IScrollStandardERC20(_token).burn(_from, _amount);
|
||||
|
||||
// 3. Generate message passed to L1StandardERC20Gateway.
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL1ERC20Gateway.finalizeWithdrawERC20.selector,
|
||||
_l1Token,
|
||||
_token,
|
||||
_from,
|
||||
_to,
|
||||
_amount,
|
||||
_data
|
||||
);
|
||||
|
||||
// 4. send message to L2ScrollMessenger
|
||||
IL2ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, _gasLimit);
|
||||
|
||||
emit WithdrawERC20(_l1Token, _token, _from, _to, _amount, _data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
import { IERC1155Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC1155/IERC1155Upgradeable.sol";
|
||||
import { ERC1155HolderUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC1155/utils/ERC1155HolderUpgradeable.sol";
|
||||
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
import {IERC1155Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC1155/IERC1155Upgradeable.sol";
|
||||
import {ERC1155HolderUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC1155/utils/ERC1155HolderUpgradeable.sol";
|
||||
|
||||
import { IL2ERC1155Gateway } from "./IL2ERC1155Gateway.sol";
|
||||
import { IL2ScrollMessenger } from "../IL2ScrollMessenger.sol";
|
||||
import { IL1ERC1155Gateway } from "../../L1/gateways/IL1ERC1155Gateway.sol";
|
||||
import { ScrollGatewayBase, IScrollGateway } from "../../libraries/gateway/ScrollGatewayBase.sol";
|
||||
import { IScrollERC1155 } from "../../libraries/token/IScrollERC1155.sol";
|
||||
import {IL2ERC1155Gateway} from "./IL2ERC1155Gateway.sol";
|
||||
import {IL2ScrollMessenger} from "../IL2ScrollMessenger.sol";
|
||||
import {IL1ERC1155Gateway} from "../../L1/gateways/IL1ERC1155Gateway.sol";
|
||||
import {ScrollGatewayBase, IScrollGateway} from "../../libraries/gateway/ScrollGatewayBase.sol";
|
||||
import {IScrollERC1155} from "../../libraries/token/IScrollERC1155.sol";
|
||||
|
||||
/// @title L2ERC1155Gateway
|
||||
/// @notice The `L2ERC1155Gateway` is used to withdraw ERC1155 compatible NFTs in layer 2 and
|
||||
@@ -21,203 +21,203 @@ import { IScrollERC1155 } from "../../libraries/token/IScrollERC1155.sol";
|
||||
/// This will be changed if we have more specific scenarios.
|
||||
// @todo Current implementation doesn't support calling from `L2GatewayRouter`.
|
||||
contract L2ERC1155Gateway is OwnableUpgradeable, ERC1155HolderUpgradeable, ScrollGatewayBase, IL2ERC1155Gateway {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
|
||||
/// @notice Emitted when token mapping for ERC1155 token is updated.
|
||||
/// @param _l1Token The address of corresponding ERC1155 token in layer 2.
|
||||
/// @param _l1Token The address of ERC1155 token in layer 1.
|
||||
event UpdateTokenMapping(address _l2Token, address _l1Token);
|
||||
/// @notice Emitted when token mapping for ERC1155 token is updated.
|
||||
/// @param _l1Token The address of corresponding ERC1155 token in layer 2.
|
||||
/// @param _l1Token The address of ERC1155 token in layer 1.
|
||||
event UpdateTokenMapping(address _l2Token, address _l1Token);
|
||||
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
/// @notice Mapping from layer 2 token address to layer 1 token address for ERC1155 NFT.
|
||||
// solhint-disable-next-line var-name-mixedcase
|
||||
mapping(address => address) public tokenMapping;
|
||||
/// @notice Mapping from layer 2 token address to layer 1 token address for ERC1155 NFT.
|
||||
// solhint-disable-next-line var-name-mixedcase
|
||||
mapping(address => address) public tokenMapping;
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
function initialize(address _counterpart, address _messenger) external initializer {
|
||||
OwnableUpgradeable.__Ownable_init();
|
||||
ScrollGatewayBase._initialize(_counterpart, address(0), _messenger);
|
||||
}
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
|
||||
/// @inheritdoc IL2ERC1155Gateway
|
||||
function withdrawERC1155(
|
||||
address _token,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external override {
|
||||
_withdrawERC1155(_token, msg.sender, _tokenId, _amount, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ERC1155Gateway
|
||||
function withdrawERC1155(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external override {
|
||||
_withdrawERC1155(_token, _to, _tokenId, _amount, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ERC1155Gateway
|
||||
function batchWithdrawERC1155(
|
||||
address _token,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256[] calldata _amounts,
|
||||
uint256 _gasLimit
|
||||
) external override {
|
||||
_batchWithdrawERC1155(_token, msg.sender, _tokenIds, _amounts, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ERC1155Gateway
|
||||
function batchWithdrawERC1155(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256[] calldata _amounts,
|
||||
uint256 _gasLimit
|
||||
) external override {
|
||||
_batchWithdrawERC1155(_token, _to, _tokenIds, _amounts, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ERC1155Gateway
|
||||
function finalizeDepositERC1155(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount
|
||||
) external override nonReentrant onlyCallByCounterpart {
|
||||
IScrollERC1155(_l2Token).mint(_to, _tokenId, _amount, "");
|
||||
|
||||
emit FinalizeDepositERC1155(_l1Token, _l2Token, _from, _to, _tokenId, _amount);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ERC1155Gateway
|
||||
function finalizeBatchDepositERC1155(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256[] calldata _amounts
|
||||
) external override nonReentrant onlyCallByCounterpart {
|
||||
IScrollERC1155(_l2Token).batchMint(_to, _tokenIds, _amounts, "");
|
||||
|
||||
emit FinalizeBatchDepositERC1155(_l1Token, _l2Token, _from, _to, _tokenIds, _amounts);
|
||||
}
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
/// @notice Update layer 2 to layer 1 token mapping.
|
||||
/// @param _l1Token The address of corresponding ERC1155 token in layer 2.
|
||||
/// @param _l1Token The address of ERC1155 token in layer 1.
|
||||
function updateTokenMapping(address _l2Token, address _l1Token) external onlyOwner {
|
||||
require(_l1Token != address(0), "map to zero address");
|
||||
|
||||
tokenMapping[_l2Token] = _l1Token;
|
||||
|
||||
emit UpdateTokenMapping(_l2Token, _l1Token);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
/// @dev Internal function to withdraw ERC1155 NFT to layer 2.
|
||||
/// @param _token The address of ERC1155 NFT in layer 1.
|
||||
/// @param _to The address of recipient in layer 2.
|
||||
/// @param _tokenId The token id to withdraw.
|
||||
/// @param _amount The amount of token to withdraw.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the withdraw on layer 2.
|
||||
function _withdrawERC1155(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) internal nonReentrant {
|
||||
require(_amount > 0, "withdraw zero amount");
|
||||
|
||||
address _l1Token = tokenMapping[_token];
|
||||
require(_l1Token != address(0), "token not supported");
|
||||
|
||||
// 1. burn token
|
||||
IScrollERC1155(_token).burn(msg.sender, _tokenId, _amount);
|
||||
|
||||
// 2. Generate message passed to L1ERC1155Gateway.
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL1ERC1155Gateway.finalizeWithdrawERC1155.selector,
|
||||
_l1Token,
|
||||
_token,
|
||||
msg.sender,
|
||||
_to,
|
||||
_tokenId,
|
||||
_amount
|
||||
);
|
||||
|
||||
// 3. Send message to L2ScrollMessenger.
|
||||
IL2ScrollMessenger(messenger).sendMessage(counterpart, msg.value, _message, _gasLimit);
|
||||
|
||||
emit WithdrawERC1155(_l1Token, _token, msg.sender, _to, _tokenId, _amount);
|
||||
}
|
||||
|
||||
/// @dev Internal function to batch withdraw ERC1155 NFT to layer 2.
|
||||
/// @param _token The address of ERC1155 NFT in layer 1.
|
||||
/// @param _to The address of recipient in layer 2.
|
||||
/// @param _tokenIds The list of token ids to withdraw.
|
||||
/// @param _amounts The list of corresponding number of token to withdraw.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the withdraw on layer 1.
|
||||
function _batchWithdrawERC1155(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256[] calldata _amounts,
|
||||
uint256 _gasLimit
|
||||
) internal nonReentrant {
|
||||
require(_tokenIds.length > 0, "no token to withdraw");
|
||||
require(_tokenIds.length == _amounts.length, "length mismatch");
|
||||
|
||||
for (uint256 i = 0; i < _amounts.length; i++) {
|
||||
require(_amounts[i] > 0, "withdraw zero amount");
|
||||
function initialize(address _counterpart, address _messenger) external initializer {
|
||||
OwnableUpgradeable.__Ownable_init();
|
||||
ScrollGatewayBase._initialize(_counterpart, address(0), _messenger);
|
||||
}
|
||||
|
||||
address _l1Token = tokenMapping[_token];
|
||||
require(_l1Token != address(0), "token not supported");
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
// 1. transfer token to this contract
|
||||
IScrollERC1155(_token).batchBurn(msg.sender, _tokenIds, _amounts);
|
||||
/// @inheritdoc IL2ERC1155Gateway
|
||||
function withdrawERC1155(
|
||||
address _token,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_withdrawERC1155(_token, msg.sender, _tokenId, _amount, _gasLimit);
|
||||
}
|
||||
|
||||
// 2. Generate message passed to L1ERC1155Gateway.
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL1ERC1155Gateway.finalizeBatchWithdrawERC1155.selector,
|
||||
_l1Token,
|
||||
_token,
|
||||
msg.sender,
|
||||
_to,
|
||||
_tokenIds,
|
||||
_amounts
|
||||
);
|
||||
/// @inheritdoc IL2ERC1155Gateway
|
||||
function withdrawERC1155(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_withdrawERC1155(_token, _to, _tokenId, _amount, _gasLimit);
|
||||
}
|
||||
|
||||
// 3. Send message to L2ScrollMessenger.
|
||||
IL2ScrollMessenger(messenger).sendMessage{ value: msg.value }(counterpart, msg.value, _message, _gasLimit);
|
||||
/// @inheritdoc IL2ERC1155Gateway
|
||||
function batchWithdrawERC1155(
|
||||
address _token,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256[] calldata _amounts,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_batchWithdrawERC1155(_token, msg.sender, _tokenIds, _amounts, _gasLimit);
|
||||
}
|
||||
|
||||
emit BatchWithdrawERC1155(_l1Token, _token, msg.sender, _to, _tokenIds, _amounts);
|
||||
}
|
||||
/// @inheritdoc IL2ERC1155Gateway
|
||||
function batchWithdrawERC1155(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256[] calldata _amounts,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_batchWithdrawERC1155(_token, _to, _tokenIds, _amounts, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ERC1155Gateway
|
||||
function finalizeDepositERC1155(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount
|
||||
) external override nonReentrant onlyCallByCounterpart {
|
||||
IScrollERC1155(_l2Token).mint(_to, _tokenId, _amount, "");
|
||||
|
||||
emit FinalizeDepositERC1155(_l1Token, _l2Token, _from, _to, _tokenId, _amount);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ERC1155Gateway
|
||||
function finalizeBatchDepositERC1155(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256[] calldata _amounts
|
||||
) external override nonReentrant onlyCallByCounterpart {
|
||||
IScrollERC1155(_l2Token).batchMint(_to, _tokenIds, _amounts, "");
|
||||
|
||||
emit FinalizeBatchDepositERC1155(_l1Token, _l2Token, _from, _to, _tokenIds, _amounts);
|
||||
}
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
/// @notice Update layer 2 to layer 1 token mapping.
|
||||
/// @param _l1Token The address of corresponding ERC1155 token in layer 2.
|
||||
/// @param _l1Token The address of ERC1155 token in layer 1.
|
||||
function updateTokenMapping(address _l2Token, address _l1Token) external onlyOwner {
|
||||
require(_l1Token != address(0), "map to zero address");
|
||||
|
||||
tokenMapping[_l2Token] = _l1Token;
|
||||
|
||||
emit UpdateTokenMapping(_l2Token, _l1Token);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
/// @dev Internal function to withdraw ERC1155 NFT to layer 2.
|
||||
/// @param _token The address of ERC1155 NFT in layer 1.
|
||||
/// @param _to The address of recipient in layer 2.
|
||||
/// @param _tokenId The token id to withdraw.
|
||||
/// @param _amount The amount of token to withdraw.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the withdraw on layer 2.
|
||||
function _withdrawERC1155(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) internal nonReentrant {
|
||||
require(_amount > 0, "withdraw zero amount");
|
||||
|
||||
address _l1Token = tokenMapping[_token];
|
||||
require(_l1Token != address(0), "token not supported");
|
||||
|
||||
// 1. burn token
|
||||
IScrollERC1155(_token).burn(msg.sender, _tokenId, _amount);
|
||||
|
||||
// 2. Generate message passed to L1ERC1155Gateway.
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL1ERC1155Gateway.finalizeWithdrawERC1155.selector,
|
||||
_l1Token,
|
||||
_token,
|
||||
msg.sender,
|
||||
_to,
|
||||
_tokenId,
|
||||
_amount
|
||||
);
|
||||
|
||||
// 3. Send message to L2ScrollMessenger.
|
||||
IL2ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, _gasLimit);
|
||||
|
||||
emit WithdrawERC1155(_l1Token, _token, msg.sender, _to, _tokenId, _amount);
|
||||
}
|
||||
|
||||
/// @dev Internal function to batch withdraw ERC1155 NFT to layer 2.
|
||||
/// @param _token The address of ERC1155 NFT in layer 1.
|
||||
/// @param _to The address of recipient in layer 2.
|
||||
/// @param _tokenIds The list of token ids to withdraw.
|
||||
/// @param _amounts The list of corresponding number of token to withdraw.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the withdraw on layer 1.
|
||||
function _batchWithdrawERC1155(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256[] calldata _amounts,
|
||||
uint256 _gasLimit
|
||||
) internal nonReentrant {
|
||||
require(_tokenIds.length > 0, "no token to withdraw");
|
||||
require(_tokenIds.length == _amounts.length, "length mismatch");
|
||||
|
||||
for (uint256 i = 0; i < _amounts.length; i++) {
|
||||
require(_amounts[i] > 0, "withdraw zero amount");
|
||||
}
|
||||
|
||||
address _l1Token = tokenMapping[_token];
|
||||
require(_l1Token != address(0), "token not supported");
|
||||
|
||||
// 1. transfer token to this contract
|
||||
IScrollERC1155(_token).batchBurn(msg.sender, _tokenIds, _amounts);
|
||||
|
||||
// 2. Generate message passed to L1ERC1155Gateway.
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL1ERC1155Gateway.finalizeBatchWithdrawERC1155.selector,
|
||||
_l1Token,
|
||||
_token,
|
||||
msg.sender,
|
||||
_to,
|
||||
_tokenIds,
|
||||
_amounts
|
||||
);
|
||||
|
||||
// 3. Send message to L2ScrollMessenger.
|
||||
IL2ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, _gasLimit);
|
||||
|
||||
emit BatchWithdrawERC1155(_l1Token, _token, msg.sender, _to, _tokenIds, _amounts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,54 +2,54 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { IL2ERC20Gateway } from "./IL2ERC20Gateway.sol";
|
||||
import {IL2ERC20Gateway} from "./IL2ERC20Gateway.sol";
|
||||
|
||||
// solhint-disable no-empty-blocks
|
||||
|
||||
abstract contract L2ERC20Gateway is IL2ERC20Gateway {
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function withdrawERC20(
|
||||
address _token,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_withdraw(_token, msg.sender, _amount, new bytes(0), _gasLimit);
|
||||
}
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function withdrawERC20(
|
||||
address _token,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_withdraw(_token, msg.sender, _amount, new bytes(0), _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function withdrawERC20(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_withdraw(_token, _to, _amount, new bytes(0), _gasLimit);
|
||||
}
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function withdrawERC20(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_withdraw(_token, _to, _amount, new bytes(0), _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function withdrawERC20AndCall(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes calldata _data,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_withdraw(_token, _to, _amount, _data, _gasLimit);
|
||||
}
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function withdrawERC20AndCall(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes calldata _data,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_withdraw(_token, _to, _amount, _data, _gasLimit);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
function _withdraw(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) internal virtual;
|
||||
function _withdraw(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) internal virtual;
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
import { IERC721Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol";
|
||||
import { ERC721HolderUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/utils/ERC721HolderUpgradeable.sol";
|
||||
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
import {IERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol";
|
||||
import {ERC721HolderUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/utils/ERC721HolderUpgradeable.sol";
|
||||
|
||||
import { IL2ERC721Gateway } from "./IL2ERC721Gateway.sol";
|
||||
import { IL2ScrollMessenger } from "../IL2ScrollMessenger.sol";
|
||||
import { IL1ERC721Gateway } from "../../L1/gateways/IL1ERC721Gateway.sol";
|
||||
import { ScrollGatewayBase, IScrollGateway } from "../../libraries/gateway/ScrollGatewayBase.sol";
|
||||
import { IScrollERC721 } from "../../libraries/token/IScrollERC721.sol";
|
||||
import {IL2ERC721Gateway} from "./IL2ERC721Gateway.sol";
|
||||
import {IL2ScrollMessenger} from "../IL2ScrollMessenger.sol";
|
||||
import {IL1ERC721Gateway} from "../../L1/gateways/IL1ERC721Gateway.sol";
|
||||
import {ScrollGatewayBase, IScrollGateway} from "../../libraries/gateway/ScrollGatewayBase.sol";
|
||||
import {IScrollERC721} from "../../libraries/token/IScrollERC721.sol";
|
||||
|
||||
/// @title L2ERC721Gateway
|
||||
/// @notice The `L2ERC721Gateway` is used to withdraw ERC721 compatible NFTs in layer 2 and
|
||||
@@ -21,192 +21,192 @@ import { IScrollERC721 } from "../../libraries/token/IScrollERC721.sol";
|
||||
/// This will be changed if we have more specific scenarios.
|
||||
// @todo Current implementation doesn't support calling from `L2GatewayRouter`.
|
||||
contract L2ERC721Gateway is OwnableUpgradeable, ERC721HolderUpgradeable, ScrollGatewayBase, IL2ERC721Gateway {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
|
||||
/// @notice Emitted when token mapping for ERC721 token is updated.
|
||||
/// @param _l1Token The address of corresponding ERC721 token in layer 2.
|
||||
/// @param _l1Token The address of ERC721 token in layer 1.
|
||||
event UpdateTokenMapping(address _l2Token, address _l1Token);
|
||||
/// @notice Emitted when token mapping for ERC721 token is updated.
|
||||
/// @param _l1Token The address of corresponding ERC721 token in layer 2.
|
||||
/// @param _l1Token The address of ERC721 token in layer 1.
|
||||
event UpdateTokenMapping(address _l2Token, address _l1Token);
|
||||
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
/// @notice Mapping from layer 2 token address to layer 1 token address for ERC721 NFT.
|
||||
// solhint-disable-next-line var-name-mixedcase
|
||||
mapping(address => address) public tokenMapping;
|
||||
/// @notice Mapping from layer 2 token address to layer 1 token address for ERC721 NFT.
|
||||
// solhint-disable-next-line var-name-mixedcase
|
||||
mapping(address => address) public tokenMapping;
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
function initialize(address _counterpart, address _messenger) external initializer {
|
||||
OwnableUpgradeable.__Ownable_init();
|
||||
ScrollGatewayBase._initialize(_counterpart, address(0), _messenger);
|
||||
}
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
|
||||
/// @inheritdoc IL2ERC721Gateway
|
||||
function withdrawERC721(
|
||||
address _token,
|
||||
uint256 _tokenId,
|
||||
uint256 _gasLimit
|
||||
) external override {
|
||||
_withdrawERC721(_token, msg.sender, _tokenId, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ERC721Gateway
|
||||
function withdrawERC721(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _gasLimit
|
||||
) external override {
|
||||
_withdrawERC721(_token, _to, _tokenId, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ERC721Gateway
|
||||
function batchWithdrawERC721(
|
||||
address _token,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256 _gasLimit
|
||||
) external override {
|
||||
_batchWithdrawERC721(_token, msg.sender, _tokenIds, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ERC721Gateway
|
||||
function batchWithdrawERC721(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256 _gasLimit
|
||||
) external override {
|
||||
_batchWithdrawERC721(_token, _to, _tokenIds, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ERC721Gateway
|
||||
function finalizeDepositERC721(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _tokenId
|
||||
) external override nonReentrant onlyCallByCounterpart {
|
||||
IScrollERC721(_l2Token).mint(_to, _tokenId);
|
||||
|
||||
emit FinalizeDepositERC721(_l1Token, _l2Token, _from, _to, _tokenId);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ERC721Gateway
|
||||
function finalizeBatchDepositERC721(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds
|
||||
) external override nonReentrant onlyCallByCounterpart {
|
||||
for (uint256 i = 0; i < _tokenIds.length; i++) {
|
||||
IScrollERC721(_l2Token).mint(_to, _tokenIds[i]);
|
||||
function initialize(address _counterpart, address _messenger) external initializer {
|
||||
OwnableUpgradeable.__Ownable_init();
|
||||
ScrollGatewayBase._initialize(_counterpart, address(0), _messenger);
|
||||
}
|
||||
|
||||
emit FinalizeBatchDepositERC721(_l1Token, _l2Token, _from, _to, _tokenIds);
|
||||
}
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
/// @notice Update layer 2 to layer 1 token mapping.
|
||||
/// @param _l1Token The address of corresponding ERC721 token in layer 2.
|
||||
/// @param _l1Token The address of ERC721 token in layer 1.
|
||||
function updateTokenMapping(address _l2Token, address _l1Token) external onlyOwner {
|
||||
require(_l1Token != address(0), "map to zero address");
|
||||
|
||||
tokenMapping[_l2Token] = _l1Token;
|
||||
|
||||
emit UpdateTokenMapping(_l2Token, _l1Token);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
/// @dev Internal function to withdraw ERC721 NFT to layer 1.
|
||||
/// @param _token The address of ERC721 NFT in layer 2.
|
||||
/// @param _to The address of recipient in layer 1.
|
||||
/// @param _tokenId The token id to withdraw.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the withdraw on layer 1.
|
||||
function _withdrawERC721(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _gasLimit
|
||||
) internal nonReentrant {
|
||||
address _l1Token = tokenMapping[_token];
|
||||
require(_l1Token != address(0), "token not supported");
|
||||
|
||||
// 1. burn token
|
||||
// @note in case the token has given too much power to the gateway, we check owner here.
|
||||
require(IScrollERC721(_token).ownerOf(_tokenId) == msg.sender, "token not owned");
|
||||
IScrollERC721(_token).burn(_tokenId);
|
||||
|
||||
// 2. Generate message passed to L1ERC721Gateway.
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL1ERC721Gateway.finalizeWithdrawERC721.selector,
|
||||
_l1Token,
|
||||
_token,
|
||||
msg.sender,
|
||||
_to,
|
||||
_tokenId
|
||||
);
|
||||
|
||||
// 3. Send message to L2ScrollMessenger.
|
||||
IL2ScrollMessenger(messenger).sendMessage(counterpart, msg.value, _message, _gasLimit);
|
||||
|
||||
emit WithdrawERC721(_l1Token, _token, msg.sender, _to, _tokenId);
|
||||
}
|
||||
|
||||
/// @dev Internal function to batch withdraw ERC721 NFT to layer 1.
|
||||
/// @param _token The address of ERC721 NFT in layer 2.
|
||||
/// @param _to The address of recipient in layer 1.
|
||||
/// @param _tokenIds The list of token ids to withdraw.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the withdraw on layer 1.
|
||||
function _batchWithdrawERC721(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256 _gasLimit
|
||||
) internal nonReentrant {
|
||||
require(_tokenIds.length > 0, "no token to withdraw");
|
||||
|
||||
address _l1Token = tokenMapping[_token];
|
||||
require(_l1Token != address(0), "token not supported");
|
||||
|
||||
// 1. transfer token to this contract
|
||||
for (uint256 i = 0; i < _tokenIds.length; i++) {
|
||||
// @note in case the token has given too much power to the gateway, we check owner here.
|
||||
require(IScrollERC721(_token).ownerOf(_tokenIds[i]) == msg.sender, "token not owned");
|
||||
IScrollERC721(_token).burn(_tokenIds[i]);
|
||||
/// @inheritdoc IL2ERC721Gateway
|
||||
function withdrawERC721(
|
||||
address _token,
|
||||
uint256 _tokenId,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_withdrawERC721(_token, msg.sender, _tokenId, _gasLimit);
|
||||
}
|
||||
|
||||
// 2. Generate message passed to L1ERC721Gateway.
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL1ERC721Gateway.finalizeBatchWithdrawERC721.selector,
|
||||
_l1Token,
|
||||
_token,
|
||||
msg.sender,
|
||||
_to,
|
||||
_tokenIds
|
||||
);
|
||||
/// @inheritdoc IL2ERC721Gateway
|
||||
function withdrawERC721(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_withdrawERC721(_token, _to, _tokenId, _gasLimit);
|
||||
}
|
||||
|
||||
// 3. Send message to L2ScrollMessenger.
|
||||
IL2ScrollMessenger(messenger).sendMessage{ value: msg.value }(counterpart, msg.value, _message, _gasLimit);
|
||||
/// @inheritdoc IL2ERC721Gateway
|
||||
function batchWithdrawERC721(
|
||||
address _token,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_batchWithdrawERC721(_token, msg.sender, _tokenIds, _gasLimit);
|
||||
}
|
||||
|
||||
emit BatchWithdrawERC721(_l1Token, _token, msg.sender, _to, _tokenIds);
|
||||
}
|
||||
/// @inheritdoc IL2ERC721Gateway
|
||||
function batchWithdrawERC721(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
_batchWithdrawERC721(_token, _to, _tokenIds, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ERC721Gateway
|
||||
function finalizeDepositERC721(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _tokenId
|
||||
) external override nonReentrant onlyCallByCounterpart {
|
||||
IScrollERC721(_l2Token).mint(_to, _tokenId);
|
||||
|
||||
emit FinalizeDepositERC721(_l1Token, _l2Token, _from, _to, _tokenId);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ERC721Gateway
|
||||
function finalizeBatchDepositERC721(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds
|
||||
) external override nonReentrant onlyCallByCounterpart {
|
||||
for (uint256 i = 0; i < _tokenIds.length; i++) {
|
||||
IScrollERC721(_l2Token).mint(_to, _tokenIds[i]);
|
||||
}
|
||||
|
||||
emit FinalizeBatchDepositERC721(_l1Token, _l2Token, _from, _to, _tokenIds);
|
||||
}
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
/// @notice Update layer 2 to layer 1 token mapping.
|
||||
/// @param _l1Token The address of corresponding ERC721 token in layer 2.
|
||||
/// @param _l1Token The address of ERC721 token in layer 1.
|
||||
function updateTokenMapping(address _l2Token, address _l1Token) external onlyOwner {
|
||||
require(_l1Token != address(0), "map to zero address");
|
||||
|
||||
tokenMapping[_l2Token] = _l1Token;
|
||||
|
||||
emit UpdateTokenMapping(_l2Token, _l1Token);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
/// @dev Internal function to withdraw ERC721 NFT to layer 1.
|
||||
/// @param _token The address of ERC721 NFT in layer 2.
|
||||
/// @param _to The address of recipient in layer 1.
|
||||
/// @param _tokenId The token id to withdraw.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the withdraw on layer 1.
|
||||
function _withdrawERC721(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _gasLimit
|
||||
) internal nonReentrant {
|
||||
address _l1Token = tokenMapping[_token];
|
||||
require(_l1Token != address(0), "token not supported");
|
||||
|
||||
// 1. burn token
|
||||
// @note in case the token has given too much power to the gateway, we check owner here.
|
||||
require(IScrollERC721(_token).ownerOf(_tokenId) == msg.sender, "token not owned");
|
||||
IScrollERC721(_token).burn(_tokenId);
|
||||
|
||||
// 2. Generate message passed to L1ERC721Gateway.
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL1ERC721Gateway.finalizeWithdrawERC721.selector,
|
||||
_l1Token,
|
||||
_token,
|
||||
msg.sender,
|
||||
_to,
|
||||
_tokenId
|
||||
);
|
||||
|
||||
// 3. Send message to L2ScrollMessenger.
|
||||
IL2ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, _gasLimit);
|
||||
|
||||
emit WithdrawERC721(_l1Token, _token, msg.sender, _to, _tokenId);
|
||||
}
|
||||
|
||||
/// @dev Internal function to batch withdraw ERC721 NFT to layer 1.
|
||||
/// @param _token The address of ERC721 NFT in layer 2.
|
||||
/// @param _to The address of recipient in layer 1.
|
||||
/// @param _tokenIds The list of token ids to withdraw.
|
||||
/// @param _gasLimit Estimated gas limit required to complete the withdraw on layer 1.
|
||||
function _batchWithdrawERC721(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256 _gasLimit
|
||||
) internal nonReentrant {
|
||||
require(_tokenIds.length > 0, "no token to withdraw");
|
||||
|
||||
address _l1Token = tokenMapping[_token];
|
||||
require(_l1Token != address(0), "token not supported");
|
||||
|
||||
// 1. transfer token to this contract
|
||||
for (uint256 i = 0; i < _tokenIds.length; i++) {
|
||||
// @note in case the token has given too much power to the gateway, we check owner here.
|
||||
require(IScrollERC721(_token).ownerOf(_tokenIds[i]) == msg.sender, "token not owned");
|
||||
IScrollERC721(_token).burn(_tokenIds[i]);
|
||||
}
|
||||
|
||||
// 2. Generate message passed to L1ERC721Gateway.
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL1ERC721Gateway.finalizeBatchWithdrawERC721.selector,
|
||||
_l1Token,
|
||||
_token,
|
||||
msg.sender,
|
||||
_to,
|
||||
_tokenIds
|
||||
);
|
||||
|
||||
// 3. Send message to L2ScrollMessenger.
|
||||
IL2ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, _gasLimit);
|
||||
|
||||
emit BatchWithdrawERC721(_l1Token, _token, msg.sender, _to, _tokenIds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
|
||||
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
|
||||
|
||||
import { IL1ETHGateway } from "../../L1/gateways/IL1ETHGateway.sol";
|
||||
import { IL2ScrollMessenger } from "../IL2ScrollMessenger.sol";
|
||||
import { IL2ETHGateway } from "./IL2ETHGateway.sol";
|
||||
import {IL1ETHGateway} from "../../L1/gateways/IL1ETHGateway.sol";
|
||||
import {IL2ScrollMessenger} from "../IL2ScrollMessenger.sol";
|
||||
import {IL2ETHGateway} from "./IL2ETHGateway.sol";
|
||||
|
||||
import { ScrollGatewayBase } from "../../libraries/gateway/ScrollGatewayBase.sol";
|
||||
import {ScrollGatewayBase} from "../../libraries/gateway/ScrollGatewayBase.sol";
|
||||
|
||||
/// @title L2ETHGateway
|
||||
/// @notice The `L2ETHGateway` contract is used to withdraw ETH token in layer 2 and
|
||||
@@ -16,94 +16,94 @@ import { ScrollGatewayBase } from "../../libraries/gateway/ScrollGatewayBase.sol
|
||||
/// @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 *
|
||||
***************/
|
||||
/***************
|
||||
* 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));
|
||||
/// @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);
|
||||
}
|
||||
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL1ETHGateway.finalizeWithdrawETH.selector,
|
||||
_from,
|
||||
_to,
|
||||
_amount,
|
||||
_data
|
||||
);
|
||||
IL2ScrollMessenger(messenger).sendMessage{ value: msg.value }(counterpart, _amount, _message, _gasLimit);
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
emit WithdrawETH(_from, _to, _amount, _data);
|
||||
}
|
||||
/// @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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
|
||||
import { IL2GatewayRouter } from "./IL2GatewayRouter.sol";
|
||||
import { IL2ETHGateway } from "./IL2ETHGateway.sol";
|
||||
import { IL2ERC20Gateway } from "./IL2ERC20Gateway.sol";
|
||||
import { IL2ScrollMessenger } from "../IL2ScrollMessenger.sol";
|
||||
import { IL1ETHGateway } from "../../L1/gateways/IL1ETHGateway.sol";
|
||||
import { IScrollGateway } from "../../libraries/gateway/IScrollGateway.sol";
|
||||
import { IScrollStandardERC20 } from "../../libraries/token/IScrollStandardERC20.sol";
|
||||
import {IL2GatewayRouter} from "./IL2GatewayRouter.sol";
|
||||
import {IL2ETHGateway} from "./IL2ETHGateway.sol";
|
||||
import {IL2ERC20Gateway} from "./IL2ERC20Gateway.sol";
|
||||
import {IL2ScrollMessenger} from "../IL2ScrollMessenger.sol";
|
||||
import {IL1ETHGateway} from "../../L1/gateways/IL1ETHGateway.sol";
|
||||
import {IScrollGateway} from "../../libraries/gateway/IScrollGateway.sol";
|
||||
import {IScrollStandardERC20} from "../../libraries/token/IScrollStandardERC20.sol";
|
||||
|
||||
/// @title L2GatewayRouter
|
||||
/// @notice The `L2GatewayRouter` is the main entry for withdrawing Ether and ERC20 tokens.
|
||||
@@ -18,195 +18,195 @@ import { IScrollStandardERC20 } from "../../libraries/token/IScrollStandardERC20
|
||||
/// @dev One can also use this contract to query L1/L2 token address mapping.
|
||||
/// In the future, ERC-721 and ERC-1155 tokens will be added to the router too.
|
||||
contract L2GatewayRouter is OwnableUpgradeable, IL2GatewayRouter {
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
/// @notice The address of L2ETHGateway.
|
||||
address public ethGateway;
|
||||
/// @notice The address of L2ETHGateway.
|
||||
address public ethGateway;
|
||||
|
||||
/// @notice The addess of default L2 ERC20 gateway, normally the L2StandardERC20Gateway contract.
|
||||
address public defaultERC20Gateway;
|
||||
/// @notice The addess of default L2 ERC20 gateway, normally the L2StandardERC20Gateway contract.
|
||||
address public defaultERC20Gateway;
|
||||
|
||||
/// @notice Mapping from L2 ERC20 token address to corresponding L2ERC20Gateway.
|
||||
// solhint-disable-next-line var-name-mixedcase
|
||||
mapping(address => address) public ERC20Gateway;
|
||||
/// @notice Mapping from L2 ERC20 token address to corresponding L2ERC20Gateway.
|
||||
// solhint-disable-next-line var-name-mixedcase
|
||||
mapping(address => address) public ERC20Gateway;
|
||||
|
||||
// @todo: add ERC721/ERC1155 Gateway mapping.
|
||||
// @todo: add ERC721/ERC1155 Gateway mapping.
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
function initialize(address _ethGateway, address _defaultERC20Gateway) external initializer {
|
||||
OwnableUpgradeable.__Ownable_init();
|
||||
function initialize(address _ethGateway, address _defaultERC20Gateway) external initializer {
|
||||
OwnableUpgradeable.__Ownable_init();
|
||||
|
||||
// it can be zero during initialization
|
||||
if (_defaultERC20Gateway != address(0)) {
|
||||
defaultERC20Gateway = _defaultERC20Gateway;
|
||||
// it can be zero during initialization
|
||||
if (_defaultERC20Gateway != address(0)) {
|
||||
defaultERC20Gateway = _defaultERC20Gateway;
|
||||
}
|
||||
|
||||
// it can be zero during initialization
|
||||
if (_ethGateway != address(0)) {
|
||||
ethGateway = _ethGateway;
|
||||
emit SetETHGateway(_ethGateway);
|
||||
}
|
||||
}
|
||||
|
||||
// it can be zero during initialization
|
||||
if (_ethGateway != address(0)) {
|
||||
ethGateway = _ethGateway;
|
||||
emit SetETHGateway(_ethGateway);
|
||||
}
|
||||
}
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function getL2ERC20Address(address) external pure override returns (address) {
|
||||
revert("unsupported");
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function getL1ERC20Address(address _l2Address) external view override returns (address) {
|
||||
address _gateway = getERC20Gateway(_l2Address);
|
||||
if (_gateway == address(0)) {
|
||||
return address(0);
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function getL2ERC20Address(address) external pure override returns (address) {
|
||||
revert("unsupported");
|
||||
}
|
||||
|
||||
return IL2ERC20Gateway(_gateway).getL1ERC20Address(_l2Address);
|
||||
}
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function getL1ERC20Address(address _l2Address) external view override returns (address) {
|
||||
address _gateway = getERC20Gateway(_l2Address);
|
||||
if (_gateway == address(0)) {
|
||||
return address(0);
|
||||
}
|
||||
|
||||
/// @notice Return the corresponding gateway address for given token address.
|
||||
/// @param _token The address of token to query.
|
||||
function getERC20Gateway(address _token) public view returns (address) {
|
||||
address _gateway = ERC20Gateway[_token];
|
||||
if (_gateway == address(0)) {
|
||||
_gateway = defaultERC20Gateway;
|
||||
return IL2ERC20Gateway(_gateway).getL1ERC20Address(_l2Address);
|
||||
}
|
||||
return _gateway;
|
||||
}
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function withdrawERC20(
|
||||
address _token,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
withdrawERC20AndCall(_token, msg.sender, _amount, new bytes(0), _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function withdrawERC20(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
withdrawERC20AndCall(_token, _to, _amount, new bytes(0), _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function withdrawERC20AndCall(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) public payable override {
|
||||
address _gateway = getERC20Gateway(_token);
|
||||
require(_gateway != address(0), "no gateway available");
|
||||
|
||||
// encode msg.sender with _data
|
||||
bytes memory _routerData = abi.encode(msg.sender, _data);
|
||||
|
||||
IL2ERC20Gateway(_gateway).withdrawERC20AndCall{ value: msg.value }(_token, _to, _amount, _routerData, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ETHGateway
|
||||
function withdrawETH(uint256 _amount, uint256 _gasLimit) external payable override {
|
||||
withdrawETHAndCall(msg.sender, _amount, new bytes(0), _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ETHGateway
|
||||
function withdrawETH(
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
withdrawETHAndCall(_to, _amount, new bytes(0), _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ETHGateway
|
||||
function withdrawETHAndCall(
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) public payable override {
|
||||
address _gateway = ethGateway;
|
||||
require(_gateway != address(0), "eth gateway available");
|
||||
|
||||
// encode msg.sender with _data
|
||||
bytes memory _routerData = abi.encode(msg.sender, _data);
|
||||
|
||||
IL2ETHGateway(_gateway).withdrawETHAndCall{ value: msg.value }(_to, _amount, _routerData, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ETHGateway
|
||||
function finalizeDepositETH(
|
||||
address,
|
||||
address,
|
||||
uint256,
|
||||
bytes calldata
|
||||
) external payable virtual override {
|
||||
revert("should never be called");
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function finalizeDepositERC20(
|
||||
address,
|
||||
address,
|
||||
address,
|
||||
address,
|
||||
uint256,
|
||||
bytes calldata
|
||||
) external payable virtual override {
|
||||
revert("should never be called");
|
||||
}
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
/// @notice Update the address of ETH gateway contract.
|
||||
/// @dev This function should only be called by contract owner.
|
||||
/// @param _ethGateway The address to update.
|
||||
function setETHGateway(address _ethGateway) external onlyOwner {
|
||||
ethGateway = _ethGateway;
|
||||
|
||||
emit SetETHGateway(_ethGateway);
|
||||
}
|
||||
|
||||
/// @notice Update the address of default ERC20 gateway contract.
|
||||
/// @dev This function should only be called by contract owner.
|
||||
/// @param _defaultERC20Gateway The address to update.
|
||||
function setDefaultERC20Gateway(address _defaultERC20Gateway) external onlyOwner {
|
||||
defaultERC20Gateway = _defaultERC20Gateway;
|
||||
|
||||
emit SetDefaultERC20Gateway(_defaultERC20Gateway);
|
||||
}
|
||||
|
||||
/// @notice Update the mapping from token address to gateway address.
|
||||
/// @dev This function should only be called by contract owner.
|
||||
/// @param _tokens The list of addresses of tokens to update.
|
||||
/// @param _gateways The list of addresses of gateways to update.
|
||||
function setERC20Gateway(address[] memory _tokens, address[] memory _gateways) external onlyOwner {
|
||||
require(_tokens.length == _gateways.length, "length mismatch");
|
||||
|
||||
for (uint256 i = 0; i < _tokens.length; i++) {
|
||||
ERC20Gateway[_tokens[i]] = _gateways[i];
|
||||
|
||||
emit SetERC20Gateway(_tokens[i], _gateways[i]);
|
||||
/// @notice Return the corresponding gateway address for given token address.
|
||||
/// @param _token The address of token to query.
|
||||
function getERC20Gateway(address _token) public view returns (address) {
|
||||
address _gateway = ERC20Gateway[_token];
|
||||
if (_gateway == address(0)) {
|
||||
_gateway = defaultERC20Gateway;
|
||||
}
|
||||
return _gateway;
|
||||
}
|
||||
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function withdrawERC20(
|
||||
address _token,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
withdrawERC20AndCall(_token, msg.sender, _amount, new bytes(0), _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function withdrawERC20(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
withdrawERC20AndCall(_token, _to, _amount, new bytes(0), _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function withdrawERC20AndCall(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) public payable override {
|
||||
address _gateway = getERC20Gateway(_token);
|
||||
require(_gateway != address(0), "no gateway available");
|
||||
|
||||
// encode msg.sender with _data
|
||||
bytes memory _routerData = abi.encode(msg.sender, _data);
|
||||
|
||||
IL2ERC20Gateway(_gateway).withdrawERC20AndCall{value: msg.value}(_token, _to, _amount, _routerData, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ETHGateway
|
||||
function withdrawETH(uint256 _amount, uint256 _gasLimit) external payable override {
|
||||
withdrawETHAndCall(msg.sender, _amount, new bytes(0), _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ETHGateway
|
||||
function withdrawETH(
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
uint256 _gasLimit
|
||||
) external payable override {
|
||||
withdrawETHAndCall(_to, _amount, new bytes(0), _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ETHGateway
|
||||
function withdrawETHAndCall(
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) public payable override {
|
||||
address _gateway = ethGateway;
|
||||
require(_gateway != address(0), "eth gateway available");
|
||||
|
||||
// encode msg.sender with _data
|
||||
bytes memory _routerData = abi.encode(msg.sender, _data);
|
||||
|
||||
IL2ETHGateway(_gateway).withdrawETHAndCall{value: msg.value}(_to, _amount, _routerData, _gasLimit);
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ETHGateway
|
||||
function finalizeDepositETH(
|
||||
address,
|
||||
address,
|
||||
uint256,
|
||||
bytes calldata
|
||||
) external payable virtual override {
|
||||
revert("should never be called");
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function finalizeDepositERC20(
|
||||
address,
|
||||
address,
|
||||
address,
|
||||
address,
|
||||
uint256,
|
||||
bytes calldata
|
||||
) external payable virtual override {
|
||||
revert("should never be called");
|
||||
}
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
/// @notice Update the address of ETH gateway contract.
|
||||
/// @dev This function should only be called by contract owner.
|
||||
/// @param _ethGateway The address to update.
|
||||
function setETHGateway(address _ethGateway) external onlyOwner {
|
||||
ethGateway = _ethGateway;
|
||||
|
||||
emit SetETHGateway(_ethGateway);
|
||||
}
|
||||
|
||||
/// @notice Update the address of default ERC20 gateway contract.
|
||||
/// @dev This function should only be called by contract owner.
|
||||
/// @param _defaultERC20Gateway The address to update.
|
||||
function setDefaultERC20Gateway(address _defaultERC20Gateway) external onlyOwner {
|
||||
defaultERC20Gateway = _defaultERC20Gateway;
|
||||
|
||||
emit SetDefaultERC20Gateway(_defaultERC20Gateway);
|
||||
}
|
||||
|
||||
/// @notice Update the mapping from token address to gateway address.
|
||||
/// @dev This function should only be called by contract owner.
|
||||
/// @param _tokens The list of addresses of tokens to update.
|
||||
/// @param _gateways The list of addresses of gateways to update.
|
||||
function setERC20Gateway(address[] memory _tokens, address[] memory _gateways) external onlyOwner {
|
||||
require(_tokens.length == _gateways.length, "length mismatch");
|
||||
|
||||
for (uint256 i = 0; i < _tokens.length; i++) {
|
||||
ERC20Gateway[_tokens[i]] = _gateways[i];
|
||||
|
||||
emit SetERC20Gateway(_tokens[i], _gateways[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
|
||||
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
|
||||
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
|
||||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
|
||||
|
||||
import { IL2ERC20Gateway, L2ERC20Gateway } from "./L2ERC20Gateway.sol";
|
||||
import { IL2ScrollMessenger } from "../IL2ScrollMessenger.sol";
|
||||
import { IL1ERC20Gateway } from "../../L1/gateways/IL1ERC20Gateway.sol";
|
||||
import { IScrollStandardERC20 } from "../../libraries/token/IScrollStandardERC20.sol";
|
||||
import { ScrollStandardERC20 } from "../../libraries/token/ScrollStandardERC20.sol";
|
||||
import { IScrollStandardERC20Factory } from "../../libraries/token/IScrollStandardERC20Factory.sol";
|
||||
import { ScrollGatewayBase, IScrollGateway } from "../../libraries/gateway/ScrollGatewayBase.sol";
|
||||
import {IL2ERC20Gateway, L2ERC20Gateway} from "./L2ERC20Gateway.sol";
|
||||
import {IL2ScrollMessenger} from "../IL2ScrollMessenger.sol";
|
||||
import {IL1ERC20Gateway} from "../../L1/gateways/IL1ERC20Gateway.sol";
|
||||
import {IScrollStandardERC20} from "../../libraries/token/IScrollStandardERC20.sol";
|
||||
import {ScrollStandardERC20} from "../../libraries/token/ScrollStandardERC20.sol";
|
||||
import {IScrollStandardERC20Factory} from "../../libraries/token/IScrollStandardERC20Factory.sol";
|
||||
import {ScrollGatewayBase, IScrollGateway} from "../../libraries/gateway/ScrollGatewayBase.sol";
|
||||
|
||||
/// @title L2StandardERC20Gateway
|
||||
/// @notice The `L2StandardERC20Gateway` is used to withdraw standard ERC20 tokens in layer 2 and
|
||||
@@ -22,142 +22,145 @@ import { ScrollGatewayBase, IScrollGateway } from "../../libraries/gateway/Scrol
|
||||
/// token will be minted and transfered to the recipient. Any ERC20 that requires non-standard functionality
|
||||
/// should use a separate gateway.
|
||||
contract L2StandardERC20Gateway is Initializable, ScrollGatewayBase, L2ERC20Gateway {
|
||||
using SafeERC20 for IERC20;
|
||||
using Address for address;
|
||||
using SafeERC20 for IERC20;
|
||||
using Address for address;
|
||||
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
/// @notice Mapping from l2 token address to l1 token address.
|
||||
mapping(address => address) private tokenMapping;
|
||||
/// @notice Mapping from l2 token address to l1 token address.
|
||||
mapping(address => address) private tokenMapping;
|
||||
|
||||
/// @notice The address of ScrollStandardERC20Factory.
|
||||
address public tokenFactory;
|
||||
/// @notice The address of ScrollStandardERC20Factory.
|
||||
address public tokenFactory;
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
function initialize(
|
||||
address _counterpart,
|
||||
address _router,
|
||||
address _messenger,
|
||||
address _tokenFactory
|
||||
) external initializer {
|
||||
require(_router != address(0), "zero router address");
|
||||
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
|
||||
function initialize(
|
||||
address _counterpart,
|
||||
address _router,
|
||||
address _messenger,
|
||||
address _tokenFactory
|
||||
) external initializer {
|
||||
require(_router != address(0), "zero router address");
|
||||
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
|
||||
|
||||
require(_tokenFactory != address(0), "zero token factory");
|
||||
tokenFactory = _tokenFactory;
|
||||
}
|
||||
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function getL1ERC20Address(address _l2Token) external view override returns (address) {
|
||||
return tokenMapping[_l2Token];
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function getL2ERC20Address(address _l1Token) public view override returns (address) {
|
||||
return IScrollStandardERC20Factory(tokenFactory).computeL2TokenAddress(address(this), _l1Token);
|
||||
}
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function finalizeDepositERC20(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes calldata _data
|
||||
) external payable override onlyCallByCounterpart {
|
||||
require(msg.value == 0, "nonzero msg.value");
|
||||
|
||||
{
|
||||
// avoid stack too deep
|
||||
address _expectedL2Token = IScrollStandardERC20Factory(tokenFactory).computeL2TokenAddress(
|
||||
address(this),
|
||||
_l1Token
|
||||
);
|
||||
require(_l2Token == _expectedL2Token, "l2 token mismatch");
|
||||
require(_tokenFactory != address(0), "zero token factory");
|
||||
tokenFactory = _tokenFactory;
|
||||
}
|
||||
|
||||
bytes memory _deployData;
|
||||
bytes memory _callData;
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
if (tokenMapping[_l2Token] == address(0)) {
|
||||
// first deposit, update mapping
|
||||
tokenMapping[_l2Token] = _l1Token;
|
||||
(_callData, _deployData) = abi.decode(_data, (bytes, bytes));
|
||||
} else {
|
||||
_callData = _data;
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function getL1ERC20Address(address _l2Token) external view override returns (address) {
|
||||
return tokenMapping[_l2Token];
|
||||
}
|
||||
|
||||
if (!_l2Token.isContract()) {
|
||||
_deployL2Token(_deployData, _l1Token);
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function getL2ERC20Address(address _l1Token) public view override returns (address) {
|
||||
return IScrollStandardERC20Factory(tokenFactory).computeL2TokenAddress(address(this), _l1Token);
|
||||
}
|
||||
|
||||
// @todo forward `_callData` to `_to` using transferAndCall in the near future
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
IScrollStandardERC20(_l2Token).mint(_to, _amount);
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function finalizeDepositERC20(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes calldata _data
|
||||
) external payable override onlyCallByCounterpart {
|
||||
require(msg.value == 0, "nonzero msg.value");
|
||||
|
||||
emit FinalizeDepositERC20(_l1Token, _l2Token, _from, _to, _amount, _callData);
|
||||
}
|
||||
{
|
||||
// avoid stack too deep
|
||||
address _expectedL2Token = IScrollStandardERC20Factory(tokenFactory).computeL2TokenAddress(
|
||||
address(this),
|
||||
_l1Token
|
||||
);
|
||||
require(_l2Token == _expectedL2Token, "l2 token mismatch");
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
bytes memory _deployData;
|
||||
bytes memory _callData;
|
||||
|
||||
/// @inheritdoc L2ERC20Gateway
|
||||
function _withdraw(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) internal virtual override {
|
||||
require(_amount > 0, "withdraw zero amount");
|
||||
if (tokenMapping[_l2Token] == address(0)) {
|
||||
// first deposit, update mapping
|
||||
tokenMapping[_l2Token] = _l1Token;
|
||||
(_callData, _deployData) = abi.decode(_data, (bytes, bytes));
|
||||
} else {
|
||||
_callData = _data;
|
||||
}
|
||||
|
||||
// 1. Extract real sender if this call is from L2GatewayRouter.
|
||||
address _from = msg.sender;
|
||||
if (router == msg.sender) {
|
||||
(_from, _data) = abi.decode(_data, (address, bytes));
|
||||
if (!_l2Token.isContract()) {
|
||||
_deployL2Token(_deployData, _l1Token);
|
||||
}
|
||||
|
||||
// @todo forward `_callData` to `_to` using transferAndCall in the near future
|
||||
|
||||
IScrollStandardERC20(_l2Token).mint(_to, _amount);
|
||||
|
||||
emit FinalizeDepositERC20(_l1Token, _l2Token, _from, _to, _amount, _callData);
|
||||
}
|
||||
|
||||
address _l1Token = tokenMapping[_token];
|
||||
require(_l1Token != address(0), "no corresponding l1 token");
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
// 2. Burn token.
|
||||
IScrollStandardERC20(_token).burn(_from, _amount);
|
||||
/// @inheritdoc L2ERC20Gateway
|
||||
function _withdraw(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) internal virtual override {
|
||||
require(_amount > 0, "withdraw zero amount");
|
||||
|
||||
// 3. Generate message passed to L1StandardERC20Gateway.
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL1ERC20Gateway.finalizeWithdrawERC20.selector,
|
||||
_l1Token,
|
||||
_token,
|
||||
_from,
|
||||
_to,
|
||||
_amount,
|
||||
_data
|
||||
);
|
||||
// 1. Extract real sender if this call is from L2GatewayRouter.
|
||||
address _from = msg.sender;
|
||||
if (router == msg.sender) {
|
||||
(_from, _data) = abi.decode(_data, (address, bytes));
|
||||
}
|
||||
|
||||
// 4. send message to L2ScrollMessenger
|
||||
IL2ScrollMessenger(messenger).sendMessage{ value: msg.value }(counterpart, 0, _message, _gasLimit);
|
||||
address _l1Token = tokenMapping[_token];
|
||||
require(_l1Token != address(0), "no corresponding l1 token");
|
||||
|
||||
emit WithdrawERC20(_l1Token, _token, _from, _to, _amount, _data);
|
||||
}
|
||||
// 2. Burn token.
|
||||
IScrollStandardERC20(_token).burn(_from, _amount);
|
||||
|
||||
function _deployL2Token(bytes memory _deployData, address _l1Token) internal {
|
||||
address _l2Token = IScrollStandardERC20Factory(tokenFactory).deployL2Token(address(this), _l1Token);
|
||||
(string memory _symbol, string memory _name, uint8 _decimals) = abi.decode(_deployData, (string, string, uint8));
|
||||
ScrollStandardERC20(_l2Token).initialize(_name, _symbol, _decimals, address(this), _l1Token);
|
||||
}
|
||||
// 3. Generate message passed to L1StandardERC20Gateway.
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL1ERC20Gateway.finalizeWithdrawERC20.selector,
|
||||
_l1Token,
|
||||
_token,
|
||||
_from,
|
||||
_to,
|
||||
_amount,
|
||||
_data
|
||||
);
|
||||
|
||||
// 4. send message to L2ScrollMessenger
|
||||
IL2ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, _gasLimit);
|
||||
|
||||
emit WithdrawERC20(_l1Token, _token, _from, _to, _amount, _data);
|
||||
}
|
||||
|
||||
function _deployL2Token(bytes memory _deployData, address _l1Token) internal {
|
||||
address _l2Token = IScrollStandardERC20Factory(tokenFactory).deployL2Token(address(this), _l1Token);
|
||||
(string memory _symbol, string memory _name, uint8 _decimals) = abi.decode(
|
||||
_deployData,
|
||||
(string, string, uint8)
|
||||
);
|
||||
ScrollStandardERC20(_l2Token).initialize(_name, _symbol, _decimals, address(this), _l1Token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
|
||||
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
|
||||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
|
||||
import { IL2ERC20Gateway, L2ERC20Gateway } from "./L2ERC20Gateway.sol";
|
||||
import { IL2ScrollMessenger } from "../IL2ScrollMessenger.sol";
|
||||
import { IWETH } from "../../interfaces/IWETH.sol";
|
||||
import { IL1ERC20Gateway } from "../../L1/gateways/IL1ERC20Gateway.sol";
|
||||
import { ScrollGatewayBase, IScrollGateway } from "../../libraries/gateway/ScrollGatewayBase.sol";
|
||||
import {IL2ERC20Gateway, L2ERC20Gateway} from "./L2ERC20Gateway.sol";
|
||||
import {IL2ScrollMessenger} from "../IL2ScrollMessenger.sol";
|
||||
import {IWETH} from "../../interfaces/IWETH.sol";
|
||||
import {IL1ERC20Gateway} from "../../L1/gateways/IL1ERC20Gateway.sol";
|
||||
import {ScrollGatewayBase, IScrollGateway} from "../../libraries/gateway/ScrollGatewayBase.sol";
|
||||
|
||||
/// @title L2WETHGateway
|
||||
/// @notice The `L2WETHGateway` contract is used to withdraw `WETH` token in layer 2 and
|
||||
@@ -20,121 +20,126 @@ import { ScrollGatewayBase, IScrollGateway } from "../../libraries/gateway/Scrol
|
||||
/// On finalizing deposit, the Ether will be transfered from `L2ScrollMessenger`, then
|
||||
/// wrapped as WETH and finally transfer to recipient.
|
||||
contract L2WETHGateway is Initializable, ScrollGatewayBase, L2ERC20Gateway {
|
||||
using SafeERC20 for IERC20;
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
/*************
|
||||
* Constants *
|
||||
*************/
|
||||
/*************
|
||||
* Constants *
|
||||
*************/
|
||||
|
||||
/// @notice The address of L1 WETH address.
|
||||
address public immutable l1WETH;
|
||||
/// @notice The address of L1 WETH address.
|
||||
address public immutable l1WETH;
|
||||
|
||||
/// @notice The address of L2 WETH address.
|
||||
// @todo It should be predeployed in L2 and make it a constant.
|
||||
// solhint-disable-next-line var-name-mixedcase
|
||||
address public immutable WETH;
|
||||
/// @notice The address of L2 WETH address.
|
||||
// @todo It should be predeployed in L2 and make it a constant.
|
||||
// solhint-disable-next-line var-name-mixedcase
|
||||
address public immutable WETH;
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
constructor(address _WETH, address _l1WETH) {
|
||||
WETH = _WETH;
|
||||
l1WETH = _l1WETH;
|
||||
}
|
||||
|
||||
function initialize(
|
||||
address _counterpart,
|
||||
address _router,
|
||||
address _messenger
|
||||
) external initializer {
|
||||
require(_router != address(0), "zero router address");
|
||||
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
|
||||
}
|
||||
|
||||
receive() external payable {
|
||||
require(msg.sender == WETH, "only WETH");
|
||||
}
|
||||
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function getL1ERC20Address(address) external view override returns (address) {
|
||||
return l1WETH;
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function getL2ERC20Address(address) public view override returns (address) {
|
||||
return WETH;
|
||||
}
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function finalizeDepositERC20(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes calldata _data
|
||||
) external payable override onlyCallByCounterpart {
|
||||
require(_l1Token == l1WETH, "l1 token not WETH");
|
||||
require(_l2Token == WETH, "l2 token not WETH");
|
||||
require(_amount == msg.value, "msg.value mismatch");
|
||||
|
||||
IWETH(_l2Token).deposit{ value: _amount }();
|
||||
IERC20(_l2Token).safeTransfer(_to, _amount);
|
||||
|
||||
// @todo forward `_data` to `_to` in near future
|
||||
|
||||
emit FinalizeDepositERC20(_l1Token, _l2Token, _from, _to, _amount, _data);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
/// @inheritdoc L2ERC20Gateway
|
||||
function _withdraw(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) internal virtual override {
|
||||
require(_amount > 0, "withdraw zero amount");
|
||||
require(_token == WETH, "only WETH is allowed");
|
||||
|
||||
// 1. Extract real sender if this call is from L1GatewayRouter.
|
||||
address _from = msg.sender;
|
||||
if (router == msg.sender) {
|
||||
(_from, _data) = abi.decode(_data, (address, bytes));
|
||||
constructor(address _WETH, address _l1WETH) {
|
||||
WETH = _WETH;
|
||||
l1WETH = _l1WETH;
|
||||
}
|
||||
|
||||
// 2. Transfer token into this contract.
|
||||
IERC20(_token).safeTransferFrom(_from, address(this), _amount);
|
||||
IWETH(_token).withdraw(_amount);
|
||||
function initialize(
|
||||
address _counterpart,
|
||||
address _router,
|
||||
address _messenger
|
||||
) external initializer {
|
||||
require(_router != address(0), "zero router address");
|
||||
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
|
||||
}
|
||||
|
||||
// 3. Generate message passed to L2StandardERC20Gateway.
|
||||
address _l1WETH = l1WETH;
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL1ERC20Gateway.finalizeWithdrawERC20.selector,
|
||||
_l1WETH,
|
||||
_token,
|
||||
_from,
|
||||
_to,
|
||||
_amount,
|
||||
_data
|
||||
);
|
||||
receive() external payable {
|
||||
require(msg.sender == WETH, "only WETH");
|
||||
}
|
||||
|
||||
// 4. Send message to L1ScrollMessenger.
|
||||
IL2ScrollMessenger(messenger).sendMessage{ value: _amount + msg.value }(counterpart, _amount, _message, _gasLimit);
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
emit WithdrawERC20(_l1WETH, _token, _from, _to, _amount, _data);
|
||||
}
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function getL1ERC20Address(address) external view override returns (address) {
|
||||
return l1WETH;
|
||||
}
|
||||
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function getL2ERC20Address(address) public view override returns (address) {
|
||||
return WETH;
|
||||
}
|
||||
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
/// @inheritdoc IL2ERC20Gateway
|
||||
function finalizeDepositERC20(
|
||||
address _l1Token,
|
||||
address _l2Token,
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes calldata _data
|
||||
) external payable override onlyCallByCounterpart {
|
||||
require(_l1Token == l1WETH, "l1 token not WETH");
|
||||
require(_l2Token == WETH, "l2 token not WETH");
|
||||
require(_amount == msg.value, "msg.value mismatch");
|
||||
|
||||
IWETH(_l2Token).deposit{value: _amount}();
|
||||
IERC20(_l2Token).safeTransfer(_to, _amount);
|
||||
|
||||
// @todo forward `_data` to `_to` in near future
|
||||
|
||||
emit FinalizeDepositERC20(_l1Token, _l2Token, _from, _to, _amount, _data);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
/// @inheritdoc L2ERC20Gateway
|
||||
function _withdraw(
|
||||
address _token,
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes memory _data,
|
||||
uint256 _gasLimit
|
||||
) internal virtual override {
|
||||
require(_amount > 0, "withdraw zero amount");
|
||||
require(_token == WETH, "only WETH is allowed");
|
||||
|
||||
// 1. Extract real sender if this call is from L1GatewayRouter.
|
||||
address _from = msg.sender;
|
||||
if (router == msg.sender) {
|
||||
(_from, _data) = abi.decode(_data, (address, bytes));
|
||||
}
|
||||
|
||||
// 2. Transfer token into this contract.
|
||||
IERC20(_token).safeTransferFrom(_from, address(this), _amount);
|
||||
IWETH(_token).withdraw(_amount);
|
||||
|
||||
// 3. Generate message passed to L2StandardERC20Gateway.
|
||||
address _l1WETH = l1WETH;
|
||||
bytes memory _message = abi.encodeWithSelector(
|
||||
IL1ERC20Gateway.finalizeWithdrawERC20.selector,
|
||||
_l1WETH,
|
||||
_token,
|
||||
_from,
|
||||
_to,
|
||||
_amount,
|
||||
_data
|
||||
);
|
||||
|
||||
// 4. Send message to L1ScrollMessenger.
|
||||
IL2ScrollMessenger(messenger).sendMessage{value: _amount + msg.value}(
|
||||
counterpart,
|
||||
_amount,
|
||||
_message,
|
||||
_gasLimit
|
||||
);
|
||||
|
||||
emit WithdrawERC20(_l1WETH, _token, _from, _to, _amount, _data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,61 +3,61 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
interface IL1BlockContainer {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* 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
|
||||
);
|
||||
/// @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 *
|
||||
*************************/
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/// @notice Return the latest imported block hash
|
||||
function latestBlockHash() external view returns (bytes32);
|
||||
/// @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 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 number
|
||||
function latestBlockNumber() external view returns (uint256);
|
||||
|
||||
/// @notice Return the latest imported block timestamp
|
||||
function latestBlockTimestamp() 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 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);
|
||||
/// @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 *
|
||||
****************************/
|
||||
/*****************************
|
||||
* Public Mutating 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;
|
||||
/// @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;
|
||||
}
|
||||
|
||||
@@ -3,54 +3,54 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
interface IL1GasPriceOracle {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* 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 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 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);
|
||||
/// @notice Emitted when current l1 base fee is updated.
|
||||
/// @param l1BaseFee The current l1 base fee updated.
|
||||
event L1BaseFeeUpdated(uint256 l1BaseFee);
|
||||
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/// @notice Return the current l1 fee overhead.
|
||||
function overhead() external view returns (uint256);
|
||||
/// @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 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 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 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);
|
||||
/// @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 *
|
||||
****************************/
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
/// @notice Allows whitelisted caller to modify the l1 base fee.
|
||||
/// @param _l1BaseFee New l1 base fee.
|
||||
function setL1BaseFee(uint256 _l1BaseFee) external;
|
||||
/// @notice Allows whitelisted caller to modify the l1 base fee.
|
||||
/// @param _l1BaseFee New l1 base fee.
|
||||
function setL1BaseFee(uint256 _l1BaseFee) external;
|
||||
}
|
||||
|
||||
@@ -2,302 +2,313 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { IL1BlockContainer } from "./IL1BlockContainer.sol";
|
||||
import { IL1GasPriceOracle } from "./IL1GasPriceOracle.sol";
|
||||
import {IL1BlockContainer} from "./IL1BlockContainer.sol";
|
||||
import {IL1GasPriceOracle} from "./IL1GasPriceOracle.sol";
|
||||
|
||||
import { OwnableBase } from "../../libraries/common/OwnableBase.sol";
|
||||
import { IWhitelist } from "../../libraries/common/IWhitelist.sol";
|
||||
import { ScrollPredeploy } from "../../libraries/constants/ScrollPredeploy.sol";
|
||||
import {OwnableBase} from "../../libraries/common/OwnableBase.sol";
|
||||
import {IWhitelist} from "../../libraries/common/IWhitelist.sol";
|
||||
import {ScrollPredeploy} from "../../libraries/constants/ScrollPredeploy.sol";
|
||||
|
||||
/// @title L1BlockContainer
|
||||
/// @notice This contract will maintain the list of blocks proposed in L1.
|
||||
contract L1BlockContainer is OwnableBase, IL1BlockContainer {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* 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 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 *
|
||||
***********/
|
||||
/***********
|
||||
* 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");
|
||||
/// @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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
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))
|
||||
/// @notice The address of whitelist contract.
|
||||
IWhitelist public whitelist;
|
||||
|
||||
// 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
|
||||
}
|
||||
// @todo change to ring buffer to save gas usage.
|
||||
|
||||
// 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
|
||||
}
|
||||
/// @inheritdoc IL1BlockContainer
|
||||
bytes32 public override latestBlockHash;
|
||||
|
||||
// 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()
|
||||
}
|
||||
/// @notice Mapping from block hash to corresponding state root.
|
||||
mapping(bytes32 => bytes32) public stateRoot;
|
||||
|
||||
// load the extended length
|
||||
valueOffset := add(ptr, 1)
|
||||
let extendedLen := calldataload(valueOffset)
|
||||
let bits := sub(256, mul(lengthBytes, 8))
|
||||
extendedLen := shr(bits, extendedLen)
|
||||
/// @notice Mapping from block hash to corresponding block metadata,
|
||||
/// including timestamp and height.
|
||||
mapping(bytes32 => BlockMetadata) public metadata;
|
||||
|
||||
dataLen := extendedLen
|
||||
valueOffset := add(valueOffset, lengthBytes)
|
||||
leave
|
||||
}
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
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))
|
||||
constructor(address _owner) {
|
||||
_transferOwnership(_owner);
|
||||
}
|
||||
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);
|
||||
function initialize(
|
||||
bytes32 _startBlockHash,
|
||||
uint64 _startBlockHeight,
|
||||
uint64 _startBlockTimestamp,
|
||||
uint128 _startBlockBaseFee,
|
||||
bytes32 _startStateRoot
|
||||
) external onlyOwner {
|
||||
require(latestBlockHash == bytes32(0), "already initialized");
|
||||
|
||||
emit ImportBlock(_blockHash, _height, _timestamp, _baseFee, _stateRoot);
|
||||
latestBlockHash = _startBlockHash;
|
||||
stateRoot[_startBlockHash] = _startStateRoot;
|
||||
metadata[_startBlockHash] = BlockMetadata(_startBlockHeight, _startBlockTimestamp, _startBlockBaseFee);
|
||||
|
||||
if (_updateGasPriceOracle) {
|
||||
IL1GasPriceOracle(ScrollPredeploy.L1_GAS_PRICE_ORACLE).setL1BaseFee(_baseFee);
|
||||
emit ImportBlock(_startBlockHash, _startBlockHeight, _startBlockTimestamp, _startBlockBaseFee, _startStateRoot);
|
||||
}
|
||||
}
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
/*************************
|
||||
* Public View 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);
|
||||
/// @inheritdoc IL1BlockContainer
|
||||
function latestBaseFee() external view override returns (uint256) {
|
||||
return metadata[latestBlockHash].baseFee;
|
||||
}
|
||||
|
||||
whitelist = IWhitelist(_newWhitelist);
|
||||
emit UpdateWhitelist(_oldWhitelist, _newWhitelist);
|
||||
}
|
||||
/// @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 Mutating 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,133 +2,133 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { OwnableBase } from "../../libraries/common/OwnableBase.sol";
|
||||
import { IWhitelist } from "../../libraries/common/IWhitelist.sol";
|
||||
import {OwnableBase} from "../../libraries/common/OwnableBase.sol";
|
||||
import {IWhitelist} from "../../libraries/common/IWhitelist.sol";
|
||||
|
||||
import { IL1BlockContainer } from "./IL1BlockContainer.sol";
|
||||
import { IL1GasPriceOracle } from "./IL1GasPriceOracle.sol";
|
||||
import {IL1BlockContainer} from "./IL1BlockContainer.sol";
|
||||
import {IL1GasPriceOracle} from "./IL1GasPriceOracle.sol";
|
||||
|
||||
contract L1GasPriceOracle is OwnableBase, IL1GasPriceOracle {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* 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 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 *
|
||||
*************/
|
||||
/*************
|
||||
* Constants *
|
||||
*************/
|
||||
|
||||
/// @dev The precision used in the scalar.
|
||||
uint256 private constant PRECISION = 1e9;
|
||||
/// @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 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;
|
||||
/// @dev The maximum possible l1 fee scale.
|
||||
/// x1000 should be enough.
|
||||
uint256 private constant MAX_SCALE = 1000 * PRECISION;
|
||||
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
/// @inheritdoc IL1GasPriceOracle
|
||||
uint256 public l1BaseFee;
|
||||
/// @inheritdoc IL1GasPriceOracle
|
||||
uint256 public l1BaseFee;
|
||||
|
||||
/// @inheritdoc IL1GasPriceOracle
|
||||
uint256 public override overhead;
|
||||
/// @inheritdoc IL1GasPriceOracle
|
||||
uint256 public override overhead;
|
||||
|
||||
/// @inheritdoc IL1GasPriceOracle
|
||||
uint256 public override scalar;
|
||||
/// @inheritdoc IL1GasPriceOracle
|
||||
uint256 public override scalar;
|
||||
|
||||
/// @notice The address of whitelist contract.
|
||||
IWhitelist public whitelist;
|
||||
/// @notice The address of whitelist contract.
|
||||
IWhitelist public whitelist;
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
/***************
|
||||
* 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);
|
||||
constructor(address _owner) {
|
||||
_transferOwnership(_owner);
|
||||
}
|
||||
}
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/// @inheritdoc IL1GasPriceOracle
|
||||
function setL1BaseFee(uint256 _l1BaseFee) external override {
|
||||
require(whitelist.isSenderAllowed(msg.sender), "Not whitelisted sender");
|
||||
/// @inheritdoc IL1GasPriceOracle
|
||||
function getL1Fee(bytes memory _data) external view override returns (uint256) {
|
||||
uint256 _l1GasUsed = getL1GasUsed(_data);
|
||||
uint256 _l1Fee = _l1GasUsed * l1BaseFee;
|
||||
return (_l1Fee * scalar) / PRECISION;
|
||||
}
|
||||
|
||||
l1BaseFee = _l1BaseFee;
|
||||
/// @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);
|
||||
}
|
||||
}
|
||||
|
||||
emit L1BaseFeeUpdated(_l1BaseFee);
|
||||
}
|
||||
/*****************************
|
||||
* Public Mutating Functions *
|
||||
*****************************/
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
/// @inheritdoc IL1GasPriceOracle
|
||||
function setL1BaseFee(uint256 _l1BaseFee) external override {
|
||||
require(whitelist.isSenderAllowed(msg.sender), "Not whitelisted sender");
|
||||
|
||||
/// @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");
|
||||
l1BaseFee = _l1BaseFee;
|
||||
|
||||
overhead = _overhead;
|
||||
emit OverheadUpdated(_overhead);
|
||||
}
|
||||
emit L1BaseFeeUpdated(_l1BaseFee);
|
||||
}
|
||||
|
||||
/// Allows the owner to modify the scalar.
|
||||
/// @param _scalar New scalar
|
||||
function setScalar(uint256 _scalar) external onlyOwner {
|
||||
require(_scalar <= MAX_SCALE, "exceed maximum scale");
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
scalar = _scalar;
|
||||
emit ScalarUpdated(_scalar);
|
||||
}
|
||||
/// @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");
|
||||
|
||||
/// @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);
|
||||
overhead = _overhead;
|
||||
emit OverheadUpdated(_overhead);
|
||||
}
|
||||
|
||||
whitelist = IWhitelist(_newWhitelist);
|
||||
emit UpdateWhitelist(_oldWhitelist, _newWhitelist);
|
||||
}
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { AppendOnlyMerkleTree } from "../../libraries/common/AppendOnlyMerkleTree.sol";
|
||||
import { OwnableBase } from "../../libraries/common/OwnableBase.sol";
|
||||
import {AppendOnlyMerkleTree} from "../../libraries/common/AppendOnlyMerkleTree.sol";
|
||||
import {OwnableBase} from "../../libraries/common/OwnableBase.sol";
|
||||
|
||||
/// @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).
|
||||
@@ -12,41 +12,41 @@ import { OwnableBase } from "../../libraries/common/OwnableBase.sol";
|
||||
/// _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 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;
|
||||
/// @notice The address of L2ScrollMessenger contract.
|
||||
address public messenger;
|
||||
|
||||
constructor(address _owner) {
|
||||
_transferOwnership(_owner);
|
||||
}
|
||||
constructor(address _owner) {
|
||||
_transferOwnership(_owner);
|
||||
}
|
||||
|
||||
function initialize() external {
|
||||
_initializeMerkleTree();
|
||||
}
|
||||
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");
|
||||
/// @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);
|
||||
(uint256 _currentNonce, bytes32 _currentRoot) = _appendMessageHash(_messageHash);
|
||||
|
||||
// We can use the event to compute the merkle tree locally.
|
||||
emit AppendMessage(_currentNonce, _messageHash);
|
||||
// We can use the event to compute the merkle tree locally.
|
||||
emit AppendMessage(_currentNonce, _messageHash);
|
||||
|
||||
return _currentRoot;
|
||||
}
|
||||
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");
|
||||
/// @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;
|
||||
}
|
||||
messenger = _messenger;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { FeeVault } from "../../libraries/FeeVault.sol";
|
||||
import {FeeVault} from "../../libraries/FeeVault.sol";
|
||||
|
||||
/// @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) {}
|
||||
/// @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) {}
|
||||
}
|
||||
|
||||
@@ -20,82 +20,82 @@ pragma solidity ^0.8.0;
|
||||
// solhint-disable reason-string
|
||||
|
||||
contract WETH9 {
|
||||
string public name = "Wrapped Ether";
|
||||
string public symbol = "WETH";
|
||||
uint8 public decimals = 18;
|
||||
string public name = "Wrapped Ether";
|
||||
string public symbol = "WETH";
|
||||
uint8 public decimals = 18;
|
||||
|
||||
event Approval(address indexed src, address indexed guy, uint256 wad);
|
||||
event Transfer(address indexed src, address indexed dst, uint256 wad);
|
||||
event Deposit(address indexed dst, uint256 wad);
|
||||
event Withdrawal(address indexed src, uint256 wad);
|
||||
event Approval(address indexed src, address indexed guy, uint256 wad);
|
||||
event Transfer(address indexed src, address indexed dst, uint256 wad);
|
||||
event Deposit(address indexed dst, uint256 wad);
|
||||
event Withdrawal(address indexed src, uint256 wad);
|
||||
|
||||
mapping(address => uint256) public balanceOf;
|
||||
mapping(address => mapping(address => uint256)) public allowance;
|
||||
mapping(address => uint256) public balanceOf;
|
||||
mapping(address => mapping(address => uint256)) public allowance;
|
||||
|
||||
receive() external payable {
|
||||
deposit();
|
||||
}
|
||||
|
||||
function deposit() public payable {
|
||||
unchecked {
|
||||
balanceOf[msg.sender] += msg.value;
|
||||
receive() external payable {
|
||||
deposit();
|
||||
}
|
||||
|
||||
emit Deposit(msg.sender, msg.value);
|
||||
}
|
||||
function deposit() public payable {
|
||||
unchecked {
|
||||
balanceOf[msg.sender] += msg.value;
|
||||
}
|
||||
|
||||
function withdraw(uint256 wad) public {
|
||||
require(balanceOf[msg.sender] >= wad);
|
||||
|
||||
unchecked {
|
||||
balanceOf[msg.sender] -= wad;
|
||||
emit Deposit(msg.sender, msg.value);
|
||||
}
|
||||
|
||||
payable(msg.sender).transfer(wad);
|
||||
function withdraw(uint256 wad) public {
|
||||
require(balanceOf[msg.sender] >= wad);
|
||||
|
||||
emit Withdrawal(msg.sender, wad);
|
||||
}
|
||||
unchecked {
|
||||
balanceOf[msg.sender] -= wad;
|
||||
}
|
||||
|
||||
function totalSupply() public view returns (uint256) {
|
||||
return address(this).balance;
|
||||
}
|
||||
payable(msg.sender).transfer(wad);
|
||||
|
||||
function approve(address guy, uint256 wad) public returns (bool) {
|
||||
allowance[msg.sender][guy] = wad;
|
||||
|
||||
emit Approval(msg.sender, guy, wad);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function transfer(address dst, uint256 wad) public returns (bool) {
|
||||
return transferFrom(msg.sender, dst, wad);
|
||||
}
|
||||
|
||||
function transferFrom(
|
||||
address src,
|
||||
address dst,
|
||||
uint256 wad
|
||||
) public returns (bool) {
|
||||
require(balanceOf[src] >= wad);
|
||||
|
||||
if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) {
|
||||
require(allowance[src][msg.sender] >= wad);
|
||||
|
||||
unchecked {
|
||||
allowance[src][msg.sender] -= wad;
|
||||
}
|
||||
emit Withdrawal(msg.sender, wad);
|
||||
}
|
||||
|
||||
unchecked {
|
||||
balanceOf[src] -= wad;
|
||||
balanceOf[dst] += wad;
|
||||
function totalSupply() public view returns (uint256) {
|
||||
return address(this).balance;
|
||||
}
|
||||
|
||||
emit Transfer(src, dst, wad);
|
||||
function approve(address guy, uint256 wad) public returns (bool) {
|
||||
allowance[msg.sender][guy] = wad;
|
||||
|
||||
return true;
|
||||
}
|
||||
emit Approval(msg.sender, guy, wad);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function transfer(address dst, uint256 wad) public returns (bool) {
|
||||
return transferFrom(msg.sender, dst, wad);
|
||||
}
|
||||
|
||||
function transferFrom(
|
||||
address src,
|
||||
address dst,
|
||||
uint256 wad
|
||||
) public returns (bool) {
|
||||
require(balanceOf[src] >= wad);
|
||||
|
||||
if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) {
|
||||
require(allowance[src][msg.sender] >= wad);
|
||||
|
||||
unchecked {
|
||||
allowance[src][msg.sender] -= wad;
|
||||
}
|
||||
}
|
||||
|
||||
unchecked {
|
||||
balanceOf[src] -= wad;
|
||||
balanceOf[dst] += wad;
|
||||
}
|
||||
|
||||
emit Transfer(src, dst, wad);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -2,34 +2,34 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { OwnableBase } from "../../libraries/common/OwnableBase.sol";
|
||||
import { IWhitelist } from "../../libraries/common/IWhitelist.sol";
|
||||
import {OwnableBase} from "../../libraries/common/OwnableBase.sol";
|
||||
import {IWhitelist} from "../../libraries/common/IWhitelist.sol";
|
||||
|
||||
contract Whitelist is OwnableBase, IWhitelist {
|
||||
/// @notice Emitted when account whitelist status changed.
|
||||
/// @param _account The address of account whose status is changed.
|
||||
/// @param _status The current whitelist status.
|
||||
event WhitelistStatusChanged(address indexed _account, bool _status);
|
||||
/// @notice Emitted when account whitelist status changed.
|
||||
/// @param _account The address of account whose status is changed.
|
||||
/// @param _status The current whitelist status.
|
||||
event WhitelistStatusChanged(address indexed _account, bool _status);
|
||||
|
||||
/// @notice Keep track whether the account is whitelisted.
|
||||
mapping(address => bool) private isWhitelisted;
|
||||
/// @notice Keep track whether the account is whitelisted.
|
||||
mapping(address => bool) private isWhitelisted;
|
||||
|
||||
constructor(address _owner) {
|
||||
owner = _owner;
|
||||
}
|
||||
|
||||
/// @notice See {IWhitelist-isSenderAllowed}
|
||||
function isSenderAllowed(address _sender) external view returns (bool) {
|
||||
return isWhitelisted[_sender];
|
||||
}
|
||||
|
||||
/// @notice Update the whitelist status
|
||||
/// @param _accounts The list of addresses to update.
|
||||
/// @param _status The whitelist status to update.
|
||||
function updateWhitelistStatus(address[] memory _accounts, bool _status) external onlyOwner {
|
||||
for (uint256 i = 0; i < _accounts.length; i++) {
|
||||
isWhitelisted[_accounts[i]] = _status;
|
||||
emit WhitelistStatusChanged(_accounts[i], _status);
|
||||
constructor(address _owner) {
|
||||
owner = _owner;
|
||||
}
|
||||
|
||||
/// @notice See {IWhitelist-isSenderAllowed}
|
||||
function isSenderAllowed(address _sender) external view returns (bool) {
|
||||
return isWhitelisted[_sender];
|
||||
}
|
||||
|
||||
/// @notice Update the whitelist status
|
||||
/// @param _accounts The list of addresses to update.
|
||||
/// @param _status The whitelist status to update.
|
||||
function updateWhitelistStatus(address[] memory _accounts, bool _status) external onlyOwner {
|
||||
for (uint256 i = 0; i < _accounts.length; i++) {
|
||||
isWhitelisted[_accounts[i]] = _status;
|
||||
emit WhitelistStatusChanged(_accounts[i], _status);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
interface IERC20Metadata {
|
||||
function symbol() external view returns (string memory);
|
||||
function symbol() external view returns (string memory);
|
||||
|
||||
function name() external view returns (string memory);
|
||||
function name() external view returns (string memory);
|
||||
|
||||
function decimals() external view returns (uint8);
|
||||
function decimals() external view returns (uint8);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
interface IWETH {
|
||||
function deposit() external payable;
|
||||
function deposit() external payable;
|
||||
|
||||
function withdraw(uint256 wad) external;
|
||||
function withdraw(uint256 wad) external;
|
||||
}
|
||||
|
||||
@@ -25,85 +25,88 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { IL2ScrollMessenger } from "../L2/IL2ScrollMessenger.sol";
|
||||
import { OwnableBase } from "./common/OwnableBase.sol";
|
||||
import {IL2ScrollMessenger} from "../L2/IL2ScrollMessenger.sol";
|
||||
import {OwnableBase} from "./common/OwnableBase.sol";
|
||||
|
||||
/// @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 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 Minimum balance before a withdrawal can be triggered.
|
||||
uint256 public minWithdrawAmount;
|
||||
|
||||
/// @notice Scroll L2 messenger address.
|
||||
address public messenger;
|
||||
/// @notice Scroll L2 messenger address.
|
||||
address public messenger;
|
||||
|
||||
/// @notice Wallet that will receive the fees on L1.
|
||||
address public recipient;
|
||||
/// @notice Wallet that will receive the fees on L1.
|
||||
address public recipient;
|
||||
|
||||
/// @notice Total amount of wei processed by the contract.
|
||||
uint256 public totalProcessed;
|
||||
/// @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);
|
||||
/// @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;
|
||||
minWithdrawAmount = _minWithdrawalAmount;
|
||||
recipient = _recipient;
|
||||
}
|
||||
|
||||
emit Withdrawal(value, recipient, msg.sender);
|
||||
/// @notice Allow the contract to receive ETH.
|
||||
receive() external payable {}
|
||||
|
||||
// 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 Triggers a withdrawal of funds to the L1 fee wallet.
|
||||
function withdraw() external {
|
||||
uint256 value = address(this).balance;
|
||||
|
||||
/// @notice Update the address of messenger.
|
||||
/// @param _messenger The address of messenger to update.
|
||||
function updateMessenger(address _messenger) external onlyOwner {
|
||||
messenger = _messenger;
|
||||
}
|
||||
require(
|
||||
value >= minWithdrawAmount,
|
||||
"FeeVault: withdrawal amount must be greater than minimum withdrawal amount"
|
||||
);
|
||||
|
||||
/// @notice Update the address of recipient.
|
||||
/// @param _recipient The address of recipient to update.
|
||||
function updateRecipient(address _recipient) external onlyOwner {
|
||||
recipient = _recipient;
|
||||
}
|
||||
unchecked {
|
||||
totalProcessed += value;
|
||||
}
|
||||
|
||||
/// @notice Update the minimum withdraw amount.
|
||||
/// @param _minWithdrawAmount The minimum withdraw amount to update.
|
||||
function updateMinWithdrawAmount(uint256 _minWithdrawAmount) external onlyOwner {
|
||||
minWithdrawAmount = _minWithdrawAmount;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,54 +3,68 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
interface IScrollMessenger {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* 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 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 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);
|
||||
/// @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 *
|
||||
*************************/
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/// @notice Return the sender of a cross domain message.
|
||||
function xDomainMessageSender() external view returns (address);
|
||||
/// @notice Return the sender of a cross domain message.
|
||||
function xDomainMessageSender() external view returns (address);
|
||||
|
||||
/****************************
|
||||
* Public Mutated Functions *
|
||||
****************************/
|
||||
/*****************************
|
||||
* Public Mutating 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;
|
||||
/// @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;
|
||||
|
||||
/// @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.
|
||||
/// @param refundAddress The address of account who will receive the refunded fee.
|
||||
function sendMessage(
|
||||
address target,
|
||||
uint256 value,
|
||||
bytes calldata message,
|
||||
uint256 gasLimit,
|
||||
address refundAddress
|
||||
) external payable;
|
||||
}
|
||||
|
||||
@@ -2,120 +2,128 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
|
||||
import { IWhitelist } from "./common/IWhitelist.sol";
|
||||
import { ScrollConstants } from "./constants/ScrollConstants.sol";
|
||||
import { IScrollMessenger } from "./IScrollMessenger.sol";
|
||||
import {IWhitelist} from "./common/IWhitelist.sol";
|
||||
import {ScrollConstants} from "./constants/ScrollConstants.sol";
|
||||
import {IScrollMessenger} from "./IScrollMessenger.sol";
|
||||
|
||||
abstract contract ScrollMessengerBase is OwnableUpgradeable, IScrollMessenger {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* 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 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 owner updates fee vault contract.
|
||||
/// @param _oldFeeVault The address of old fee vault contract.
|
||||
/// @param _newFeeVault The address of new fee vault contract.
|
||||
event UpdateFeeVault(address _oldFeeVault, address _newFeeVault);
|
||||
/// @notice Emitted when owner updates fee vault contract.
|
||||
/// @param _oldFeeVault The address of old fee vault contract.
|
||||
/// @param _newFeeVault The address of new fee vault contract.
|
||||
event UpdateFeeVault(address _oldFeeVault, address _newFeeVault);
|
||||
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
/*************
|
||||
* Constants *
|
||||
*************/
|
||||
|
||||
/// @notice See {IScrollMessenger-xDomainMessageSender}
|
||||
address public override xDomainMessageSender;
|
||||
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.5.0/contracts/security/ReentrancyGuard.sol
|
||||
uint256 internal constant _NOT_ENTERED = 1;
|
||||
uint256 internal constant _ENTERED = 2;
|
||||
|
||||
/// @notice The whitelist contract to track the sender who can call `sendMessage` in ScrollMessenger.
|
||||
address public whitelist;
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
/// @notice The address of counterpart ScrollMessenger contract in L1/L2.
|
||||
address public counterpart;
|
||||
/// @notice See {IScrollMessenger-xDomainMessageSender}
|
||||
address public override xDomainMessageSender;
|
||||
|
||||
/// @notice The address of fee vault, collecting cross domain messaging fee.
|
||||
address public feeVault;
|
||||
/// @notice The whitelist contract to track the sender who can call `sendMessage` in ScrollMessenger.
|
||||
address public whitelist;
|
||||
|
||||
/**********************
|
||||
* Function Modifiers *
|
||||
**********************/
|
||||
/// @notice The address of counterpart ScrollMessenger contract in L1/L2.
|
||||
address public counterpart;
|
||||
|
||||
modifier onlyWhitelistedSender(address _sender) {
|
||||
address _whitelist = whitelist;
|
||||
require(_whitelist == address(0) || IWhitelist(_whitelist).isSenderAllowed(_sender), "sender not whitelisted");
|
||||
_;
|
||||
}
|
||||
/// @notice The address of fee vault, collecting cross domain messaging fee.
|
||||
address public feeVault;
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
/**********************
|
||||
* Function Modifiers *
|
||||
**********************/
|
||||
|
||||
function _initialize(address _counterpart, address _feeVault) internal {
|
||||
OwnableUpgradeable.__Ownable_init();
|
||||
modifier onlyWhitelistedSender(address _sender) {
|
||||
address _whitelist = whitelist;
|
||||
require(_whitelist == address(0) || IWhitelist(_whitelist).isSenderAllowed(_sender), "sender not whitelisted");
|
||||
_;
|
||||
}
|
||||
|
||||
// initialize to a nonzero value
|
||||
xDomainMessageSender = ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER;
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
counterpart = _counterpart;
|
||||
feeVault = _feeVault;
|
||||
}
|
||||
function _initialize(address _counterpart, address _feeVault) internal {
|
||||
OwnableUpgradeable.__Ownable_init();
|
||||
|
||||
// allow others to send ether to messenger
|
||||
receive() external payable {}
|
||||
// initialize to a nonzero value
|
||||
xDomainMessageSender = ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER;
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
counterpart = _counterpart;
|
||||
feeVault = _feeVault;
|
||||
}
|
||||
|
||||
/// @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 = whitelist;
|
||||
// allow others to send ether to messenger
|
||||
receive() external payable {}
|
||||
|
||||
whitelist = _newWhitelist;
|
||||
emit UpdateWhitelist(_oldWhitelist, _newWhitelist);
|
||||
}
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
/// @notice Update fee vault contract.
|
||||
/// @dev This function can only called by contract owner.
|
||||
/// @param _newFeeVault The address of new fee vault contract.
|
||||
function updateFeeVault(address _newFeeVault) external onlyOwner {
|
||||
address _oldFeeVault = whitelist;
|
||||
/// @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 = whitelist;
|
||||
|
||||
feeVault = _newFeeVault;
|
||||
emit UpdateFeeVault(_oldFeeVault, _newFeeVault);
|
||||
}
|
||||
whitelist = _newWhitelist;
|
||||
emit UpdateWhitelist(_oldWhitelist, _newWhitelist);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
/// @notice Update fee vault contract.
|
||||
/// @dev This function can only called by contract owner.
|
||||
/// @param _newFeeVault The address of new fee vault contract.
|
||||
function updateFeeVault(address _newFeeVault) external onlyOwner {
|
||||
address _oldFeeVault = whitelist;
|
||||
|
||||
/// @dev Internal function to generate the correct cross domain calldata for a message.
|
||||
/// @param _sender Message sender address.
|
||||
/// @param _target Target contract address.
|
||||
/// @param _value The amount of ETH pass to the target.
|
||||
/// @param _messageNonce Nonce for the provided message.
|
||||
/// @param _message Message to send to the target.
|
||||
/// @return ABI encoded cross domain calldata.
|
||||
function _encodeXDomainCalldata(
|
||||
address _sender,
|
||||
address _target,
|
||||
uint256 _value,
|
||||
uint256 _messageNonce,
|
||||
bytes memory _message
|
||||
) internal pure returns (bytes memory) {
|
||||
return
|
||||
abi.encodeWithSignature(
|
||||
"relayMessage(address,address,uint256,uint256,bytes)",
|
||||
_sender,
|
||||
_target,
|
||||
_value,
|
||||
_messageNonce,
|
||||
_message
|
||||
);
|
||||
}
|
||||
feeVault = _newFeeVault;
|
||||
emit UpdateFeeVault(_oldFeeVault, _newFeeVault);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Internal Functions *
|
||||
**********************/
|
||||
|
||||
/// @dev Internal function to generate the correct cross domain calldata for a message.
|
||||
/// @param _sender Message sender address.
|
||||
/// @param _target Target contract address.
|
||||
/// @param _value The amount of ETH pass to the target.
|
||||
/// @param _messageNonce Nonce for the provided message.
|
||||
/// @param _message Message to send to the target.
|
||||
/// @return ABI encoded cross domain calldata.
|
||||
function _encodeXDomainCalldata(
|
||||
address _sender,
|
||||
address _target,
|
||||
uint256 _value,
|
||||
uint256 _messageNonce,
|
||||
bytes memory _message
|
||||
) internal pure returns (bytes memory) {
|
||||
return
|
||||
abi.encodeWithSignature(
|
||||
"relayMessage(address,address,uint256,uint256,bytes)",
|
||||
_sender,
|
||||
_target,
|
||||
_value,
|
||||
_messageNonce,
|
||||
_message
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,25 +3,25 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
library AddressAliasHelper {
|
||||
uint160 constant offset = uint160(0x1111000000000000000000000000000000001111);
|
||||
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 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);
|
||||
/// @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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,69 +3,69 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
abstract contract AppendOnlyMerkleTree {
|
||||
/// @dev The maximum height of the withdraw merkle tree.
|
||||
uint256 private constant MAX_TREE_HEIGHT = 40;
|
||||
/// @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 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 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 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;
|
||||
/// @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 _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.
|
||||
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;
|
||||
// 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;
|
||||
messageRoot = _hash;
|
||||
|
||||
_currentMessageIndex = nextMessageIndex;
|
||||
unchecked {
|
||||
nextMessageIndex = _currentMessageIndex + 1;
|
||||
}
|
||||
|
||||
return (_currentMessageIndex, _hash);
|
||||
}
|
||||
|
||||
branches[_height] = _hash;
|
||||
messageRoot = _hash;
|
||||
|
||||
_currentMessageIndex = nextMessageIndex;
|
||||
unchecked {
|
||||
nextMessageIndex = _currentMessageIndex + 1;
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
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);
|
||||
/// @notice Check whether the sender is allowed to do something.
|
||||
/// @param _sender The address of sender.
|
||||
function isSenderAllowed(address _sender) external view returns (bool);
|
||||
}
|
||||
|
||||
@@ -3,61 +3,61 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
abstract contract OwnableBase {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* 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);
|
||||
/// @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 *
|
||||
*************/
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
/// @notice The address of the current owner.
|
||||
address public owner;
|
||||
/// @notice The address of the current owner.
|
||||
address public owner;
|
||||
|
||||
/**********************
|
||||
* Function Modifiers *
|
||||
**********************/
|
||||
/**********************
|
||||
* Function Modifiers *
|
||||
**********************/
|
||||
|
||||
/// @dev Throws if called by any account other than the owner.
|
||||
modifier onlyOwner() {
|
||||
require(owner == msg.sender, "caller is not the owner");
|
||||
_;
|
||||
}
|
||||
/// @dev Throws if called by any account other than the owner.
|
||||
modifier onlyOwner() {
|
||||
require(owner == msg.sender, "caller is not the owner");
|
||||
_;
|
||||
}
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
/************************
|
||||
* 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 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);
|
||||
}
|
||||
/// @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 *
|
||||
**********************/
|
||||
/**********************
|
||||
* 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);
|
||||
}
|
||||
/// @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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
library ScrollConstants {
|
||||
/// @notice The address of default cross chain message sender.
|
||||
address internal constant DEFAULT_XDOMAIN_MESSAGE_SENDER = address(1);
|
||||
/// @notice The address of default cross chain message sender.
|
||||
address internal constant DEFAULT_XDOMAIN_MESSAGE_SENDER = address(1);
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
library ScrollPredeploy {
|
||||
address internal constant L1_MESSAGE_QUEUE = 0x5300000000000000000000000000000000000000;
|
||||
address internal constant L1_MESSAGE_QUEUE = 0x5300000000000000000000000000000000000000;
|
||||
|
||||
address internal constant L1_BLOCK_CONTAINER = 0x5300000000000000000000000000000000000001;
|
||||
address internal constant L1_BLOCK_CONTAINER = 0x5300000000000000000000000000000000000001;
|
||||
|
||||
address internal constant L1_GAS_PRICE_ORACLE = 0x5300000000000000000000000000000000000002;
|
||||
}
|
||||
address internal constant L1_GAS_PRICE_ORACLE = 0x5300000000000000000000000000000000000002;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
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 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 L1GatewayRouter/L2GatewayRouter contract.
|
||||
function router() external view returns (address);
|
||||
|
||||
/// @notice The address of corresponding L1ScrollMessenger/L2ScrollMessenger contract.
|
||||
function messenger() external view returns (address);
|
||||
/// @notice The address of corresponding L1ScrollMessenger/L2ScrollMessenger contract.
|
||||
function messenger() external view returns (address);
|
||||
}
|
||||
|
||||
@@ -2,85 +2,85 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { IScrollGateway } from "./IScrollGateway.sol";
|
||||
import { IScrollMessenger } from "../IScrollMessenger.sol";
|
||||
import {IScrollGateway} from "./IScrollGateway.sol";
|
||||
import {IScrollMessenger} from "../IScrollMessenger.sol";
|
||||
|
||||
abstract contract ScrollGatewayBase is IScrollGateway {
|
||||
/*************
|
||||
* Constants *
|
||||
*************/
|
||||
/*************
|
||||
* 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;
|
||||
// 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 *
|
||||
*************/
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
/// @inheritdoc IScrollGateway
|
||||
address public override counterpart;
|
||||
/// @inheritdoc IScrollGateway
|
||||
address public override counterpart;
|
||||
|
||||
/// @inheritdoc IScrollGateway
|
||||
address public override router;
|
||||
/// @inheritdoc IScrollGateway
|
||||
address public override router;
|
||||
|
||||
/// @inheritdoc IScrollGateway
|
||||
address public override messenger;
|
||||
/// @inheritdoc IScrollGateway
|
||||
address public override messenger;
|
||||
|
||||
/// @dev The status of for non-reentrant check.
|
||||
uint256 private _status;
|
||||
/// @dev The status of for non-reentrant check.
|
||||
uint256 private _status;
|
||||
|
||||
/**********************
|
||||
* Function Modifiers *
|
||||
**********************/
|
||||
/**********************
|
||||
* Function Modifiers *
|
||||
**********************/
|
||||
|
||||
modifier nonReentrant() {
|
||||
// On the first call to nonReentrant, _notEntered will be true
|
||||
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
|
||||
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;
|
||||
// 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;
|
||||
// By storing the original value once again, a refund is triggered (see
|
||||
// https://eips.ethereum.org/EIPS/eip-2200)
|
||||
_status = _NOT_ENTERED;
|
||||
}
|
||||
|
||||
// for reentrancy guard
|
||||
_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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
interface IGasOracle {
|
||||
/// @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 estimateMessageFee(
|
||||
address _sender,
|
||||
address _to,
|
||||
bytes memory _message,
|
||||
uint256 _gasLimit
|
||||
) external view returns (uint256);
|
||||
/// @notice Estimate fee for cross chain message call.
|
||||
/// @param _sender The address of sender who invoke the call.
|
||||
/// @param _to The target address to receive the call.
|
||||
/// @param _message The message will be passed to the target address.
|
||||
function estimateMessageFee(
|
||||
address _sender,
|
||||
address _to,
|
||||
bytes memory _message,
|
||||
uint256 _gasLimit
|
||||
) external view returns (uint256);
|
||||
}
|
||||
|
||||
@@ -2,101 +2,101 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
|
||||
import "./IGasOracle.sol";
|
||||
|
||||
/// @title Simple Gas Oracle
|
||||
contract SimpleGasOracle is OwnableUpgradeable, IGasOracle {
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
/**********
|
||||
* Events *
|
||||
**********/
|
||||
|
||||
/// @notice Emitted when owner update default FeeConfig.
|
||||
/// @param _baseFees The amount base fee to pay.
|
||||
/// @param _feesPerByte The amount fee to pay per message byte.
|
||||
event UpdateDefaultFeeConfig(uint256 _baseFees, uint256 _feesPerByte);
|
||||
/// @notice Emitted when owner update default FeeConfig.
|
||||
/// @param _baseFees The amount base fee to pay.
|
||||
/// @param _feesPerByte The amount fee to pay per message byte.
|
||||
event UpdateDefaultFeeConfig(uint256 _baseFees, uint256 _feesPerByte);
|
||||
|
||||
/// @notice Emitted when owner update custom FeeConfig.
|
||||
/// @param _sender The address of custom message sender.
|
||||
/// @param _baseFees The amount base fee to pay.
|
||||
/// @param _feesPerByte The amount fee to pay per message byte.
|
||||
event UpdateCustomFeeConfig(address indexed _sender, uint256 _baseFees, uint256 _feesPerByte);
|
||||
/// @notice Emitted when owner update custom FeeConfig.
|
||||
/// @param _sender The address of custom message sender.
|
||||
/// @param _baseFees The amount base fee to pay.
|
||||
/// @param _feesPerByte The amount fee to pay per message byte.
|
||||
event UpdateCustomFeeConfig(address indexed _sender, uint256 _baseFees, uint256 _feesPerByte);
|
||||
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
/*************
|
||||
* Variables *
|
||||
*************/
|
||||
|
||||
struct FeeConfig {
|
||||
uint128 baseFees;
|
||||
uint128 feesPerByte;
|
||||
}
|
||||
|
||||
/// @notice The default cross chain message FeeConfig.
|
||||
FeeConfig public defaultFeeConfig;
|
||||
|
||||
/// @notice Mapping from sender address to custom FeeConfig.
|
||||
mapping(address => FeeConfig) public customFeeConfig;
|
||||
|
||||
/// @notice Whether the sender should user custom FeeConfig.
|
||||
mapping(address => bool) public hasCustomConfig;
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
function initialize() external initializer {
|
||||
OwnableUpgradeable.__Ownable_init();
|
||||
}
|
||||
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/// @inheritdoc IGasOracle
|
||||
function estimateMessageFee(
|
||||
address _sender,
|
||||
address,
|
||||
bytes memory _message,
|
||||
uint256
|
||||
) external view override returns (uint256) {
|
||||
FeeConfig memory _feeConfig;
|
||||
if (hasCustomConfig[_sender]) {
|
||||
_feeConfig = customFeeConfig[_sender];
|
||||
} else {
|
||||
_feeConfig = defaultFeeConfig;
|
||||
struct FeeConfig {
|
||||
uint128 baseFees;
|
||||
uint128 feesPerByte;
|
||||
}
|
||||
|
||||
unchecked {
|
||||
return _feeConfig.baseFees + uint256(_feeConfig.feesPerByte) * _message.length;
|
||||
/// @notice The default cross chain message FeeConfig.
|
||||
FeeConfig public defaultFeeConfig;
|
||||
|
||||
/// @notice Mapping from sender address to custom FeeConfig.
|
||||
mapping(address => FeeConfig) public customFeeConfig;
|
||||
|
||||
/// @notice Whether the sender should user custom FeeConfig.
|
||||
mapping(address => bool) public hasCustomConfig;
|
||||
|
||||
/***************
|
||||
* Constructor *
|
||||
***************/
|
||||
|
||||
function initialize() external initializer {
|
||||
OwnableUpgradeable.__Ownable_init();
|
||||
}
|
||||
}
|
||||
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
/*************************
|
||||
* Public View Functions *
|
||||
*************************/
|
||||
|
||||
/// @notice Update default fee config.
|
||||
/// @param _baseFees The amount of baseFees to update.
|
||||
/// @param _feesPerByte The amount of fees per byte to update.
|
||||
function updateDefaultFeeConfig(uint128 _baseFees, uint128 _feesPerByte) external onlyOwner {
|
||||
defaultFeeConfig = FeeConfig(_baseFees, _feesPerByte);
|
||||
/// @inheritdoc IGasOracle
|
||||
function estimateMessageFee(
|
||||
address _sender,
|
||||
address,
|
||||
bytes memory _message,
|
||||
uint256
|
||||
) external view override returns (uint256) {
|
||||
FeeConfig memory _feeConfig;
|
||||
if (hasCustomConfig[_sender]) {
|
||||
_feeConfig = customFeeConfig[_sender];
|
||||
} else {
|
||||
_feeConfig = defaultFeeConfig;
|
||||
}
|
||||
|
||||
emit UpdateDefaultFeeConfig(_baseFees, _feesPerByte);
|
||||
}
|
||||
unchecked {
|
||||
return _feeConfig.baseFees + uint256(_feeConfig.feesPerByte) * _message.length;
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice Update custom fee config for sender.
|
||||
/// @param _sender The address of sender to update custom FeeConfig.
|
||||
/// @param _baseFees The amount of baseFees to update.
|
||||
/// @param _feesPerByte The amount of fees per byte to update.
|
||||
function updateCustomFeeConfig(
|
||||
address _sender,
|
||||
uint128 _baseFees,
|
||||
uint128 _feesPerByte
|
||||
) external onlyOwner {
|
||||
customFeeConfig[_sender] = FeeConfig(_baseFees, _feesPerByte);
|
||||
hasCustomConfig[_sender] = true;
|
||||
/************************
|
||||
* Restricted Functions *
|
||||
************************/
|
||||
|
||||
emit UpdateCustomFeeConfig(_sender, _baseFees, _feesPerByte);
|
||||
}
|
||||
/// @notice Update default fee config.
|
||||
/// @param _baseFees The amount of baseFees to update.
|
||||
/// @param _feesPerByte The amount of fees per byte to update.
|
||||
function updateDefaultFeeConfig(uint128 _baseFees, uint128 _feesPerByte) external onlyOwner {
|
||||
defaultFeeConfig = FeeConfig(_baseFees, _feesPerByte);
|
||||
|
||||
emit UpdateDefaultFeeConfig(_baseFees, _feesPerByte);
|
||||
}
|
||||
|
||||
/// @notice Update custom fee config for sender.
|
||||
/// @param _sender The address of sender to update custom FeeConfig.
|
||||
/// @param _baseFees The amount of baseFees to update.
|
||||
/// @param _feesPerByte The amount of fees per byte to update.
|
||||
function updateCustomFeeConfig(
|
||||
address _sender,
|
||||
uint128 _baseFees,
|
||||
uint128 _feesPerByte
|
||||
) external onlyOwner {
|
||||
customFeeConfig[_sender] = FeeConfig(_baseFees, _feesPerByte);
|
||||
hasCustomConfig[_sender] = true;
|
||||
|
||||
emit UpdateCustomFeeConfig(_sender, _baseFees, _feesPerByte);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
interface IERC677Receiver {
|
||||
function onTokenTransfer(
|
||||
address sender,
|
||||
uint256 value,
|
||||
bytes memory data
|
||||
) external;
|
||||
function onTokenTransfer(
|
||||
address sender,
|
||||
uint256 value,
|
||||
bytes memory data
|
||||
) external;
|
||||
}
|
||||
|
||||
@@ -2,60 +2,60 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { IERC1155 } from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
|
||||
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
|
||||
|
||||
interface IScrollERC1155 is IERC1155 {
|
||||
/// @notice Return the address of Gateway the token belongs to.
|
||||
function gateway() external view returns (address);
|
||||
/// @notice Return the address of Gateway the token belongs to.
|
||||
function gateway() external view returns (address);
|
||||
|
||||
/// @notice Return the address of counterpart token.
|
||||
function counterpart() external view returns (address);
|
||||
/// @notice Return the address of counterpart token.
|
||||
function counterpart() external view returns (address);
|
||||
|
||||
/// @notice Mint some token to recipient's account.
|
||||
/// @dev Gateway Utilities, only gateway contract can call
|
||||
/// @param _to The address of recipient.
|
||||
/// @param _tokenId The token id to mint.
|
||||
/// @param _amount The amount of token to mint.
|
||||
/// @param _data The data passed to recipient
|
||||
function mint(
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount,
|
||||
bytes memory _data
|
||||
) external;
|
||||
/// @notice Mint some token to recipient's account.
|
||||
/// @dev Gateway Utilities, only gateway contract can call
|
||||
/// @param _to The address of recipient.
|
||||
/// @param _tokenId The token id to mint.
|
||||
/// @param _amount The amount of token to mint.
|
||||
/// @param _data The data passed to recipient
|
||||
function mint(
|
||||
address _to,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount,
|
||||
bytes memory _data
|
||||
) external;
|
||||
|
||||
/// @notice Burn some token from account.
|
||||
/// @dev Gateway Utilities, only gateway contract can call
|
||||
/// @param _from The address of account to burn token.
|
||||
/// @param _tokenId The token id to burn.
|
||||
/// @param _amount The amount of token to burn.
|
||||
function burn(
|
||||
address _from,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount
|
||||
) external;
|
||||
/// @notice Burn some token from account.
|
||||
/// @dev Gateway Utilities, only gateway contract can call
|
||||
/// @param _from The address of account to burn token.
|
||||
/// @param _tokenId The token id to burn.
|
||||
/// @param _amount The amount of token to burn.
|
||||
function burn(
|
||||
address _from,
|
||||
uint256 _tokenId,
|
||||
uint256 _amount
|
||||
) external;
|
||||
|
||||
/// @notice Batch mint some token to recipient's account.
|
||||
/// @dev Gateway Utilities, only gateway contract can call
|
||||
/// @param _to The address of recipient.
|
||||
/// @param _tokenIds The token id to mint.
|
||||
/// @param _amounts The list of corresponding amount of token to mint.
|
||||
/// @param _data The data passed to recipient
|
||||
function batchMint(
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256[] calldata _amounts,
|
||||
bytes calldata _data
|
||||
) external;
|
||||
/// @notice Batch mint some token to recipient's account.
|
||||
/// @dev Gateway Utilities, only gateway contract can call
|
||||
/// @param _to The address of recipient.
|
||||
/// @param _tokenIds The token id to mint.
|
||||
/// @param _amounts The list of corresponding amount of token to mint.
|
||||
/// @param _data The data passed to recipient
|
||||
function batchMint(
|
||||
address _to,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256[] calldata _amounts,
|
||||
bytes calldata _data
|
||||
) external;
|
||||
|
||||
/// @notice Batch burn some token from account.
|
||||
/// @dev Gateway Utilities, only gateway contract can call
|
||||
/// @param _from The address of account to burn token.
|
||||
/// @param _tokenIds The list of token ids to burn.
|
||||
/// @param _amounts The list of corresponding amount of token to burn.
|
||||
function batchBurn(
|
||||
address _from,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256[] calldata _amounts
|
||||
) external;
|
||||
/// @notice Batch burn some token from account.
|
||||
/// @dev Gateway Utilities, only gateway contract can call
|
||||
/// @param _from The address of account to burn token.
|
||||
/// @param _tokenIds The list of token ids to burn.
|
||||
/// @param _amounts The list of corresponding amount of token to burn.
|
||||
function batchBurn(
|
||||
address _from,
|
||||
uint256[] calldata _tokenIds,
|
||||
uint256[] calldata _amounts
|
||||
) external;
|
||||
}
|
||||
|
||||
@@ -2,23 +2,23 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
||||
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
||||
|
||||
interface IScrollERC721 is IERC721 {
|
||||
/// @notice Return the address of Gateway the token belongs to.
|
||||
function gateway() external view returns (address);
|
||||
/// @notice Return the address of Gateway the token belongs to.
|
||||
function gateway() external view returns (address);
|
||||
|
||||
/// @notice Return the address of counterpart token.
|
||||
function counterpart() external view returns (address);
|
||||
/// @notice Return the address of counterpart token.
|
||||
function counterpart() external view returns (address);
|
||||
|
||||
/// @notice Mint some token to recipient's account.
|
||||
/// @dev Gateway Utilities, only gateway contract can call
|
||||
/// @param _to The address of recipient.
|
||||
/// @param _tokenId The token id to mint.
|
||||
function mint(address _to, uint256 _tokenId) external;
|
||||
/// @notice Mint some token to recipient's account.
|
||||
/// @dev Gateway Utilities, only gateway contract can call
|
||||
/// @param _to The address of recipient.
|
||||
/// @param _tokenId The token id to mint.
|
||||
function mint(address _to, uint256 _tokenId) external;
|
||||
|
||||
/// @notice Burn some token from account.
|
||||
/// @dev Gateway Utilities, only gateway contract can call
|
||||
/// @param _tokenId The token id to burn.
|
||||
function burn(uint256 _tokenId) external;
|
||||
/// @notice Burn some token from account.
|
||||
/// @dev Gateway Utilities, only gateway contract can call
|
||||
/// @param _tokenId The token id to burn.
|
||||
function burn(uint256 _tokenId) external;
|
||||
}
|
||||
|
||||
@@ -2,37 +2,37 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
|
||||
import { IERC20PermitUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-IERC20PermitUpgradeable.sol";
|
||||
import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
|
||||
import {IERC20PermitUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-IERC20PermitUpgradeable.sol";
|
||||
|
||||
// The recommended ERC20 implementation for bridge token.
|
||||
// deployed in L2 when original token is on L1
|
||||
// deployed in L1 when original token is on L2
|
||||
interface IScrollStandardERC20 is IERC20Upgradeable, IERC20PermitUpgradeable {
|
||||
/// @notice Return the address of Gateway the token belongs to.
|
||||
function gateway() external view returns (address);
|
||||
/// @notice Return the address of Gateway the token belongs to.
|
||||
function gateway() external view returns (address);
|
||||
|
||||
/// @notice Return the address of counterpart token.
|
||||
function counterpart() external view returns (address);
|
||||
/// @notice Return the address of counterpart token.
|
||||
function counterpart() external view returns (address);
|
||||
|
||||
/// @dev ERC677 Standard, see https://github.com/ethereum/EIPs/issues/677
|
||||
/// Defi can use this method to transfer L1/L2 token to L2/L1,
|
||||
/// and deposit to L2/L1 contract in one transaction
|
||||
function transferAndCall(
|
||||
address receiver,
|
||||
uint256 amount,
|
||||
bytes calldata data
|
||||
) external returns (bool success);
|
||||
/// @dev ERC677 Standard, see https://github.com/ethereum/EIPs/issues/677
|
||||
/// Defi can use this method to transfer L1/L2 token to L2/L1,
|
||||
/// and deposit to L2/L1 contract in one transaction
|
||||
function transferAndCall(
|
||||
address receiver,
|
||||
uint256 amount,
|
||||
bytes calldata data
|
||||
) external returns (bool success);
|
||||
|
||||
/// @notice Mint some token to recipient's account.
|
||||
/// @dev Gateway Utilities, only gateway contract can call
|
||||
/// @param _to The address of recipient.
|
||||
/// @param _amount The amount of token to mint.
|
||||
function mint(address _to, uint256 _amount) external;
|
||||
/// @notice Mint some token to recipient's account.
|
||||
/// @dev Gateway Utilities, only gateway contract can call
|
||||
/// @param _to The address of recipient.
|
||||
/// @param _amount The amount of token to mint.
|
||||
function mint(address _to, uint256 _amount) external;
|
||||
|
||||
/// @notice Mint some token from account.
|
||||
/// @dev Gateway Utilities, only gateway contract can call
|
||||
/// @param _from The address of account to burn token.
|
||||
/// @param _amount The amount of token to mint.
|
||||
function burn(address _from, uint256 _amount) external;
|
||||
/// @notice Mint some token from account.
|
||||
/// @dev Gateway Utilities, only gateway contract can call
|
||||
/// @param _from The address of account to burn token.
|
||||
/// @param _amount The amount of token to mint.
|
||||
function burn(address _from, uint256 _amount) external;
|
||||
}
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
interface IScrollStandardERC20Factory {
|
||||
event DeployToken(address indexed _l1Token, address indexed _l2Token);
|
||||
event DeployToken(address indexed _l1Token, address indexed _l2Token);
|
||||
|
||||
/// @notice Compute the corresponding l2 token address given l1 token address.
|
||||
/// @param _gateway The address of gateway contract.
|
||||
/// @param _l1Token The address of l1 token.
|
||||
function computeL2TokenAddress(address _gateway, address _l1Token) external view returns (address);
|
||||
/// @notice Compute the corresponding l2 token address given l1 token address.
|
||||
/// @param _gateway The address of gateway contract.
|
||||
/// @param _l1Token The address of l1 token.
|
||||
function computeL2TokenAddress(address _gateway, address _l1Token) external view returns (address);
|
||||
|
||||
/// @notice Deploy the corresponding l2 token address given l1 token address.
|
||||
/// @param _gateway The address of gateway contract.
|
||||
/// @param _l1Token The address of l1 token.
|
||||
function deployL2Token(address _gateway, address _l1Token) external returns (address);
|
||||
/// @notice Deploy the corresponding l2 token address given l1 token address.
|
||||
/// @param _gateway The address of gateway contract.
|
||||
/// @param _l1Token The address of l1 token.
|
||||
function deployL2Token(address _gateway, address _l1Token) external returns (address);
|
||||
}
|
||||
|
||||
@@ -2,90 +2,90 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
|
||||
import { ERC20PermitUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol";
|
||||
import { IScrollStandardERC20 } from "./IScrollStandardERC20.sol";
|
||||
import { IERC677Receiver } from "./IERC677Receiver.sol";
|
||||
import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
|
||||
import {ERC20PermitUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol";
|
||||
import {IScrollStandardERC20} from "./IScrollStandardERC20.sol";
|
||||
import {IERC677Receiver} from "./IERC677Receiver.sol";
|
||||
|
||||
contract ScrollStandardERC20 is ERC20PermitUpgradeable, IScrollStandardERC20 {
|
||||
/// @inheritdoc IScrollStandardERC20
|
||||
address public override gateway;
|
||||
/// @inheritdoc IScrollStandardERC20
|
||||
address public override gateway;
|
||||
|
||||
/// @inheritdoc IScrollStandardERC20
|
||||
address public override counterpart;
|
||||
/// @inheritdoc IScrollStandardERC20
|
||||
address public override counterpart;
|
||||
|
||||
uint8 private decimals_;
|
||||
uint8 private decimals_;
|
||||
|
||||
modifier onlyGateway() {
|
||||
require(gateway == msg.sender, "Only Gateway");
|
||||
_;
|
||||
}
|
||||
|
||||
function initialize(
|
||||
string memory _name,
|
||||
string memory _symbol,
|
||||
uint8 _decimals,
|
||||
address _gateway,
|
||||
address _counterpart
|
||||
) external initializer {
|
||||
__ERC20Permit_init(_name);
|
||||
__ERC20_init(_name, _symbol);
|
||||
|
||||
decimals_ = _decimals;
|
||||
gateway = _gateway;
|
||||
counterpart = _counterpart;
|
||||
}
|
||||
|
||||
function decimals() public view override returns (uint8) {
|
||||
return decimals_;
|
||||
}
|
||||
|
||||
/// @dev ERC677 Standard, see https://github.com/ethereum/EIPs/issues/677
|
||||
/// Defi can use this method to transfer L1/L2 token to L2/L1,
|
||||
/// and deposit to L2/L1 contract in one transaction
|
||||
function transferAndCall(
|
||||
address receiver,
|
||||
uint256 amount,
|
||||
bytes calldata data
|
||||
) external returns (bool success) {
|
||||
ERC20Upgradeable.transfer(receiver, amount);
|
||||
if (isContract(receiver)) {
|
||||
contractFallback(receiver, amount, data);
|
||||
modifier onlyGateway() {
|
||||
require(gateway == msg.sender, "Only Gateway");
|
||||
_;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function contractFallback(
|
||||
address to,
|
||||
uint256 value,
|
||||
bytes memory data
|
||||
) private {
|
||||
IERC677Receiver receiver = IERC677Receiver(to);
|
||||
receiver.onTokenTransfer(msg.sender, value, data);
|
||||
}
|
||||
function initialize(
|
||||
string memory _name,
|
||||
string memory _symbol,
|
||||
uint8 _decimals,
|
||||
address _gateway,
|
||||
address _counterpart
|
||||
) external initializer {
|
||||
__ERC20Permit_init(_name);
|
||||
__ERC20_init(_name, _symbol);
|
||||
|
||||
function isContract(address _addr) private view returns (bool hasCode) {
|
||||
uint256 length;
|
||||
// solhint-disable-next-line no-inline-assembly
|
||||
assembly {
|
||||
length := extcodesize(_addr)
|
||||
decimals_ = _decimals;
|
||||
gateway = _gateway;
|
||||
counterpart = _counterpart;
|
||||
}
|
||||
return length > 0;
|
||||
}
|
||||
|
||||
/// @notice Mint some token to recipient's account.
|
||||
/// @dev Gateway Utilities, only gateway contract can call
|
||||
/// @param _to The address of recipient.
|
||||
/// @param _amount The amount of token to mint.
|
||||
function mint(address _to, uint256 _amount) external onlyGateway {
|
||||
_mint(_to, _amount);
|
||||
}
|
||||
function decimals() public view override returns (uint8) {
|
||||
return decimals_;
|
||||
}
|
||||
|
||||
/// @notice Mint some token from account.
|
||||
/// @dev Gateway Utilities, only gateway contract can call
|
||||
/// @param _from The address of account to burn token.
|
||||
/// @param _amount The amount of token to mint.
|
||||
function burn(address _from, uint256 _amount) external onlyGateway {
|
||||
_burn(_from, _amount);
|
||||
}
|
||||
/// @dev ERC677 Standard, see https://github.com/ethereum/EIPs/issues/677
|
||||
/// Defi can use this method to transfer L1/L2 token to L2/L1,
|
||||
/// and deposit to L2/L1 contract in one transaction
|
||||
function transferAndCall(
|
||||
address receiver,
|
||||
uint256 amount,
|
||||
bytes calldata data
|
||||
) external returns (bool success) {
|
||||
ERC20Upgradeable.transfer(receiver, amount);
|
||||
if (isContract(receiver)) {
|
||||
contractFallback(receiver, amount, data);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function contractFallback(
|
||||
address to,
|
||||
uint256 value,
|
||||
bytes memory data
|
||||
) private {
|
||||
IERC677Receiver receiver = IERC677Receiver(to);
|
||||
receiver.onTokenTransfer(msg.sender, value, data);
|
||||
}
|
||||
|
||||
function isContract(address _addr) private view returns (bool hasCode) {
|
||||
uint256 length;
|
||||
// solhint-disable-next-line no-inline-assembly
|
||||
assembly {
|
||||
length := extcodesize(_addr)
|
||||
}
|
||||
return length > 0;
|
||||
}
|
||||
|
||||
/// @notice Mint some token to recipient's account.
|
||||
/// @dev Gateway Utilities, only gateway contract can call
|
||||
/// @param _to The address of recipient.
|
||||
/// @param _amount The amount of token to mint.
|
||||
function mint(address _to, uint256 _amount) external onlyGateway {
|
||||
_mint(_to, _amount);
|
||||
}
|
||||
|
||||
/// @notice Mint some token from account.
|
||||
/// @dev Gateway Utilities, only gateway contract can call
|
||||
/// @param _from The address of account to burn token.
|
||||
/// @param _amount The amount of token to mint.
|
||||
function burn(address _from, uint256 _amount) external onlyGateway {
|
||||
_burn(_from, _amount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,43 +2,43 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import { Clones } from "@openzeppelin/contracts/proxy/Clones.sol";
|
||||
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
|
||||
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
|
||||
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
||||
|
||||
import { IScrollStandardERC20Factory } from "./IScrollStandardERC20Factory.sol";
|
||||
import {IScrollStandardERC20Factory} from "./IScrollStandardERC20Factory.sol";
|
||||
|
||||
/// @title ScrollStandardERC20Factory
|
||||
/// @notice The `ScrollStandardERC20Factory` is used to deploy `ScrollStandardERC20` for `L2StandardERC20Gateway`.
|
||||
/// It uses the `Clones` contract to deploy contract with minimum gas usage.
|
||||
/// @dev The implementation of deployed token is non-upgradable. This design may be changed in the future.
|
||||
contract ScrollStandardERC20Factory is Ownable, IScrollStandardERC20Factory {
|
||||
/// @notice The address of `ScrollStandardERC20` implementation.
|
||||
address public implementation;
|
||||
/// @notice The address of `ScrollStandardERC20` implementation.
|
||||
address public implementation;
|
||||
|
||||
constructor(address _implementation) {
|
||||
require(_implementation != address(0), "zero implementation address");
|
||||
constructor(address _implementation) {
|
||||
require(_implementation != address(0), "zero implementation address");
|
||||
|
||||
implementation = _implementation;
|
||||
}
|
||||
implementation = _implementation;
|
||||
}
|
||||
|
||||
/// @inheritdoc IScrollStandardERC20Factory
|
||||
function computeL2TokenAddress(address _gateway, address _l1Token) external view returns (address) {
|
||||
// In StandardERC20Gateway, all corresponding l2 tokens are depoyed by Create2 with salt,
|
||||
// we can calculate the l2 address directly.
|
||||
bytes32 _salt = _getSalt(_gateway, _l1Token);
|
||||
/// @inheritdoc IScrollStandardERC20Factory
|
||||
function computeL2TokenAddress(address _gateway, address _l1Token) external view returns (address) {
|
||||
// In StandardERC20Gateway, all corresponding l2 tokens are depoyed by Create2 with salt,
|
||||
// we can calculate the l2 address directly.
|
||||
bytes32 _salt = _getSalt(_gateway, _l1Token);
|
||||
|
||||
return Clones.predictDeterministicAddress(implementation, _salt);
|
||||
}
|
||||
return Clones.predictDeterministicAddress(implementation, _salt);
|
||||
}
|
||||
|
||||
/// @inheritdoc IScrollStandardERC20Factory
|
||||
/// @dev This function should only be called by owner to avoid DDoS attack on StandardTokenBridge.
|
||||
function deployL2Token(address _gateway, address _l1Token) external onlyOwner returns (address) {
|
||||
bytes32 _salt = _getSalt(_gateway, _l1Token);
|
||||
/// @inheritdoc IScrollStandardERC20Factory
|
||||
/// @dev This function should only be called by owner to avoid DDoS attack on StandardTokenBridge.
|
||||
function deployL2Token(address _gateway, address _l1Token) external onlyOwner returns (address) {
|
||||
bytes32 _salt = _getSalt(_gateway, _l1Token);
|
||||
|
||||
return Clones.cloneDeterministic(implementation, _salt);
|
||||
}
|
||||
return Clones.cloneDeterministic(implementation, _salt);
|
||||
}
|
||||
|
||||
function _getSalt(address _gateway, address _l1Token) internal pure returns (bytes32) {
|
||||
return keccak256(abi.encodePacked(_gateway, keccak256(abi.encodePacked(_l1Token))));
|
||||
}
|
||||
function _getSalt(address _gateway, address _l1Token) internal pure returns (bytes32) {
|
||||
return keccak256(abi.encodePacked(_gateway, keccak256(abi.encodePacked(_l1Token))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,485 +3,498 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
library PatriciaMerkleTrieVerifier {
|
||||
/// @notice Internal function to validates a proof from eth_getProof.
|
||||
/// @param account The address of the contract.
|
||||
/// @param storageKey The storage slot to verify.
|
||||
/// @param proof The rlp encoding result of eth_getProof.
|
||||
/// @return stateRoot The computed state root. Must be checked by the caller.
|
||||
/// @return storageValue The value of `storageKey`.
|
||||
///
|
||||
/// @dev The code is based on
|
||||
/// 1. https://eips.ethereum.org/EIPS/eip-1186
|
||||
/// 2. https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/
|
||||
/// 3. https://github.com/ethereum/go-ethereum/blob/master/trie/proof.go#L114
|
||||
/// 4. https://github.com/privacy-scaling-explorations/zkevm-chain/blob/master/contracts/templates/PatriciaValidator.sol
|
||||
///
|
||||
/// The encoding order of `proof` is
|
||||
/// ```text
|
||||
/// | 1 byte | ... | 1 byte | ... |
|
||||
/// | account proof length | account proof | storage proof length | storage proof |
|
||||
/// ```
|
||||
function verifyPatriciaProof(
|
||||
address account,
|
||||
bytes32 storageKey,
|
||||
bytes calldata proof
|
||||
) internal pure returns (bytes32 stateRoot, bytes32 storageValue) {
|
||||
assembly {
|
||||
// hashes 32 bytes of `v`
|
||||
function keccak_32(v) -> r {
|
||||
mstore(0x00, v)
|
||||
r := keccak256(0x00, 0x20)
|
||||
}
|
||||
// hashes the last 20 bytes of `v`
|
||||
function keccak_20(v) -> r {
|
||||
mstore(0x00, v)
|
||||
r := keccak256(0x0c, 0x14)
|
||||
}
|
||||
// reverts with error `msg`.
|
||||
// make sure the length of error string <= 32
|
||||
function revertWith(msg) {
|
||||
// keccak("Error(string)")
|
||||
mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(0x04, 0x20) // str.offset
|
||||
mstore(0x44, msg)
|
||||
let msgLen
|
||||
for {} msg {} {
|
||||
msg := shl(8, msg)
|
||||
msgLen := add(msgLen, 1)
|
||||
/// @notice Internal function to validates a proof from eth_getProof.
|
||||
/// @param account The address of the contract.
|
||||
/// @param storageKey The storage slot to verify.
|
||||
/// @param proof The rlp encoding result of eth_getProof.
|
||||
/// @return stateRoot The computed state root. Must be checked by the caller.
|
||||
/// @return storageValue The value of `storageKey`.
|
||||
///
|
||||
/// @dev The code is based on
|
||||
/// 1. https://eips.ethereum.org/EIPS/eip-1186
|
||||
/// 2. https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/
|
||||
/// 3. https://github.com/ethereum/go-ethereum/blob/master/trie/proof.go#L114
|
||||
/// 4. https://github.com/privacy-scaling-explorations/zkevm-chain/blob/master/contracts/templates/PatriciaValidator.sol
|
||||
///
|
||||
/// The encoding order of `proof` is
|
||||
/// ```text
|
||||
/// | 1 byte | ... | 1 byte | ... |
|
||||
/// | account proof length | account proof | storage proof length | storage proof |
|
||||
/// ```
|
||||
function verifyPatriciaProof(
|
||||
address account,
|
||||
bytes32 storageKey,
|
||||
bytes calldata proof
|
||||
) internal pure returns (bytes32 stateRoot, bytes32 storageValue) {
|
||||
assembly {
|
||||
// hashes 32 bytes of `v`
|
||||
function keccak_32(v) -> r {
|
||||
mstore(0x00, v)
|
||||
r := keccak256(0x00, 0x20)
|
||||
}
|
||||
// hashes the last 20 bytes of `v`
|
||||
function keccak_20(v) -> r {
|
||||
mstore(0x00, v)
|
||||
r := keccak256(0x0c, 0x14)
|
||||
}
|
||||
// reverts with error `msg`.
|
||||
// make sure the length of error string <= 32
|
||||
function revertWith(msg) {
|
||||
// keccak("Error(string)")
|
||||
mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// special function for decoding the storage value
|
||||
// because of the prefix truncation if value > 31 bytes
|
||||
// see `loadValue`
|
||||
function decodeItem(word, len) -> ret {
|
||||
// default
|
||||
ret := word
|
||||
|
||||
// RLP single byte
|
||||
if lt(word, 0x80) {
|
||||
leave
|
||||
}
|
||||
|
||||
// truncated
|
||||
if gt(len, 32) {
|
||||
leave
|
||||
}
|
||||
|
||||
// value is >= 0x80 and <= 32 bytes.
|
||||
// `len` should be at least 2 (prefix byte + value)
|
||||
// otherwise the RLP is malformed.
|
||||
let bits := mul(len, 8)
|
||||
// sub 8 bits - the prefix
|
||||
bits := sub(bits, 8)
|
||||
let mask := shl(bits, 0xff)
|
||||
// invert the mask
|
||||
mask := not(mask)
|
||||
// should hold the value - prefix byte
|
||||
ret := and(ret, mask)
|
||||
}
|
||||
|
||||
// returns the `len` of the whole RLP list at `ptr`
|
||||
// and the offset for the first value inside the list.
|
||||
function decodeListLength(ptr) -> len, startOffset {
|
||||
let b0 := byte(0, calldataload(ptr))
|
||||
// In most cases, it is a long list. So we reorder the branch to reduce branch prediction miss.
|
||||
|
||||
// 0xf8 - 0xff, long list, length > 55
|
||||
if gt(b0, 0xf7) {
|
||||
// the RLP encoding consists of a single byte with value 0xf7
|
||||
// plus the length in bytes of the length of the payload in binary form,
|
||||
// followed by the length of the payload, followed by the concatenation
|
||||
// of the RLP encodings of the items.
|
||||
// the extended length is ignored
|
||||
let lengthBytes := sub(b0, 0xf7)
|
||||
if gt(lengthBytes, 32) {
|
||||
invalid()
|
||||
}
|
||||
|
||||
// load the extended length
|
||||
startOffset := add(ptr, 1)
|
||||
let extendedLen := calldataload(startOffset)
|
||||
let bits := sub(256, mul(lengthBytes, 8))
|
||||
extendedLen := shr(bits, extendedLen)
|
||||
|
||||
len := add(extendedLen, lengthBytes)
|
||||
len := add(len, 1)
|
||||
startOffset := add(startOffset, lengthBytes)
|
||||
leave
|
||||
}
|
||||
// 0xc0 - 0xf7, short list, length <= 55
|
||||
if gt(b0, 0xbf) {
|
||||
// the RLP encoding consists of a single byte with value 0xc0
|
||||
// plus the length of the list followed by the concatenation of
|
||||
// the RLP encodings of the items.
|
||||
len := sub(b0, 0xbf)
|
||||
startOffset := add(ptr, 1)
|
||||
leave
|
||||
}
|
||||
revertWith("Not list")
|
||||
}
|
||||
|
||||
// returns the kind, calldata offset of the value and the length in bytes
|
||||
// for the RLP encoded data item at `ptr`. used in `decodeFlat`
|
||||
// kind = 0 means string/bytes, kind = 1 means list.
|
||||
function decodeValue(ptr) -> kind, 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
|
||||
}
|
||||
|
||||
kind := 1
|
||||
// 0xc0 - 0xf7, short list, length <= 55
|
||||
if lt(b0, 0xf8) {
|
||||
// intentionally ignored
|
||||
// dataLen := sub(firstByte, 0xc0)
|
||||
valueOffset := add(ptr, 1)
|
||||
leave
|
||||
}
|
||||
|
||||
// 0xf8 - 0xff, long list, length > 55
|
||||
{
|
||||
// the extended length is ignored
|
||||
dataLen := sub(b0, 0xf7)
|
||||
valueOffset := add(ptr, 1)
|
||||
leave
|
||||
}
|
||||
}
|
||||
|
||||
// decodes all RLP encoded data and stores their DATA items
|
||||
// [length - 128 bits | calldata offset - 128 bits] in a continous memory region.
|
||||
// Expects that the RLP starts with a list that defines the length
|
||||
// of the whole RLP region.
|
||||
function decodeFlat(_ptr) -> ptr, memStart, nItems, hash {
|
||||
ptr := _ptr
|
||||
|
||||
// load free memory ptr
|
||||
// doesn't update the ptr and leaves the memory region dirty
|
||||
memStart := mload(0x40)
|
||||
|
||||
let payloadLen, startOffset := decodeListLength(ptr)
|
||||
// reuse memStart region and hash
|
||||
calldatacopy(memStart, ptr, payloadLen)
|
||||
hash := keccak256(memStart, payloadLen)
|
||||
|
||||
let memPtr := memStart
|
||||
let ptrStop := add(ptr, payloadLen)
|
||||
ptr := startOffset
|
||||
|
||||
// decode until the end of the list
|
||||
for {
|
||||
|
||||
} lt(ptr, ptrStop) {
|
||||
|
||||
} {
|
||||
let kind, len, valuePtr := decodeValue(ptr)
|
||||
ptr := add(len, valuePtr)
|
||||
|
||||
if iszero(kind) {
|
||||
// store the length of the data and the calldata offset
|
||||
// low -------> high
|
||||
// | 128 bits | 128 bits |
|
||||
// | calldata offset | value length |
|
||||
mstore(memPtr, or(shl(128, len), valuePtr))
|
||||
memPtr := add(memPtr, 0x20)
|
||||
}
|
||||
}
|
||||
|
||||
if iszero(eq(ptr, ptrStop)) {
|
||||
invalid()
|
||||
}
|
||||
|
||||
nItems := div(sub(memPtr, memStart), 32)
|
||||
}
|
||||
|
||||
// prefix gets truncated to 256 bits
|
||||
// `depth` is untrusted and can lead to bogus
|
||||
// shifts/masks. In that case, the remaining verification
|
||||
// steps must fail or lead to an invalid stateRoot hash
|
||||
// if the proof data is 'spoofed but valid'
|
||||
function derivePath(key, depth) -> path {
|
||||
path := key
|
||||
|
||||
let bits := mul(depth, 4)
|
||||
{
|
||||
let mask := not(0)
|
||||
mask := shr(bits, mask)
|
||||
path := and(path, mask)
|
||||
}
|
||||
|
||||
// even prefix
|
||||
let prefix := 0x20
|
||||
if mod(depth, 2) {
|
||||
// odd
|
||||
prefix := 0x3
|
||||
}
|
||||
|
||||
// the prefix may be shifted outside bounds
|
||||
// this is intended, see `loadValue`
|
||||
bits := sub(256, bits)
|
||||
prefix := shl(bits, prefix)
|
||||
path := or(prefix, path)
|
||||
}
|
||||
|
||||
// loads and aligns a value from calldata
|
||||
// given the `len|offset` stored at `memPtr`
|
||||
function loadValue(memPtr, idx) -> value {
|
||||
let tmp := mload(add(memPtr, mul(32, idx)))
|
||||
// assuming 0xffffff is sufficient for storing calldata offset
|
||||
let offset := and(tmp, 0xffffff)
|
||||
let len := shr(128, tmp)
|
||||
|
||||
if gt(len, 31) {
|
||||
// special case - truncating the value is intended.
|
||||
// this matches the behavior in `derivePath` that truncates to 256 bits.
|
||||
offset := add(offset, sub(len, 32))
|
||||
value := calldataload(offset)
|
||||
leave
|
||||
}
|
||||
|
||||
// everything else is
|
||||
// < 32 bytes - align the value
|
||||
let bits := mul(sub(32, len), 8)
|
||||
value := calldataload(offset)
|
||||
value := shr(bits, value)
|
||||
}
|
||||
|
||||
// loads and aligns a value from calldata
|
||||
// given the `len|offset` stored at `memPtr`
|
||||
// Same as `loadValue` except it returns also the size
|
||||
// of the value.
|
||||
function loadValueLen(memPtr, idx) -> value, len {
|
||||
let tmp := mload(add(memPtr, mul(32, idx)))
|
||||
// assuming 0xffffff is sufficient for storing calldata offset
|
||||
let offset := and(tmp, 0xffffff)
|
||||
len := shr(128, tmp)
|
||||
|
||||
if gt(len, 31) {
|
||||
// special case - truncating the value is intended.
|
||||
// this matches the behavior in `derivePath` that truncates to 256 bits.
|
||||
offset := add(offset, sub(len, 32))
|
||||
value := calldataload(offset)
|
||||
leave
|
||||
}
|
||||
|
||||
// everything else is
|
||||
// < 32 bytes - align the value
|
||||
let bits := mul(sub(32, len), 8)
|
||||
value := calldataload(offset)
|
||||
value := shr(bits, value)
|
||||
}
|
||||
|
||||
function loadPair(memPtr, idx) -> offset, len {
|
||||
let tmp := mload(add(memPtr, mul(32, idx)))
|
||||
// assuming 0xffffff is sufficient for storing calldata offset
|
||||
offset := and(tmp, 0xffffff)
|
||||
len := shr(128, tmp)
|
||||
}
|
||||
|
||||
// decodes RLP at `_ptr`.
|
||||
// reverts if the number of DATA items doesn't match `nValues`.
|
||||
// returns the RLP data items at pos `v0`, `v1`
|
||||
// and the size of `v1out`
|
||||
function hashCompareSelect(_ptr, nValues, v0, v1) -> ptr, hash, v0out, v1out, v1outlen {
|
||||
ptr := _ptr
|
||||
|
||||
let memStart, nItems
|
||||
ptr, memStart, nItems, hash := decodeFlat(ptr)
|
||||
|
||||
if iszero(eq(nItems, nValues)) {
|
||||
revertWith("Node items mismatch")
|
||||
}
|
||||
|
||||
v0out, v1outlen := loadValueLen(memStart, v0)
|
||||
v1out, v1outlen := loadValueLen(memStart, v1)
|
||||
}
|
||||
|
||||
// traverses the tree from the root to the node before the leaf.
|
||||
// based on https://github.com/ethereum/go-ethereum/blob/master/trie/proof.go#L114
|
||||
function walkTree(key, _ptr) -> ptr, rootHash, expectedHash, path {
|
||||
ptr := _ptr
|
||||
|
||||
// the first byte is the number of nodes
|
||||
let nodes := byte(0, calldataload(ptr))
|
||||
ptr := add(ptr, 1)
|
||||
|
||||
// keeps track of ascend/descend - however you may look at a tree
|
||||
let depth
|
||||
|
||||
// treat the leaf node with different logic
|
||||
for {
|
||||
let i := 1
|
||||
} lt(i, nodes) {
|
||||
i := add(i, 1)
|
||||
} {
|
||||
let memStart, nItems, hash
|
||||
ptr, memStart, nItems, hash := decodeFlat(ptr)
|
||||
|
||||
// first item is considered the root node.
|
||||
// Otherwise verifies that the hash of the current node
|
||||
// is the same as the previous choosen one.
|
||||
switch i
|
||||
case 1 {
|
||||
rootHash := hash
|
||||
}
|
||||
default {
|
||||
require(eq(hash, expectedHash), "Hash mismatch")
|
||||
}
|
||||
|
||||
switch nItems
|
||||
case 2 {
|
||||
// extension node
|
||||
// load the second item.
|
||||
// this is the hash of the next node.
|
||||
let value, len := loadValueLen(memStart, 1)
|
||||
expectedHash := value
|
||||
|
||||
// get the byte length of the first item
|
||||
// Note: the value itself is not validated
|
||||
// and it is instead assumed that any invalid
|
||||
// value is invalidated by comparing the root hash.
|
||||
let prefixLen := shr(128, mload(memStart))
|
||||
depth := add(depth, prefixLen)
|
||||
}
|
||||
case 17 {
|
||||
let bits := sub(252, mul(depth, 4))
|
||||
let nibble := and(shr(bits, key), 0xf)
|
||||
|
||||
// load the value at pos `nibble`
|
||||
let value, len := loadValueLen(memStart, nibble)
|
||||
|
||||
expectedHash := value
|
||||
depth := add(depth, 1)
|
||||
}
|
||||
default {
|
||||
// everything else is unexpected
|
||||
revertWith("Invalid node")
|
||||
}
|
||||
}
|
||||
|
||||
// lastly, derive the path of the choosen one (TM)
|
||||
path := derivePath(key, depth)
|
||||
}
|
||||
|
||||
// shared variable names
|
||||
let storageHash
|
||||
let encodedPath
|
||||
let path
|
||||
let hash
|
||||
let vlen
|
||||
// starting point
|
||||
let ptr := proof.offset
|
||||
|
||||
{
|
||||
// account proof
|
||||
// Note: this doesn't work if there are no intermediate nodes before the leaf.
|
||||
// This is not possible in practice because of the fact that there must be at least
|
||||
// 2 accounts in the tree to make a transaction to a existing contract possible.
|
||||
// Thus, 2 leaves.
|
||||
let prevHash
|
||||
let key := keccak_20(account)
|
||||
// `stateRoot` is a return value and must be checked by the caller
|
||||
ptr, stateRoot, prevHash, path := walkTree(key, ptr)
|
||||
|
||||
let memStart, nItems
|
||||
ptr, memStart, nItems, hash := decodeFlat(ptr)
|
||||
|
||||
// the hash of the leaf must match the previous hash from the node
|
||||
require(eq(hash, prevHash), "Account leaf hash mismatch")
|
||||
|
||||
// 2 items
|
||||
// - encoded path
|
||||
// - account leaf RLP (4 items)
|
||||
require(eq(nItems, 2), "Account leaf node mismatch")
|
||||
|
||||
encodedPath := loadValue(memStart, 0)
|
||||
// the calculated path must match the encoded path in the leaf
|
||||
require(eq(path, encodedPath), "Account encoded path mismatch")
|
||||
|
||||
// Load the position, length of the second element (RLP encoded)
|
||||
let leafPtr, leafLen := loadPair(memStart, 1)
|
||||
leafPtr, memStart, nItems, hash := decodeFlat(leafPtr)
|
||||
|
||||
// the account leaf should contain 4 values,
|
||||
// we want:
|
||||
// - storageHash @ 2
|
||||
require(eq(nItems, 4), "Account leaf items mismatch")
|
||||
storageHash := loadValue(memStart, 2)
|
||||
}
|
||||
|
||||
{
|
||||
// storage proof
|
||||
let rootHash
|
||||
let key := keccak_32(storageKey)
|
||||
ptr, rootHash, hash, path := walkTree(key, ptr)
|
||||
|
||||
// leaf should contain 2 values
|
||||
// - encoded path @ 0
|
||||
// - storageValue @ 1
|
||||
ptr, hash, encodedPath, storageValue, vlen := hashCompareSelect(ptr, 2, 0, 1)
|
||||
// the calculated path must match the encoded path in the leaf
|
||||
require(eq(path, encodedPath), "Storage encoded path mismatch")
|
||||
|
||||
switch rootHash
|
||||
case 0 {
|
||||
// in the case that the leaf is the only element, then
|
||||
// the hash of the leaf must match the value from the account leaf
|
||||
require(eq(hash, storageHash), "Storage root mismatch")
|
||||
}
|
||||
default {
|
||||
// otherwise the root hash of the storage tree
|
||||
// must match the value from the account leaf
|
||||
require(eq(rootHash, storageHash), "Storage root mismatch")
|
||||
}
|
||||
|
||||
// storageValue is a return value
|
||||
storageValue := decodeItem(storageValue, vlen)
|
||||
}
|
||||
|
||||
// the one and only boundary check
|
||||
// in case an attacker crafted a malicous payload
|
||||
// and succeeds in the prior verification steps
|
||||
// then this should catch any bogus accesses
|
||||
if iszero(eq(ptr, add(proof.offset, proof.length))) {
|
||||
revertWith("Proof length mismatch")
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// special function for decoding the storage value
|
||||
// because of the prefix truncation if value > 31 bytes
|
||||
// see `loadValue`
|
||||
function decodeItem(word, len) -> ret {
|
||||
// default
|
||||
ret := word
|
||||
|
||||
// RLP single byte
|
||||
if lt(word, 0x80) {
|
||||
leave
|
||||
}
|
||||
|
||||
// truncated
|
||||
if gt(len, 32) {
|
||||
leave
|
||||
}
|
||||
|
||||
// value is >= 0x80 and <= 32 bytes.
|
||||
// `len` should be at least 2 (prefix byte + value)
|
||||
// otherwise the RLP is malformed.
|
||||
let bits := mul(len, 8)
|
||||
// sub 8 bits - the prefix
|
||||
bits := sub(bits, 8)
|
||||
let mask := shl(bits, 0xff)
|
||||
// invert the mask
|
||||
mask := not(mask)
|
||||
// should hold the value - prefix byte
|
||||
ret := and(ret, mask)
|
||||
}
|
||||
|
||||
// returns the `len` of the whole RLP list at `ptr`
|
||||
// and the offset for the first value inside the list.
|
||||
function decodeListLength(ptr) -> len, startOffset {
|
||||
let b0 := byte(0, calldataload(ptr))
|
||||
// In most cases, it is a long list. So we reorder the branch to reduce branch prediction miss.
|
||||
|
||||
// 0xf8 - 0xff, long list, length > 55
|
||||
if gt(b0, 0xf7) {
|
||||
// the RLP encoding consists of a single byte with value 0xf7
|
||||
// plus the length in bytes of the length of the payload in binary form,
|
||||
// followed by the length of the payload, followed by the concatenation
|
||||
// of the RLP encodings of the items.
|
||||
// the extended length is ignored
|
||||
let lengthBytes := sub(b0, 0xf7)
|
||||
if gt(lengthBytes, 32) {
|
||||
invalid()
|
||||
}
|
||||
|
||||
// load the extended length
|
||||
startOffset := add(ptr, 1)
|
||||
let extendedLen := calldataload(startOffset)
|
||||
let bits := sub(256, mul(lengthBytes, 8))
|
||||
extendedLen := shr(bits, extendedLen)
|
||||
|
||||
len := add(extendedLen, lengthBytes)
|
||||
len := add(len, 1)
|
||||
startOffset := add(startOffset, lengthBytes)
|
||||
leave
|
||||
}
|
||||
// 0xc0 - 0xf7, short list, length <= 55
|
||||
if gt(b0, 0xbf) {
|
||||
// the RLP encoding consists of a single byte with value 0xc0
|
||||
// plus the length of the list followed by the concatenation of
|
||||
// the RLP encodings of the items.
|
||||
len := sub(b0, 0xbf)
|
||||
startOffset := add(ptr, 1)
|
||||
leave
|
||||
}
|
||||
revertWith("Not list")
|
||||
}
|
||||
|
||||
// returns the kind, calldata offset of the value and the length in bytes
|
||||
// for the RLP encoded data item at `ptr`. used in `decodeFlat`
|
||||
// kind = 0 means string/bytes, kind = 1 means list.
|
||||
function decodeValue(ptr) -> kind, 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
|
||||
}
|
||||
|
||||
kind := 1
|
||||
// 0xc0 - 0xf7, short list, length <= 55
|
||||
if lt(b0, 0xf8) {
|
||||
// intentionally ignored
|
||||
// dataLen := sub(firstByte, 0xc0)
|
||||
valueOffset := add(ptr, 1)
|
||||
leave
|
||||
}
|
||||
|
||||
// 0xf8 - 0xff, long list, length > 55
|
||||
{
|
||||
// the extended length is ignored
|
||||
dataLen := sub(b0, 0xf7)
|
||||
valueOffset := add(ptr, 1)
|
||||
leave
|
||||
}
|
||||
}
|
||||
|
||||
// decodes all RLP encoded data and stores their DATA items
|
||||
// [length - 128 bits | calldata offset - 128 bits] in a continous memory region.
|
||||
// Expects that the RLP starts with a list that defines the length
|
||||
// of the whole RLP region.
|
||||
function decodeFlat(_ptr) -> ptr, memStart, nItems, hash {
|
||||
ptr := _ptr
|
||||
|
||||
// load free memory ptr
|
||||
// doesn't update the ptr and leaves the memory region dirty
|
||||
memStart := mload(0x40)
|
||||
|
||||
let payloadLen, startOffset := decodeListLength(ptr)
|
||||
// reuse memStart region and hash
|
||||
calldatacopy(memStart, ptr, payloadLen)
|
||||
hash := keccak256(memStart, payloadLen)
|
||||
|
||||
let memPtr := memStart
|
||||
let ptrStop := add(ptr, payloadLen)
|
||||
ptr := startOffset
|
||||
|
||||
// decode until the end of the list
|
||||
for {} lt(ptr, ptrStop) {} {
|
||||
let kind, len, valuePtr := decodeValue(ptr)
|
||||
ptr := add(len, valuePtr)
|
||||
|
||||
if iszero(kind) {
|
||||
// store the length of the data and the calldata offset
|
||||
// low -------> high
|
||||
// | 128 bits | 128 bits |
|
||||
// | calldata offset | value length |
|
||||
mstore(memPtr, or(shl(128, len), valuePtr))
|
||||
memPtr := add(memPtr, 0x20)
|
||||
}
|
||||
}
|
||||
|
||||
if iszero(eq(ptr, ptrStop)) {
|
||||
invalid()
|
||||
}
|
||||
|
||||
nItems := div( sub(memPtr, memStart), 32 )
|
||||
}
|
||||
|
||||
// prefix gets truncated to 256 bits
|
||||
// `depth` is untrusted and can lead to bogus
|
||||
// shifts/masks. In that case, the remaining verification
|
||||
// steps must fail or lead to an invalid stateRoot hash
|
||||
// if the proof data is 'spoofed but valid'
|
||||
function derivePath(key, depth) -> path {
|
||||
path := key
|
||||
|
||||
let bits := mul(depth, 4)
|
||||
{
|
||||
let mask := not(0)
|
||||
mask := shr(bits, mask)
|
||||
path := and(path, mask)
|
||||
}
|
||||
|
||||
// even prefix
|
||||
let prefix := 0x20
|
||||
if mod(depth, 2) {
|
||||
// odd
|
||||
prefix := 0x3
|
||||
}
|
||||
|
||||
// the prefix may be shifted outside bounds
|
||||
// this is intended, see `loadValue`
|
||||
bits := sub(256, bits)
|
||||
prefix := shl(bits, prefix)
|
||||
path := or(prefix, path)
|
||||
}
|
||||
|
||||
// loads and aligns a value from calldata
|
||||
// given the `len|offset` stored at `memPtr`
|
||||
function loadValue(memPtr, idx) -> value {
|
||||
let tmp := mload(add(memPtr, mul(32, idx)))
|
||||
// assuming 0xffffff is sufficient for storing calldata offset
|
||||
let offset := and(tmp, 0xffffff)
|
||||
let len := shr(128, tmp)
|
||||
|
||||
if gt(len, 31) {
|
||||
// special case - truncating the value is intended.
|
||||
// this matches the behavior in `derivePath` that truncates to 256 bits.
|
||||
offset := add(offset, sub(len, 32))
|
||||
value := calldataload(offset)
|
||||
leave
|
||||
}
|
||||
|
||||
// everything else is
|
||||
// < 32 bytes - align the value
|
||||
let bits := mul( sub(32, len), 8)
|
||||
value := calldataload(offset)
|
||||
value := shr(bits, value)
|
||||
}
|
||||
|
||||
// loads and aligns a value from calldata
|
||||
// given the `len|offset` stored at `memPtr`
|
||||
// Same as `loadValue` except it returns also the size
|
||||
// of the value.
|
||||
function loadValueLen(memPtr, idx) -> value, len {
|
||||
let tmp := mload(add(memPtr, mul(32, idx)))
|
||||
// assuming 0xffffff is sufficient for storing calldata offset
|
||||
let offset := and(tmp, 0xffffff)
|
||||
len := shr(128, tmp)
|
||||
|
||||
if gt(len, 31) {
|
||||
// special case - truncating the value is intended.
|
||||
// this matches the behavior in `derivePath` that truncates to 256 bits.
|
||||
offset := add(offset, sub(len, 32))
|
||||
value := calldataload(offset)
|
||||
leave
|
||||
}
|
||||
|
||||
// everything else is
|
||||
// < 32 bytes - align the value
|
||||
let bits := mul( sub(32, len), 8)
|
||||
value := calldataload(offset)
|
||||
value := shr(bits, value)
|
||||
}
|
||||
|
||||
function loadPair(memPtr, idx) -> offset, len {
|
||||
let tmp := mload(add(memPtr, mul(32, idx)))
|
||||
// assuming 0xffffff is sufficient for storing calldata offset
|
||||
offset := and(tmp, 0xffffff)
|
||||
len := shr(128, tmp)
|
||||
}
|
||||
|
||||
// decodes RLP at `_ptr`.
|
||||
// reverts if the number of DATA items doesn't match `nValues`.
|
||||
// returns the RLP data items at pos `v0`, `v1`
|
||||
// and the size of `v1out`
|
||||
function hashCompareSelect(_ptr, nValues, v0, v1) -> ptr, hash, v0out, v1out, v1outlen {
|
||||
ptr := _ptr
|
||||
|
||||
let memStart, nItems
|
||||
ptr, memStart, nItems, hash := decodeFlat(ptr)
|
||||
|
||||
if iszero( eq(nItems, nValues) ) {
|
||||
revertWith('Node items mismatch')
|
||||
}
|
||||
|
||||
v0out, v1outlen := loadValueLen(memStart, v0)
|
||||
v1out, v1outlen := loadValueLen(memStart, v1)
|
||||
}
|
||||
|
||||
// traverses the tree from the root to the node before the leaf.
|
||||
// based on https://github.com/ethereum/go-ethereum/blob/master/trie/proof.go#L114
|
||||
function walkTree(key, _ptr) -> ptr, rootHash, expectedHash, path {
|
||||
ptr := _ptr
|
||||
|
||||
// the first byte is the number of nodes
|
||||
let nodes := byte(0, calldataload(ptr))
|
||||
ptr := add(ptr, 1)
|
||||
|
||||
// keeps track of ascend/descend - however you may look at a tree
|
||||
let depth
|
||||
|
||||
// treat the leaf node with different logic
|
||||
for { let i := 1 } lt(i, nodes) { i := add(i, 1) } {
|
||||
let memStart, nItems, hash
|
||||
ptr, memStart, nItems, hash := decodeFlat(ptr)
|
||||
|
||||
// first item is considered the root node.
|
||||
// Otherwise verifies that the hash of the current node
|
||||
// is the same as the previous choosen one.
|
||||
switch i
|
||||
case 1 {
|
||||
rootHash := hash
|
||||
} default {
|
||||
require(eq(hash, expectedHash), 'Hash mismatch')
|
||||
}
|
||||
|
||||
switch nItems
|
||||
case 2 {
|
||||
// extension node
|
||||
// load the second item.
|
||||
// this is the hash of the next node.
|
||||
let value, len := loadValueLen(memStart, 1)
|
||||
expectedHash := value
|
||||
|
||||
// get the byte length of the first item
|
||||
// Note: the value itself is not validated
|
||||
// and it is instead assumed that any invalid
|
||||
// value is invalidated by comparing the root hash.
|
||||
let prefixLen := shr(128, mload(memStart))
|
||||
depth := add(depth, prefixLen)
|
||||
}
|
||||
case 17 {
|
||||
let bits := sub(252, mul(depth, 4))
|
||||
let nibble := and(shr(bits, key), 0xf)
|
||||
|
||||
// load the value at pos `nibble`
|
||||
let value, len := loadValueLen(memStart, nibble)
|
||||
|
||||
expectedHash := value
|
||||
depth := add(depth, 1)
|
||||
}
|
||||
default {
|
||||
// everything else is unexpected
|
||||
revertWith('Invalid node')
|
||||
}
|
||||
}
|
||||
|
||||
// lastly, derive the path of the choosen one (TM)
|
||||
path := derivePath(key, depth)
|
||||
}
|
||||
|
||||
// shared variable names
|
||||
let storageHash
|
||||
let encodedPath
|
||||
let path
|
||||
let hash
|
||||
let vlen
|
||||
// starting point
|
||||
let ptr := proof.offset
|
||||
|
||||
{
|
||||
// account proof
|
||||
// Note: this doesn't work if there are no intermediate nodes before the leaf.
|
||||
// This is not possible in practice because of the fact that there must be at least
|
||||
// 2 accounts in the tree to make a transaction to a existing contract possible.
|
||||
// Thus, 2 leaves.
|
||||
let prevHash
|
||||
let key := keccak_20(account)
|
||||
// `stateRoot` is a return value and must be checked by the caller
|
||||
ptr, stateRoot, prevHash, path := walkTree(key, ptr)
|
||||
|
||||
let memStart, nItems
|
||||
ptr, memStart, nItems, hash := decodeFlat(ptr)
|
||||
|
||||
// the hash of the leaf must match the previous hash from the node
|
||||
require(eq(hash, prevHash), 'Account leaf hash mismatch')
|
||||
|
||||
// 2 items
|
||||
// - encoded path
|
||||
// - account leaf RLP (4 items)
|
||||
require(eq(nItems, 2), "Account leaf node mismatch")
|
||||
|
||||
encodedPath := loadValue(memStart, 0)
|
||||
// the calculated path must match the encoded path in the leaf
|
||||
require(eq(path, encodedPath), 'Account encoded path mismatch')
|
||||
|
||||
// Load the position, length of the second element (RLP encoded)
|
||||
let leafPtr, leafLen := loadPair(memStart, 1)
|
||||
leafPtr, memStart, nItems, hash := decodeFlat(leafPtr)
|
||||
|
||||
// the account leaf should contain 4 values,
|
||||
// we want:
|
||||
// - storageHash @ 2
|
||||
require(eq(nItems, 4), "Account leaf items mismatch")
|
||||
storageHash := loadValue(memStart, 2)
|
||||
}
|
||||
|
||||
{
|
||||
// storage proof
|
||||
let rootHash
|
||||
let key := keccak_32(storageKey)
|
||||
ptr, rootHash, hash, path := walkTree(key, ptr)
|
||||
|
||||
// leaf should contain 2 values
|
||||
// - encoded path @ 0
|
||||
// - storageValue @ 1
|
||||
ptr, hash, encodedPath, storageValue, vlen := hashCompareSelect(ptr, 2, 0, 1)
|
||||
// the calculated path must match the encoded path in the leaf
|
||||
require(eq(path, encodedPath), 'Storage encoded path mismatch')
|
||||
|
||||
switch rootHash
|
||||
case 0 {
|
||||
// in the case that the leaf is the only element, then
|
||||
// the hash of the leaf must match the value from the account leaf
|
||||
require(eq(hash, storageHash), 'Storage root mismatch')
|
||||
}
|
||||
default {
|
||||
// otherwise the root hash of the storage tree
|
||||
// must match the value from the account leaf
|
||||
require(eq(rootHash, storageHash), 'Storage root mismatch')
|
||||
}
|
||||
|
||||
// storageValue is a return value
|
||||
storageValue := decodeItem(storageValue, vlen)
|
||||
}
|
||||
|
||||
// the one and only boundary check
|
||||
// in case an attacker crafted a malicous payload
|
||||
// and succeeds in the prior verification steps
|
||||
// then this should catch any bogus accesses
|
||||
if iszero( eq(ptr, add(proof.offset, proof.length)) ) {
|
||||
revertWith('Proof length mismatch')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,36 +3,36 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
library WithdrawTrieVerifier {
|
||||
function verifyMerkleProof(
|
||||
bytes32 _root,
|
||||
bytes32 _hash,
|
||||
uint256 _nonce,
|
||||
bytes memory _proof
|
||||
) internal pure returns (bool) {
|
||||
require(_proof.length % 256 == 0, "Invalid proof");
|
||||
uint256 _length = _proof.length / 256;
|
||||
function verifyMerkleProof(
|
||||
bytes32 _root,
|
||||
bytes32 _hash,
|
||||
uint256 _nonce,
|
||||
bytes memory _proof
|
||||
) internal pure returns (bool) {
|
||||
require(_proof.length % 256 == 0, "Invalid proof");
|
||||
uint256 _length = _proof.length / 256;
|
||||
|
||||
for (uint256 i = 0; i < _length; i++) {
|
||||
bytes32 item;
|
||||
assembly {
|
||||
item := mload(add(add(_proof, 0x20), mul(i, 0x20)))
|
||||
}
|
||||
if (_nonce % 2 == 0) {
|
||||
_hash = _efficientHash(_hash, item);
|
||||
} else {
|
||||
_hash = _efficientHash(item, _hash);
|
||||
}
|
||||
_nonce /= 2;
|
||||
for (uint256 i = 0; i < _length; i++) {
|
||||
bytes32 item;
|
||||
assembly {
|
||||
item := mload(add(add(_proof, 0x20), mul(i, 0x20)))
|
||||
}
|
||||
if (_nonce % 2 == 0) {
|
||||
_hash = _efficientHash(_hash, item);
|
||||
} else {
|
||||
_hash = _efficientHash(item, _hash);
|
||||
}
|
||||
_nonce /= 2;
|
||||
}
|
||||
return _hash == _root;
|
||||
}
|
||||
return _hash == _root;
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,254 +3,257 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
interface PoseidonUnit2 {
|
||||
function poseidon(uint256[2] memory) external view returns (uint256);
|
||||
function poseidon(uint256[2] memory) external view returns (uint256);
|
||||
}
|
||||
|
||||
library ZkTrieVerifier {
|
||||
/// @notice Internal function to validates a proof from eth_getProof.
|
||||
/// @param poseidon The address of poseidon hash contract.
|
||||
/// @param account The address of the contract.
|
||||
/// @param storageKey The storage slot to verify.
|
||||
/// @param proof The rlp encoding result of eth_getProof.
|
||||
/// @return stateRoot The computed state root. Must be checked by the caller.
|
||||
/// @return storageValue The value of `storageKey`.
|
||||
///
|
||||
/// @dev The code is based on
|
||||
/// 1. https://github.com/scroll-tech/go-ethereum/blob/staging/trie/zk_trie.go#L176
|
||||
/// 2. https://github.com/scroll-tech/zktrie/blob/main/trie/zk_trie_proof.go#L30
|
||||
///
|
||||
/// The encoding order of `proof` is
|
||||
/// ```text
|
||||
/// | 1 byte | ... | 1 byte | ... |
|
||||
/// | account proof length | account proof | storage proof length | storage proof |
|
||||
/// ```
|
||||
function verifyZkTrieProof(
|
||||
address poseidon,
|
||||
address account,
|
||||
bytes32 storageKey,
|
||||
bytes calldata proof
|
||||
) internal view returns (bytes32 stateRoot, bytes32 storageValue) {
|
||||
assembly {
|
||||
// reverts with error `msg`.
|
||||
// make sure the length of error string <= 32
|
||||
function revertWith(msg) {
|
||||
// keccak("Error(string)")
|
||||
mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(0x04, 0x20) // str.offset
|
||||
mstore(0x44, msg)
|
||||
let msgLen
|
||||
for {
|
||||
/// @notice Internal function to validates a proof from eth_getProof.
|
||||
/// @param poseidon The address of poseidon hash contract.
|
||||
/// @param account The address of the contract.
|
||||
/// @param storageKey The storage slot to verify.
|
||||
/// @param proof The rlp encoding result of eth_getProof.
|
||||
/// @return stateRoot The computed state root. Must be checked by the caller.
|
||||
/// @return storageValue The value of `storageKey`.
|
||||
///
|
||||
/// @dev The code is based on
|
||||
/// 1. https://github.com/scroll-tech/go-ethereum/blob/staging/trie/zk_trie.go#L176
|
||||
/// 2. https://github.com/scroll-tech/zktrie/blob/main/trie/zk_trie_proof.go#L30
|
||||
///
|
||||
/// The encoding order of `proof` is
|
||||
/// ```text
|
||||
/// | 1 byte | ... | 1 byte | ... |
|
||||
/// | account proof length | account proof | storage proof length | storage proof |
|
||||
/// ```
|
||||
function verifyZkTrieProof(
|
||||
address poseidon,
|
||||
address account,
|
||||
bytes32 storageKey,
|
||||
bytes calldata proof
|
||||
) internal view returns (bytes32 stateRoot, bytes32 storageValue) {
|
||||
assembly {
|
||||
// reverts with error `msg`.
|
||||
// make sure the length of error string <= 32
|
||||
function revertWith(msg) {
|
||||
// keccak("Error(string)")
|
||||
mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(0x04, 0x20) // str.offset
|
||||
mstore(0x44, msg)
|
||||
let msgLen
|
||||
for {
|
||||
|
||||
} msg {
|
||||
} msg {
|
||||
|
||||
} {
|
||||
msg := shl(8, msg)
|
||||
msgLen := add(msgLen, 1)
|
||||
} {
|
||||
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)
|
||||
}
|
||||
}
|
||||
// compute poseidon hash of two uint256
|
||||
function poseidon_hash(hasher, v0, v1) -> r {
|
||||
let x := mload(0x40)
|
||||
// keccack256("poseidon(uint256[2])")
|
||||
mstore(x, 0x29a5f2f600000000000000000000000000000000000000000000000000000000)
|
||||
mstore(add(x, 0x04), v0)
|
||||
mstore(add(x, 0x24), v1)
|
||||
let success := staticcall(gas(), hasher, x, 0x44, 0x20, 0x20)
|
||||
require(success, "poseidon hash failed")
|
||||
r := mload(0x20)
|
||||
}
|
||||
// compute poseidon hash of 1 uint256
|
||||
function hash_uint256(hasher, v) -> r {
|
||||
r := poseidon_hash(hasher, shr(128, v), and(v, 0xffffffffffffffffffffffffffffffff))
|
||||
}
|
||||
|
||||
// traverses the tree from the root to the node before the leaf.
|
||||
// based on https://github.com/ethereum/go-ethereum/blob/master/trie/proof.go#L114
|
||||
function walkTree(hasher, key, _ptr) -> ptr, rootHash, expectedHash {
|
||||
ptr := _ptr
|
||||
|
||||
// the first byte is the number of nodes + 1
|
||||
let nodes := sub(byte(0, calldataload(ptr)), 1)
|
||||
ptr := add(ptr, 1)
|
||||
|
||||
// treat the leaf node with different logic
|
||||
for {
|
||||
let depth := 1
|
||||
} lt(depth, nodes) {
|
||||
depth := add(depth, 1)
|
||||
} {
|
||||
// must be a parent node with two children
|
||||
let nodeType := byte(0, calldataload(ptr))
|
||||
ptr := add(ptr, 1)
|
||||
require(eq(nodeType, 0), "Invalid parent node")
|
||||
|
||||
// load left/right child hash
|
||||
let childHashL := calldataload(ptr)
|
||||
ptr := add(ptr, 0x20)
|
||||
let childHashR := calldataload(ptr)
|
||||
ptr := add(ptr, 0x20)
|
||||
let hash := poseidon_hash(hasher, childHashL, childHashR)
|
||||
|
||||
// first item is considered the root node.
|
||||
// Otherwise verifies that the hash of the current node
|
||||
// is the same as the previous choosen one.
|
||||
switch depth
|
||||
case 1 {
|
||||
rootHash := hash
|
||||
}
|
||||
default {
|
||||
require(eq(hash, expectedHash), "Hash mismatch")
|
||||
}
|
||||
|
||||
// decide which path to walk based on key
|
||||
switch and(key, 1)
|
||||
case 0 {
|
||||
expectedHash := childHashL
|
||||
}
|
||||
default {
|
||||
expectedHash := childHashR
|
||||
}
|
||||
key := shr(1, key)
|
||||
}
|
||||
}
|
||||
|
||||
function checkProofMagicBytes(hasher, _ptr) -> ptr {
|
||||
ptr := _ptr
|
||||
let x := mload(0x40)
|
||||
calldatacopy(x, ptr, 0x2d)
|
||||
x := keccak256(x, 0x2d)
|
||||
require(
|
||||
eq(x, 0x950654da67865a81bc70e45f3230f5179f08e29c66184bf746f71050f117b3b8),
|
||||
"Invalid ProofMagicBytes"
|
||||
)
|
||||
ptr := add(ptr, 0x2d) // skip ProofMagicBytes
|
||||
}
|
||||
|
||||
// shared variable names
|
||||
let storageHash
|
||||
// starting point
|
||||
let ptr := proof.offset
|
||||
|
||||
// verify account proof
|
||||
{
|
||||
let leafHash
|
||||
let key := hash_uint256(poseidon, shl(96, account))
|
||||
|
||||
// `stateRoot` is a return value and must be checked by the caller
|
||||
ptr, stateRoot, leafHash := walkTree(poseidon, key, ptr)
|
||||
|
||||
require(eq(1, byte(0, calldataload(ptr))), "Invalid leaf node")
|
||||
ptr := add(ptr, 0x01) // skip NodeType
|
||||
require(eq(calldataload(ptr), key), "Node key mismatch")
|
||||
ptr := add(ptr, 0x20) // skip NodeKey
|
||||
{
|
||||
let valuePreimageLength := and(shr(224, calldataload(ptr)), 0xffff)
|
||||
// @todo check CompressedFlag
|
||||
ptr := add(ptr, 0x04) // skip CompressedFlag
|
||||
ptr := add(ptr, valuePreimageLength) // skip ValuePreimage
|
||||
}
|
||||
|
||||
// compute value hash for State Account Leaf Node
|
||||
{
|
||||
let tmpHash1 := calldataload(ptr)
|
||||
ptr := add(ptr, 0x20) // skip nonce/codesize/0
|
||||
tmpHash1 := poseidon_hash(poseidon, tmpHash1, calldataload(ptr))
|
||||
ptr := add(ptr, 0x20) // skip balance
|
||||
storageHash := calldataload(ptr)
|
||||
ptr := add(ptr, 0x20) // skip StorageRoot
|
||||
let tmpHash2 := hash_uint256(poseidon, calldataload(ptr))
|
||||
ptr := add(ptr, 0x20) // skip KeccakCodeHash
|
||||
tmpHash2 := poseidon_hash(poseidon, storageHash, tmpHash2)
|
||||
tmpHash2 := poseidon_hash(poseidon, tmpHash1, tmpHash2)
|
||||
tmpHash2 := poseidon_hash(poseidon, tmpHash2, calldataload(ptr))
|
||||
ptr := add(ptr, 0x20) // skip PoseidonCodeHash
|
||||
|
||||
tmpHash1 := poseidon_hash(poseidon, 1, key)
|
||||
tmpHash1 := poseidon_hash(poseidon, tmpHash1, tmpHash2)
|
||||
|
||||
require(eq(leafHash, tmpHash1), "Invalid leaf node hash")
|
||||
}
|
||||
|
||||
require(eq(0x20, byte(0, calldataload(ptr))), "Invalid KeyPreimage length")
|
||||
ptr := add(ptr, 0x01) // skip KeyPreimage length
|
||||
require(eq(shl(96, account), calldataload(ptr)), "Invalid KeyPreimage")
|
||||
ptr := add(ptr, 0x20) // skip KeyPreimage
|
||||
|
||||
// compare ProofMagicBytes
|
||||
ptr := checkProofMagicBytes(poseidon, ptr)
|
||||
}
|
||||
|
||||
// verify storage proof
|
||||
{
|
||||
let leafHash
|
||||
let key := hash_uint256(poseidon, storageKey)
|
||||
{
|
||||
let rootHash
|
||||
ptr, rootHash, leafHash := walkTree(poseidon, key, ptr)
|
||||
|
||||
switch rootHash
|
||||
case 0 {
|
||||
// in the case that the leaf is the only element, then
|
||||
// the hash of the leaf must match the value from the account leaf
|
||||
require(eq(leafHash, storageHash), "Storage root mismatch")
|
||||
}
|
||||
default {
|
||||
// otherwise the root hash of the storage tree
|
||||
// must match the value from the account leaf
|
||||
require(eq(rootHash, storageHash), "Storage root mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
switch byte(0, calldataload(ptr))
|
||||
case 1 {
|
||||
ptr := add(ptr, 0x01) // skip NodeType
|
||||
require(eq(calldataload(ptr), key), "Node key mismatch")
|
||||
ptr := add(ptr, 0x20) // skip NodeKey
|
||||
{
|
||||
let valuePreimageLength := and(shr(224, calldataload(ptr)), 0xffff)
|
||||
// @todo check CompressedFlag
|
||||
ptr := add(ptr, 0x04) // skip CompressedFlag
|
||||
ptr := add(ptr, valuePreimageLength) // skip ValuePreimage
|
||||
}
|
||||
|
||||
storageValue := calldataload(ptr)
|
||||
ptr := add(ptr, 0x20) // skip StorageValue
|
||||
|
||||
mstore(0x00, hash_uint256(poseidon, storageValue))
|
||||
key := poseidon_hash(poseidon, 1, key)
|
||||
mstore(0x00, poseidon_hash(poseidon, key, mload(0x00)))
|
||||
require(eq(leafHash, mload(0x00)), "Invalid leaf node hash")
|
||||
|
||||
require(eq(0x20, byte(0, calldataload(ptr))), "Invalid KeyPreimage length")
|
||||
ptr := add(ptr, 0x01) // skip KeyPreimage length
|
||||
require(eq(storageKey, calldataload(ptr)), "Invalid KeyPreimage")
|
||||
ptr := add(ptr, 0x20) // skip KeyPreimage
|
||||
}
|
||||
case 2 {
|
||||
ptr := add(ptr, 0x01) // skip NodeType
|
||||
require(eq(leafHash, 0), "Invalid empty node hash")
|
||||
}
|
||||
default {
|
||||
revertWith("Invalid leaf node")
|
||||
}
|
||||
|
||||
// compare ProofMagicBytes
|
||||
ptr := checkProofMagicBytes(poseidon, ptr)
|
||||
}
|
||||
|
||||
// the one and only boundary check
|
||||
// in case an attacker crafted a malicous payload
|
||||
// and succeeds in the prior verification steps
|
||||
// then this should catch any bogus accesses
|
||||
if iszero(eq(ptr, add(proof.offset, proof.length))) {
|
||||
revertWith("Proof length mismatch")
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
// compute poseidon hash of two uint256
|
||||
function poseidon_hash(hasher, v0, v1) -> r {
|
||||
let x := mload(0x40)
|
||||
// keccack256("poseidon(uint256[2])")
|
||||
mstore(x, 0x29a5f2f600000000000000000000000000000000000000000000000000000000)
|
||||
mstore(add(x, 0x04), v0)
|
||||
mstore(add(x, 0x24), v1)
|
||||
let success := staticcall(gas(), hasher, x, 0x44, 0x20, 0x20)
|
||||
require(success, "poseidon hash failed")
|
||||
r := mload(0x20)
|
||||
}
|
||||
// compute poseidon hash of 1 uint256
|
||||
function hash_uint256(hasher, v) -> r {
|
||||
r := poseidon_hash(hasher, shr(128, v), and(v, 0xffffffffffffffffffffffffffffffff))
|
||||
}
|
||||
|
||||
// traverses the tree from the root to the node before the leaf.
|
||||
// based on https://github.com/ethereum/go-ethereum/blob/master/trie/proof.go#L114
|
||||
function walkTree(hasher, key, _ptr) -> ptr, rootHash, expectedHash {
|
||||
ptr := _ptr
|
||||
|
||||
// the first byte is the number of nodes + 1
|
||||
let nodes := sub(byte(0, calldataload(ptr)), 1)
|
||||
ptr := add(ptr, 1)
|
||||
|
||||
// treat the leaf node with different logic
|
||||
for {
|
||||
let depth := 1
|
||||
} lt(depth, nodes) {
|
||||
depth := add(depth, 1)
|
||||
} {
|
||||
// must be a parent node with two children
|
||||
let nodeType := byte(0, calldataload(ptr))
|
||||
ptr := add(ptr, 1)
|
||||
require(eq(nodeType, 0), "Invalid parent node")
|
||||
|
||||
// load left/right child hash
|
||||
let childHashL := calldataload(ptr)
|
||||
ptr := add(ptr, 0x20)
|
||||
let childHashR := calldataload(ptr)
|
||||
ptr := add(ptr, 0x20)
|
||||
let hash := poseidon_hash(hasher, childHashL, childHashR)
|
||||
|
||||
// first item is considered the root node.
|
||||
// Otherwise verifies that the hash of the current node
|
||||
// is the same as the previous choosen one.
|
||||
switch depth
|
||||
case 1 {
|
||||
rootHash := hash
|
||||
}
|
||||
default {
|
||||
require(eq(hash, expectedHash), "Hash mismatch")
|
||||
}
|
||||
|
||||
// decide which path to walk based on key
|
||||
switch and(key, 1)
|
||||
case 0 {
|
||||
expectedHash := childHashL
|
||||
}
|
||||
default {
|
||||
expectedHash := childHashR
|
||||
}
|
||||
key := shr(1, key)
|
||||
}
|
||||
}
|
||||
|
||||
function checkProofMagicBytes(hasher, _ptr) -> ptr {
|
||||
ptr := _ptr
|
||||
let x := mload(0x40)
|
||||
calldatacopy(x, ptr, 0x2d)
|
||||
x := keccak256(x, 0x2d)
|
||||
require(eq(x, 0x950654da67865a81bc70e45f3230f5179f08e29c66184bf746f71050f117b3b8), "Invalid ProofMagicBytes")
|
||||
ptr := add(ptr, 0x2d) // skip ProofMagicBytes
|
||||
}
|
||||
|
||||
// shared variable names
|
||||
let storageHash
|
||||
// starting point
|
||||
let ptr := proof.offset
|
||||
|
||||
// verify account proof
|
||||
{
|
||||
let leafHash
|
||||
let key := hash_uint256(poseidon, shl(96, account))
|
||||
|
||||
// `stateRoot` is a return value and must be checked by the caller
|
||||
ptr, stateRoot, leafHash := walkTree(poseidon, key, ptr)
|
||||
|
||||
require(eq(1, byte(0, calldataload(ptr))), "Invalid leaf node")
|
||||
ptr := add(ptr, 0x01) // skip NodeType
|
||||
require(eq(calldataload(ptr), key), "Node key mismatch")
|
||||
ptr := add(ptr, 0x20) // skip NodeKey
|
||||
{
|
||||
let valuePreimageLength := and(shr(224, calldataload(ptr)), 0xffff)
|
||||
// @todo check CompressedFlag
|
||||
ptr := add(ptr, 0x04) // skip CompressedFlag
|
||||
ptr := add(ptr, valuePreimageLength) // skip ValuePreimage
|
||||
}
|
||||
|
||||
// compute value hash for State Account Leaf Node
|
||||
{
|
||||
let tmpHash1 := calldataload(ptr)
|
||||
ptr := add(ptr, 0x20) // skip nonce/codesize/0
|
||||
tmpHash1 := poseidon_hash(poseidon, tmpHash1, calldataload(ptr))
|
||||
ptr := add(ptr, 0x20) // skip balance
|
||||
storageHash := calldataload(ptr)
|
||||
ptr := add(ptr, 0x20) // skip StorageRoot
|
||||
let tmpHash2 := hash_uint256(poseidon, calldataload(ptr))
|
||||
ptr := add(ptr, 0x20) // skip KeccakCodeHash
|
||||
tmpHash2 := poseidon_hash(poseidon, storageHash, tmpHash2)
|
||||
tmpHash2 := poseidon_hash(poseidon, tmpHash1, tmpHash2)
|
||||
tmpHash2 := poseidon_hash(poseidon, tmpHash2, calldataload(ptr))
|
||||
ptr := add(ptr, 0x20) // skip PoseidonCodeHash
|
||||
|
||||
tmpHash1 := poseidon_hash(poseidon, 1, key)
|
||||
tmpHash1 := poseidon_hash(poseidon, tmpHash1, tmpHash2)
|
||||
|
||||
require(eq(leafHash, tmpHash1), "Invalid leaf node hash")
|
||||
}
|
||||
|
||||
require(eq(0x20, byte(0, calldataload(ptr))), "Invalid KeyPreimage length")
|
||||
ptr := add(ptr, 0x01) // skip KeyPreimage length
|
||||
require(eq(shl(96, account), calldataload(ptr)), "Invalid KeyPreimage")
|
||||
ptr := add(ptr, 0x20) // skip KeyPreimage
|
||||
|
||||
// compare ProofMagicBytes
|
||||
ptr := checkProofMagicBytes(poseidon, ptr)
|
||||
}
|
||||
|
||||
// verify storage proof
|
||||
{
|
||||
let leafHash
|
||||
let key := hash_uint256(poseidon, storageKey)
|
||||
{
|
||||
let rootHash
|
||||
ptr, rootHash, leafHash := walkTree(poseidon, key, ptr)
|
||||
|
||||
switch rootHash
|
||||
case 0 {
|
||||
// in the case that the leaf is the only element, then
|
||||
// the hash of the leaf must match the value from the account leaf
|
||||
require(eq(leafHash, storageHash), "Storage root mismatch")
|
||||
}
|
||||
default {
|
||||
// otherwise the root hash of the storage tree
|
||||
// must match the value from the account leaf
|
||||
require(eq(rootHash, storageHash), "Storage root mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
switch byte(0, calldataload(ptr))
|
||||
case 1 {
|
||||
ptr := add(ptr, 0x01) // skip NodeType
|
||||
require(eq(calldataload(ptr), key), "Node key mismatch")
|
||||
ptr := add(ptr, 0x20) // skip NodeKey
|
||||
{
|
||||
let valuePreimageLength := and(shr(224, calldataload(ptr)), 0xffff)
|
||||
// @todo check CompressedFlag
|
||||
ptr := add(ptr, 0x04) // skip CompressedFlag
|
||||
ptr := add(ptr, valuePreimageLength) // skip ValuePreimage
|
||||
}
|
||||
|
||||
storageValue := calldataload(ptr)
|
||||
ptr := add(ptr, 0x20) // skip StorageValue
|
||||
|
||||
mstore(0x00, hash_uint256(poseidon, storageValue))
|
||||
key := poseidon_hash(poseidon, 1, key)
|
||||
mstore(0x00, poseidon_hash(poseidon, key, mload(0x00)))
|
||||
require(eq(leafHash, mload(0x00)), "Invalid leaf node hash")
|
||||
|
||||
require(eq(0x20, byte(0, calldataload(ptr))), "Invalid KeyPreimage length")
|
||||
ptr := add(ptr, 0x01) // skip KeyPreimage length
|
||||
require(eq(storageKey, calldataload(ptr)), "Invalid KeyPreimage")
|
||||
ptr := add(ptr, 0x20) // skip KeyPreimage
|
||||
}
|
||||
case 2 {
|
||||
ptr := add(ptr, 0x01) // skip NodeType
|
||||
require(eq(leafHash, 0), "Invalid empty node hash")
|
||||
}
|
||||
default {
|
||||
revertWith("Invalid leaf node")
|
||||
}
|
||||
|
||||
// compare ProofMagicBytes
|
||||
ptr := checkProofMagicBytes(poseidon, ptr)
|
||||
}
|
||||
|
||||
// the one and only boundary check
|
||||
// in case an attacker crafted a malicous payload
|
||||
// and succeeds in the prior verification steps
|
||||
// then this should catch any bogus accesses
|
||||
if iszero(eq(ptr, add(proof.offset, proof.length))) {
|
||||
revertWith("Proof length mismatch")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user