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 ...@@ -29,9 +29,15 @@ contract CanonicalTransactionChain is ICanonicalTransactionChain, Lib_AddressRes
// L2 tx gas-related // L2 tx gas-related
uint256 constant public MIN_ROLLUP_TX_GAS = 100000; uint256 constant public MIN_ROLLUP_TX_GAS = 100000;
uint256 constant public MAX_ROLLUP_TX_SIZE = 50000; uint256 constant public MAX_ROLLUP_TX_SIZE = 50000;
uint256 immutable public L2_GAS_DISCOUNT_DIVISOR;
uint256 immutable public ENQUEUE_GAS_COST; // The approximate cost of calling the enqueue function
uint256 immutable public ENQUEUE_L2_GAS_PREPAID; 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) // Encoding-related (all in bytes)
uint256 constant internal BATCH_CONTEXT_SIZE = 16; uint256 constant internal BATCH_CONTEXT_SIZE = 16;
...@@ -69,12 +75,70 @@ contract CanonicalTransactionChain is ICanonicalTransactionChain, Lib_AddressRes ...@@ -69,12 +75,70 @@ contract CanonicalTransactionChain is ICanonicalTransactionChain, Lib_AddressRes
Lib_AddressResolver(_libAddressManager) Lib_AddressResolver(_libAddressManager)
{ {
maxTransactionGasLimit = _maxTransactionGasLimit; maxTransactionGasLimit = _maxTransactionGasLimit;
L2_GAS_DISCOUNT_DIVISOR = _l2GasDiscountDivisor; l2GasDiscountDivisor = _l2GasDiscountDivisor;
ENQUEUE_GAS_COST = _enqueueGasCost; enqueueGasCost = _enqueueGasCost;
ENQUEUE_L2_GAS_PREPAID = _l2GasDiscountDivisor * _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 * * Public Functions *
********************/ ********************/
...@@ -260,13 +324,13 @@ contract CanonicalTransactionChain is ICanonicalTransactionChain, Lib_AddressRes ...@@ -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. // 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 // 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 // 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 // '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 // only need to burn a fraction of the provided L1 gas, which is determined by the
// L2_GAS_DISCOUNT_DIVISOR. // l2GasDiscountDivisor.
if(_gasLimit > ENQUEUE_L2_GAS_PREPAID) { if(_gasLimit > enqueueL2GasPrepaid) {
uint256 gasToConsume = (_gasLimit - ENQUEUE_L2_GAS_PREPAID) / L2_GAS_DISCOUNT_DIVISOR; uint256 gasToConsume = (_gasLimit - enqueueL2GasPrepaid) / l2GasDiscountDivisor;
uint256 startingGas = gasleft(); uint256 startingGas = gasleft();
// Although this check is not necessary (burn below will run out of gas if not true), it // Although this check is not necessary (burn below will run out of gas if not true), it
......
...@@ -16,6 +16,12 @@ interface ICanonicalTransactionChain { ...@@ -16,6 +16,12 @@ interface ICanonicalTransactionChain {
* Events * * Events *
**********/ **********/
event L2GasParamsUpdated(
uint256 l2GasDiscountDivisor,
uint256 enqueueGasCost,
uint256 enqueueL2GasPrepaid
);
event TransactionEnqueued( event TransactionEnqueued(
address indexed _l1TxOrigin, address indexed _l1TxOrigin,
address indexed _target, address indexed _target,
...@@ -57,12 +63,29 @@ interface ICanonicalTransactionChain { ...@@ -57,12 +63,29 @@ interface ICanonicalTransactionChain {
uint256 blockNumber; 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 * * Public Functions *
********************/ ********************/
/** /**
* Accesses the batch storage container. * Accesses the batch storage container.
* @return Reference to the batch storage container. * @return Reference to the batch storage container.
......
...@@ -271,17 +271,17 @@ describe('[GAS BENCHMARK] CanonicalTransactionChain', () => { ...@@ -271,17 +271,17 @@ describe('[GAS BENCHMARK] CanonicalTransactionChain', () => {
}) })
describe('enqueue [ @skip-on-coverage ]', () => { describe('enqueue [ @skip-on-coverage ]', () => {
let ENQUEUE_L2_GAS_PREPAID let enqueueL2GasPrepaid
let data let data
beforeEach(async () => { beforeEach(async () => {
CanonicalTransactionChain = CanonicalTransactionChain.connect(sequencer) CanonicalTransactionChain = CanonicalTransactionChain.connect(sequencer)
ENQUEUE_L2_GAS_PREPAID = enqueueL2GasPrepaid =
await CanonicalTransactionChain.ENQUEUE_L2_GAS_PREPAID() await CanonicalTransactionChain.enqueueL2GasPrepaid()
data = '0x' + '12'.repeat(1234) data = '0x' + '12'.repeat(1234)
}) })
it('cost to enqueue a transaction above the prepaid threshold', async () => { 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( const res = await CanonicalTransactionChain.enqueue(
NON_ZERO_ADDRESS, NON_ZERO_ADDRESS,
...@@ -293,7 +293,7 @@ describe('[GAS BENCHMARK] CanonicalTransactionChain', () => { ...@@ -293,7 +293,7 @@ describe('[GAS BENCHMARK] CanonicalTransactionChain', () => {
console.log('Benchmark complete.') console.log('Benchmark complete.')
expectApprox(gasUsed, 187_081, { expectApprox(gasUsed, 220_677, {
absoluteUpperDeviation: 500, absoluteUpperDeviation: 500,
// Assert a lower bound of 1% reduction on gas cost. If your tests are breaking because your // 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! // contracts are too efficient, consider updating the target value!
...@@ -302,7 +302,7 @@ describe('[GAS BENCHMARK] CanonicalTransactionChain', () => { ...@@ -302,7 +302,7 @@ describe('[GAS BENCHMARK] CanonicalTransactionChain', () => {
}) })
it('cost to enqueue a transaction below the prepaid threshold', async () => { 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( const res = await CanonicalTransactionChain.enqueue(
NON_ZERO_ADDRESS, NON_ZERO_ADDRESS,
......
...@@ -107,7 +107,7 @@ describe('CanonicalTransactionChain', () => { ...@@ -107,7 +107,7 @@ describe('CanonicalTransactionChain', () => {
AddressManager.address, AddressManager.address,
MAX_GAS_LIMIT, MAX_GAS_LIMIT,
L2_GAS_DISCOUNT_DIVISOR, L2_GAS_DISCOUNT_DIVISOR,
ENQUEUE_GAS_COST ENQUEUE_GAS_COST,
) )
const batches = await Factory__ChainStorageContainer.deploy( const batches = await Factory__ChainStorageContainer.deploy(
...@@ -135,6 +135,63 @@ describe('CanonicalTransactionChain', () => { ...@@ -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', () => { describe('enqueue', () => {
const target = NON_ZERO_ADDRESS const target = NON_ZERO_ADDRESS
const gasLimit = 500_000 const gasLimit = 500_000
...@@ -176,9 +233,9 @@ describe('CanonicalTransactionChain', () => { ...@@ -176,9 +233,9 @@ describe('CanonicalTransactionChain', () => {
it('should revert if transaction gas limit does not cover rollup burn', async () => { it('should revert if transaction gas limit does not cover rollup burn', async () => {
const _enqueueL2GasPrepaid = const _enqueueL2GasPrepaid =
await CanonicalTransactionChain.ENQUEUE_L2_GAS_PREPAID() await CanonicalTransactionChain.enqueueL2GasPrepaid()
const l2GasDiscountDivisor = const l2GasDiscountDivisor =
await CanonicalTransactionChain.L2_GAS_DISCOUNT_DIVISOR() await CanonicalTransactionChain.l2GasDiscountDivisor()
const data = '0x' + '12'.repeat(1234) const data = '0x' + '12'.repeat(1234)
// Create a tx with high L2 gas limit, but insufficient L1 gas limit to cover burn. // Create a tx with high L2 gas limit, but insufficient L1 gas limit to cover burn.
...@@ -187,7 +244,7 @@ describe('CanonicalTransactionChain', () => { ...@@ -187,7 +244,7 @@ describe('CanonicalTransactionChain', () => {
// additional gas overhead, it will be enough trigger the gas burn, but not enough to cover // additional gas overhead, it will be enough trigger the gas burn, but not enough to cover
// it. // it.
const l1GasLimit = const l1GasLimit =
(l2GasLimit - _enqueueL2GasPrepaid) / l2GasDiscountDivisor (l2GasLimit - _enqueueL2GasPrepaid) / l2GasDiscountDivisor
await expect( await expect(
CanonicalTransactionChain.enqueue(target, l2GasLimit, data, { CanonicalTransactionChain.enqueue(target, l2GasLimit, data, {
...@@ -223,13 +280,13 @@ describe('CanonicalTransactionChain', () => { ...@@ -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 () => { it('the cost to enqueue transactions is consistent for different L2 gas amounts below the prepaid threshold', async () => {
const ENQUEUE_L2_GAS_PREPAID = const enqueueL2GasPrepaid =
await CanonicalTransactionChain.ENQUEUE_L2_GAS_PREPAID() await CanonicalTransactionChain.enqueueL2GasPrepaid()
const data = '0x' + '12'.repeat(1234) const data = '0x' + '12'.repeat(1234)
const l2GasLimit1 = ENQUEUE_L2_GAS_PREPAID - 1 const l2GasLimit1 = enqueueL2GasPrepaid - 1
const l2GasLimit2 = ENQUEUE_L2_GAS_PREPAID - 100 const l2GasLimit2 = enqueueL2GasPrepaid - 100
// The first enqueue is more expensive because it's writing to an empty slot, // 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. // 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