Reserve contest - Franfran's results

A permissionless platform to launch and govern asset-backed stable currencies.

General Information

Platform: Code4rena

Start Date: 06/01/2023

Pot Size: $210,500 USDC

Total HM: 27

Participants: 73

Period: 14 days

Judge: 0xean

Total Solo HM: 18

Id: 203

League: ETH

Reserve

Findings Distribution

Researcher Performance

Rank: 11/73

Findings: 1

Award: $4,418.17

🌟 Selected for report: 1

🚀 Solo Findings: 1

Findings Information

🌟 Selected for report: Franfran

Labels

bug
2 (Med Risk)
judge review requested
satisfactory
selected for report
sponsor disputed
M-06

Awards

4418.1679 USDC - $4,418.17

External Links

Lines of code

https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/Furnace.sol#L77-L79 https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/StRSR.sol#L509-L512 https://github.com/reserve-protocol/protocol/blob/946d9b101dd77275c6cbfe0bfe9457927bd221a9/contracts/p1/StRSR.sol#L490-L493

Vulnerability details

Impact

For two instances in the codebase (Furnace and StRSR), the composed rewards calculation seems to be wrong. How the rewards are working in these two snippets is that we are first measuring how much period or rewardPeriod occured since the last payout and calculating in only one step the rewards that should be distributed over these periods. In other words, it is composing the ratio over periods.

Proof of Concept

Taken from the comments, we can write the formula of the next rewards payout as:

with n = (i+1) > 0, n is the number of periods rewards{0} = rsrRewards() payout{i+1} = rewards{i} * payoutRatio rewards{i+1} = rewards{i} - payout{i+1} rewards{i+1} = rewards{i} * (1 - payoutRatio)

Generalization: ui+1=ui∗(1−r)u_{i+1} = u_{i} * (1 - r)

It's a geometric mean whose growth rate is (1 - r).

Claculation of the sum:

You can play with the graph here.

For a practical example, let's say that our rsrRewardsAtLastPayout is 5, with a rewardRatio of 0.9 If we had to calculate our compounded rewards, from the formula given by the comments above, we could calculate manually for the first elements. Let's take the sum for n = 3:

S=u2+u1+u0S = u_{2} + u_{1} + u_{0} u2=u1∗(1−0.9)u_{2} = u_{1} * (1-0.9) u1=u0∗(1−0.9)u_{1} = u_{0} * (1-0.9) u0=rsrRewardsAtLastPayoutu_{0} = rsrRewardsAtLastPayout

So,

S=u0∗(1−0.9)∗(1−0.9)+u0∗(1−0.9)+u0S = u_{0} * (1-0.9) * (1-0.9) + u_{0} * (1-0.9) + u_{0}

For the values given above, that's

S=5∗0.12+5∗0.1+5S = 5 * 0.1² + 5 * 0.1 + 5 S=5.55S = 5.55

If we do the same calculation with the sum formula

Tools Used

Manual inspection

Rather than dividing by 1 (1e18 from the Fixed library), divide it by the ratio.

// Furnace.sol
// Paying out the ratio r, N times, equals paying out the ratio (1 - (1-r)^N) 1 time.
uint192 payoutRatio = FIX_ONE.minus(FIX_ONE.minus(ratio).powu(numPeriods));

uint256 amount = payoutRatio * lastPayoutBal / ratio;
// StRSR.sol
uint192 payoutRatio = FIX_ONE - FixLib.powu(FIX_ONE - rewardRatio, numPeriods);

// payout: {qRSR} = D18{1} * {qRSR} / r
uint256 payout = (payoutRatio * rsrRewardsAtLastPayout) / rewardRatio;

#0 - c4-judge

2023-01-24T16:30:29Z

0xean marked the issue as satisfactory

#1 - c4-sponsor

2023-01-26T18:49:51Z

tbrent marked the issue as sponsor disputed

#2 - tbrent

2023-01-26T18:52:45Z

I think there is a mistake in the math here, possibly arising from the fact that rsrRewards() doesn't correspond to how much rewards has been handed out, but how much is available to be handed out.

I don't understand why he is computing the sum of u_i. If u_0 is the value of rsrRewards() at time 0, and u_1 is the value of rsrRewards() at time 1, why is the sum of u_i for all i interesting? This is double-counting balances, since only some of u_i is handed out each time.

As the number of payouts approach infinity, the total amount handed out approaches u_0.

#3 - c4-sponsor

2023-01-27T18:04:09Z

tmattimore requested judge review

#4 - 0xean

2023-01-31T15:58:55Z

would be good to get the warden to comment here during QA - will see if we can have the occur to clear up the difference in understanding

#5 - 0xean

2023-02-13T22:18:57Z

I want to apologize that I missed the fact that no response was given during QA and currently believe this issue to be invalid.

#6 - iFrostizz

2023-02-14T07:45:20Z

Hey friends, sorry for not hopping into the discussion earlier ! I'll make sure to subscribe to it next time. My reasoning was that if the staker's rewards doesn't compound over time, then there is no reason for them to stay in the pool and not harvest the rewards, which is a costly process if they would have to harvest each cycle.

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