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

LoveToken Holders can `deposit` and `withdraw` any portion of `LoveTokens`. Breaks Contracts expected Functionality.

Summary

The Soulmate Protocol's Staking mechanism is expected to handle integral LoveToken values, such as 1, 2, 3, and so on, for both deposits and withdrawals. However, the Staking::deposit and Staking::withdraw functions lack protection against fractional LoveToken values. Despite fractional values potentially having units such as Wei and Gwei, the protocol only expects integral LoveToken values.

...
...
...
// ............................
// .............. ||
// ....... \/
@> function deposit(uint256 amount) public {
if (loveToken.balanceOf(address(stakingVault)) == 0) {
revert Staking__NoMoreRewards();
}
// No require needed because of overflow protection
userStakes[msg.sender] += amount;
loveToken.transferFrom(msg.sender, address(this), amount);
emit Deposited(msg.sender, amount);
}
/// @notice Decrease the userStakes variable and transfer LoveToken to the user withdrawing.
// ............................
// .............. ||
// ....... \/
@> function withdraw(uint256 amount) public {
// No require needed because of overflow protection
userStakes[msg.sender] -= amount;
loveToken.transfer(msg.sender, amount);
emit Withdrew(msg.sender, amount);
}
...
...
...

Vulnerability Details

PoC of any value `deposit` & `withdraw`:
  1. Place the following test code snippet into the test/unit/soulmateTest.t.sol file. Put it at the very bottom but before the last closing semicolon }.

function test_depositAndWithdrawAnyAmount() public {
address alice = makeAddr("ALICE");
address bob = makeAddr("BOB");
vm.startPrank(alice);
soulmateContract.mintSoulmateToken();
vm.stopPrank();
vm.startPrank(bob);
soulmateContract.mintSoulmateToken();
vm.stopPrank();
vm.warp(block.timestamp + 1 days + 1);
vm.startPrank(alice);
airdropContract.claim();
vm.stopPrank();
uint256 aliceBalance = loveToken.balanceOf(alice);
vm.startPrank(alice);
loveToken.approve(address(stakingContract), aliceBalance / 2);
stakingContract.deposit(aliceBalance / 2);
stakingContract.withdraw(aliceBalance / 2);
vm.stopPrank();
assertEq(loveToken.balanceOf(alice), aliceBalance);
}
  1. Open Your Bash Terminal and execute the following commands one by one...

forge test --mt "test_depositAndWithdrawAnyAmount"
  1. Test would get Passed!

Impact

Potentially Breaks the Protocol | Contract's functionality and expectations.

Tools Used

Foundry Framework (Solidity | Rust)
Foundry invariant fuzz
Foundry console2.sol

Recommendations

Update Staking.sol like this...

...
...
...
error Staking__NoMoreRewards();
error Staking__StakingPeriodTooShort();
+ error Staking__InvalidAmountOfLoveTokens(uint256 amount);
...
...
...
function deposit(uint256 amount) public {
+ if (amount % 10 ** loveToken.decimals() != 0) {
+ revert Staking__InvalidAmountOfLoveTokens(amount);
+ }
if (loveToken.balanceOf(address(stakingVault)) == 0) {
revert Staking__NoMoreRewards();
}
// No require needed because of overflow protection
userStakes[msg.sender] += amount;
loveToken.transferFrom(msg.sender, address(this), amount);
emit Deposited(msg.sender, amount);
}
/// @notice Decrease the userStakes variable and transfer LoveToken to the user withdrawing.
function withdraw(uint256 amount) public {
+ if (amount % 10 ** loveToken.decimals() != 0) {
+ revert Staking__InvalidAmountOfLoveTokens(amount);
+ }
// No require needed because of overflow protection
userStakes[msg.sender] -= amount;
loveToken.transfer(msg.sender, amount);
emit Withdrew(msg.sender, amount);
}
...
...
...
Updates

Lead Judging Commences

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.