Due to double scaling in RToken.transfer
and RToken.transferFrom
, DEToken holders will suffer from fund loss on withdrawal. Morever, RToken can be double-spent.
When RTokens are transferred, RToken calculates and transfers scaledAmount
of underlying asset.
In transfer
, scaledAmount = amount / lendingPool.liquidityIndex
:
In transferFrom
, scaledAmount = amount / RToken.liquidityIndex
:
As we can see, there is inconsistency between scaledAmount
calculations of transfer
and transferFrom
. (check NOTE at the bottom of this report)
Morover, double scaling happens in RToken._update
:
Let's check how this double scaling affects the token behavior. Consider the following scenario:
Current liquidityIndex
is 1
Alice deposits 10 USD to LendingPool and receives 10 RToken.
liquidityIndex
is increased to 2
Alice's RToken balance increased to 20
Alice transferrs 10 RToken to Bob
Transferred underlying balance is 10 / liquidityIndex / liquidityIndex = 2.5
Alice's underlying balance is 7.5. and RToken balance is 15
Bob's underlying balance is 2.5 and RToken balance is 5
Bob transferrs all his balance back to Alice. He wants to repay 10 RToken but he cannot because his balance is only 5 due to the bug
Transferred underlying balance is 5 / liquidityIndex / liquidityIndex = 1.25
Alice's underlying balance is 8.75 and RToken balance is 17.5
Bob's underlying balance is 1.25 and RToken balance is 2.5
Alice withdraws all his RToken from LendingPool. LendingPool returns only 8.75 USD even though RToken claimed amount is 17.5 due to this check
In the above thought experiment, we observed the following two issues:
Double spend problem: Bob transferrs all balance 5
back to Alice but his RToken balance is still non-zero value: 2.5
User fund loss: Alice lost 1.25 USD after interacting with Bob
Of course, a normal user won't transfer RToken back and forth with other users. But in this protocol, exact same scenario must happen: it's when users deposit RToken to and withdraw RToken from StabilityPool. i.e. StabilityPool is Bob
Scenario - DEHolder loses fund
Depositor deposits 10_000 crvUSD to LendingPool and receives some RToken
Depositor deposits those RToken to StabilityPool and receives some DEToken
Borrower mints an NFT worth of 20_000 crvUSD
Borrower deposits the NFT into LendingPool
100 day passes
Borrower fully repays their debt
Depositor withdraws all DEToken from StabilityPool and receives some RToken
Depositor withdraws all RToken from LendingPool and receives crvUSD...
Depositor is left with around 9_012.34567 crvUSD. He loses nearly 1k USD due to the mentioned vulnerability
Scenario - RToken holder can double spend
Depositor deposits 10_000 crvUSD to LendingPool and receives some RToken
Depositor deposits those RToken to StabilityPool and receives some DEToken
Borrower mints an NFT worth of 20_000 crvUSD
Borrower deposits the NFT into LendingPool
100 day passes
Borrower fully repays their debt
Depositor withdraws all DEToken from StabilityPool and receives some RToken
Depositor transferrs all RToken to another user
Depositor's remaining balance is greater than zero
How to run the PoC
First, follow the steps in foundry-hardhat integration tutorial
Then, create a file test/poc.t.sol
and put the following content, and run forge test poc.t.sol -vvv
Console Output:
RToken can be double spent.
Depositors lose fund in spite of being depositors.
Manual Review, Foundry
NOTE
Inconsistency between transfer
and transferFrom
calculation is not important. Because scaling calculation should not be done there in the first place.
Correct fix would be removing double scaling altogether.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.