Canto Invitational - xuwinnie's results

Incentivization Primitive for Real World Assets on Canto.

General Information

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

Canto

Findings Distribution

Researcher Performance

Rank: 2/5

Findings: 3

Award: $0.00

QA:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: bin2chen

Also found by: xuwinnie

Labels

bug
3 (High Risk)
satisfactory
duplicate-10

Awards

Data not available

External Links

Lines of code

https://github.com/code-423n4/2024-01-canto/blob/5e0d6f1f981993f83d0db862bcf1b2a49bb6ff50/src/LendingLedger.sol#L69

Vulnerability details

Impact

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.

Proof of Concept

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.

Tools Used

Manual

Only use one of BLOCK_EPOCH and WEEK.

Assessed type

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

Findings Information

🌟 Selected for report: bin2chen

Also found by: xuwinnie

Labels

bug
3 (High Risk)
satisfactory
duplicate-9

Awards

Data not available

External Links

Lines of code

https://github.com/code-423n4/2024-01-canto/blob/5e0d6f1f981993f83d0db862bcf1b2a49bb6ff50/src/LendingLedger.sol#L65

Vulnerability details

Impact

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."

Proof of Concept

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.

Tools Used

Manual

uint256 nextEpoch = epoch + BLOCK_EPOCH;

Assessed type

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

Findings Information

🌟 Selected for report: bin2chen

Also found by: xuwinnie

Labels

bug
2 (Med Risk)
satisfactory
duplicate-12

Awards

Data not available

External Links

Lines of code

https://github.com/code-423n4/2024-01-canto/blob/5e0d6f1f981993f83d0db862bcf1b2a49bb6ff50/src/LendingLedger.sol#L71

Vulnerability details

Impact

Attacker can call update_market frequently to halt the increase of secRewardsPerShare.

Proof of Concept

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.

Tools Used

Manual

Do the Scaling.

market.secRewardsPerShare += uint128((blockDelta * 1e18) / marketSupply); // TODO: Scaling

Assessed type

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

Findings Information

🌟 Selected for report: rvierdiiev

Also found by: Stormy, bin2chen, erebus, xuwinnie

Labels

bug
QA (Quality Assurance)
downgraded by judge
grade-b
primary issue
sponsor acknowledged
Q-05

Awards

Data not available

External Links

Lines of code

https://github.com/code-423n4/2024-01-canto/blob/5e0d6f1f981993f83d0db862bcf1b2a49bb6ff50/src/LendingLedger.sol#L88-L91

Vulnerability details

Impact

Debt should round up in function sync_ledger, otherwise malicious user can manipulate secRewardDebt to get extra rewards.

Proof of Concept

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.

Tools Used

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;

Assessed type

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

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