Commit 4a6f2c10 authored by Maurelian's avatar Maurelian Committed by Kelvin Fichter

feat(contracts): add gas burn params as storage vars

parent 09fa3d1f
......@@ -29,9 +29,15 @@ contract CanonicalTransactionChain is ICanonicalTransactionChain, Lib_AddressRes
// L2 tx gas-related
uint256 constant public MIN_ROLLUP_TX_GAS = 100000;
uint256 constant public MAX_ROLLUP_TX_SIZE = 50000;
uint256 immutable public L2_GAS_DISCOUNT_DIVISOR;
uint256 immutable public ENQUEUE_GAS_COST;
uint256 immutable public ENQUEUE_L2_GAS_PREPAID;
// The approximate cost of calling the enqueue function
uint256 public enqueueGasCost;
// The ratio of the cost of L1 gas to the cost of L2 gas
uint256 public l2GasDiscountDivisor;
// The amount of L2 gas which can be forwarded to L2 without spam prevention via 'gas burn'.
// Calculated as the product of l2GasDiscountDivisor * enqueueGasCost.
// See comments in enqueue() for further detail.
uint256 public enqueueL2GasPrepaid;
// Encoding-related (all in bytes)
uint256 constant internal BATCH_CONTEXT_SIZE = 16;
......@@ -69,12 +75,70 @@ contract CanonicalTransactionChain is ICanonicalTransactionChain, Lib_AddressRes
Lib_AddressResolver(_libAddressManager)
{
maxTransactionGasLimit = _maxTransactionGasLimit;
L2_GAS_DISCOUNT_DIVISOR = _l2GasDiscountDivisor;
ENQUEUE_GAS_COST = _enqueueGasCost;
ENQUEUE_L2_GAS_PREPAID = _l2GasDiscountDivisor * _enqueueGasCost;
l2GasDiscountDivisor = _l2GasDiscountDivisor;
enqueueGasCost = _enqueueGasCost;
enqueueL2GasPrepaid = _l2GasDiscountDivisor * _enqueueGasCost;
}
/**********************
* Function Modifiers *
**********************/
/**
* Modifier to enforce that, if configured, only the OVM_Sequencer contract may
* successfully call a method.
*/
modifier onlySequencer() {
require(
msg.sender == resolve("OVM_Sequencer"),
"Only callable by the Sequencer."
);
_;
}
/*******************************
* Authorized Setter Functions *
*******************************/
/**
* Allows the Sequencer to update the gas amount which is 'prepaid' during enqueue.
* The value of enqueueL2GasPrepaid is immediately updated as well.
*/
function setEnqueueGasCost(uint256 _enqueueGasCost)
external
onlySequencer
{
enqueueGasCost = _enqueueGasCost;
enqueueL2GasPrepaid = l2GasDiscountDivisor * _enqueueGasCost;
emit L2GasParamsUpdated(
l2GasDiscountDivisor,
enqueueGasCost,
enqueueL2GasPrepaid
);
}
/**
* Allows the Sequencer to update the L2 Gas Discount Divisor, which is defined as the ratio
* of the cost of gas on L1 to L2.
* The value of enqueueL2GasPrepaid is immediately updated as well.
*/
function setGasDivisor(uint256 _l2GasDiscountDivisor)
external
onlySequencer
{
l2GasDiscountDivisor = _l2GasDiscountDivisor;
enqueueL2GasPrepaid = _l2GasDiscountDivisor * enqueueGasCost;
emit L2GasParamsUpdated(
l2GasDiscountDivisor,
enqueueGasCost,
enqueueL2GasPrepaid
);
}
/********************
* Public Functions *
********************/
......@@ -260,13 +324,13 @@ contract CanonicalTransactionChain is ICanonicalTransactionChain, Lib_AddressRes
// 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
// from L1 to L2 is not underpriced. Therefore, we define 'enqueueL2GasPrepaid' 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;
// l2GasDiscountDivisor.
if(_gasLimit > enqueueL2GasPrepaid) {
uint256 gasToConsume = (_gasLimit - enqueueL2GasPrepaid) / l2GasDiscountDivisor;
uint256 startingGas = gasleft();
// Although this check is not necessary (burn below will run out of gas if not true), it
......
......@@ -16,6 +16,12 @@ interface ICanonicalTransactionChain {
* Events *
**********/
event L2GasParamsUpdated(
uint256 l2GasDiscountDivisor,
uint256 enqueueGasCost,
uint256 enqueueL2GasPrepaid
);
event TransactionEnqueued(
address indexed _l1TxOrigin,
address indexed _target,
......@@ -57,12 +63,29 @@ interface ICanonicalTransactionChain {
uint256 blockNumber;
}
/*******************************
* Authorized Setter Functions *
*******************************/
/**
* Allows the Sequencer to update the gas amount which is 'prepaid' during enqueue.
* The value of enqueueL2GasPrepaid is immediately updated as well.
*/
function setEnqueueGasCost(uint256 _enqueueGasCost)
external;
/**
* Allows the Sequencer to update the L2 Gas Discount Divisor, which is defined as the ratio
* of the cost of gas on L1 to L2.
* The value of enqueueL2GasPrepaid is immediately updated as well.
*/
function setGasDivisor(uint256 _l2GasDiscountDivisor)
external;
/********************
* Public Functions *
********************/
/**
* Accesses the batch storage container.
* @return Reference to the batch storage container.
......
......@@ -271,17 +271,17 @@ describe('[GAS BENCHMARK] CanonicalTransactionChain', () => {
})
describe('enqueue [ @skip-on-coverage ]', () => {
let ENQUEUE_L2_GAS_PREPAID
let enqueueL2GasPrepaid
let data
beforeEach(async () => {
CanonicalTransactionChain = CanonicalTransactionChain.connect(sequencer)
ENQUEUE_L2_GAS_PREPAID =
await CanonicalTransactionChain.ENQUEUE_L2_GAS_PREPAID()
enqueueL2GasPrepaid =
await CanonicalTransactionChain.enqueueL2GasPrepaid()
data = '0x' + '12'.repeat(1234)
})
it('cost to enqueue a transaction above the prepaid threshold', async () => {
const l2GasLimit = 2 * ENQUEUE_L2_GAS_PREPAID
const l2GasLimit = 2 * enqueueL2GasPrepaid
const res = await CanonicalTransactionChain.enqueue(
NON_ZERO_ADDRESS,
......@@ -293,7 +293,7 @@ describe('[GAS BENCHMARK] CanonicalTransactionChain', () => {
console.log('Benchmark complete.')
expectApprox(gasUsed, 187_081, {
expectApprox(gasUsed, 220_677, {
absoluteUpperDeviation: 500,
// Assert a lower bound of 1% reduction on gas cost. If your tests are breaking because your
// contracts are too efficient, consider updating the target value!
......@@ -302,7 +302,7 @@ describe('[GAS BENCHMARK] CanonicalTransactionChain', () => {
})
it('cost to enqueue a transaction below the prepaid threshold', async () => {
const l2GasLimit = ENQUEUE_L2_GAS_PREPAID - 1
const l2GasLimit = enqueueL2GasPrepaid - 1
const res = await CanonicalTransactionChain.enqueue(
NON_ZERO_ADDRESS,
......
......@@ -107,7 +107,7 @@ describe('CanonicalTransactionChain', () => {
AddressManager.address,
MAX_GAS_LIMIT,
L2_GAS_DISCOUNT_DIVISOR,
ENQUEUE_GAS_COST
ENQUEUE_GAS_COST,
)
const batches = await Factory__ChainStorageContainer.deploy(
......@@ -135,6 +135,63 @@ describe('CanonicalTransactionChain', () => {
)
})
describe('Gas param setters', () => {
describe('setGasDivisor', async () => {
it('should revert when not called by the sequencer', async () => {
await expect(
CanonicalTransactionChain.connect(signer).setGasDivisor(32)
).to.be.revertedWith('Only callable by the Sequencer.')
})
it('should update the l2GasDiscountDivisor and enqueueL2GasPrepaid correctly', async () => {
const newGasDivisor = 19
await CanonicalTransactionChain.connect(sequencer).setGasDivisor(
newGasDivisor
)
const enqueueGasCost = await CanonicalTransactionChain.enqueueGasCost()
const enqueueL2GasPrepaid =
await CanonicalTransactionChain.enqueueL2GasPrepaid()
expect(enqueueL2GasPrepaid).to.equal(newGasDivisor * enqueueGasCost)
})
it('should emit an L2GasParamsUpdated event', async () => {
await expect(
CanonicalTransactionChain.connect(sequencer).setGasDivisor(88)
).to.emit(CanonicalTransactionChain, 'L2GasParamsUpdated')
})
})
describe('setEnqueueGasCost', async () => {
it('should revert when not called by the sequencer', async () => {
await expect(
CanonicalTransactionChain.connect(signer).setEnqueueGasCost(60000)
).to.be.revertedWith('Only callable by the Sequencer.')
})
it('should update the enqueueGasCost and enqueueL2GasPrepaid correctly', async () => {
const newEnqueueGasCost = 31113
await CanonicalTransactionChain.connect(sequencer).setEnqueueGasCost(
newEnqueueGasCost
)
const l2GasDiscountDivisor =
await CanonicalTransactionChain.l2GasDiscountDivisor()
const enqueueL2GasPrepaid =
await CanonicalTransactionChain.enqueueL2GasPrepaid()
expect(enqueueL2GasPrepaid).to.equal(
l2GasDiscountDivisor * newEnqueueGasCost
)
})
it('should emit an L2GasParamsUpdated event', async () => {
await expect(
CanonicalTransactionChain.connect(sequencer).setEnqueueGasCost(31514)
).to.emit(CanonicalTransactionChain, 'L2GasParamsUpdated')
})
})
})
describe('enqueue', () => {
const target = NON_ZERO_ADDRESS
const gasLimit = 500_000
......@@ -176,9 +233,9 @@ describe('CanonicalTransactionChain', () => {
it('should revert if transaction gas limit does not cover rollup burn', async () => {
const _enqueueL2GasPrepaid =
await CanonicalTransactionChain.ENQUEUE_L2_GAS_PREPAID()
await CanonicalTransactionChain.enqueueL2GasPrepaid()
const l2GasDiscountDivisor =
await CanonicalTransactionChain.L2_GAS_DISCOUNT_DIVISOR()
await CanonicalTransactionChain.l2GasDiscountDivisor()
const data = '0x' + '12'.repeat(1234)
// Create a tx with high L2 gas limit, but insufficient L1 gas limit to cover burn.
......@@ -187,7 +244,7 @@ describe('CanonicalTransactionChain', () => {
// additional gas overhead, it will be enough trigger the gas burn, but not enough to cover
// it.
const l1GasLimit =
(l2GasLimit - _enqueueL2GasPrepaid) / l2GasDiscountDivisor
(l2GasLimit - _enqueueL2GasPrepaid) / l2GasDiscountDivisor
await expect(
CanonicalTransactionChain.enqueue(target, l2GasLimit, data, {
......@@ -223,13 +280,13 @@ describe('CanonicalTransactionChain', () => {
})
})
describe('with _gaslimit below the ENQUEUE_L2_GAS_PREPAID threshold', async () => {
describe('with _gaslimit below the enqueueL2GasPrepaid 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 CanonicalTransactionChain.ENQUEUE_L2_GAS_PREPAID()
const enqueueL2GasPrepaid =
await CanonicalTransactionChain.enqueueL2GasPrepaid()
const data = '0x' + '12'.repeat(1234)
const l2GasLimit1 = ENQUEUE_L2_GAS_PREPAID - 1
const l2GasLimit2 = ENQUEUE_L2_GAS_PREPAID - 100
const l2GasLimit1 = enqueueL2GasPrepaid - 1
const l2GasLimit2 = enqueueL2GasPrepaid - 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.
......
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