Tangy Coconut Crocodile
Medium
When L2 to L1 messages are sent via the L2CrossDomainMessenger.sol
contract, the messages are appended to the tree, which has a max depth of 32. This max depth limits the maximum number of elements in the tree to 2^32 - 1, which is not large enough to avoid being fully DoS-ed. Someone could fill all 2^32 - 1 slots by sending dummy transactions, making it impossible for legitimate L2 to L1 messages to be sent, as the entire queue would be full.
The MAX_DEPTH of the L2Messages tree is 32, meaning the number of transactions that can be placed in the tree is 2^32 - 1, which is not a large enough number to prevent a potential DoS attack.
None needed
None needed. Though, if the gas fee is lower the attack is more feasible.
- Call the
L2CrossDomainMessenger
contract for "x" times such that the L2 message tree is full and not accepting any L2 messages to L1. For efficiency make the calls simple and send batch calls in a single transaction to minimize gas
Sending messages from L2 to L1 will be impossible. Permanent DoS.
As we can see in L2CrossDomainMessenger.sol:114, the messages are appended to the L2ToL1MessagePasser
contract's tree structure. L2ToL1MessagePasser.sol::appendMessage() calls the internal _appendMessageHash function, where the following check inside _appendMessageHash
is our target for DoSing the entire L2 -> L1 message process:
function _appendMessageHash(bytes32 leafHash) internal {
//..
-> if (leafNodesCount >= _MAX_DEPOSIT_COUNT) {
revert MerkleTreeFull();
}
//..
}
If leafNodesCount
reaches _MAX_DEPOSIT_COUNT
, which is 2^32 - 1, any transaction attempting to pass from L2 -> L1 will automatically revert, as the tree is full.
Now, let's take the worst-case scenario, where the number of transactions sent from L2 -> L1 is "0", and calling L2CrossDomainMessenger::sendMessage()
500 times in a single transaction costs $0.10 (a reasonable estimate considering EIP-4844).
First, calculate 2^32 - 1, which is approximately L2CrossDomainMessenger
. Since we are sending 500 messages per transaction, the total number of calls would be:
Each call incurs a $0.10 fee, so the total cost would be:
In comparison, let's look at how Scroll implements its tree structure. The Scroll tree has a depth of 40 Link, which not only increases the cost of an attack due to the number of iterations required but also makes appending messages more expensive, as there are 8 additional iterations in the loop to calculate the root. However, let's assume the best-case scenario, where calling sendMessage
500 times in 1 transaction costs $0.01, and run the same calculation:
The maximum number of transactions needed is
Dividing by 500 messages per transaction:
The overall cost to consume the entire tree would be:
Increase the _MAX_TREE_DEPTH