Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: high
Valid

RAAC Reward Distribution Can Be Sandwiched to Extract Value Through Momentary Deposits

Link to Affected Code:\

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/pools/StabilityPool/StabilityPool.sol#L251-L259
https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/pools/StabilityPool/StabilityPool.sol#L326-L337

function calculateRaacRewards(address user) public view returns (uint256) {
uint256 userDeposit = userDeposits[user];
uint256 totalDeposits = deToken.totalSupply();
uint256 totalRewards = raacToken.balanceOf(address(this));
if (totalDeposits < 1e6) return 0;
return (totalRewards * userDeposit) / totalDeposits;
}
function depositRAACFromPool(uint256 amount) external onlyLiquidityPool validAmount(amount) {
uint256 preBalance = raacToken.balanceOf(address(this));
raacToken.safeTransferFrom(msg.sender, address(this), amount);
// ...
}

Description:


The StabilityPool's reward distribution mechanism is vulnerable to sandwich attacks around depositRAACFromPool transactions. The vulnerability exists because calculateRaacRewards() calculates rewards based on the current RAAC balance in the pool and the user's current deposit ratio, without considering deposit duration.

When RAAC rewards are being deposited to the pool via depositRAACFromPool, an attacker can front-run this transaction with a large deposit, capture a portion of the rewards, and immediately withdraw - all without maintaining any meaningful stake in the protocol.

Impact:

  • Attackers can extract RAAC rewards without meaningful protocol participation

  • Legitimate long-term stakers receive reduced rewards

  • Undermines the protocol's reward incentive mechanism

  • Economic value extraction through MEV

Proof of Concept:
Initial State:

  • StabilityPool has 10M rToken in deposits

  • 1000 RAAC rewards pending to be deposited via depositRAACFromPool

Attack Sequence:

  1. Attacker monitors mempool and sees pending depositRAACFromPool transaction

  2. Attacker front-runs with:

stabilityPool.deposit(1_000_000e18) // Deposits 1M rToken
  • Now owns ~9.09% of pool (1M/11M total deposits)

  1. depositRAACFromPool executes:

  • 1000 RAAC gets deposited to StabilityPool

  1. Attacker immediately back-runs with:

stabilityPool.withdraw(myDeTokenBalance) // Withdraws entire position

Result:

  • Attacker receives:

    • 1M rToken back (original deposit)

    • ~90.9 RAAC (9.09% of 1000 RAAC rewards)

  • Attack took 2-3 blocks

  • No long-term stake required

  • Can be repeated for every reward deposit

Recommended Mitigation:

  1. Implement reward vesting with minimum lock periods:

struct UserPosition {
uint256 amount;
uint256 depositTimestamp;
uint256 lastRewardIndex;
}
mapping(address => UserPosition) public userPositions;
uint256 public constant MIN_LOCK_DURATION = 7 days;
uint256 public rewardIndex;
function calculateRaacRewards(address user) public view returns (uint256) {
UserPosition storage position = userPositions[user];
if (block.timestamp - position.depositTimestamp < MIN_LOCK_DURATION) {
return 0;
}
return position.amount * (rewardIndex - position.lastRewardIndex) / 1e18;
}
Updates

Lead Judging Commences

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

StabilityPool::calculateRaacRewards is vulnerable to just in time deposits

Support

FAQs

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

Give us feedback!