-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
383 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# utils | ||
|
||
This package is meant to hold utilities used by | ||
[Optimistic Ethereum](https://github.com/ethereum-optimism/optimism) written in | ||
Golang. | ||
|
||
## Packages | ||
|
||
### Fees | ||
|
||
Package fees includes helpers for dealing with fees on Optimistic Ethereum | ||
|
||
#### `EncodeTxGasLimit(data []byte, l1GasPrice, l2GasLimit, l2GasPrice *big.Int) *big.Int` | ||
|
||
Encodes `tx.gasLimit` based on the variables that are used to determine it. | ||
|
||
#### `DecodeL2GasLimit(gasLimit *big.Int) *big.Int` | ||
|
||
Accepts the return value of `eth_estimateGas` and decodes the L2 gas limit that | ||
is encoded in the return value. This is the gas limit that is passed to the user | ||
contract within the OVM. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package fees | ||
|
||
import ( | ||
"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 + 200*params.TxDataNonZeroGasEIP2028 | ||
|
||
// feeScalar is used to scale the calculations in EncodeL2GasLimit | ||
// to prevent them from being too large | ||
const feeScalar uint64 = 10_000_000 | ||
|
||
// 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) | ||
|
||
const tenThousand = 10000 | ||
|
||
var BigTenThousand = new(big.Int).SetUint64(tenThousand) | ||
|
||
// 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 | ||
// roundedL2GasLimit = ceilmod(l2GasLimit, 10_000) | ||
// l1Fee = l1GasPrice * l1GasLimit | ||
// l2Fee = l2GasPrice * roundedL2GasLimit | ||
// sum = l1Fee + l2Fee | ||
// scaled = sum / scalar | ||
// rounded = ceilmod(scaled, tenThousand) | ||
// roundedScaledL2GasLimit = roundedL2GasLimit / tenThousand | ||
// result = rounded + roundedScaledL2GasLimit | ||
// 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) | ||
roundedL2GasLimit := Ceilmod(l2GasLimit, BigTenThousand) | ||
l1Fee := new(big.Int).Mul(l1GasPrice, l1GasLimit) | ||
l2Fee := new(big.Int).Mul(l2GasPrice, roundedL2GasLimit) | ||
sum := new(big.Int).Add(l1Fee, l2Fee) | ||
scaled := new(big.Int).Div(sum, bigFeeScalar) | ||
rounded := Ceilmod(scaled, BigTenThousand) | ||
roundedScaledL2GasLimit := new(big.Int).Div(roundedL2GasLimit, BigTenThousand) | ||
result := new(big.Int).Add(rounded, roundedScaledL2GasLimit) | ||
return result | ||
} | ||
|
||
func Ceilmod(a, b *big.Int) *big.Int { | ||
remainder := new(big.Int).Mod(a, b) | ||
if remainder.Cmp(common.Big0) == 0 { | ||
return a | ||
} | ||
sum := new(big.Int).Add(a, b) | ||
rounded := new(big.Int).Sub(sum, remainder) | ||
return rounded | ||
} | ||
|
||
// DecodeL2GasLimit decodes the L2 gas limit from an encoded L2 gas limit | ||
func DecodeL2GasLimit(gasLimit *big.Int) *big.Int { | ||
scaled := new(big.Int).Mod(gasLimit, BigTenThousand) | ||
return new(big.Int).Mul(scaled, BigTenThousand) | ||
} | ||
|
||
func DecodeL2GasLimitU64(gasLimit uint64) uint64 { | ||
scaled := gasLimit % tenThousand | ||
return scaled * tenThousand | ||
} | ||
|
||
// 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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package fees | ||
|
||
import ( | ||
"math/big" | ||
"testing" | ||
|
||
"github.com/ethereum/go-ethereum/params" | ||
) | ||
|
||
var l1GasLimitTests = map[string]struct { | ||
data []byte | ||
overhead uint64 | ||
expect *big.Int | ||
}{ | ||
"simple": {[]byte{}, 0, big.NewInt(0)}, | ||
"simple-overhead": {[]byte{}, 10, big.NewInt(10)}, | ||
"zeros": {[]byte{0x00, 0x00, 0x00, 0x00}, 10, big.NewInt(26)}, | ||
"ones": {[]byte{0x01, 0x02, 0x03, 0x04}, 200, big.NewInt(16*4 + 200)}, | ||
} | ||
|
||
func TestL1GasLimit(t *testing.T) { | ||
for name, tt := range l1GasLimitTests { | ||
t.Run(name, func(t *testing.T) { | ||
got := calculateL1GasLimit(tt.data, tt.overhead) | ||
if got.Cmp(tt.expect) != 0 { | ||
t.Fatal("Calculated gas limit does not match") | ||
} | ||
}) | ||
} | ||
} | ||
|
||
var feeTests = map[string]struct { | ||
dataLen int | ||
l1GasPrice uint64 | ||
l2GasLimit uint64 | ||
l2GasPrice uint64 | ||
}{ | ||
"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: 99_970_000, | ||
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, tt.dataLen) | ||
l1GasPrice := new(big.Int).SetUint64(tt.l1GasPrice) | ||
l2GasLimit := new(big.Int).SetUint64(tt.l2GasLimit) | ||
l2GasPrice := new(big.Int).SetUint64(tt.l2GasPrice) | ||
|
||
fee := EncodeTxGasLimit(data, l1GasPrice, l2GasLimit, l2GasPrice) | ||
decodedGasLimit := DecodeL2GasLimit(fee) | ||
roundedL2GasLimit := Ceilmod(l2GasLimit, BigTenThousand) | ||
if roundedL2GasLimit.Cmp(decodedGasLimit) != 0 { | ||
t.Errorf("rollup fee check failed: expected %d, got %d", l2GasLimit.Uint64(), decodedGasLimit) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module github.com/ethereum-optimism/optimism/go/utils | ||
|
||
go 1.15 | ||
|
||
require github.com/ethereum/go-ethereum v1.9.10 |
Oops, something went wrong.