Skip to content

Latest commit

 

History

History
79 lines (49 loc) · 4.34 KB

090.md

File metadata and controls

79 lines (49 loc) · 4.34 KB

Shambolic Banana Barbel

High

Sequencer will be underpaid because of incorrect commitScalar

Summary

GasPriceOracle.sol underprices the cost of calling commitBatch(), so the l1DataCost paid by users will be substantially less than the true cost to the sequencer. This is made worse when transactions are heavily weighted towards L1 cost, in which case the sequencer can be responsible for payments 100X as large as the revenue collected.

Root Cause

When new L2 transactions are submitted, the transaction cost is calculated as the L2 gas price plus an l1DataFee, which is intended to cover the cost of posting the data to L1.

The l1DataFee is actually calculated in go-ethereum rollup/fees/rollup_fee.go#L153, but is equivalent to this calculation in GasPriceOracle.sol as:

function getL1FeeCurie(bytes memory _data) internal view returns (uint256) {
    // We have bounded the value of `commitScalar` and `blobScalar`, the whole expression won't overflow.
    return (commitScalar * l1BaseFee + blobScalar * _data.length * l1BlobBaseFee) / PRECISION;
}

We can summarize as:

  • We pay 1 gas per byte that goes into a blob (because GAS_PER_BLOB == 2**17), so we can calculate the cost of the blob as blobScalar * _data.length * l1BlobBaseFee.
  • We have to call commitBatch() as well, so we take the gas cost of that call and multiply by the l1BaseFee.

The config specifies that the blobScalar = 0.4e9 and the commitScalar = 230e9.

This commitScalar is underpriced by a margin of 100X, as it should be 230_000e9, not 230e9.

The result is that transactions that are largely weighted towards the L1 fee will cost the sequencer way more to commit to than the user paid.

In an extreme, we can imagine an attack where users performed the most L1 data intensive transactions possible, which consist of filling a full batch with a single transaction that uses 128kb of calldata.

Let's look at the relative costs for the attacker and the sequencer of such a transaction:

  • The attacker's fee is calculated as intrinsic gas + calldata + l1DataFee.
  • Since blobs are 128kb, we need 1024 * 128 = 131,072 bytes of calldata to fill the blob.
  • Assuming we need half non-zero to avoid compression, we can calculate the calldata cost as: 1024 * 128 * (4 + 16 / 2) = 1,310,720.
  • Therefore, the total gas used by the attacker is 21_000 + 1,310,720 = 1,331,720.
  • The l1DataFee is calculated as above. If we assume an l1BaseFee of 30 gwei, this gives a cost of 52,428 + (230 * 30) = 59,328 gwei.
  • Assuming an L2 gas price of 0.001 gwei (estimate from L1 message queue), our total cost is (1,331,720 * 0.001) + 59,328 = 60,659 gwei = $0.13.

On the other hands, let's look at the sequencer's cost:

  • The blob cost is the same as the attacker's l1DataFee, as that is calculated correctly at 52,428 gwei.
  • The transaction cost, based on the Foundry estimate, is 230,000 gas. At 30 gwei per gas, that equals 230_000 * 30 = 6,900,000 gwei.
  • The total cost to the sequencer is 6,900,000 + 52,428 = 6,952,428 gwei = $15.98.

This mismatch requires sequencers to spend more than 100X to progress the chain, when only X was collected in sequencer fees from the user.

Internal Preconditions

None

External Preconditions

None

Attack Path

Note that this mispricing will cause the chain to slowly bleed revenue, regardless of any "attack". However, the most accute version of this attack would be as follows:

  1. User send a transfer of 0 value on L2 with 128kb of calldata.
  2. This costs them approximately $0.13 in L2 fees, but will require the sequencer to spent $15.98 in L1 gas to commit the batch.
  3. The user can repeat this attack indefinitely, causing the sequencer to spend more money than it receives.

Impact

Sequencers can be forced to spend 100X more than the fee revenue received in order to progress the chain, making the chain uneconomical.

PoC

N/A

Mitigation

commitBatch() should be set to 230_000e9.

Note that it appears that this value was pulled from Scroll's current on chain implementation. This issue has been reported directly to Scroll as well, and has been deemed valid and awarded with a bounty via their Immunefi bounty program.