Part 2

Zaros
PerpetualsDEXFoundrySolidity
70,000 USDC
View results
Submission Details
Severity: medium
Valid

Staking design is not fair for users who staked earlier and longer

Summary

The staking implementation used is not based on Sushiswap MasterChef and Synthetix. The problem with Zaros staking algorithm, is it did not consider the staking duration and precedence, this causes unfair staking rewards to be provided.

Vulnerability Details

The Zaros staking system operates by checking the last change whenever the market distributes rewards. If there is a difference in rewards being distributed, the vault allocates them to users. However, a critical flaw in this system is that it allows any user to stake a large amount right before rewards are distributed, claim the majority of the rewards, and then immediately unstake. This effectively enables them to steal rewards from other users who have been staking for longer, as there is no minimum staking period to prevent such behavior.

The proof of concept further demonstrates that users’ shares remain unchanged unless rewards are distributed. More importantly, it highlights how users can exploit the system by staking a large amount, capturing most of the rewards, and unstaking immediately, which undermines fairness and disincentivizes long-term staking.

Add to 2025-01-zaros-part-2/test/Base.t.sol

function getPoCVaultConfig() internal returns (VaultConfig memory) {
VaultConfig memory poc_vault = getPoCVaultConfig_Vaults();
return poc_vault;
}

Add to 2025-01-zaros-part-2/script/vaults/Vaults.sol

function getPoCVaultConfig_Vaults() public view returns (VaultConfig memory) {
return vaultsConfig[USDC_CORE_VAULT_ID];
}

Add to 2025-01-zaros-part-2/test/integration/market-making/vault-router-branch/stake/stake.t.sol

function test_POC_stake_algorithm(
uint128 vaultId,
uint128 assetsToDeposit
)
external
whenQualityOfSharesIsMoreThanMinAmount
{
// ensure valid vault and load vault config
vaultId = uint128(8); //usdc core
VaultConfig memory fuzzVaultConfig = getPoCVaultConfig();
// ensure valid deposit amount and perform the deposit
address user = users.naruto.account;
address poc_madara = users.madara.account;
assetsToDeposit = 1e18;
fundUserAndDepositInVault(user, vaultId, assetsToDeposit);
fundUserAndDepositInVault(poc_madara, vaultId, assetsToDeposit);
// save and verify pre state
StakeState memory pre = _getStakeState(user, vaultId, IERC20(fuzzVaultConfig.indexToken));
// save and verify pre state
StakeState memory pre_madara = _getStakeState(poc_madara, vaultId, IERC20(fuzzVaultConfig.indexToken));
console.log(pre_madara.stakerVaultBal);
console.log("pre_madara");
assertGt(pre.stakerVaultBal, 0, "Staker vault balance > 0 after deposit");
assertEq(pre.marketEngineVaultBal, 0, "MarketEngine has no vault shares");
assertEq(pre.totalShares, 0, "Staking totalShares 0 as no stakers");
assertEq(pre.valuePerShare, 0, "Staking valuePerShare 0 as no stakers and no value distributed");
assertEq(pre.stakerShares, 0, "Staker has no staking shares prior to staking");
assertEq(pre.stakerLastValuePerShare, 0, "Staker has no value per share prior to staking");
// perform the poc_madara stake
vm.startPrank(poc_madara);
marketMakingEngine.stake(vaultId, pre.stakerVaultBal);
// perform the stake
vm.startPrank(user);
marketMakingEngine.stake(vaultId, pre.stakerVaultBal);
// save and verify post state
StakeState memory post = _getStakeState(user, vaultId, IERC20(fuzzVaultConfig.indexToken));
// save and verify post state
StakeState memory post_madara = _getStakeState(poc_madara, vaultId, IERC20(fuzzVaultConfig.indexToken));
assertEq(post.stakerVaultBal, 0, "Staker has no vault shares after staking them");
assertEq(post.marketEngineVaultBal, pre.stakerVaultBal + pre_madara.stakerVaultBal, "MarketEngine received stakers vault shares");
console.log("post.vaulePerShare");
console.log(post.valuePerShare);
console.log("post_madara.vaulePerShare");
console.log(post_madara.valuePerShare); //@audit if use masterchef algo, will be fairer
assertEq(post.valuePerShare, 0, "Staking valuePerShare 0 as no value distributed");
}

Impact

Unfair rewards distributed to users,

Tools Used

Foundry

Recommendations

Use masterchef staking algorithm and impose a minimum staking period.

Updates

Lead Judging Commences

inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Staking design is not fair for users who staked earlier and longer, frontrun fee distribution with big stake then unstake

Support

FAQs

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