Commit 4392f275 authored by Maurelian's avatar Maurelian Committed by Kelvin Fichter

feat(contracts): only burn gas in enqueue for high l2 gas limits

fixup! feat(contracts): only burn gas in enqueue for high l2 gas limits
parent 3f28385a
......@@ -37,6 +37,8 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, Lib_Ad
uint256 constant public MIN_ROLLUP_TX_GAS = 100000;
uint256 constant public MAX_ROLLUP_TX_SIZE = 50000;
uint256 constant public L2_GAS_DISCOUNT_DIVISOR = 32;
uint256 constant public ENQUEUE_GAS_COST = 60000;
uint256 public ENQUEUE_L2_GAS_PREPAID = L2_GAS_DISCOUNT_DIVISOR * ENQUEUE_GAS_COST;
// Encoding-related (all in bytes)
uint256 constant internal BATCH_CONTEXT_SIZE = 16;
......@@ -273,10 +275,15 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, Lib_Ad
"Transaction gas limit too low to enqueue."
);
// We need to consume some amount of L1 gas in order to rate limit transactions going into
// L2. However, L2 is cheaper than L1 so we only need to burn some small proportion of the
// provided L1 gas.
uint256 gasToConsume = _gasLimit/L2_GAS_DISCOUNT_DIVISOR;
// Transactions submitted to the queue lack a method for paying gas fees to the Sequencer.
// So we need to prevent spam attacks by ensuring that the cost of enqueueing a transaction
// from L1 to L2 is not underpriced. Therefore, we define 'ENQUEUE_L2_GAS_PREPAID' as a
// threshold. If the _gasLimit for the enqueued transaction is above this threshold, then we
// 'charge' to user by burning additional L1 gas. Since gas is cheaper on L2 than L1, we
// only need to burn a fraction of the provided L1 gas, which is determined by the
// L2_GAS_DISCOUNT_DIVISOR.
if(_gasLimit > ENQUEUE_L2_GAS_PREPAID) {
uint256 gasToConsume = (_gasLimit - ENQUEUE_L2_GAS_PREPAID) / L2_GAS_DISCOUNT_DIVISOR;
uint256 startingGas = gasleft();
// Although this check is not necessary (burn below will run out of gas if not true), it
......@@ -286,12 +293,11 @@ contract OVM_CanonicalTransactionChain is iOVM_CanonicalTransactionChain, Lib_Ad
"Insufficient gas for L2 rate limiting burn."
);
// Here we do some "dumb" work in order to burn gas, although we should probably replace
// this with something like minting gas token later on.
uint256 i;
while(startingGas - gasleft() < gasToConsume) {
i++;
}
}
bytes32 transactionHash = keccak256(
abi.encode(
......
/* External Imports */
import { ethers } from 'hardhat'
import { Signer, ContractFactory, Contract } from 'ethers'
import { Signer, ContractFactory, Contract, constants } from 'ethers'
import { smockit, MockContract } from '@eth-optimism/smock'
import {
AppendSequencerBatchParams,
......@@ -18,6 +18,7 @@ import {
FORCE_INCLUSION_PERIOD_BLOCKS,
getEthTime,
getNextBlockNumber,
NON_ZERO_ADDRESS,
} from '../../../helpers'
// Still have some duplication from OVM_CanonicalTransactionChain.spec.ts, but it's so minimal that
......@@ -196,4 +197,44 @@ describe('[GAS BENCHMARK] OVM_CanonicalTransactionChain', () => {
)
}).timeout(100000000)
})
describe('enqueue [ @skip-on-coverage ]', () => {
let ENQUEUE_L2_GAS_PREPAID
let data
beforeEach(async () => {
OVM_CanonicalTransactionChain =
OVM_CanonicalTransactionChain.connect(sequencer)
ENQUEUE_L2_GAS_PREPAID =
await OVM_CanonicalTransactionChain.ENQUEUE_L2_GAS_PREPAID()
data = '0x' + '12'.repeat(1234)
})
it('cost to enqueue a transaction above the prepaid threshold', async () => {
const l2GasLimit = 2 * ENQUEUE_L2_GAS_PREPAID
const res = await OVM_CanonicalTransactionChain.enqueue(
NON_ZERO_ADDRESS,
l2GasLimit,
data
)
const receipt = await res.wait()
console.log('Benchmark complete.')
console.log('Gas used:', receipt.gasUsed.toNumber())
})
it('cost to enqueue a transaction below the prepaid threshold', async () => {
const l2GasLimit = ENQUEUE_L2_GAS_PREPAID - 1
const res = await OVM_CanonicalTransactionChain.enqueue(
NON_ZERO_ADDRESS,
l2GasLimit,
data
)
const receipt = await res.wait()
console.log('Benchmark complete.')
console.log('Gas used:', receipt.gasUsed.toNumber())
})
})
})
......@@ -201,13 +201,23 @@ describe('OVM_CanonicalTransactionChain', () => {
})
it('should revert if transaction gas limit does not cover rollup burn', async () => {
const ENQUEUE_L2_GAS_PREPAID =
await OVM_CanonicalTransactionChain.ENQUEUE_L2_GAS_PREPAID()
const L2_GAS_DISCOUNT_DIVISOR =
await OVM_CanonicalTransactionChain.L2_GAS_DISCOUNT_DIVISOR()
const data = '0x' + '12'.repeat(1234)
// Create a tx with high L2 gas limit, but insufficient L1 gas limit to cover burn.
const l2GasLimit = 2 * ENQUEUE_L2_GAS_PREPAID
// This l1GasLimit is equivalent to the gasToConsume amount calculated in the CTC. After
// additional gas overhead, it will be enough trigger the gas burn, but not enough to cover
// it.
const l1GasLimit =
(l2GasLimit - ENQUEUE_L2_GAS_PREPAID) / L2_GAS_DISCOUNT_DIVISOR
await expect(
OVM_CanonicalTransactionChain.enqueue(target, gasLimit, data, {
gasLimit: gasLimit / L2_GAS_DISCOUNT_DIVISOR + 30_000, // offset constant overhead
OVM_CanonicalTransactionChain.enqueue(target, l2GasLimit, data, {
gasLimit: l1GasLimit,
})
).to.be.revertedWith('Insufficient gas for L2 rate limiting burn.')
})
......@@ -238,6 +248,39 @@ describe('OVM_CanonicalTransactionChain', () => {
}
})
})
describe('with _gaslimit below the ENQUEUE_L2_GAS_PREPAID threshold', async () => {
it('the cost to enqueue transactions is consistent for different L2 gas amounts below the prepaid threshold', async () => {
const ENQUEUE_L2_GAS_PREPAID =
await OVM_CanonicalTransactionChain.ENQUEUE_L2_GAS_PREPAID()
const data = '0x' + '12'.repeat(1234)
const l2GasLimit1 = ENQUEUE_L2_GAS_PREPAID - 1
const l2GasLimit2 = ENQUEUE_L2_GAS_PREPAID - 100
// The first enqueue is more expensive because it's writing to an empty slot,
// so we need to pre-load the buffer or the test will fail.
await OVM_CanonicalTransactionChain.enqueue(
NON_ZERO_ADDRESS,
l2GasLimit1,
data
)
const res1 = await OVM_CanonicalTransactionChain.enqueue(
NON_ZERO_ADDRESS,
l2GasLimit1,
data
)
const receipt1 = await res1.wait()
const res2 = await OVM_CanonicalTransactionChain.enqueue(
NON_ZERO_ADDRESS,
l2GasLimit2,
data
)
const receipt2 = await res2.wait()
expect(receipt1.gasUsed).to.equal(receipt2.gasUsed)
})
})
})
describe('getQueueElement', () => {
......
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