diff --git a/l2geth/core/rollup_fee.go b/l2geth/core/rollup_fee.go index 17662547df22..efcdbdfd9911 100644 --- a/l2geth/core/rollup_fee.go +++ b/l2geth/core/rollup_fee.go @@ -15,8 +15,12 @@ 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 @@ -24,22 +28,37 @@ var bigHundredMillion = new(big.Int).SetUint64(hundredMillion) 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 @@ -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 { @@ -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 } diff --git a/l2geth/core/rollup_fee_test.go b/l2geth/core/rollup_fee_test.go index dfe68107ad3f..bcc8025e69e9 100644 --- a/l2geth/core/rollup_fee_test.go +++ b/l2geth/core/rollup_fee_test.go @@ -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) } @@ -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)