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: 60/127
Findings: 3
Award: $196.74
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Arz
Also found by: 0xStalin, 0xpiken, AlexCzm, Chinmay, HighDuty, Infect3d, JCN, Neon2835, Tendency, TheSchnilch, almurhasan, asui, c47ch3m4ll, critical-or-high, deliriusz, ether_sky, evmboi32, kaden, klau5, santipu_, sl1, zhaojohnson
46.8502 USDC - $46.85
when the guild token is non transferable, there will always be less reward for the guild holders and if guild tokens become transferable, there may remain 0 rewards for the guild holders.
so gaugeProfitIndex[gauge] = 1e18+(100e18*1e18)/250e18 = 1e18+0.4e18 =1.4e18. 4. Alice calls for rewards and function claimGaugeRewards is called, see the function claimGaugeRewards code, function claimGaugeRewards( address user, address gauge ) public returns (uint256 creditEarned) { uint256 _userGaugeWeight = uint256( GuildToken(guild).getUserGaugeWeight(user, gauge) ); if (_userGaugeWeight == 0) { return 0; } uint256 _gaugeProfitIndex = gaugeProfitIndex[gauge]; uint256 _userGaugeProfitIndex = userGaugeProfitIndex[user][gauge]; if (_gaugeProfitIndex == 0) { _gaugeProfitIndex = 1e18; } if (_userGaugeProfitIndex == 0) { _userGaugeProfitIndex = 1e18; } uint256 deltaIndex = _gaugeProfitIndex - _userGaugeProfitIndex; if (deltaIndex != 0) { creditEarned = (_userGaugeWeight * deltaIndex) / 1e18; userGaugeProfitIndex[user][gauge] = _gaugeProfitIndex; } if (creditEarned != 0) { emit ClaimRewards(block.timestamp, user, gauge, creditEarned); CreditToken(credit).transfer(user, creditEarned); } }
as initially userGaugeProfitIndex[alice][gauge] = 0,so userGaugeProfitIndex[alice][gauge] sets to 1e18. gaugeProfitIndex[gauge] = 1.4e18. So deltaIndex = 1.4e18-1e18 = 0.4e18.alice’s creditearned = (200e180.4e18)/1e18 = 80e18, similarly bob’s creditearned = (50e180.4e18)/1e18 = 20e18.now userGaugeProfitIndex[alice][gauge] = 1.4e18, userGaugeProfitIndex[bob][gauge]= 1.4e18. 5. Now there is 400 rewards, 50% i.e 200 goes for creditSplit and other 200 goes to alice and bob for guild voting. gaugeProfitIndex[gauge] = 1.4e18+(200e18*1e18)/250e18 = 1e18+0.8e18 =2.2e18. 6. Alice and bob before claiming the reward , john increase(add) 166 weight to the gauge,see the code, function _incrementGaugeWeight( address user, address gauge, uint256 weight ) internal override { uint256 _lastGaugeLoss = lastGaugeLoss[gauge]; uint256 _lastGaugeLossApplied = lastGaugeLossApplied[gauge][user]; if (getUserGaugeWeight[user][gauge] == 0) { lastGaugeLossApplied[gauge][user] = block.timestamp; } else { require( _lastGaugeLossApplied >= _lastGaugeLoss, "GuildToken: pending loss" ); }
ProfitManager(profitManager).claimGaugeRewards(user, gauge); super._incrementGaugeWeight(user, gauge, weight); }
function _incrementGaugeWeight is called which call ProfitManager(profitManager).claimGaugeRewards(john, gauge), see claimGaugeRewards function , as GuildToken(guild).getUserGaugeWeight(john, gauge) = 0, creditEarned return 0 and john’s ProfitIndex is not updated(this is the attack vector).now john’s weight to the gauge is 160 i.e GuildToken(guild).getUserGaugeWeight(john, gauge) = 166. 7. Alice and bob before claiming the reward, John calls the function claimGaugeRewards, gaugeProfitIndex[gauge] = 2.2e18. as initially userGaugeProfitIndex[alice][gauge] = 0,so userGaugeProfitIndex[alice][gauge] sets to 1e18.So deltaIndex = 2.2e18-1e18 = 1.2e18. john’s creditearned = (166e18*1.2e18)/1e18 = 199.2e18. Now alice and bob have no rewards to claim. 8. Alice and bob before claiming the reward,john can claim the rewards by calling two functions( function incrementGauge ,function claimGaugeRewards) in a single transaction by creating a smart contract.When guild token will be transferable , attacker/john can transfer guild token to another address and attacker can always steal rewards of other guild holders.
manual review
when the function incrementGauge is called, if caller weight is 0, set userGaugeProfitIndex[caller][gauge] to gaugeProfitIndex[gauge] in function claimGaugeRewards.
Other
#0 - c4-pre-sort
2024-01-02T19:17:40Z
0xSorryNotSorry marked the issue as insufficient quality report
#1 - c4-pre-sort
2024-01-02T19:18:08Z
0xSorryNotSorry marked the issue as duplicate of #1211
#2 - c4-judge
2024-01-29T03:56:48Z
Trumpero marked the issue as satisfactory
143.0739 USDC - $143.07
https://github.com/code-423n4/2023-12-ethereumcreditguild/blob/main/src/loan/LendingTerm.sol#L754
borrower will get back collateral but loss willbe applied for the gauge.
Let assume bob’s borrowamount = 20000e18, bob’s collateral amount = 15e18.lending term interest rate = 0.10e18. After 1 year bob’s interest = 2000e18 , bob’s loan debt = 22000e18(assume loan.borrowCreditMultiplier = 1e18, present creditMultiplier = 1e18).
The lendingterm is offboarded , bob’s loanid is called for auction , now bob’s loan.calldebt = 22000e18.
Let assume _AUCTION_DURATION = 30 minutes, _MIDPOINT = 10 minutes,50% of auction first phase has gone and bidder bids, collateralReceived = 7.5e18,creditAsked = 22_000e18, bob gets back 7.5e18 collateral token.
See the function onBid, uint256 creditMultiplier = ProfitManager(refs.profitManager) .creditMultiplier(); uint256 borrowAmount = loans[loanId].borrowAmount; uint256 principal = (borrowAmount * loans[loanId].borrowCreditMultiplier) / creditMultiplier; int256 pnl; uint256 interest; if (creditFromBidder >= principal) { interest = creditFromBidder - principal; pnl = int256(interest); } else { pnl = int256(creditFromBidder) - int256(principal); principal = creditFromBidder; require( collateralToBorrower == 0, "LendingTerm: invalid collateral movement" ); } let assume now creditmultiplier is 0.9e18. So principle = (20000e18*1e18)/0.9e18 = 22222e18. As creditfrombidder is less than principle , interest will not be applied, loss will be applied which is unfair because borrower will get back collateral but loss willbe applied for the gauge.
manual review
Other
#0 - c4-pre-sort
2024-01-04T07:38:48Z
0xSorryNotSorry marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-01-04T07:39:15Z
0xSorryNotSorry marked the issue as duplicate of #914
#2 - c4-pre-sort
2024-01-05T12:25:18Z
0xSorryNotSorry marked the issue as duplicate of #886
#3 - c4-judge
2024-01-30T00:27:38Z
Trumpero changed the severity to QA (Quality Assurance)
#4 - c4-judge
2024-01-30T14:33:11Z
This previously downgraded issue has been upgraded by Trumpero
#5 - c4-judge
2024-01-30T14:33:37Z
Trumpero marked the issue as not a duplicate
#6 - c4-judge
2024-01-30T14:33:52Z
Trumpero marked the issue as duplicate of #1069
#7 - Trumpero
2024-01-30T14:34:37Z
This issue is a dup of #1069 but I only give it 50% partial credit since it lacks quality and maximum impact.
#8 - c4-judge
2024-01-30T14:34:42Z
Trumpero marked the issue as partial-50
#9 - c4-judge
2024-01-30T14:34:46Z
Trumpero marked the issue as satisfactory
#10 - c4-judge
2024-01-30T14:37:18Z
Trumpero changed the severity to QA (Quality Assurance)
#11 - thebrittfactor
2024-01-30T17:57:06Z
For transparency, the judge requested some label updates to notate this finding as Medium Risk
and Partial-50
.
#12 - c4-judge
2024-01-30T18:01:33Z
Trumpero removed the grade
#13 - c4-judge
2024-01-31T13:43:04Z
Trumpero changed the severity to 3 (High Risk)
🌟 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
guild holders can get unfairly more rewards than other guild holders.
So gaugeProfitIndex[gauge] = 1e18+(100e18*1e18)/250e18 = 1e18+0.4e18 =1.4e18.
see the code, function _decrementGaugeWeight( address user, address gauge, uint256 weight ) internal override { uint256 _lastGaugeLoss = lastGaugeLoss[gauge]; uint256 _lastGaugeLossApplied = lastGaugeLossApplied[gauge][user]; require( _lastGaugeLossApplied >= _lastGaugeLoss, "GuildToken: pending loss" );
// update the user profit index and claim rewards ProfitManager(profitManager).claimGaugeRewards(user, gauge); // check if gauge is currently using its allocated debt ceiling. // To decrement gauge weight, guild holders might have to call loans if the debt ceiling is used. uint256 issuance = LendingTerm(gauge).issuance(); if (issuance != 0) { uint256 debtCeilingAfterDecrement = LendingTerm(gauge).debtCeiling(-int256(weight)); require( issuance <= debtCeilingAfterDecrement, "GuildToken: debt ceiling used" ); } super._decrementGaugeWeight(user, gauge, weight);
}
Bob is watching gauge1 100 profit and frontrun the alice’s claim reward by decreasing gauge2 weights(bob decreases 200 weights ,assume that debt ceiling is not reached) and increasing gauge1 weights(bob increases/add 200 weight).So now bob’s GuildToken(guild).getUserGaugeWeight(bob, gauge1) = 250. 5. see the function claimGaugeRewards function claimGaugeRewards( address user, address gauge ) public returns (uint256 creditEarned) { uint256 _userGaugeWeight = uint256( GuildToken(guild).getUserGaugeWeight(user, gauge) ); if (_userGaugeWeight == 0) { return 0; } uint256 _gaugeProfitIndex = gaugeProfitIndex[gauge]; uint256 _userGaugeProfitIndex = userGaugeProfitIndex[user][gauge]; if (_gaugeProfitIndex == 0) { _gaugeProfitIndex = 1e18; } if (_userGaugeProfitIndex == 0) { _userGaugeProfitIndex = 1e18; } uint256 deltaIndex = _gaugeProfitIndex - _userGaugeProfitIndex; if (deltaIndex != 0) { creditEarned = (_userGaugeWeight * deltaIndex) / 1e18; userGaugeProfitIndex[user][gauge] = _gaugeProfitIndex; } if (creditEarned != 0) { emit ClaimRewards(block.timestamp, user, gauge, creditEarned); CreditToken(credit).transfer(user, creditEarned); } }
Now bob’s creditearned = (250e18*0.4e18)/1e18 = 100e18 . There is no reward for alice to claim. Bob can apply the same process for gauge2 profit by watching profit transaction in the mempool.
manual review
Create time based reward mechanism i.e per second rewards for the guild holders.
Other
#0 - c4-pre-sort
2024-01-02T18:20:49Z
0xSorryNotSorry marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-01-02T18:21:08Z
0xSorryNotSorry marked the issue as duplicate of #877
#2 - c4-judge
2024-01-25T09:21:10Z
Trumpero marked the issue as not a duplicate
#3 - c4-judge
2024-01-25T09:21:20Z
Trumpero marked the issue as duplicate of #994
#4 - c4-judge
2024-01-25T09:48:04Z
Trumpero marked the issue as unsatisfactory: Invalid
#5 - c4-judge
2024-01-25T18:10:22Z
Trumpero changed the severity to 2 (Med Risk)
#6 - c4-judge
2024-01-25T18:15:14Z
Trumpero marked the issue as satisfactory