Bundler calls can be identified by MEV bots and front-run
Description
Since being a bundler is permissionless, anyone can call Teller.processBundle
to submit any valid bundle of operations. When submitting a bundle, the bundler pays for the gas spent in both verifying the proofs and executing the actions via Ethereum transaction fees. During the transaction, it then gets reimbursed for that gas via a transfer of unwrapped assets earmarked for gas.
However, this presents a perverse economic incentive for MEV-aware Ethereum block builders. Before including a processBundle
transaction from a benign bundler in an Ethereum block, if a block builder simulates the transaction, they will find that if they front-run the transaction with an identical transaction sent from their own address instead, the transaction will happen in the same way, except they pay the gas cost and then they are paid the gas refund instead of the bundler. Doing this would cause the real bundler's transaction to revert, but the real bundler still pays the gas for verifying the proofs.
In processBundle
,
function processBundle(
Bundle calldata bundle
)
external
override
whenNotPaused
nonReentrant
onlyEoa
returns (uint256[] memory opDigests, OperationResult[] memory opResults)
{
Operation[] calldata ops = bundle.operations;
// --- snip ---
(bool success, uint256 perJoinSplitVerifyGas) = _verifyAllProofsMetered(
ops,
opDigests
);
require(success, "Batch JoinSplit verify failed");
uint256 numOps = ops.length;
opResults = new OperationResult[](numOps);
for (uint256 i = 0; i < numOps; i++) {
try
_handler.handleOperation(
ops[i],
perJoinSplitVerifyGas,
msg.sender
)
returns (OperationResult memory result) {
// --- snip ---
Note that first, a call to _verifyAllProofsMetered
occurs, which expensively verifies the proofs and measures the gas required, setting perJoinSplitVerifyGas
. Next, the call to handleOperation
calls _processJoinSplitsReservingFee
, which checks the nullifiers. This is what reverts in a second call, because the nullifiers will already have been used.
This means that, from a MEV-seeking block builder's perspective, if they front-run the bundler's transaction, they will still be paid for the gas price of verifying the proof. They need to pay it in their transaction, but the real bundler's reverted transaction will repay them about the same amount. So, they profit if they execute this front-run, and the real bundler is not repaid for the gas they spend on the proof verification.
Impact
Block builders are perversely incentivized to front-run the submission of bundles by bundlers. In a perfect economy, this means all bundlers must work with block builders or else their transactions will be reverted, front-run by the block builder issuing the same transaction, and they will pay for the gas for the verification circuit without any reimbursement. This disincentivizes block builders from building blocks.
Recommendations
Check the nullifiers of the joinsplits before checking the proofs so that a repeat submission of the same Operation
fails much more cheaply, rendering the front-running of bundle submissions economically unviable.
Remediation
Nocturne will ensure that bundlers submit their transactions through Flashbots Protect, which protects against front-running.