Skip to content

Commit

Permalink
l2 geth: new fee logic
Browse files Browse the repository at this point in the history
  • Loading branch information
tynes committed May 28, 2021
1 parent a25acbb commit 75ccc47
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 102 deletions.
98 changes: 38 additions & 60 deletions l2geth/core/rollup_fee.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,50 @@ const overhead uint64 = 4200

// hundredMillion is a constant used in the gas encoding formula
const hundredMillion uint64 = 100_000_000
const hundredBillion uint64 = 100_000_000_000
const feeScalar uint64 = 1000

var bigHundredMillion = new(big.Int).SetUint64(hundredMillion)
var bigHundredBillion = new(big.Int).SetUint64(hundredBillion)
var bigFeeScalar = new(big.Int).SetUint64(feeScalar)

// 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`
// fee = (floor((l1GasLimit*l1GasPrice + l2GasLimit*l2GasPrice) / max(tx.gasPrice, 1)) + l2GasLimit) * tx.gasPrice
func CalculateRollupFee(data []byte, l1GasPrice, l2GasLimit, l2GasPrice *big.Int) (*big.Int, error) {
if err := VerifyL1GasPrice(l1GasPrice); err != nil {
if err := VerifyGasPrice(l1GasPrice); err != nil {
return nil, fmt.Errorf("invalid L1 gas price %d: %w", l1GasPrice, err)
}
if err := VerifyL2GasPrice(l2GasPrice); err != nil {
if err := VerifyGasPrice(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
sum := new(big.Int).Add(l1Fee, l2Fee)
scaled := new(big.Int).Div(sum, bigFeeScalar)
result := new(big.Int).Add(scaled, l2GasLimit)
return result, nil
}

// VerifyGasPrice returns an error if the gas price doesn't satisfy
// the constraints.
func VerifyGasPrice(gasPrice *big.Int) error {
if gasPrice.Cmp(common.Big0) == 0 {
return nil
}
if gasPrice.Cmp(bigHundredBillion) < 0 {
return fmt.Errorf("too small: %w", errInvalidGasPrice)
}
mod := new(big.Int).Mod(gasPrice, bigHundredMillion)
if mod.Cmp(common.Big0) != 0 {
return fmt.Errorf("incorrect multiple: %w", errInvalidGasPrice)
}
return nil
}

// calculateL1GasLimit computes the L1 gasLimit based on the calldata and
Expand All @@ -54,60 +73,17 @@ func calculateL1GasLimit(data []byte, overhead uint64) *big.Int {
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 {
// RoundGasPrice rounds the gas price up to the next valid gas price
func RoundGasPrice(gasPrice *big.Int) *big.Int {
if gasPrice.Cmp(common.Big0) == 0 {
return big.NewInt(1)
return new(big.Int)
}
if gasPrice.Cmp(common.Big1) == 0 {
return new(big.Int).Add(bigHundredMillion, common.Big1)
mod := new(big.Int).Mod(gasPrice, bigHundredBillion)
if mod.Cmp(common.Big0) == 0 {
return gasPrice
}
gp := new(big.Int).Sub(gasPrice, common.Big1)
mod := ceilModOneHundredMillion(gp)
return new(big.Int).Add(mod, common.Big1)
sum := new(big.Int).Add(gasPrice, bigHundredBillion)
return new(big.Int).Sub(sum, mod)
}

func DecodeL2GasLimit(gasLimit *big.Int) *big.Int {
Expand All @@ -116,11 +92,13 @@ func DecodeL2GasLimit(gasLimit *big.Int) *big.Int {

func zeroesAndOnes(data []byte) (uint64, uint64) {
var zeroes uint64
var ones uint64
for _, byt := range data {
if byt == 0 {
zeroes++
} else {
ones++
}
}
ones := uint64(len(data)) - zeroes
return zeroes, ones
}
117 changes: 75 additions & 42 deletions l2geth/core/rollup_fee_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,49 +7,24 @@ import (
"testing"
)

var roundingL1GasPriceTests = map[string]struct {
var roundingGasPriceTests = 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)},
"simple": {10, hundredBillion},
"one-over": {hundredBillion + 1, 2 * hundredBillion},
"exact": {hundredBillion, hundredBillion},
"one-under": {hundredBillion - 1, hundredBillion},
"small": {3, hundredBillion},
"two": {2, hundredBillion},
"one": {1, hundredBillion},
"zero": {0, 0},
}

func TestRoundL1GasPrice(t *testing.T) {
for name, tt := range roundingL1GasPriceTests {
func TestRoundGasPrice(t *testing.T) {
for name, tt := range roundingGasPriceTests {
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))
got := RoundGasPrice(new(big.Int).SetUint64(tt.input))
if got.Uint64() != tt.expect {
t.Fatalf("Mismatched rounding to nearest, got %d expected %d", got, tt.expect)
}
Expand Down Expand Up @@ -86,17 +61,75 @@ var feeTests = map[string]struct {
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: hundredBillion,
l2GasLimit: 437118,
l2GasPrice: hundredBillion,
err: nil,
},
"zero-l2-gasprice": {
dataLen: 10,
l1GasPrice: hundredBillion,
l2GasLimit: 196205,
l2GasPrice: 0,
err: nil,
},
"one-l2-gasprice": {
dataLen: 10,
l1GasPrice: hundredBillion,
l2GasLimit: 196205,
l2GasPrice: 1,
err: errInvalidGasPrice,
},
"zero-l1-gasprice": {
dataLen: 10,
l1GasPrice: 0,
l2GasLimit: 196205,
l2GasPrice: hundredBillion,
err: nil,
},
"one-l1-gasprice": {
dataLen: 10,
l1GasPrice: 1,
l2GasLimit: 23255,
l2GasPrice: hundredBillion,
err: errInvalidGasPrice,
},
"zero-gasprices": {
dataLen: 10,
l1GasPrice: 0,
l2GasLimit: 23255,
l2GasPrice: 0,
err: nil,
},
"bad-l2-gasprice": {
dataLen: 10,
l1GasPrice: 0,
l2GasLimit: 23255,
l2GasPrice: hundredBillion - 1,
err: errInvalidGasPrice,
},
"bad-l1-gasprice": {
dataLen: 10,
l1GasPrice: hundredBillion - 1,
l2GasLimit: 44654,
l2GasPrice: hundredBillion,
err: errInvalidGasPrice,
},
"max-gaslimit": {
dataLen: 10,
l1GasPrice: hundredBillion,
l2GasLimit: 0x4ffffff,
l2GasPrice: hundredBillion,
err: nil,
},
}

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)
Expand Down

0 comments on commit 75ccc47

Please sign in to comment.