Open hats-bug-reporter[bot] opened 7 months ago
Please provide a PoC with your findings -- the descriptions above are mixing the use of ovUSDC
and oUSDC
, and it's not clear where the exploit is that you suggest.
ovUSDC
is an instance of OrigamiInvestmentVault
:
OrigamiInvestmentVault::investWithToken()
will mint new ovUSDC
tokensoUSDC
is an instance of OrigamiOToken
:
OrigamiOToken::investWithToken()
will mint new oUSDC
tokensCalling OrigamiInvestmentVault::investWithToken()
does not mint oUSDC
tokens
I think you are conflating the totalSupply() between ovUSDC
and oUSDC
.
This example isn't correct:
lets say the totalSupply and the vestedReserves both are 10,000 and alice decide to invest/deposit with 1000 USDC, when she calls the investWithToken this will happen : investWithToken --> investWithToken(inside the oUSDC vault) --> mint 1000 oUSDC to the investment vault --> call _issueSharesFromReserves which calculates the ovUSDC like this 1000(Invest amount) * 11000 / 1000 = 1100;
Using the starting amounts for totalSupply()
and totalReserves()
from your example
ovUSDC.totalSupply() == 10_000
ovUSDC.totalReserves() == 10_000
ovUSDC.reservesPerShare() == 1e18
ovUSDC.investWithToken(1_000 USDC):
- oUSDC.investWithToken(1_000 USDC) ==> 1000 oUSDC minted
- oUSDC.totalSupply() += 1_000
- new ovUSDC shares == 1_000 * ovUSDC.totalSupply() / ovUSDC.totalReserves()
== 1_000 * 10_000 / 10_000
== 1_000
- 1_000 new ovUSDC shares minted to alice
- 1_000 oUSDC added as vested reserves to ovUSDC.
- ovUSDC.totalSupply() == 11_000
- ovUSDC.totalReserves() == 11_000
- ovUSDC.reservesPerShare() == 1e18 (the same)
hey frontier159, thanks for response.
while the totalSupply is not for the oUSDC then the scenario we mentioned is impossible to happen.
thanks for details you provide.
Github username: @0Ksecurity Twitter username: -- Submission hash (on-chain): 0x44a88142e9efbde958fac789573563bb263c5e5bd7e9fbd04a4ed5c21eb3e2ab Severity: high
Description: Description\ an attacker can get most of the USDC tokens that exist in the protocol by calling the
investWithToken
andexitWithToken
more than once, this is possible because the origamiInvestment open the possibility to exit withtoToken == oUSDC
which this can be used to decreasetotalReserve
inrepricingToken.sol
without changing the totalSupply of the oUSDC. (more in attack scenario)Attack Scenario\
-lets say Alice decide to deposit 1000 USDC into the origami vault by calling the
InvestWithToken
inOrigamiInvestmentVault
contract,this function will mint oUSDC to this vault by calling theInvestWithToken
in theorigamiOtoken.sol
and then mint ovTokens to alice by calling_issueSharesFromReserves
inrepricingToken.sol
, lets take a look at the function in origami investInvestWithToken
inside theorigamiOtoken.sol
which will mint oUSDC to the vault:this function calls the
_mint
which increase the totalsupply of the oUSDC:_issueSharesFromReserves
will mint ovUSDC depending on the totalSuuply of oUSDC and totalReserves which is the vesting amount + pending amount.exitToToken
function and setting thetoToken == reserveToken(oUSDC)
and this will lead to execute the logic below:now, we can see that the
vestedReserves
will decrease with thereserveTokenAmount
and in this case that alice asked for toToken to be equal to the oUSDC, there is no burn of the oUSDC which that mean the totalSupply did not increase. this will give alice more ovUSDC any time she repeat these steps, lets take an example:lets say the totalSupply and the vestedReserves both are 10,000 and alice decide to invest/deposit with 1000 USDC, when she calls the
investWithToken
this will happen : investWithToken --> investWithToken(inside the oUSDC vault) --> mint 1000 oUSDC to the investment vault --> call_issueSharesFromReserves
which calculates the ovUSDC like this1000(Invest amount) * 11000 / 1000 = 1100;
now, after alice have 1100 of ovUSDC the
vestedReserves
is updated to 11000 too, now she will call theexitToken with quoteData.toToken == reserveToken(oUSDC)
which do the follow: exitToken --> call _redeemReservesFromShares with investment share of 1100 ovUSDC --> get the invest amount1100(ovUSDC) * 11000(totalReserve) / 11000 == 1100
and then burn 1100 from the reserve without doing any changes to the totalSupply of oUSDC:alice repeat this step for second time:
get ovUSDC : 1100 USDC
* 11000 / 9900 = 1222 ovUSDC
call exitToken toToken == reserveToken(oUSDC) :
1222 * 9900 / 11000 == 1099
decrease the vestedReserves which is totalReserve we use in calculating with 1099 which become 8801 :this step can be repeated till the totalReserve decrease to favorable amount for alice while totalsupply is the same and this lead to mint and then redeem most of the USDC inside the protocol.
note: in some point of the scenario above we didn't calculated round down + fee but the attack still in alice favour when the protocol have more USDC.
Attachments
Proof of Concept (PoC) File
Revised Code File (Optional) recommend to update state of the
vestedReserves
before the calculation and manage the totalSupply of the oUSDC in case when users get oUSDC to do invest or when they exit to avoid scenario above.Files: