You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository has been archived by the owner on Jan 12, 2025. It is now read-only.
DuplicateA valid issue that is a duplicate of an issue with `Has Duplicates` labelMediumA Medium severity issue.RewardA payout will be made for this issue
MlumStaking user can frontrun new reward tokens and harvest immediatelly
Summary
All new rewards can be stolen by sandwiching rewards with 0 lockDuration.
Vulnerability Detail
The way that users in MlumStaking receive rewards is first there should be some reward token transferred to the contract and the next caller will trigger _updatePool function, which will increase the _accRewardsPerShare based on the amount that has been sent and then divided by all the positions in the system.
The problem is that this rewardToken transfer can be sandwiched by anyone to harvest immediate rewards. It will happen since there is no minimum lockDuration and positions opened with 0 can be closed right after.
Impact is max when there is sequence of the following actions (note that due to the nature of IotaEVM, deployers will be forced to not submit 2 consecutive transactions for deploy and transferring reward tokens, since it can either fail or send the tokens to wrong address, depending on which transaction will be processed first, this gives the needed time for Alice to insert her tx between the 2):
10e18 rewardTokens are sent, when there are no positions in the contract
Alice frontrun step 1 by createPosition with 1 wei and 0 lockDuration
Important variables:
Alice’s rewardDebt = 0
Alice’s amountWithMultiplier = 1
_stakedSupplyWithMultiplier = 1
_accRewardsPerShare = 0
Alice call withdrawFromPosition with 1 wei, and pool is updated:
All new rewardToken transfer can be immediately stolen.
Here is a POC demonstrating how Alice takes the entire balance of rewardTokentwice. It can be placed in MlumStaking.t.sol:
function testC1() public {
_stakingToken.mint(ALICE, 10 ether);
vm.startPrank(ALICE);
_stakingToken.approve(address(_pool), 1 ether);
_pool.createPosition(1 ether, 0);
vm.stopPrank();
IMlumStaking.StakingPosition memory pos = _pool.getStakingPosition(1);
console.log("Rewards after create", _pool.pendingRewards(1));
console.log("Rewards balance after create", _rewardToken.balanceOf(ALICE));
console.log(pos.amountWithMultiplier);
console.log(pos.rewardDebt);
console.log(_pool._accRewardsPerShare());
_rewardToken.mint(address(_pool), 100000e6);
console.log("Rewards balance in staking", _rewardToken.balanceOf(address(_pool)));
vm.prank(ALICE);
_pool.withdrawFromPosition(1, 1 ether);
console.log("Rewards balance after withdraw", _rewardToken.balanceOf(ALICE));
console.log("RRewards balance in staking after", _rewardToken.balanceOf(address(_pool)));
console.log(_pool._accRewardsPerShare());
// =======================
vm.startPrank(ALICE);
_stakingToken.approve(address(_pool), 1 ether);
_pool.createPosition(1 ether, 0);
vm.stopPrank();
console.log(_pool._accRewardsPerShare());
_rewardToken.mint(address(_pool), 200e6);
console.log("Rewards balance in staking", _rewardToken.balanceOf(address(_pool)));
vm.prank(ALICE);
_pool.withdrawFromPosition(2, 1 ether);
console.log("Rewards balance after withdraw", _rewardToken.balanceOf(ALICE));
console.log("RRewards balance in staking after", _rewardToken.balanceOf(address(_pool)));
console.log(_pool._accRewardsPerShare());
}
Another possible scenario is when there are already some positions, everyone can sandwich the transferring of rewardToken, creating positions with 0 lockDuration and any satisfying amount of stakedToken, then backrun the transfer by withdraw and close the position taking the difference of previous _accRewardsPerShare and next one updated in _updatePool. This way no one will be incentivized to lock his tokens for any amount of time, when he can simply harvest the position immediately.
It order of events will look like that → MlumStaking::createPosition → rewardToken::transfer(MlumStaking, 1000e18) → MlumStaking::withdrawFromPosition.
Impact
All the rewardTokens can be stolen from the first staker in MlumStaking
sherlock-admin4
changed the title
Soft Mint Lizard - MlumStaking user can frontrun new reward tokens and harvest immediatelly
scammed - MlumStaking user can frontrun new reward tokens and harvest immediatelly
Jul 29, 2024
Sign up for freeto subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Labels
DuplicateA valid issue that is a duplicate of an issue with `Has Duplicates` labelMediumA Medium severity issue.RewardA payout will be made for this issue
scammed
High
MlumStaking user can frontrun new reward tokens and harvest immediatelly
Summary
All new rewards can be stolen by sandwiching rewards with 0
lockDuration
.Vulnerability Detail
The way that users in
MlumStaking
receive rewards is first there should be some reward token transferred to the contract and the next caller will trigger_updatePool
function, which will increase the_accRewardsPerShare
based on the amount that has been sent and then divided by all the positions in the system.The problem is that this
rewardToken
transfer can be sandwiched by anyone to harvest immediate rewards. It will happen since there is no minimumlockDuration
and positions opened with 0 can be closed right after.Impact is max when there is sequence of the following actions (note that due to the nature of IotaEVM, deployers will be forced to not submit 2 consecutive transactions for deploy and transferring reward tokens, since it can either fail or send the tokens to wrong address, depending on which transaction will be processed first, this gives the needed time for Alice to insert her tx between the 2):
rewardTokens
are sent, when there are no positions in the contractcreatePosition
with 1 wei and 0lockDuration
Important variables:
rewardDebt
= 0amountWithMultiplier
= 1_stakedSupplyWithMultiplier
= 1_accRewardsPerShare
= 0withdrawFromPosition
with 1 wei, and pool is updated:_harvestPosition
the entirerewardToken
balance is sent to Alice:rewardToken
transfer can be immediately stolen.Here is a POC demonstrating how Alice takes the entire balance of
rewardToken
twice. It can be placed inMlumStaking.t.sol
:Another possible scenario is when there are already some positions, everyone can sandwich the transferring of
rewardToken
, creating positions with 0lockDuration
and any satisfying amount ofstakedToken
, then backrun the transfer by withdraw and close the position taking the difference of previous_accRewardsPerShare
and next one updated in_updatePool
. This way no one will be incentivized to lock his tokens for any amount of time, when he can simply harvest the position immediately.It order of events will look like that →
MlumStaking::createPosition
→rewardToken::transfer(MlumStaking, 1000e18)
→MlumStaking::withdrawFromPosition
.Impact
All the
rewardTokens
can be stolen from the first staker inMlumStaking
Code Snippet
https://github.com/sherlock-audit/2024-06-magicsea/blob/main/magicsea-staking/src/MlumStaking.sol#L354-L390
https://github.com/sherlock-audit/2024-06-magicsea/blob/main/magicsea-staking/src/MlumStaking.sol#L496-L502
https://github.com/sherlock-audit/2024-06-magicsea/blob/main/magicsea-staking/src/MlumStaking.sol#L674-L686
Tool used
Manual Review
Recommendation
Enforce some minimum stake duration
Duplicate of #74
The text was updated successfully, but these errors were encountered: