Platform: Code4rena
Start Date: 25/01/2024
Pot Size: $16,425 USDC
Total HM: 4
Participants: 5
Period: 4 days
Judge: Alex the Entrprenerd
Total Solo HM: 1
Id: 326
League: ETH
Rank: 2/5
Findings: 3
Award: $0.00
🌟 Selected for report: 0
🚀 Solo Findings: 0
Data not available
Rewards and voting weights are aligned on a weekly basis. In contract LendingLedger, reward is recorded for each epoch(block.number). However, when calling gauge_relative_weight_write
, we should actually pass a timestamp, or the weight cannot be retrieved correctly.
uint256 public constant BLOCK_EPOCH = 100_000; // 100000 blocks, roughly 1 week uint256 cantoReward = (blockDelta * cantoPerBlock[epoch] * gaugeController.gauge_relative_weight_write(_market, epoch)) / 1e18;
When calling gauge_relative_weight_write
, we are passing the first block.number in the epoch. However, we should pass the first block.timestamp in the epoch.
uint256 public constant WEEK = 7 days; function _gauge_relative_weight(address _gauge, uint256 _time) private view returns (uint256) { uint256 t = (_time / WEEK) * WEEK; uint256 total_weight = points_sum[t].bias; if (total_weight > 0) { uint256 gauge_weight = points_weight[_gauge][t].bias; return (MULTIPLIER * gauge_weight) / total_weight; } else { return 0; } }
Besides, although 100000 blocks is roughly 1 week, we cannot guarantee a precise match nor alignment, so we should only use one of BLOCK_EPOCH and WEEK.
Manual
Only use one of BLOCK_EPOCH and WEEK.
Context
#0 - c4-judge
2024-01-30T15:17:57Z
GalloDaSballo marked the issue as duplicate of #10
#1 - c4-judge
2024-02-01T10:19:56Z
GalloDaSballo marked the issue as satisfactory
Data not available
Rewards and voting weights are aligned on a weekly basis. However, nextEpoch
is calculated incorrectly, which may break the invariant "The total rewards that are sent for one block should never be higher than the rewards that were configured for this block."
uint256 i = market.lastRewardBlock; while (i < block.number) { uint256 epoch = (i / BLOCK_EPOCH) * BLOCK_EPOCH; // Rewards and voting weights are aligned on a weekly basis uint256 nextEpoch = i + BLOCK_EPOCH; uint256 blockDelta = Math.min(nextEpoch, block.number) - i; uint256 cantoReward = (blockDelta * cantoPerBlock[epoch] * gaugeController.gauge_relative_weight_write(_market, epoch)) / 1e18; market.accCantoPerShare += uint128((cantoReward * 1e18) / marketSupply); market.secRewardsPerShare += uint128((blockDelta * 1e18) / marketSupply); // TODO: Scaling i += blockDelta; }
Suppose BLOCK_EPOCH is 10, from block 30 to block 40 reward is 10 per block, from block 40 to block 50 reward is 1 per block. lastRewardBlock is 35, current block is 50. Then epoch will be 30, nextEpoch will be 45(should be 40) and blockDelta will be 10. Which means reward is calculated as 10 from block 35 to block 45. However, the real reward is only 1 from block 40 to block 45, which breaks the invariant.
Manual
uint256 nextEpoch = epoch + BLOCK_EPOCH;
Context
#0 - c4-judge
2024-01-30T15:18:35Z
GalloDaSballo marked the issue as duplicate of #9
#1 - c4-judge
2024-02-01T10:14:16Z
GalloDaSballo marked the issue as satisfactory
Data not available
Attacker can call update_market frequently to halt the increase of secRewardsPerShare.
market.secRewardsPerShare += uint128((blockDelta * 1e18) / marketSupply); // TODO: Scaling
Suppose marketSupply is 1e21, then the attacker can call update_market once every 999 blocks, then secRewardsPerShare will never get increased.
Manual
Do the Scaling.
market.secRewardsPerShare += uint128((blockDelta * 1e18) / marketSupply); // TODO: Scaling
Context
#0 - c4-judge
2024-01-30T11:45:18Z
GalloDaSballo marked the issue as primary issue
#1 - c4-judge
2024-01-30T11:45:30Z
GalloDaSballo marked the issue as primary issue
#2 - c4-judge
2024-01-30T11:46:26Z
GalloDaSballo marked the issue as primary issue
#3 - c4-judge
2024-01-30T15:17:29Z
GalloDaSballo marked the issue as duplicate of #12
#4 - c4-judge
2024-02-01T10:27:54Z
GalloDaSballo marked the issue as satisfactory
🌟 Selected for report: rvierdiiev
Data not available
Debt should round up in function sync_ledger, otherwise malicious user can manipulate secRewardDebt to get extra rewards.
if (_delta >= 0) { user.amount += uint256(_delta); user.rewardDebt += int256((uint256(_delta) * market.accCantoPerShare) / 1e18); user.secRewardDebt += int256((uint256(_delta) * market.secRewardsPerShare) / 1e18);
When calling sync_ledger, rewardDebt and secRewardDebt are rounded down. User can gain at most 1 wei extra CANTO, which is negligible. However, since secRewardDebt is not scaled (to the amount of token), suppose 1 secRewardDebt represents 1 USDC of rewards, exploiting this rounding issue will be profitable.
Manual
if (_delta >= 0) { user.amount += uint256(_delta); user.rewardDebt += int256((uint256(_delta) * market.accCantoPerShare) / 1e18) + 1; user.secRewardDebt += int256((uint256(_delta) * market.secRewardsPerShare) / 1e18) + 1;
Context
#0 - c4-judge
2024-01-30T15:38:28Z
GalloDaSballo marked the issue as primary issue
#1 - OpenCoreCH
2024-01-31T11:02:41Z
As the warden mentions, impact for CANTO rewards is negligible. For secondary rewards in the current form without scaling, it could indeed be a problem, although I think that the underlying problem there is the missing scaling (https://github.com/code-423n4/2024-01-canto-findings/issues/12), as just rounding up would not really solve the problem (then, users would lose rewards regularly).
#2 - c4-sponsor
2024-01-31T11:02:44Z
OpenCoreCH (sponsor) acknowledged
#3 - c4-judge
2024-02-01T10:16:42Z
GalloDaSballo changed the severity to QA (Quality Assurance)
#4 - GalloDaSballo
2024-02-01T10:17:45Z
While we have been seeing quite a lot of issues that have Rounding as the Root Cause
It is unacceptable for us to compare a finding that simply shows a rounding error, to one that demonstrates a complete exploit
For this reason am downgrading to QA
This doesn't mean that rounding cannot be weaponized, but that the submissions we have here is unable to demonstrate an impact that goes beyond the rounding itself
#5 - c4-judge
2024-02-01T10:24:12Z
GalloDaSballo marked the issue as grade-b