Pizza lovers lose money when they claim their slices
Description
When the owner register Pizza lovers through airdrop::register_pizza_lover
, they get a random slice with airdrop::get_random_slice
, it is between 100-500 APT.
But in fact it is 100-500 Octas, which is the smallest unit, 1 APT = 10⁸ octas.
see code below :
entry fun get_random_slice(user_addr: address) acquires ModuleData, State {
let state = borrow_global_mut<State>(get_resource_address());
let time = timestamp::now_microseconds();
let random_val = time % 401;
let random_amount = 100 + random_val;
table::add(&mut state.users_claimed_amount, user_addr, random_amount);
}
The problem is reside in the fact that for the pizza lovers to claim his/her slice, he/she needs to create a transaction that cost a certain amount of APT(actually Octas) for the gas, and the gas cost is higher than the reward.
Risk
The risk is that everytime a pizza lover will claim his/her slice, they will lose money.
The game is not profitable.
Proof of Concept
The following test scenario, shows the problem :
Start a local node in a seperate terminal
aptos node run-local-testnet --with-faucet
initialize an account
aptos init --profile local --network local
Create an another user
aptos init --profile user --network local
See the aptos balance
aptos account balance --account user --url http:
{
"Result": [
{
"asset_type": "coin",
"coin_type": "0x1::aptos_coin::AptosCoin",
"balance": 100000000
}
]
}
Deploy
aptos move publish --profile local --named-addresses pizza_drop=local
NOTE: local_account, user_account and local_private_key are found in .aptos/config.yaml
.
Fund the pizza drop
aptos move run --function-id LOCAL_ACCOUNT::airdrop::fund_pizza_drop --args u64:10000 --url http:
Get the APT balance
aptos move view --function-id LOCAL_ACCOUNT::airdrop::get_actual_apt_balance --url http:
{
"Result": [
"10000"
]
}
Register the user as a pizza lover
aptos move run --function-id LOCAL_ACCOUNT::airdrop::register_pizza_lover --args address:USER_ACCOUNT --url http:
See if user register correctly
aptos move view --function-id LOCAL_ACCOUNT::airdrop::is_registered --args address:USER_ACCOUNT --url http:
{
"Result": [
true
]
}
See the amount to claim
aptos move view --function-id LOCAL_ACCOUNT::airdrop::get_claimed_amount --args address:USER_ACCOUNT --url http:
{
"Result": [
"363"
]
}
The user claim the slice
aptos move run --function-id LOCAL_ACCOUNT::airdrop::claim_pizza_slice --url http:
{
"Result": {
"transaction_hash": "0xff7c8ffff2233ffde2ebd3704ec218489547c428a62e48b5c129f75a56810cfd",
"gas_used": 939,
"gas_unit_price": 100,
"sender": "acc574bf21fb6d64c454f056c80b1b5d2c95839af2ada72bbf827e38c55754c9",
"sequence_number": 0,
"replay_protector": {
"SequenceNumber": 0
},
"success": true,
"timestamp_us": 1756629490224223,
"version": 3230,
"vm_status": "Executed successfully"
}
}
The gas unit price is already 100 octas and the actual gas used is 939, so 939 x 100 = 93 900 octas.
note: The mainnet fee is even higher, see: https://www.theblock.co/data/on-chain-metrics/aptos/average-transaction-fee-on-aptos-7dma-in-apt.
Get the smart contract APT balance
aptos move view --function-id LOCAL_ACCOUNT::airdrop::get_actual_apt_balance --url http:
{
"Result": [
"9637"
]
}
The smart contract apt balance is correct.
The user apt balance
aptos account balance --account user --url http:
{
"Result": [
{
"asset_type": "coin",
"coin_type": "0x1::aptos_coin::AptosCoin",
"balance": 99906463
}
]
}
The initial balance of user was : 100000000(see part 4), and now is reduce to 99906463, because gas fee was higher than the reward.
The user loses money.
Recommended Mitigation
The recommended mitigation is to multiple the reward by at least 1000 for local environment and even higher for the mainnet.
entry fun get_random_slice(user_addr: address) acquires ModuleData, State {
let state = borrow_global_mut<State>(get_resource_address());
let time = timestamp::now_microseconds();
- let random_val = time % 401;
- let random_amount = 100 + random_val; // 100-500 APT (in Octas: 10^8 smallest unit)
+ let random_val = (time % 401) * 1000;
+ let random_amount = 100000 + random_val; // 100000-500000 (in Octas: 10^8 smallest unit)
table::add(&mut state.users_claimed_amount, user_addr, random_amount);
}