Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
e1d66363
Unverified
Commit
e1d66363
authored
Jun 01, 2021
by
smartcontracts
Committed by
GitHub
Jun 01, 2021
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'develop' into regenesis/0.4.0
parents
95ccef51
5a798497
Changes
25
Show whitespace changes
Inline
Side-by-side
Showing
25 changed files
with
577 additions
and
385 deletions
+577
-385
fuzzy-dogs-share.md
.changeset/fuzzy-dogs-share.md
+6
-0
late-countries-guess.md
.changeset/late-countries-guess.md
+5
-0
static-analysis.yml
.github/workflows/static-analysis.yml
+61
-0
.gitignore
.gitignore
+3
-0
.nvmrc
.nvmrc
+1
-0
README.md
README.md
+11
-3
erc20.spec.ts
integration-tests/test/erc20.spec.ts
+5
-1
fee-payment.spec.ts
integration-tests/test/fee-payment.spec.ts
+3
-4
native-eth.spec.ts
integration-tests/test/native-eth.spec.ts
+2
-2
rpc.spec.ts
integration-tests/test/rpc.spec.ts
+32
-16
rollup_fee.go
l2geth/core/rollup_fee.go
+0
-126
rollup_gasprice.go
l2geth/eth/gasprice/rollup_gasprice.go
+23
-30
api.go
l2geth/internal/ethapi/api.go
+11
-13
client.go
l2geth/rollup/client.go
+1
-1
rollup_fee.go
l2geth/rollup/fees/rollup_fee.go
+94
-0
rollup_fee_test.go
l2geth/rollup/fees/rollup_fee_test.go
+103
-0
sync_service.go
l2geth/rollup/sync_service.go
+29
-21
sync_service_test.go
l2geth/rollup/sync_service_test.go
+4
-3
yarn.lock
ops/docker/hardhat/yarn.lock
+3
-3
README.md
packages/contracts/README.md
+76
-0
package.json
packages/contracts/package.json
+3
-0
slither.config.json
packages/contracts/slither.config.json
+12
-0
README.md
packages/core-utils/README.md
+16
-23
fees.ts
packages/core-utils/src/fees.ts
+13
-66
fees.spec.ts
packages/core-utils/test/fees/fees.spec.ts
+60
-73
No files found.
.changeset/fuzzy-dogs-share.md
0 → 100644
View file @
e1d66363
---
'
@eth-optimism/l2geth'
:
patch
'
@eth-optimism/core-utils'
:
patch
---
Implement the next fee spec in both geth and in core-utils
.changeset/late-countries-guess.md
0 → 100644
View file @
e1d66363
---
'
@eth-optimism/contracts'
:
patch
---
Update contracts README to add deploy instructions.
.github/workflows/static-analysis.yml
0 → 100644
View file @
e1d66363
name
:
Static analysis
on
:
push
:
branches
:
-
master
-
develop
pull_request
:
workflow_dispatch
:
env
:
PYTEST_ADDOPTS
:
"
--color=yes"
jobs
:
slither
:
name
:
Slither run
runs-on
:
ubuntu-latest
steps
:
-
uses
:
actions/checkout@v2
-
name
:
Fetch history
run
:
git fetch
-
name
:
Setup node
uses
:
actions/setup-node@v1
with
:
node-version
:
'
12.x'
-
name
:
Get yarn cache directory path
id
:
yarn-cache-dir-path
run
:
echo "::set-output name=dir::$(yarn cache dir)"
-
uses
:
actions/cache@v2
id
:
yarn-cache
with
:
path
:
${{ steps.yarn-cache-dir-path.outputs.dir }}
key
:
${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys
:
|
${{ runner.os }}-yarn-
-
name
:
Install Dependencies
# only install dependencies if there was a change in the deps
# if: steps.yarn-cache.outputs.cache-hit != 'true'
run
:
yarn install
-
name
:
Build
run
:
yarn build
-
name
:
Set up Python
3.8
uses
:
actions/setup-python@v2
with
:
python-version
:
'
3.8'
-
name
:
Install Slither
run
:
pip3 install slither-analyzer
-
name
:
Run analysis
working-directory
:
./packages/contracts
shell
:
bash
run
:
yarn test:slither
continue-on-error
:
true
.gitignore
View file @
e1d66363
...
...
@@ -14,6 +14,9 @@ cache-ovm
l2geth/build/bin
packages/contracts/deployments/custom
packages/contracts/coverage*
packages/contracts/@ens*
packages/contracts/@openzeppelin*
packages/contracts/hardhat*
packages/data-transport-layer/db
...
...
.nvmrc
0 → 100644
View file @
e1d66363
14.17.0
README.md
View file @
e1d66363
...
...
@@ -110,3 +110,11 @@ can be hard to filter through. In order to view the logs from a specific service
```
docker-compose logs --follow <service name>
```
### Static analysis
To run
`slither`
locally in
`./packages/contracts`
do
```
pip3 install slither-analyzer
yarn test:slither
```
integration-tests/test/erc20.spec.ts
View file @
e1d66363
import
{
Contract
,
ContractFactory
,
Wallet
}
from
'
ethers
'
import
{
ethers
}
from
'
hardhat
'
import
{
TxGasLimit
,
TxGasPrice
}
from
'
@eth-optimism/core-utils
'
import
chai
,
{
expect
}
from
'
chai
'
import
{
GWEI
}
from
'
./shared/utils
'
import
{
OptimismEnv
}
from
'
./shared/env
'
...
...
@@ -64,7 +65,10 @@ describe('Basic ERC20 interactions', async () => {
const
receipt
=
await
transfer
.
wait
()
// The expected fee paid is the value returned by eth_estimateGas
const
expectedFeePaid
=
await
ERC20
.
estimateGas
.
transfer
(
other
.
address
,
100
)
const
gasLimit
=
await
ERC20
.
estimateGas
.
transfer
(
other
.
address
,
100
)
const
gasPrice
=
await
wallet
.
getGasPrice
()
expect
(
gasPrice
).
to
.
deep
.
equal
(
TxGasPrice
)
const
expectedFeePaid
=
gasLimit
.
mul
(
gasPrice
)
// There are two events from the transfer with the first being
// the ETH fee paid and the second of the value transfered (100)
...
...
integration-tests/test/fee-payment.spec.ts
View file @
e1d66363
...
...
@@ -3,7 +3,7 @@ import chaiAsPromised from 'chai-as-promised'
chai
.
use
(
chaiAsPromised
)
import
{
BigNumber
,
utils
}
from
'
ethers
'
import
{
OptimismEnv
}
from
'
./shared/env
'
import
{
L2
GasLimit
}
from
'
@eth-optimism/core-utils
'
import
{
Tx
GasLimit
}
from
'
@eth-optimism/core-utils
'
describe
(
'
Fee Payment Integration Tests
'
,
async
()
=>
{
let
env
:
OptimismEnv
...
...
@@ -29,7 +29,7 @@ describe('Fee Payment Integration Tests', async () => {
)
const
executionGas
=
await
(
env
.
ovmEth
.
provider
as
any
).
send
(
'
eth_estimateExecutionGas
'
,
[
tx
])
const
decoded
=
L2
GasLimit
.
decode
(
gas
)
const
decoded
=
Tx
GasLimit
.
decode
(
gas
)
expect
(
BigNumber
.
from
(
executionGas
)).
deep
.
eq
(
decoded
)
})
...
...
@@ -38,8 +38,7 @@ describe('Fee Payment Integration Tests', async () => {
const
balanceBefore
=
await
env
.
l2Wallet
.
getBalance
()
expect
(
balanceBefore
.
gt
(
amount
))
const
gas
=
await
env
.
ovmEth
.
estimateGas
.
transfer
(
other
,
amount
)
const
tx
=
await
env
.
ovmEth
.
transfer
(
other
,
amount
,
{
gasPrice
:
1
})
const
tx
=
await
env
.
ovmEth
.
transfer
(
other
,
amount
)
const
receipt
=
await
tx
.
wait
()
expect
(
receipt
.
status
).
to
.
eq
(
1
)
...
...
integration-tests/test/native-eth.spec.ts
View file @
e1d66363
...
...
@@ -45,13 +45,13 @@ describe('Native ETH Integration Tests', async () => {
const
amount
=
utils
.
parseEther
(
'
0.5
'
)
const
addr
=
'
0x
'
+
'
1234
'
.
repeat
(
10
)
const
gas
=
await
env
.
ovmEth
.
estimateGas
.
transfer
(
addr
,
amount
)
expect
(
gas
).
to
.
be
.
deep
.
eq
(
BigNumber
.
from
(
0x
23284d28fe
6d
))
expect
(
gas
).
to
.
be
.
deep
.
eq
(
BigNumber
.
from
(
0x
0ef89721
6d
))
})
it
(
'
Should estimate gas for ETH withdraw
'
,
async
()
=>
{
const
amount
=
utils
.
parseEther
(
'
0.5
'
)
const
gas
=
await
env
.
ovmEth
.
estimateGas
.
withdraw
(
amount
)
expect
(
gas
).
to
.
be
.
deep
.
eq
(
BigNumber
.
from
(
0x207ad91a77b4
))
expect
(
gas
).
to
.
be
.
deep
.
eq
(
BigNumber
.
from
(
61400489396
))
})
})
...
...
integration-tests/test/rpc.spec.ts
View file @
e1d66363
import
{
injectL2Context
,
L2GasLimit
,
roundL1GasPrice
,
TxGasLimit
,
TxGasPrice
,
toRpcHexString
,
}
from
'
@eth-optimism/core-utils
'
import
{
Wallet
,
BigNumber
,
Contract
}
from
'
ethers
'
import
{
ethers
}
from
'
hardhat
'
import
chai
,
{
expect
}
from
'
chai
'
import
{
sleep
,
l2Provider
,
GWEI
}
from
'
./shared/utils
'
import
{
sleep
,
l2Provider
,
l1Provider
}
from
'
./shared/utils
'
import
chaiAsPromised
from
'
chai-as-promised
'
import
{
OptimismEnv
}
from
'
./shared/env
'
import
{
...
...
@@ -130,11 +131,25 @@ describe('Basic RPC tests', () => {
const
tx
=
{
...
DEFAULT_TRANSACTION
,
gasLimit
:
1
,
gasPrice
:
1
,
gasPrice
:
TxGasPrice
,
}
const
fee
=
tx
.
gasPrice
.
mul
(
tx
.
gasLimit
)
const
gasLimit
=
59300000001
await
expect
(
env
.
l2Wallet
.
sendTransaction
(
tx
)).
to
.
be
.
rejectedWith
(
'
fee too low: 1, use at least tx.gasLimit = 33600000000001 and tx.gasPrice = 1
'
`fee too low:
${
fee
}
, use at least tx.gasLimit =
${
gasLimit
}
and tx.gasPrice =
${
TxGasPrice
.
toString
()}
`
)
})
it
(
'
should reject a transaction with an incorrect gas price
'
,
async
()
=>
{
const
tx
=
{
...
DEFAULT_TRANSACTION
,
gasLimit
:
1
,
gasPrice
:
TxGasPrice
.
sub
(
1
),
}
await
expect
(
env
.
l2Wallet
.
sendTransaction
(
tx
)).
to
.
be
.
rejectedWith
(
`tx.gasPrice must be
${
TxGasPrice
.
toString
()}
`
)
})
...
...
@@ -198,7 +213,7 @@ describe('Basic RPC tests', () => {
it
(
'
correctly exposes revert data for contract calls
'
,
async
()
=>
{
const
req
:
TransactionRequest
=
{
...
revertingTx
,
gasLimit
:
9341119
08999999
,
// override gas estimation
gasLimit
:
598
08999999
,
// override gas estimation
}
const
tx
=
await
wallet
.
sendTransaction
(
req
)
...
...
@@ -221,7 +236,7 @@ describe('Basic RPC tests', () => {
it
(
'
correctly exposes revert data for contract creations
'
,
async
()
=>
{
const
req
:
TransactionRequest
=
{
...
revertingDeployTx
,
gasLimit
:
1
0513919
08999999
,
// override gas estimation
gasLimit
:
1
770
08999999
,
// override gas estimation
}
const
tx
=
await
wallet
.
sendTransaction
(
req
)
...
...
@@ -309,12 +324,14 @@ describe('Basic RPC tests', () => {
})
describe
(
'
eth_gasPrice
'
,
()
=>
{
it
(
'
gas price should be 1 gwei
'
,
async
()
=>
{
expect
(
await
provider
.
getGasPrice
()).
to
.
be
.
deep
.
equal
(
1
)
it
(
'
gas price should be the fee scalar
'
,
async
()
=>
{
expect
(
await
provider
.
getGasPrice
()).
to
.
be
.
deep
.
equal
(
TxGasPrice
.
toNumber
()
)
})
})
describe
(
'
eth_estimateGas (returns the fee)
'
,
()
=>
{
describe
(
'
eth_estimateGas (returns the
scaled
fee)
'
,
()
=>
{
it
(
'
gas estimation is deterministic
'
,
async
()
=>
{
let
lastEstimate
:
BigNumber
for
(
let
i
=
0
;
i
<
10
;
i
++
)
{
...
...
@@ -336,7 +353,7 @@ describe('Basic RPC tests', () => {
to
:
DEFAULT_TRANSACTION
.
to
,
value
:
0
,
})
expect
(
estimate
).
to
.
be
.
eq
(
33600000119751
)
expect
(
estimate
).
to
.
be
.
eq
(
0x0dce9004c7
)
})
it
(
'
should return a gas estimate that grows with the size of data
'
,
async
()
=>
{
...
...
@@ -347,7 +364,6 @@ describe('Basic RPC tests', () => {
for
(
const
data
of
dataLen
)
{
const
tx
=
{
to
:
'
0x
'
+
'
1234
'
.
repeat
(
10
),
gasPrice
:
'
0x1
'
,
value
:
'
0x0
'
,
data
:
'
0x
'
+
'
00
'
.
repeat
(
data
),
from
:
'
0x
'
+
'
1234
'
.
repeat
(
10
),
...
...
@@ -357,16 +373,16 @@ describe('Basic RPC tests', () => {
tx
,
])
const
decoded
=
L2
GasLimit
.
decode
(
estimate
)
const
decoded
=
Tx
GasLimit
.
decode
(
estimate
)
expect
(
decoded
).
to
.
deep
.
eq
(
BigNumber
.
from
(
l2Gaslimit
))
expect
(
estimate
.
toString
().
endsWith
(
l2Gaslimit
.
toString
()))
const
l2GasPrice
=
BigNumber
.
from
(
0
)
// The L2GasPrice should be fetched from the L2GasPrice oracle contract,
// but it does not yet exist. Use the default value for now
const
l2GasPrice
=
BigNumber
.
from
(
1
)
const
expected
=
L2GasLimit
.
encode
({
const
expected
=
TxGasLimit
.
encode
({
data
:
tx
.
data
,
l1GasPrice
:
roundL1GasPrice
(
l1GasPrice
)
,
l1GasPrice
,
l2GasLimit
:
BigNumber
.
from
(
l2Gaslimit
),
l2GasPrice
,
})
...
...
l2geth/core/rollup_fee.go
deleted
100644 → 0
View file @
95ccef51
package
core
import
(
"errors"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
)
// overhead represents the fixed cost of batch submission of a single
// transaction in gas
const
overhead
uint64
=
4200
// hundredMillion is a constant used in the gas encoding formula
const
hundredMillion
uint64
=
100
_000_000
var
bigHundredMillion
=
new
(
big
.
Int
)
.
SetUint64
(
hundredMillion
)
// errInvalidGasPrice is the error returned when a user submits an incorrect gas
// price. The gas price must satisfy a particular equation depending on if it
// is a L1 gas price or a L2 gas price
var
errInvalidGasPrice
=
errors
.
New
(
"rollup fee: invalid gas price"
)
// CalculateFee calculates the fee that must be paid to the Rollup sequencer, taking into
// account the cost of publishing data to L1.
// l2_gas_price * l2_gas_limit + l1_gas_price * l1_gas_used
// where the l2 gas price must satisfy the equation `x % (10**8) = 1`
// and the l1 gas price must satisfy the equation `x % (10**8) = 0`
func
CalculateRollupFee
(
data
[]
byte
,
l1GasPrice
,
l2GasLimit
,
l2GasPrice
*
big
.
Int
)
(
*
big
.
Int
,
error
)
{
if
err
:=
VerifyL1GasPrice
(
l1GasPrice
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"invalid L1 gas price %d: %w"
,
l1GasPrice
,
err
)
}
if
err
:=
VerifyL2GasPrice
(
l2GasPrice
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"invalid L2 gas price %d: %w"
,
l2GasPrice
,
err
)
}
l1GasLimit
:=
calculateL1GasLimit
(
data
,
overhead
)
l1Fee
:=
new
(
big
.
Int
)
.
Mul
(
l1GasPrice
,
l1GasLimit
)
l2Fee
:=
new
(
big
.
Int
)
.
Mul
(
l2GasLimit
,
l2GasPrice
)
fee
:=
new
(
big
.
Int
)
.
Add
(
l1Fee
,
l2Fee
)
return
fee
,
nil
}
// calculateL1GasLimit computes the L1 gasLimit based on the calldata and
// constant sized overhead. The overhead can be decreased as the cost of the
// batch submission goes down via contract optimizations. This will not overflow
// under standard network conditions.
func
calculateL1GasLimit
(
data
[]
byte
,
overhead
uint64
)
*
big
.
Int
{
zeroes
,
ones
:=
zeroesAndOnes
(
data
)
zeroesCost
:=
zeroes
*
params
.
TxDataZeroGas
onesCost
:=
ones
*
params
.
TxDataNonZeroGasEIP2028
gasLimit
:=
zeroesCost
+
onesCost
+
overhead
return
new
(
big
.
Int
)
.
SetUint64
(
gasLimit
)
}
// ceilModOneHundredMillion rounds the input integer up to the nearest modulus
// of one hundred million
func
ceilModOneHundredMillion
(
num
*
big
.
Int
)
*
big
.
Int
{
if
new
(
big
.
Int
)
.
Mod
(
num
,
bigHundredMillion
)
.
Cmp
(
common
.
Big0
)
==
0
{
return
num
}
sum
:=
new
(
big
.
Int
)
.
Add
(
num
,
bigHundredMillion
)
mod
:=
new
(
big
.
Int
)
.
Mod
(
num
,
bigHundredMillion
)
return
new
(
big
.
Int
)
.
Sub
(
sum
,
mod
)
}
// VerifyL1GasPrice returns an error if the number is an invalid possible L1 gas
// price
func
VerifyL1GasPrice
(
l1GasPrice
*
big
.
Int
)
error
{
if
new
(
big
.
Int
)
.
Mod
(
l1GasPrice
,
bigHundredMillion
)
.
Cmp
(
common
.
Big0
)
!=
0
{
return
errInvalidGasPrice
}
return
nil
}
// VerifyL2GasPrice returns an error if the number is an invalid possible L2 gas
// price
func
VerifyL2GasPrice
(
l2GasPrice
*
big
.
Int
)
error
{
isNonZero
:=
l2GasPrice
.
Cmp
(
common
.
Big0
)
!=
0
isNotModHundredMillion
:=
new
(
big
.
Int
)
.
Mod
(
l2GasPrice
,
bigHundredMillion
)
.
Cmp
(
common
.
Big1
)
!=
0
if
isNonZero
&&
isNotModHundredMillion
{
return
errInvalidGasPrice
}
if
l2GasPrice
.
Cmp
(
common
.
Big0
)
==
0
{
return
errInvalidGasPrice
}
return
nil
}
// RoundL1GasPrice returns a ceilModOneHundredMillion where 0
// is the identity function
func
RoundL1GasPrice
(
gasPrice
*
big
.
Int
)
*
big
.
Int
{
return
ceilModOneHundredMillion
(
gasPrice
)
}
// RoundL2GasPriceBig implements the algorithm:
// if gasPrice is 0; return 1
// if gasPrice is 1; return 10**8 + 1
// return ceilModOneHundredMillion(gasPrice-1)+1
func
RoundL2GasPrice
(
gasPrice
*
big
.
Int
)
*
big
.
Int
{
if
gasPrice
.
Cmp
(
common
.
Big0
)
==
0
{
return
big
.
NewInt
(
1
)
}
if
gasPrice
.
Cmp
(
common
.
Big1
)
==
0
{
return
new
(
big
.
Int
)
.
Add
(
bigHundredMillion
,
common
.
Big1
)
}
gp
:=
new
(
big
.
Int
)
.
Sub
(
gasPrice
,
common
.
Big1
)
mod
:=
ceilModOneHundredMillion
(
gp
)
return
new
(
big
.
Int
)
.
Add
(
mod
,
common
.
Big1
)
}
func
DecodeL2GasLimit
(
gasLimit
*
big
.
Int
)
*
big
.
Int
{
return
new
(
big
.
Int
)
.
Mod
(
gasLimit
,
bigHundredMillion
)
}
func
zeroesAndOnes
(
data
[]
byte
)
(
uint64
,
uint64
)
{
var
zeroes
uint64
for
_
,
byt
:=
range
data
{
if
byt
==
0
{
zeroes
++
}
}
ones
:=
uint64
(
len
(
data
))
-
zeroes
return
zeroes
,
ones
}
l2geth/eth/gasprice/rollup_gasprice.go
View file @
e1d66363
...
...
@@ -5,62 +5,55 @@ import (
"math/big"
"sync"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/log"
)
// RollupOracle holds the L1 and L2 gas prices for fee calculation
type
RollupOracle
struct
{
dataPrice
*
big
.
Int
execution
Price
*
big
.
Int
dataPriceLock
sync
.
RWMutex
execution
PriceLock
sync
.
RWMutex
l1GasPrice
*
big
.
Int
l2Gas
Price
*
big
.
Int
l1GasPriceLock
sync
.
RWMutex
l2Gas
PriceLock
sync
.
RWMutex
}
// NewRollupOracle returns an initialized RollupOracle
func
NewRollupOracle
(
dataPrice
*
big
.
Int
,
execution
Price
*
big
.
Int
)
*
RollupOracle
{
func
NewRollupOracle
(
l1GasPrice
*
big
.
Int
,
l2Gas
Price
*
big
.
Int
)
*
RollupOracle
{
return
&
RollupOracle
{
dataPrice
:
data
Price
,
executionPrice
:
execution
Price
,
l1GasPrice
:
l1Gas
Price
,
l2GasPrice
:
l2Gas
Price
,
}
}
// SuggestL1GasPrice returns the gas price which should be charged per byte of published
// data by the sequencer.
func
(
gpo
*
RollupOracle
)
SuggestL1GasPrice
(
ctx
context
.
Context
)
(
*
big
.
Int
,
error
)
{
gpo
.
data
PriceLock
.
RLock
()
defer
gpo
.
data
PriceLock
.
RUnlock
()
return
gpo
.
data
Price
,
nil
gpo
.
l1Gas
PriceLock
.
RLock
()
defer
gpo
.
l1Gas
PriceLock
.
RUnlock
()
return
gpo
.
l1Gas
Price
,
nil
}
// SetL1GasPrice returns the current L1 gas price
func
(
gpo
*
RollupOracle
)
SetL1GasPrice
(
dataPrice
*
big
.
Int
)
error
{
gpo
.
dataPriceLock
.
Lock
()
defer
gpo
.
dataPriceLock
.
Unlock
()
if
err
:=
core
.
VerifyL1GasPrice
(
dataPrice
);
err
!=
nil
{
return
err
}
gpo
.
dataPrice
=
dataPrice
log
.
Info
(
"Set L1 Gas Price"
,
"gasprice"
,
gpo
.
dataPrice
)
func
(
gpo
*
RollupOracle
)
SetL1GasPrice
(
gasPrice
*
big
.
Int
)
error
{
gpo
.
l1GasPriceLock
.
Lock
()
defer
gpo
.
l1GasPriceLock
.
Unlock
()
gpo
.
l1GasPrice
=
gasPrice
log
.
Info
(
"Set L1 Gas Price"
,
"gasprice"
,
gpo
.
l1GasPrice
)
return
nil
}
// SuggestL2GasPrice returns the gas price which should be charged per unit of gas
// set manually by the sequencer depending on congestion
func
(
gpo
*
RollupOracle
)
SuggestL2GasPrice
(
ctx
context
.
Context
)
(
*
big
.
Int
,
error
)
{
gpo
.
execution
PriceLock
.
RLock
()
defer
gpo
.
execution
PriceLock
.
RUnlock
()
return
gpo
.
execution
Price
,
nil
gpo
.
l2Gas
PriceLock
.
RLock
()
defer
gpo
.
l2Gas
PriceLock
.
RUnlock
()
return
gpo
.
l2Gas
Price
,
nil
}
// SetL2GasPrice returns the current L2 gas price
func
(
gpo
*
RollupOracle
)
SetL2GasPrice
(
executionPrice
*
big
.
Int
)
error
{
gpo
.
executionPriceLock
.
Lock
()
defer
gpo
.
executionPriceLock
.
Unlock
()
if
err
:=
core
.
VerifyL2GasPrice
(
executionPrice
);
err
!=
nil
{
return
err
}
gpo
.
executionPrice
=
executionPrice
log
.
Info
(
"Set L2 Gas Price"
,
"gasprice"
,
gpo
.
executionPrice
)
func
(
gpo
*
RollupOracle
)
SetL2GasPrice
(
gasPrice
*
big
.
Int
)
error
{
gpo
.
l2GasPriceLock
.
Lock
()
defer
gpo
.
l2GasPriceLock
.
Unlock
()
gpo
.
l2GasPrice
=
gasPrice
log
.
Info
(
"Set L2 Gas Price"
,
"gasprice"
,
gpo
.
l2GasPrice
)
return
nil
}
l2geth/internal/ethapi/api.go
View file @
e1d66363
...
...
@@ -45,15 +45,17 @@ import (
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rollup/fees"
"github.com/ethereum/go-ethereum/rpc"
"github.com/tyler-smith/go-bip39"
)
const
(
defaultGasPrice
=
params
.
Wei
defaultGasPrice
=
params
.
Wei
*
fees
.
TxGasPrice
)
var
errOVMUnsupported
=
errors
.
New
(
"OVM: Unsupported RPC Method"
)
var
bigDefaultGasPrice
=
new
(
big
.
Int
)
.
SetUint64
(
defaultGasPrice
)
// PublicEthereumAPI provides an API to access Ethereum related information.
// It offers only methods that operate on public data that is freely available to anyone.
...
...
@@ -68,7 +70,7 @@ func NewPublicEthereumAPI(b Backend) *PublicEthereumAPI {
// GasPrice always returns 1 gwei. See `DoEstimateGas` below for context.
func
(
s
*
PublicEthereumAPI
)
GasPrice
(
ctx
context
.
Context
)
(
*
hexutil
.
Big
,
error
)
{
return
(
*
hexutil
.
Big
)(
big
.
NewInt
(
defaultGasPrice
)
),
nil
return
(
*
hexutil
.
Big
)(
big
DefaultGasPrice
),
nil
}
// ProtocolVersion returns the current Ethereum protocol version this node supports
...
...
@@ -1037,31 +1039,27 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
if
err
!=
nil
{
return
0
,
err
}
// 2a. fetch the data price, depends on how the sequencer has chosen to update their values based on the
// l1 gas prices
l1GasPrice
,
err
:=
b
.
SuggestL1GasPrice
(
ctx
)
if
err
!=
nil
{
return
0
,
err
}
// 2b. fetch the execution gas price, by the typical mempool dynamics
l2GasPrice
,
err
:=
b
.
SuggestL2GasPrice
(
ctx
)
if
err
!=
nil
{
return
0
,
err
}
var
data
[]
byte
if
args
.
Data
==
nil
{
data
=
[]
byte
{}
}
else
{
data
:=
[]
byte
{}
if
args
.
Data
!=
nil
{
data
=
*
args
.
Data
}
// 3. calculate the fee
// 3. calculate the fee using just the calldata. The additional overhead of
// RLP encoding is covered inside of EncodeL2GasLimit
l2GasLimit
:=
new
(
big
.
Int
)
.
SetUint64
(
uint64
(
gasUsed
))
fee
,
err
:=
core
.
CalculateRollupFee
(
data
,
l1GasPrice
,
l2GasLimit
,
l2GasPrice
)
if
err
!=
nil
{
return
0
,
err
fee
:=
fees
.
EncodeTxGasLimit
(
data
,
l1GasPrice
,
l2GasLimit
,
l2GasPrice
)
if
!
fee
.
IsUint64
()
{
return
0
,
fmt
.
Errorf
(
"estimate gas overflow: %s"
,
fee
)
}
return
(
hexutil
.
Uint64
)(
fee
.
Uint64
()),
nil
}
...
...
l2geth/rollup/client.go
View file @
e1d66363
...
...
@@ -565,7 +565,7 @@ func (c *Client) GetTransactionBatch(index uint64) (*Batch, []*types.Transaction
Get
(
"/batch/transaction/index/{index}"
)
if
err
!=
nil
{
return
nil
,
nil
,
fmt
.
Errorf
(
"Cannot get transaction batch %d
"
,
index
)
return
nil
,
nil
,
fmt
.
Errorf
(
"Cannot get transaction batch %d
: %w"
,
index
,
err
)
}
txBatch
,
ok
:=
response
.
Result
()
.
(
*
TransactionBatchResponse
)
if
!
ok
{
...
...
l2geth/rollup/fees/rollup_fee.go
0 → 100644
View file @
e1d66363
package
fees
import
(
"math/big"
"github.com/ethereum/go-ethereum/params"
)
// overhead represents the fixed cost of batch submission of a single
// transaction in gas.
const
overhead
uint64
=
4200
+
200
*
params
.
TxDataNonZeroGasEIP2028
// hundredMillion is a constant used in the gas encoding formula
const
hundredMillion
uint64
=
100
_000_000
// feeScalar is used to scale the calculations in EncodeL2GasLimit
// to prevent them from being too large
const
feeScalar
uint64
=
1000
// TxGasPrice is a constant that determines the result of `eth_gasPrice`
// It is scaled upwards by 50%
// tx.gasPrice is hard coded to 1500 * wei and all transactions must set that
// gas price.
const
TxGasPrice
uint64
=
feeScalar
+
(
feeScalar
/
2
)
// BigTxGasPrice is the L2GasPrice as type big.Int
var
BigTxGasPrice
=
new
(
big
.
Int
)
.
SetUint64
(
TxGasPrice
)
var
bigFeeScalar
=
new
(
big
.
Int
)
.
SetUint64
(
feeScalar
)
var
bigHundredMillion
=
new
(
big
.
Int
)
.
SetUint64
(
hundredMillion
)
// EncodeTxGasLimit computes the `tx.gasLimit` based on the L1/L2 gas prices and
// the L2 gas limit. The L2 gas limit is encoded inside of the lower order bits
// of the number like so: [ | l2GasLimit ]
// [ tx.gaslimit ]
// The lower order bits must be large enough to fit the L2 gas limit, so 10**8
// is chosen. If higher order bits collide with any bits from the L2 gas limit,
// the L2 gas limit will not be able to be decoded.
// An explicit design goal of this scheme was to make the L2 gas limit be human
// readable. The entire number is interpreted as the gas limit when computing
// the fee, so increasing the L2 Gas limit will increase the fee paid.
// The calculation is:
// l1GasLimit = zero_count(data) * 4 + non_zero_count(data) * 16 + overhead
// l1Fee = l1GasPrice * l1GasLimit
// l2Fee = l2GasPrice * l2GasLimit
// sum = l1Fee + l2Fee
// scaled = sum / scalar
// rounded = ceilmod(scaled, hundredMillion)
// result = rounded + l2GasLimit
// Note that for simplicity purposes, only the calldata is passed into this
// function when in reality the RLP encoded transaction should be. The
// additional cost is added to the overhead constant to prevent the need to RLP
// encode transactions during calls to `eth_estimateGas`
func
EncodeTxGasLimit
(
data
[]
byte
,
l1GasPrice
,
l2GasLimit
,
l2GasPrice
*
big
.
Int
)
*
big
.
Int
{
l1GasLimit
:=
calculateL1GasLimit
(
data
,
overhead
)
l1Fee
:=
new
(
big
.
Int
)
.
Mul
(
l1GasPrice
,
l1GasLimit
)
l2Fee
:=
new
(
big
.
Int
)
.
Mul
(
l2GasPrice
,
l2GasLimit
)
sum
:=
new
(
big
.
Int
)
.
Add
(
l1Fee
,
l2Fee
)
scaled
:=
new
(
big
.
Int
)
.
Div
(
sum
,
bigFeeScalar
)
remainder
:=
new
(
big
.
Int
)
.
Mod
(
scaled
,
bigHundredMillion
)
scaledSum
:=
new
(
big
.
Int
)
.
Add
(
scaled
,
bigHundredMillion
)
rounded
:=
new
(
big
.
Int
)
.
Sub
(
scaledSum
,
remainder
)
result
:=
new
(
big
.
Int
)
.
Add
(
rounded
,
l2GasLimit
)
return
result
}
// DecodeL2GasLimit decodes the L2 gas limit from an encoded L2 gas limit
func
DecodeL2GasLimit
(
gasLimit
*
big
.
Int
)
*
big
.
Int
{
return
new
(
big
.
Int
)
.
Mod
(
gasLimit
,
bigHundredMillion
)
}
// calculateL1GasLimit computes the L1 gasLimit based on the calldata and
// constant sized overhead. The overhead can be decreased as the cost of the
// batch submission goes down via contract optimizations. This will not overflow
// under standard network conditions.
func
calculateL1GasLimit
(
data
[]
byte
,
overhead
uint64
)
*
big
.
Int
{
zeroes
,
ones
:=
zeroesAndOnes
(
data
)
zeroesCost
:=
zeroes
*
params
.
TxDataZeroGas
onesCost
:=
ones
*
params
.
TxDataNonZeroGasEIP2028
gasLimit
:=
zeroesCost
+
onesCost
+
overhead
return
new
(
big
.
Int
)
.
SetUint64
(
gasLimit
)
}
func
zeroesAndOnes
(
data
[]
byte
)
(
uint64
,
uint64
)
{
var
zeroes
uint64
var
ones
uint64
for
_
,
byt
:=
range
data
{
if
byt
==
0
{
zeroes
++
}
else
{
ones
++
}
}
return
zeroes
,
ones
}
l2geth/
core
/rollup_fee_test.go
→
l2geth/
rollup/fees
/rollup_fee_test.go
View file @
e1d66363
package
core
package
fees
import
(
"errors"
"math"
"math/big"
"testing"
)
var
roundingL1GasPriceTests
=
map
[
string
]
struct
{
input
uint64
expect
uint64
}{
"simple"
:
{
10
,
pow10
(
8
)},
"one-over"
:
{
pow10
(
8
)
+
1
,
2
*
pow10
(
8
)},
"exact"
:
{
pow10
(
8
),
pow10
(
8
)},
"one-under"
:
{
pow10
(
8
)
-
1
,
pow10
(
8
)},
"small"
:
{
3
,
pow10
(
8
)},
"two"
:
{
2
,
pow10
(
8
)},
"one"
:
{
1
,
pow10
(
8
)},
"zero"
:
{
0
,
0
},
}
func
TestRoundL1GasPrice
(
t
*
testing
.
T
)
{
for
name
,
tt
:=
range
roundingL1GasPriceTests
{
t
.
Run
(
name
,
func
(
t
*
testing
.
T
)
{
got
:=
RoundL1GasPrice
(
new
(
big
.
Int
)
.
SetUint64
(
tt
.
input
))
if
got
.
Uint64
()
!=
tt
.
expect
{
t
.
Fatalf
(
"Mismatched rounding to nearest, got %d expected %d"
,
got
,
tt
.
expect
)
}
})
}
}
var
roundingL2GasPriceTests
=
map
[
string
]
struct
{
input
uint64
expect
uint64
}{
"simple"
:
{
10
,
pow10
(
8
)
+
1
},
"one-over"
:
{
pow10
(
8
)
+
2
,
2
*
pow10
(
8
)
+
1
},
"exact"
:
{
pow10
(
8
)
+
1
,
pow10
(
8
)
+
1
},
"one-under"
:
{
pow10
(
8
),
pow10
(
8
)
+
1
},
"small"
:
{
3
,
pow10
(
8
)
+
1
},
"two"
:
{
2
,
pow10
(
8
)
+
1
},
"one"
:
{
1
,
pow10
(
8
)
+
1
},
"zero"
:
{
0
,
1
},
}
func
TestRoundL2GasPrice
(
t
*
testing
.
T
)
{
for
name
,
tt
:=
range
roundingL2GasPriceTests
{
t
.
Run
(
name
,
func
(
t
*
testing
.
T
)
{
got
:=
RoundL2GasPrice
(
new
(
big
.
Int
)
.
SetUint64
(
tt
.
input
))
if
got
.
Uint64
()
!=
tt
.
expect
{
t
.
Fatalf
(
"Mismatched rounding to nearest, got %d expected %d"
,
got
,
tt
.
expect
)
}
})
}
}
"github.com/ethereum/go-ethereum/params"
)
var
l1GasLimitTests
=
map
[
string
]
struct
{
data
[]
byte
...
...
@@ -84,38 +34,70 @@ var feeTests = map[string]struct {
l1GasPrice
uint64
l2GasLimit
uint64
l2GasPrice
uint64
err
error
}{
"simple"
:
{
100
,
100
_000_000
,
437118
,
100
_000_001
,
nil
},
"zero-l2-gasprice"
:
{
10
,
100
_000_000
,
196205
,
0
,
errInvalidGasPrice
},
"one-l2-gasprice"
:
{
10
,
100
_000_000
,
196205
,
1
,
nil
},
"zero-l1-gasprice"
:
{
10
,
0
,
196205
,
100
_000_001
,
nil
},
"one-l1-gasprice"
:
{
10
,
1
,
23255
,
23254
,
errInvalidGasPrice
},
"simple"
:
{
dataLen
:
10
,
l1GasPrice
:
params
.
GWei
,
l2GasLimit
:
437118
,
l2GasPrice
:
params
.
GWei
,
},
"zero-l2-gasprice"
:
{
dataLen
:
10
,
l1GasPrice
:
params
.
GWei
,
l2GasLimit
:
196205
,
l2GasPrice
:
0
,
},
"one-l2-gasprice"
:
{
dataLen
:
10
,
l1GasPrice
:
params
.
GWei
,
l2GasLimit
:
196205
,
l2GasPrice
:
1
,
},
"zero-l1-gasprice"
:
{
dataLen
:
10
,
l1GasPrice
:
0
,
l2GasLimit
:
196205
,
l2GasPrice
:
params
.
GWei
,
},
"one-l1-gasprice"
:
{
dataLen
:
10
,
l1GasPrice
:
1
,
l2GasLimit
:
23255
,
l2GasPrice
:
params
.
GWei
,
},
"zero-gasprices"
:
{
dataLen
:
10
,
l1GasPrice
:
0
,
l2GasLimit
:
23255
,
l2GasPrice
:
0
,
},
"max-gaslimit"
:
{
dataLen
:
10
,
l1GasPrice
:
params
.
GWei
,
l2GasLimit
:
99999999
,
l2GasPrice
:
params
.
GWei
,
},
"larger-divisor"
:
{
dataLen
:
10
,
l1GasPrice
:
0
,
l2GasLimit
:
10
,
l2GasPrice
:
0
,
},
}
func
TestCalculateRollupFee
(
t
*
testing
.
T
)
{
for
name
,
tt
:=
range
feeTests
{
t
.
Run
(
name
,
func
(
t
*
testing
.
T
)
{
data
:=
make
([]
byte
,
0
,
tt
.
dataLen
)
data
:=
make
([]
byte
,
tt
.
dataLen
)
l1GasPrice
:=
new
(
big
.
Int
)
.
SetUint64
(
tt
.
l1GasPrice
)
l2GasLimit
:=
new
(
big
.
Int
)
.
SetUint64
(
tt
.
l2GasLimit
)
l2GasPrice
:=
new
(
big
.
Int
)
.
SetUint64
(
tt
.
l2GasPrice
)
fee
,
err
:=
CalculateRollupFee
(
data
,
l1GasPrice
,
l2GasLimit
,
l2GasPrice
)
if
!
errors
.
Is
(
err
,
tt
.
err
)
{
t
.
Fatalf
(
"Cannot calculate fee: %s"
,
err
)
}
if
err
==
nil
{
fee
:=
EncodeTxGasLimit
(
data
,
l1GasPrice
,
l2GasLimit
,
l2GasPrice
)
decodedGasLimit
:=
DecodeL2GasLimit
(
fee
)
if
l2GasLimit
.
Cmp
(
decodedGasLimit
)
!=
0
{
t
.
Errorf
(
"rollup fee check failed: expected %d, got %d"
,
l2GasLimit
.
Uint64
(),
decodedGasLimit
)
}
}
})
}
}
func
pow10
(
x
int
)
uint64
{
return
uint64
(
math
.
Pow10
(
x
))
}
l2geth/rollup/sync_service.go
View file @
e1d66363
...
...
@@ -21,6 +21,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/gasprice"
"github.com/ethereum/go-ethereum/rollup/fees"
)
// errShortRemoteTip is an error for when the remote tip is shorter than the
...
...
@@ -445,13 +446,7 @@ func (s *SyncService) updateL2GasPrice(hash *common.Hash) error {
return
err
}
result
:=
state
.
GetState
(
s
.
gpoAddress
,
l2GasPriceSlot
)
gasPrice
:=
result
.
Big
()
if
err
:=
core
.
VerifyL2GasPrice
(
gasPrice
);
err
!=
nil
{
gp
:=
core
.
RoundL2GasPrice
(
gasPrice
)
log
.
Warn
(
"Invalid gas price detected in state"
,
"state"
,
gasPrice
,
"using"
,
gp
)
gasPrice
=
gp
}
s
.
RollupGpo
.
SetL2GasPrice
(
gasPrice
)
s
.
RollupGpo
.
SetL2GasPrice
(
result
.
Big
())
return
nil
}
...
...
@@ -728,11 +723,18 @@ func (s *SyncService) applyBatchedTransaction(tx *types.Transaction) error {
// verifyFee will verify that a valid fee is being paid.
func
(
s
*
SyncService
)
verifyFee
(
tx
*
types
.
Transaction
)
error
{
if
tx
.
GasPrice
()
.
Cmp
(
common
.
Big0
)
==
0
{
// Exit early if fees are enforced and the gasPrice is set to 0
if
s
.
enforceFees
&&
tx
.
GasPrice
()
.
Cmp
(
common
.
Big0
)
==
0
{
if
s
.
enforceFees
{
return
errors
.
New
(
"cannot accept 0 gas price transaction"
)
}
// If fees are not enforced and the gas price is 0, return early
return
nil
}
// When the gas price is non zero, it must be equal to the constant
if
tx
.
GasPrice
()
.
Cmp
(
fees
.
BigTxGasPrice
)
!=
0
{
return
fmt
.
Errorf
(
"tx.gasPrice must be %d"
,
fees
.
TxGasPrice
)
}
l1GasPrice
,
err
:=
s
.
RollupGpo
.
SuggestL1GasPrice
(
context
.
Background
())
if
err
!=
nil
{
return
err
...
...
@@ -743,22 +745,29 @@ func (s *SyncService) verifyFee(tx *types.Transaction) error {
}
// Calculate the fee based on decoded L2 gas limit
gas
:=
new
(
big
.
Int
)
.
SetUint64
(
tx
.
Gas
())
l2GasLimit
:=
core
.
DecodeL2GasLimit
(
gas
)
fee
,
err
:=
core
.
CalculateRollupFee
(
tx
.
Data
(),
l1GasPrice
,
l2GasLimit
,
l2GasPrice
)
l2GasLimit
:=
fees
.
DecodeL2GasLimit
(
gas
)
// Only count the calldata here as the overhead of the fully encoded
// RLP transaction is handled inside of EncodeL2GasLimit
fee
:=
fees
.
EncodeTxGasLimit
(
tx
.
Data
(),
l1GasPrice
,
l2GasLimit
,
l2GasPrice
)
if
err
!=
nil
{
return
err
}
// If fees are not enforced and the gas price is 0, return early
if
!
s
.
enforceFees
&&
tx
.
GasPrice
()
.
Cmp
(
common
.
Big0
)
==
0
{
return
nil
}
// This should only happen if the transaction fee is greater than 18.44 ETH
if
!
fee
.
IsUint64
()
{
return
fmt
.
Errorf
(
"fee overflow: %s"
,
fee
.
String
())
}
// Make sure that the fee is paid
if
tx
.
Gas
()
<
fee
.
Uint64
()
{
return
fmt
.
Errorf
(
"fee too low: %d, use at least tx.gasLimit = %d and tx.gasPrice = 1"
,
tx
.
Gas
(),
fee
.
Uint64
())
// Compute the user's fee
paying
:=
new
(
big
.
Int
)
.
Mul
(
new
(
big
.
Int
)
.
SetUint64
(
tx
.
Gas
()),
tx
.
GasPrice
())
// Compute the minimum expected fee
expecting
:=
new
(
big
.
Int
)
.
Mul
(
fee
,
fees
.
BigTxGasPrice
)
if
paying
.
Cmp
(
expecting
)
==
-
1
{
return
fmt
.
Errorf
(
"fee too low: %d, use at least tx.gasLimit = %d and tx.gasPrice = %d"
,
paying
,
fee
.
Uint64
(),
fees
.
BigTxGasPrice
)
}
// Protect users from overpaying by too much
overpaying
:=
new
(
big
.
Int
)
.
Sub
(
paying
,
expecting
)
threshold
:=
new
(
big
.
Int
)
.
Mul
(
expecting
,
common
.
Big3
)
if
overpaying
.
Cmp
(
threshold
)
==
1
{
return
fmt
.
Errorf
(
"fee too large: %d"
,
paying
)
}
return
nil
}
...
...
@@ -776,7 +785,6 @@ func (s *SyncService) ValidateAndApplySequencerTransaction(tx *types.Transaction
if
err
:=
s
.
verifyFee
(
tx
);
err
!=
nil
{
return
err
}
s
.
txLock
.
Lock
()
defer
s
.
txLock
.
Unlock
()
log
.
Trace
(
"Sequencer transaction validation"
,
"hash"
,
tx
.
Hash
()
.
Hex
())
...
...
l2geth/rollup/sync_service_test.go
View file @
e1d66363
...
...
@@ -506,7 +506,8 @@ func TestSyncServiceL1GasPrice(t *testing.T) {
t
.
Fatal
(
err
)
}
if
gasAfter
.
Cmp
(
core
.
RoundL1GasPrice
(
big
.
NewInt
(
1
)))
!=
0
{
expect
,
_
:=
service
.
client
.
GetL1GasPrice
()
if
gasAfter
.
Cmp
(
expect
)
!=
0
{
t
.
Fatal
(
"expected 100 gas price, got"
,
gasAfter
)
}
}
...
...
@@ -532,7 +533,7 @@ func TestSyncServiceL2GasPrice(t *testing.T) {
if
err
!=
nil
{
t
.
Fatal
(
"Cannot get state db"
)
}
l2GasPrice
:=
big
.
NewInt
(
10000000
1
)
l2GasPrice
:=
big
.
NewInt
(
10000000
0000
)
state
.
SetState
(
service
.
gpoAddress
,
l2GasPriceSlot
,
common
.
BigToHash
(
l2GasPrice
))
root
,
_
:=
state
.
Commit
(
false
)
...
...
@@ -821,7 +822,7 @@ func (m *mockClient) SyncStatus(backend Backend) (*SyncStatus, error) {
}
func
(
m
*
mockClient
)
GetL1GasPrice
()
(
*
big
.
Int
,
error
)
{
price
:=
core
.
RoundL1GasPrice
(
big
.
NewInt
(
2
)
)
price
:=
big
.
NewInt
(
1
)
return
price
,
nil
}
...
...
ops/docker/hardhat/yarn.lock
View file @
e1d66363
...
...
@@ -2252,9 +2252,9 @@ wrappy@1:
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
ws@^7.2.1:
version "7.4.
4
"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.
4.tgz#383bc9742cb202292c9077ceab6f6047b17f2d59
"
integrity sha512-
Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw
==
version "7.4.
6
"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.
6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c
"
integrity sha512-
YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A
==
xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.0:
version "4.0.2"
...
...
packages/contracts/README.md
View file @
e1d66363
...
...
@@ -84,5 +84,81 @@ You can also build specific components as follows:
yarn build:contracts
```
### Deploying the Contracts
To deploy the contracts first clone, install, and build the contracts package.
Next set the following env vars:
```
bash
CONTRACTS_TARGET_NETWORK
=
...
CONTRACTS_DEPLOYER_KEY
=
...
CONTRACTS_RPC_URL
=
...
```
Then to perform the actual deployment run:
```
bash
npx hardhat deploy
\
--network
...
\
# `network` MUST equal your env var `CONTRACTS_TARGET_NETWORK`
--ovm-address-manager-owner
...
\
--ovm-proposer-address
...
\
--ovm-relayer-address
...
\
--ovm-sequencer-address
...
\
--scc-fraud-proof-window
...
\
--scc-sequencer-publish-window
...
```
This will deploy the contracts to the network specified in your env and create
an artifacts directory in
`./deployments`
.
To view all deployment options run:
```
bash
npx hardhat deploy
--help
Hardhat version 2.2.1
Usage: hardhat
[
GLOBAL OPTIONS] deploy
[
--ctc-force-inclusion-period-seconds
<INT>]
[
--ctc-max-transaction-gas-limit
<INT>]
--deploy-scripts
<STRING>
[
--em-max-gas-per-queue-per-epoch
<INT>]
[
--em-max-transaction-gas-limit
<INT>]
[
--em-min-transaction-gas-limit
<INT>]
[
--em-ovm-chain-id
<INT>]
[
--em-seconds-per-epoch
<INT>]
--export
<STRING>
--export-all
<STRING>
--gasprice
<STRING>
[
--l1-block-time-seconds
<INT>]
[
--no-compile
]
[
--no-impersonation
]
--ovm-address-manager-owner
<STRING>
--ovm-proposer-address
<STRING>
--ovm-relayer-address
<STRING>
--ovm-sequencer-address
<STRING>
[
--reset
]
[
--scc-fraud-proof-window
<INT>]
[
--scc-sequencer-publish-window
<INT>]
[
--silent
]
--tags
<STRING>
[
--watch
]
--write
<BOOLEAN>
OPTIONS:
--ctc-force-inclusion-period-seconds
Number of seconds that the sequencer has to include transactions before the L1 queue.
(
default: 2592000
)
--ctc-max-transaction-gas-limit
Max gas limit
for
L1 queue transactions.
(
default: 9000000
)
--deploy-scripts
override deploy script folder path
--em-max-gas-per-queue-per-epoch
Maximum gas allowed
in
a given queue
for
each epoch.
(
default: 250000000
)
--em-max-transaction-gas-limit
Maximum allowed transaction gas limit.
(
default: 9000000
)
--em-min-transaction-gas-limit
Minimum allowed transaction gas limit.
(
default: 50000
)
--em-ovm-chain-id
Chain ID
for
the L2 network.
(
default: 420
)
--em-seconds-per-epoch
Number of seconds
in
each epoch.
(
default: 0
)
--export
export
current network deployments
--export-all
export
all deployments into one file
--gasprice
gas price to use
for
transactions
--l1-block-time-seconds
Number of seconds on average between every L1 block.
(
default: 15
)
--no-compile
disable pre compilation
--no-impersonation
do
not impersonate unknown accounts
--ovm-address-manager-owner
Address that will own the Lib_AddressManager. Must be provided or this deployment will fail.
--ovm-proposer-address
Address of the account that will propose state roots. Must be provided or this deployment will fail.
--ovm-relayer-address
Address of the message relayer. Must be provided or this deployment will fail.
--ovm-sequencer-address
Address of the sequencer. Must be provided or this deployment will fail.
--reset
whether to delete deployments files first
--scc-fraud-proof-window
Number of seconds
until
a transaction is considered finalized.
(
default: 604800
)
--scc-sequencer-publish-window
Number of seconds that the sequencer is exclusively allowed to post state roots.
(
default: 1800
)
--silent
whether to remove log
--tags
specify which deploy script to execute via tags, separated by commas
--watch
redeploy on every change of contract or deploy script
--write
whether to write deployments to file
deploy: Deploy contracts
For global options
help
run: hardhat
help
```
### Verifying Deployments on Etherscan
If you are using a network which Etherscan supports you can verify your contracts with:
```
bash
npx hardhat etherscan-verify
--api-key
...
--network
...
```
## Security
Please refer to our
[
Security Policy
](
https://github.com/ethereum-optimism/.github/security/policy
)
for information about how to disclose security issues with this code.
packages/contracts/package.json
View file @
e1d66363
...
...
@@ -32,6 +32,9 @@
"test:contracts"
:
"hardhat test --show-stack-traces"
,
"test:gas"
:
"hardhat test
\"
test/contracts/OVM/execution/OVM_StateManager.gas-spec.ts
\"
--no-compile --show-stack-traces"
,
"test:coverage"
:
"NODE_OPTIONS=--max_old_space_size=8192 hardhat coverage"
,
"test:slither"
:
"slither ."
,
"pretest:slither"
:
"rm -f @openzeppelin && rm -f @ens && rm -f hardhat && ln -s ../../node_modules/@openzeppelin @openzeppelin && ln -s ../../node_modules/@ens @ens && ln -s ../../node_modules/hardhat hardhat"
,
"posttest:slither"
:
"rm -f @openzeppelin && rm -f @ens && rm -f hardhat"
,
"lint"
:
"yarn lint:fix && yarn lint:check"
,
"lint:fix"
:
"yarn run lint:fix:typescript"
,
"lint:fix:typescript"
:
"prettier --config .prettierrc.json --write
\"
hardhat.config.ts
\"
\"
{src,test}/**/*.ts
\"
"
,
...
...
packages/contracts/slither.config.json
0 → 100644
View file @
e1d66363
{
"detectors_to_exclude"
:
"conformance-to-solidity-naming-conventions,assembly-usage,low-level-calls,block-timestamp"
,
"exclude_informational"
:
false
,
"exclude_low"
:
false
,
"exclude_medium"
:
false
,
"exclude_high"
:
false
,
"solc_disable_warnings"
:
false
,
"hardhat_ignore_compile"
:
true
,
"disable_color"
:
false
,
"exclude_dependencies"
:
true
,
"filter_paths"
:
"@openzeppelin|hardhat|contracts/test-helpers|contracts/test-libraries"
}
packages/core-utils/README.md
View file @
e1d66363
...
...
@@ -25,36 +25,29 @@ $ yarn lint
### L2 Fees
The Layer 2 fee is encoded in
`tx.gasLimit`
. The Layer 2
`gasLimit`
is encoded
in the lower order bits of the
`tx.gasLimit`
. For this scheme to work, both the
L1 gas price and the L2 gas price must satisfy specific equations. There are
functions that help ensure that the correct gas prices are used.
-
`roundL1GasPrice`
-
`roundL2GasPrice`
The Layer 2 fee is based on both the cost of submitting the data to L1 as well
as the cost of execution on L2. To make libraries like
`ethers`
just work, the
return value of
`eth_estimateGas`
has been modified to return the fee. A new RPC
endpoint
`eth_estimateExecutionGas`
has been added that returns the L2 gas used.
To locally encode the
`tx.gasLimit`
, the
`L2GasLimit`
methods
`encode`
and
`decode`
should be used.
`TxGasLimit`
can be used to
`encode`
and
`decode`
the L2 Gas Limit
locally.
```
typescript
import
{
L2GasLimit
,
roundL1GasPrice
,
roundL2GasPrice
}
from
'
@eth-optimism/core-utils
'
import
{
TxGasLimit
}
from
'
@eth-optimism/core-utils
'
import
{
JsonRpcProvider
}
from
'
ethers
'
const
p
rovider
=
new
JsonRpcProvider
(
'
https://mainnet.optimism.io
'
)
const
gasLimit
=
await
provider
.
send
(
'
eth_estimateExecutionGas
'
,
[
tx
]
)
const
L2P
rovider
=
new
JsonRpcProvider
(
'
https://mainnet.optimism.io
'
)
const
L1Provider
=
new
JsonRpcProvider
(
'
http://127.0.0.1:8545
'
)
const
encoded
=
L2GasLimit
.
encode
({
const
l2GasLimit
=
await
L2Provider
.
send
(
'
eth_estimateExecutionGas
'
,
[
tx
])
const
l1GasPrice
=
await
L1Provider
.
getGasPrice
()
const
encoded
=
TxGasLimit
.
encode
({
data
:
'
0x
'
,
l1GasPrice
:
roundL1GasPrice
(
1
)
,
l2GasLimit
:
gasLimit
,
l2GasPrice
:
roundL2GasPrice
(
1
)
,
l1GasPrice
,
l2GasLimit
,
l2GasPrice
:
10000000
,
})
const
decoded
=
L2
GasLimit
.
decode
(
encoded
)
const
decoded
=
Tx
GasLimit
.
decode
(
encoded
)
assert
(
decoded
.
eq
(
gasLimit
))
const
estimate
=
await
L2Provider
.
estimateGas
()
assert
(
estimate
.
eq
(
encoded
))
```
packages/core-utils/src/fees.ts
View file @
e1d66363
...
...
@@ -6,9 +6,11 @@ import { BigNumber } from 'ethers'
import
{
remove0x
}
from
'
./common
'
const
hundredMillion
=
BigNumber
.
from
(
100
_000_000
)
const
feeScalar
=
1000
export
const
TxGasPrice
=
BigNumber
.
from
(
feeScalar
+
feeScalar
/
2
)
const
txDataZeroGas
=
4
const
txDataNonZeroGasEIP2028
=
16
const
overhead
=
4200
const
overhead
=
4200
+
200
*
txDataNonZeroGasEIP2028
export
interface
EncodableL2GasLimit
{
data
:
Buffer
|
string
...
...
@@ -29,17 +31,15 @@ function encode(input: EncodableL2GasLimit): BigNumber {
if
(
typeof
l2GasPrice
===
'
number
'
)
{
l2GasPrice
=
BigNumber
.
from
(
l2GasPrice
)
}
if
(
!
verifyL2GasPrice
(
l2GasPrice
))
{
throw
new
Error
(
`Invalid L2 Gas Price:
${
l2GasPrice
.
toString
()}
`
)
}
if
(
!
verifyL1GasPrice
(
l1GasPrice
))
{
throw
new
Error
(
`Invalid L1 Gas Price:
${
l1GasPrice
.
toString
()}
`
)
}
const
l1GasLimit
=
calculateL1GasLimit
(
data
)
const
l1Fee
=
l1Gas
Price
.
mul
(
l1GasLimit
)
const
l1Fee
=
l1Gas
Limit
.
mul
(
l1GasPrice
)
const
l2Fee
=
l2GasLimit
.
mul
(
l2GasPrice
)
return
l1Fee
.
add
(
l2Fee
)
const
sum
=
l1Fee
.
add
(
l2Fee
)
const
scaled
=
sum
.
div
(
feeScalar
)
const
remainder
=
scaled
.
mod
(
hundredMillion
)
const
scaledSum
=
scaled
.
add
(
hundredMillion
)
const
rounded
=
scaledSum
.
sub
(
remainder
)
return
rounded
.
add
(
l2GasLimit
)
}
function
decode
(
fee
:
BigNumber
|
number
):
BigNumber
{
...
...
@@ -49,39 +49,17 @@ function decode(fee: BigNumber | number): BigNumber {
return
fee
.
mod
(
hundredMillion
)
}
export
const
L2
GasLimit
=
{
export
const
Tx
GasLimit
=
{
encode
,
decode
,
}
export
function
verifyL2GasPrice
(
gasPrice
:
BigNumber
|
number
):
boolean
{
if
(
typeof
gasPrice
===
'
number
'
)
{
gasPrice
=
BigNumber
.
from
(
gasPrice
)
}
// If the gas price is not equal to 0 and the gas price mod
// one hundred million is not one
if
(
!
gasPrice
.
eq
(
0
)
&&
!
gasPrice
.
mod
(
hundredMillion
).
eq
(
1
))
{
return
false
}
if
(
gasPrice
.
eq
(
0
))
{
return
false
}
return
true
}
export
function
verifyL1GasPrice
(
gasPrice
:
BigNumber
|
number
):
boolean
{
if
(
typeof
gasPrice
===
'
number
'
)
{
gasPrice
=
BigNumber
.
from
(
gasPrice
)
}
return
gasPrice
.
mod
(
hundredMillion
).
eq
(
0
)
}
export
function
calculateL1GasLimit
(
data
:
string
|
Buffer
):
number
{
export
function
calculateL1GasLimit
(
data
:
string
|
Buffer
):
BigNumber
{
const
[
zeroes
,
ones
]
=
zeroesAndOnes
(
data
)
const
zeroesCost
=
zeroes
*
txDataZeroGas
const
onesCost
=
ones
*
txDataNonZeroGasEIP2028
const
gasLimit
=
zeroesCost
+
onesCost
+
overhead
return
gasLimit
return
BigNumber
.
from
(
gasLimit
)
}
export
function
zeroesAndOnes
(
data
:
Buffer
|
string
):
Array
<
number
>
{
...
...
@@ -99,34 +77,3 @@ export function zeroesAndOnes(data: Buffer | string): Array<number> {
}
return
[
zeros
,
ones
]
}
export
function
roundL1GasPrice
(
gasPrice
:
BigNumber
|
number
):
BigNumber
{
if
(
typeof
gasPrice
===
'
number
'
)
{
gasPrice
=
BigNumber
.
from
(
gasPrice
)
}
return
ceilModOneHundredMillion
(
gasPrice
)
}
function
ceilModOneHundredMillion
(
num
:
BigNumber
):
BigNumber
{
if
(
num
.
mod
(
hundredMillion
).
eq
(
0
))
{
return
num
}
const
sum
=
num
.
add
(
hundredMillion
)
const
mod
=
num
.
mod
(
hundredMillion
)
return
sum
.
sub
(
mod
)
}
export
function
roundL2GasPrice
(
gasPrice
:
BigNumber
|
number
):
BigNumber
{
if
(
typeof
gasPrice
===
'
number
'
)
{
gasPrice
=
BigNumber
.
from
(
gasPrice
)
}
if
(
gasPrice
.
eq
(
0
))
{
return
BigNumber
.
from
(
1
)
}
if
(
gasPrice
.
eq
(
1
))
{
return
hundredMillion
.
add
(
1
)
}
const
gp
=
gasPrice
.
sub
(
1
)
const
mod
=
ceilModOneHundredMillion
(
gp
)
return
mod
.
add
(
1
)
}
packages/core-utils/test/fees/fees.spec.ts
View file @
e1d66363
import
{
expect
}
from
'
../setup
'
import
*
as
fees
from
'
../../src/fees
'
import
{
BigNumber
}
from
'
ethers
'
import
{
BigNumber
,
utils
}
from
'
ethers
'
const
hundredBillion
=
10
**
11
const
million
=
10
**
6
describe
(
'
Fees
'
,
()
=>
{
it
(
'
should count zeros and ones
'
,
()
=>
{
...
...
@@ -18,115 +21,99 @@ describe('Fees', () => {
}
})
describe
(
'
Round L1 Gas Price
'
,
()
=>
{
const
roundL1GasPriceTests
=
[
{
input
:
10
,
expect
:
10
**
8
,
name
:
'
simple
'
},
{
input
:
10
**
8
+
1
,
expect
:
2
*
10
**
8
,
name
:
'
one-over
'
},
{
input
:
10
**
8
,
expect
:
10
**
8
,
name
:
'
exact
'
},
{
input
:
10
**
8
-
1
,
expect
:
10
**
8
,
name
:
'
one-under
'
},
{
input
:
3
,
expect
:
10
**
8
,
name
:
'
small
'
},
{
input
:
2
,
expect
:
10
**
8
,
name
:
'
two
'
},
{
input
:
1
,
expect
:
10
**
8
,
name
:
'
one
'
},
{
input
:
0
,
expect
:
0
,
name
:
'
zero
'
},
]
for
(
const
test
of
roundL1GasPriceTests
)
{
it
(
`should pass for
${
test
.
name
}
case`
,
()
=>
{
const
got
=
fees
.
roundL1GasPrice
(
test
.
input
)
const
expected
=
BigNumber
.
from
(
test
.
expect
)
expect
(
got
).
to
.
deep
.
equal
(
expected
)
})
}
})
describe
(
'
Round L2 Gas Price
'
,
()
=>
{
const
roundL2GasPriceTests
=
[
{
input
:
10
,
expect
:
10
**
8
+
1
,
name
:
'
simple
'
},
{
input
:
10
**
8
+
2
,
expect
:
2
*
10
**
8
+
1
,
name
:
'
one-over
'
},
{
input
:
10
**
8
+
1
,
expect
:
10
**
8
+
1
,
name
:
'
exact
'
},
{
input
:
10
**
8
,
expect
:
10
**
8
+
1
,
name
:
'
one-under
'
},
{
input
:
3
,
expect
:
10
**
8
+
1
,
name
:
'
small
'
},
{
input
:
2
,
expect
:
10
**
8
+
1
,
name
:
'
two
'
},
{
input
:
1
,
expect
:
10
**
8
+
1
,
name
:
'
one
'
},
{
input
:
0
,
expect
:
1
,
name
:
'
zero
'
},
]
for
(
const
test
of
roundL2GasPriceTests
)
{
it
(
`should pass for
${
test
.
name
}
case`
,
()
=>
{
const
got
=
fees
.
roundL2GasPrice
(
test
.
input
)
const
expected
=
BigNumber
.
from
(
test
.
expect
)
expect
(
got
).
to
.
deep
.
equal
(
expected
)
})
}
})
describe
(
'
Rollup Fees
'
,
()
=>
{
const
rollupFeesTests
=
[
{
name
:
'
simple
'
,
dataLen
:
10
,
l1GasPrice
:
100
_000_000
,
l2GasPrice
:
100
_000_001
,
l1GasPrice
:
utils
.
parseUnits
(
'
1
'
,
'
gwei
'
)
,
l2GasPrice
:
utils
.
parseUnits
(
'
1
'
,
'
gwei
'
)
,
l2GasLimit
:
437118
,
error
:
false
,
},
{
name
:
'
small-gasprices-max-gaslimit
'
,
dataLen
:
10
,
l1GasPrice
:
utils
.
parseUnits
(
'
1
'
,
'
wei
'
),
l2GasPrice
:
utils
.
parseUnits
(
'
1
'
,
'
wei
'
),
l2GasLimit
:
0x4ffffff
,
},
{
name
:
'
large-gasprices-max-gaslimit
'
,
dataLen
:
10
,
l1GasPrice
:
utils
.
parseUnits
(
'
1
'
,
'
ether
'
),
l2GasPrice
:
utils
.
parseUnits
(
'
1
'
,
'
ether
'
),
l2GasLimit
:
0x4ffffff
,
},
{
name
:
'
small-gasprices-max-gaslimit
'
,
dataLen
:
10
,
l1GasPrice
:
utils
.
parseUnits
(
'
1
'
,
'
ether
'
),
l2GasPrice
:
utils
.
parseUnits
(
'
1
'
,
'
ether
'
),
l2GasLimit
:
1
,
},
{
name
:
'
max-gas-limit
'
,
dataLen
:
10
,
l1GasPrice
:
utils
.
parseUnits
(
'
5
'
,
'
ether
'
),
l2GasPrice
:
utils
.
parseUnits
(
'
5
'
,
'
ether
'
),
l2GasLimit
:
10
**
8
-
1
,
},
{
name
:
'
zero-l2-gasprice
'
,
dataLen
:
10
,
l1GasPrice
:
100
_000_000
,
l1GasPrice
:
hundredBillion
,
l2GasPrice
:
0
,
l2GasLimit
:
196205
,
error
:
true
,
},
{
name
:
'
one-l2-gasprice
'
,
dataLen
:
10
,
l1GasPrice
:
100
_000_000
,
l1GasPrice
:
hundredBillion
,
l2GasPrice
:
1
,
l2GasLimit
:
196205
,
error
:
false
,
},
{
name
:
'
zero-l1-gasprice
'
,
dataLen
:
10
,
l1GasPrice
:
0
,
l2GasPrice
:
100
_000_001
,
l2GasPrice
:
hundredBillion
,
l2GasLimit
:
196205
,
error
:
false
,
},
{
name
:
'
one-l1-gasprice
'
,
dataLen
:
10
,
l1GasPrice
:
1
,
l2GasPrice
:
23254
,
l2GasPrice
:
hundredBillion
,
l2GasLimit
:
23255
,
error
:
true
,
},
{
name
:
'
zero-gasprices
'
,
dataLen
:
10
,
l1GasPrice
:
0
,
l2GasPrice
:
0
,
l2GasLimit
:
23255
,
},
{
name
:
'
larger-divisor
'
,
dataLen
:
10
,
l1GasPrice
:
0
,
l2GasLimit
:
10
,
l2GasPrice
:
0
,
},
]
for
(
const
test
of
rollupFeesTests
)
{
it
(
`should pass for
${
test
.
name
}
case`
,
()
=>
{
const
data
=
Buffer
.
alloc
(
test
.
dataLen
)
let
got
let
err
=
false
try
{
got
=
fees
.
L2GasLimit
.
encode
({
const
got
=
fees
.
TxGasLimit
.
encode
({
data
,
l1GasPrice
:
test
.
l1GasPrice
,
l2GasPrice
:
test
.
l2GasPrice
,
l2GasLimit
:
test
.
l2GasLimit
,
})
}
catch
(
e
)
{
err
=
true
}
expect
(
err
).
to
.
equal
(
test
.
error
)
if
(
!
err
)
{
const
decoded
=
fees
.
L2GasLimit
.
decode
(
got
)
const
decoded
=
fees
.
TxGasLimit
.
decode
(
got
)
expect
(
decoded
).
to
.
deep
.
eq
(
BigNumber
.
from
(
test
.
l2GasLimit
))
}
})
}
})
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment