diff --git a/.changeset/tasty-adults-explode.md b/.changeset/tasty-adults-explode.md new file mode 100644 index 0000000000000..3a62429e159b2 --- /dev/null +++ b/.changeset/tasty-adults-explode.md @@ -0,0 +1,5 @@ +--- +'@eth-optimism/core-utils': patch +--- + +Add a `calldataCost` function that computes the cost of calldata diff --git a/packages/contracts/tasks/fetch-batches.ts b/packages/contracts/tasks/fetch-batches.ts index d97c30ec09e3f..e784bf7e74a81 100644 --- a/packages/contracts/tasks/fetch-batches.ts +++ b/packages/contracts/tasks/fetch-batches.ts @@ -1,7 +1,11 @@ import { ethers } from 'ethers' import { task } from 'hardhat/config' import * as types from 'hardhat/internal/core/params/argumentTypes' -import { BatchType, SequencerBatch } from '@eth-optimism/core-utils' +import { + BatchType, + SequencerBatch, + calldataCost, +} from '@eth-optimism/core-utils' import { names } from '../src/address-names' import { getContractFromArtifact } from '../src/deploy-utils' @@ -52,28 +56,42 @@ task('fetch-batches') const tx = await provider.getTransaction(event.transactionHash) const batch = (SequencerBatch as any).fromHex(tx.data) - // Add an extra field to the resulting json - // so that the serialization sizes can be observed + // Add extra fields to the resulting json + // so that the serialization sizes and gas usage can be observed const json = batch.toJSON() json.sizes = { legacy: 0, zlib: 0, } + json.gasUsage = { + legacy: 0, + zlib: 0, + } // Create a copy of the batch to serialize in // the alternative format const copy = (SequencerBatch as any).fromHex(tx.data) + let legacy: Buffer + let zlib: Buffer if (batch.type === BatchType.ZLIB) { copy.type = BatchType.LEGACY - json.sizes.legacy = copy.encode().length - json.sizes.zlib = batch.encode().length + legacy = copy.encode() + zlib = batch.encode() } else { copy.type = BatchType.ZLIB - json.sizes.zlib = copy.encode().length - json.sizes.legacy = batch.encode().length + zlib = copy.encode() + legacy = batch.encode() } - json.compressionRatio = json.sizes.zlib / json.sizes.legacy + json.sizes.legacy = legacy.length + json.sizes.zlib = zlib.length + + json.sizes.compressionRatio = json.sizes.zlib / json.sizes.legacy + + json.gasUsage.legacy = calldataCost(legacy).toNumber() + json.gasUsage.zlib = calldataCost(zlib).toNumber() + json.gasUsage.compressionRatio = + json.gasUsage.zlib / json.gasUsage.legacy batches.push(json) } diff --git a/packages/core-utils/src/optimism/fees.ts b/packages/core-utils/src/optimism/fees.ts index f0dc245d77ec4..db33b156685fa 100644 --- a/packages/core-utils/src/optimism/fees.ts +++ b/packages/core-utils/src/optimism/fees.ts @@ -6,8 +6,8 @@ import { BigNumber } from 'ethers' import { remove0x } from '../common' -const txDataZeroGas = 4 -const txDataNonZeroGasEIP2028 = 16 +export const txDataZeroGas = 4 +export const txDataNonZeroGasEIP2028 = 16 const big10 = BigNumber.from(10) export const scaleDecimals = ( @@ -63,3 +63,17 @@ export const zeroesAndOnes = (data: Buffer | string): Array => { } return [zeros, ones] } + +/** + * Computes the L1 calldata cost of bytes based + * on the London hardfork. + * + * @param data {Buffer|string} Bytes + * @returns {BigNumber} Gas consumed by the bytes + */ +export const calldataCost = (data: Buffer | string): BigNumber => { + const [zeros, ones] = zeroesAndOnes(data) + const zeroCost = BigNumber.from(zeros).mul(txDataZeroGas) + const nonZeroCost = BigNumber.from(ones).mul(txDataNonZeroGasEIP2028) + return zeroCost.add(nonZeroCost) +} diff --git a/packages/core-utils/test/fees.spec.ts b/packages/core-utils/test/fees.spec.ts index 7e89e356af72b..b8b61cefdb843 100644 --- a/packages/core-utils/test/fees.spec.ts +++ b/packages/core-utils/test/fees.spec.ts @@ -1,4 +1,8 @@ -import { zeroesAndOnes } from '../src' +import './setup' + +import { BigNumber } from 'ethers' + +import { zeroesAndOnes, calldataCost } from '../src' describe('Fees', () => { it('should count zeros and ones', () => { @@ -15,4 +19,19 @@ describe('Fees', () => { ones.should.eq(test.ones) } }) + + it('should compute calldata costs', () => { + const cases = [ + { input: '0x', output: BigNumber.from(0) }, + { input: '0x00', output: BigNumber.from(4) }, + { input: '0xff', output: BigNumber.from(16) }, + { input: Buffer.alloc(32), output: BigNumber.from(4 * 32) }, + { input: Buffer.alloc(32, 0xff), output: BigNumber.from(16 * 32) }, + ] + + for (const test of cases) { + const cost = calldataCost(test.input) + cost.should.deep.eq(test.output) + } + }) })