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();
}
userStakes[msg.sender] += amount;
loveToken.transferFrom(msg.sender, address(this), amount);
emit Deposited(msg.sender, amount);
}
@> function withdraw(uint256 amount) public {
userStakes[msg.sender] -= amount;
loveToken.transfer(msg.sender, amount);
emit Withdrew(msg.sender, amount);
}
...
...
...
Vulnerability Details
PoC of any value `deposit` & `withdraw`:
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);
}
Open Your Bash Terminal
and execute the following commands one by one...
forge test --mt "test_depositAndWithdrawAnyAmount"
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);
}
...
...
...