Commit 6d32d701 authored by Mark Tyneway's avatar Mark Tyneway Committed by Kelvin Fichter

core-utils: `calculateL1Cost` helper

Add a helper function for computing the L1 cost.
It looks byte by byte and sums how much the calldata
would cost given the calldata costs for 0 or non 0 bytes.
parent 7bd88e81
---
'@eth-optimism/core-utils': patch
---
Expose lower level API for tx fees
......@@ -5,79 +5,48 @@
import { BigNumber } from 'ethers'
import { remove0x } from './common'
const feeScalar = 10_000_000
const txDataZeroGas = 4
const txDataNonZeroGasEIP2028 = 16
const overhead = 2750
const tenThousand = BigNumber.from(10_000)
export const TxGasPrice = BigNumber.from(feeScalar + feeScalar / 2)
export interface EncodableL2GasLimit {
data: Buffer | string
l1GasPrice: BigNumber | number
l2GasLimit: BigNumber | number
l2GasPrice: BigNumber | number
}
const encode = (input: EncodableL2GasLimit): BigNumber => {
const { data } = input
let { l1GasPrice, l2GasLimit, l2GasPrice } = input
if (typeof l1GasPrice === 'number') {
l1GasPrice = BigNumber.from(l1GasPrice)
}
if (typeof l2GasLimit === 'number') {
l2GasLimit = BigNumber.from(l2GasLimit)
}
if (typeof l2GasPrice === 'number') {
l2GasPrice = BigNumber.from(l2GasPrice)
}
const l1GasLimit = calculateL1GasLimit(data)
const roundedL2GasLimit = ceilmod(l2GasLimit, tenThousand)
const l1Fee = l1GasLimit.mul(l1GasPrice)
const l2Fee = roundedL2GasLimit.mul(l2GasPrice)
const sum = l1Fee.add(l2Fee)
const scaled = sum.div(feeScalar)
const rounded = ceilmod(scaled, tenThousand)
const roundedScaledL2GasLimit = roundedL2GasLimit.div(tenThousand)
return rounded.add(roundedScaledL2GasLimit)
}
const decode = (fee: BigNumber | number): BigNumber => {
if (typeof fee === 'number') {
fee = BigNumber.from(fee)
}
const scaled = fee.mod(tenThousand)
return scaled.mul(tenThousand)
}
export const TxGasLimit = {
encode,
decode,
}
const big10 = BigNumber.from(10)
export const ceilmod = (a: BigNumber | number, b: BigNumber | number) => {
if (typeof a === 'number') {
a = BigNumber.from(a)
}
if (typeof b === 'number') {
b = BigNumber.from(b)
}
const remainder = a.mod(b)
if (remainder.eq(0)) {
return a
}
const sum = a.add(b)
const rounded = sum.sub(remainder)
return rounded
export const scaleDecimals = (
value: number | BigNumber,
decimals: number | BigNumber
): BigNumber => {
value = BigNumber.from(value)
decimals = BigNumber.from(decimals)
// 10**decimals
const divisor = big10.pow(decimals)
return value.div(divisor)
}
export const calculateL1GasLimit = (data: string | Buffer): BigNumber => {
// data is the RLP encoded unsigned transaction
export const calculateL1GasUsed = (
data: string | Buffer,
overhead: number | BigNumber
): BigNumber => {
const [zeroes, ones] = zeroesAndOnes(data)
const zeroesCost = zeroes * txDataZeroGas
const onesCost = ones * txDataNonZeroGasEIP2028
const gasLimit = zeroesCost + onesCost + overhead
return BigNumber.from(gasLimit)
// Add a buffer to account for the signature
const onesCost = (ones + 68) * txDataNonZeroGasEIP2028
return BigNumber.from(onesCost).add(zeroesCost).add(overhead)
}
export const calculateL1Fee = (
data: string | Buffer,
overhead: number | BigNumber,
l1GasPrice: number | BigNumber,
scalar: number | BigNumber,
decimals: number | BigNumber
): BigNumber => {
const l1GasUsed = calculateL1GasUsed(data, overhead)
const l1Fee = l1GasUsed.mul(l1GasPrice)
const scaled = l1Fee.mul(scalar)
const result = scaleDecimals(scaled, decimals)
return result
}
// Count the number of zero bytes and non zero bytes in a buffer
export const zeroesAndOnes = (data: Buffer | string): Array<number> => {
if (typeof data === 'string') {
data = Buffer.from(remove0x(data), 'hex')
......
......@@ -20,109 +20,4 @@ describe('Fees', () => {
ones.should.eq(test.ones)
}
})
describe('Rollup Fees', () => {
const rollupFeesTests = [
{
name: 'simple',
dataLen: 10,
l1GasPrice: utils.parseUnits('1', 'gwei'),
l2GasPrice: utils.parseUnits('1', 'gwei'),
l2GasLimit: 437118,
},
{
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: 99_970_000,
},
{
name: 'zero-l2-gasprice',
dataLen: 10,
l1GasPrice: hundredBillion,
l2GasPrice: 0,
l2GasLimit: 196205,
},
{
name: 'one-l2-gasprice',
dataLen: 10,
l1GasPrice: hundredBillion,
l2GasPrice: 1,
l2GasLimit: 196205,
},
{
name: 'zero-l1-gasprice',
dataLen: 10,
l1GasPrice: 0,
l2GasPrice: hundredBillion,
l2GasLimit: 196205,
},
{
name: 'one-l1-gasprice',
dataLen: 10,
l1GasPrice: 1,
l2GasPrice: hundredBillion,
l2GasLimit: 23255,
},
{
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)
const got = fees.TxGasLimit.encode({
data,
l1GasPrice: test.l1GasPrice,
l2GasPrice: test.l2GasPrice,
l2GasLimit: test.l2GasLimit,
})
const decoded = fees.TxGasLimit.decode(got)
const roundedL2GasLimit = fees.ceilmod(test.l2GasLimit, 10_000)
expect(decoded).to.deep.eq(roundedL2GasLimit)
})
}
it('should decode numbers', () => {
const x = 1
expect(fees.TxGasLimit.decode(x)).to.deep.equal(
BigNumber.from(x * tenThousand)
)
})
})
})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment