Beginner FriendlyGameFi
100 EXP
View results
Submission Details
Impact: low
Likelihood: medium
Invalid

MEV issue in claim pizza slice

MEV issue in claim pizza slice

A user calls claim_pizza_slice to withdraw APT their assigned APT slice, provided the contract has sufficient balance. The function ensures that the user is registered, has not already claimed, and that the internal balance is enough. The issue is that when the pizza pool balance is low, multiple users racing to claim may cause a last-slice problem.

Validators on APTOS can influence which transations get executed first when balance is insufficient, enabled MEV. This allows validators to favor specific addresses or front-run claims.

// Check if contract has sufficient balance
@> assert!(state.balance >= amount, E_INSUFFICIENT_FUND);
// Register user to receive APT if not already registered
if (!coin::is_account_registered<AptosCoin>(user_addr)) {
coin::register<AptosCoin>(user);
};
transfer_from_contract(user_addr, amount);
state.balance = state.balance - amount;

Risk

Likelihood: Low

When the pizza pool balance approaches depletion, multiple claim transaction may compete. Validators can choose trannsaction inclusion and ordering within blocks.

Impact: Medium

Some users may be unfairly excluded from claiming despite being eligible.

Proof of Concept

#[test(deployer = @pizza_drop, user1 = @0x123, user2 = @0x456, framework = @0x1)]
#[expected_failure(abort_code = E_INSUFFICIENT_FUND)]
fun test_transaction_ordering_matters(deployer: &signer, user1: &signer, user2: &signer, framework: &signer) acquires State, ModuleData {
use aptos_framework::account;
use aptos_framework::timestamp;
use aptos_framework::aptos_coin;
// Setup
timestamp::set_time_has_started_for_testing(framework);
let (burn_cap, mint_cap) = aptos_coin::initialize_for_test(framework);
account::create_account_for_test(@pizza_drop);
account::create_account_for_test(signer::address_of(user1));
account::create_account_for_test(signer::address_of(user2));
debug::print(&b"=== MULTIPLE USERS TEST ===");
// Initialize and fund
init_module(deployer);
let funding_amount = 100;
let deployer_coins = coin::mint<AptosCoin>(funding_amount, &mint_cap);
coin::register<AptosCoin>(deployer);
coin::deposit<AptosCoin>(@pizza_drop, deployer_coins);
fund_pizza_drop(deployer, 100);
// Register both users
let user1_addr = signer::address_of(user1);
let user2_addr = signer::address_of(user2);
register_pizza_lover(deployer, user1_addr);
register_pizza_lover(deployer, user2_addr);
assert!(is_registered(user1_addr), 1);
assert!(is_registered(user2_addr), 2);
let amount1 = get_claimed_amount(user1_addr);
let amount2 = get_claimed_amount(user2_addr);
assert!(amount1 == 100, 3);
assert!(amount1 == 100, 4);
// Both users claim but the order matters due to limited funds
claim_pizza_slice(user1);
claim_pizza_slice(user2);
// Clean up
coin::destroy_burn_cap(burn_cap);
coin::destroy_mint_cap(mint_cap);
}

When validators include the transaction of user1 before user2, user1 successfully claims their slice, depleting the pool.
Consequently, user2's transaction fails due to insufficient funds, even though they were eligible to claim.
Which transaction is going to be included first is does not depend on the time of arrival, but on the validator's choice.

Recommended Mitigation

Use a commit-reveal scheme or a random selection mechanism to determine claim order fairly. This reduces the ability of validators to manipulate transaction ordering for MEV.

Updates

Appeal created

bube Lead Judge 12 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.