Platform: Code4rena
Start Date: 29/03/2024
Pot Size: $36,500 USDC
Total HM: 5
Participants: 72
Period: 5 days
Judge: 3docSec
Total Solo HM: 1
Id: 357
League: ETH
Rank: 43/72
Findings: 1
Award: $8.28
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: immeas
Also found by: 0xAkira, 0xCiphky, 0xGreyWolf, 0xJaeger, 0xMosh, 0xabhay, 0xlemon, 0xmystery, 0xweb3boy, Aamir, Abdessamed, Aymen0909, Breeje, DanielArmstrong, DarkTower, Dots, EaglesSecurity, FastChecker, HChang26, Honour, IceBear, JC, K42, Krace, MaslarovK, Omik, OxTenma, SAQ, Shubham, Stormreckson, Tigerfrake, Tychai0s, VAD37, ZanyBonzy, albahaca, arnie, ast3ros, asui, b0g0, bareli, baz1ka, btk, caglankaan, carrotsmuggler, cheatc0d3, dd0x7e8, grearlake, igbinosuneric, jaydhales, kaden, kartik_giri_47538, m4ttm, ni8mare, niser93, nonn_ac, oualidpro, pfapostol, pkqs90, popeye, radev_sw, samuraii77, slvDev, zabihullahazadzoi
8.2807 USDC - $8.28
When users call ousgInstantManager.sol#mintRebasingOUSG()
, some of the rOUSG shares that are supposed to send to user is accmulated in ousgInstantManager.sol
contract instead.
When performing mintRebasingOUSG()
there are three steps:
_mint()
to exchange USDC for OUSGThe issue happens in step 3. Since rOUSG is a rebasing token, to avoid precision error, transferShare()
should be used instead of transfer()
. The original implementation would suffer from 2 precision errors during calculation, first is rOUSGShares -> rOUSG, second is rOUSG -> rOUSGShares (during rousg.transfer()
). Both precision errors can be simply avoided if we use transferShare()
in the first place.
function mintRebasingOUSG( uint256 usdcAmountIn ) external override nonReentrant whenMintNotPaused returns (uint256 rousgAmountOut) { uint256 ousgAmountOut = _mint(usdcAmountIn, address(this)); ousg.approve(address(rousg), ousgAmountOut); rousg.wrap(ousgAmountOut); @> rousgAmountOut = rousg.getROUSGByShares( @> ousgAmountOut * OUSG_TO_ROUSG_SHARES_MULTIPLIER @> ); @> rousg.transfer(msg.sender, rousgAmountOut); emit InstantMintRebasingOUSG( msg.sender, usdcAmountIn, ousgAmountOut, rousgAmountOut ); }
function getROUSGByShares(uint256 _shares) public view returns (uint256) { return (_shares * getOUSGPrice()) / (1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER); }
Add the following code to mint.t.sol
and run unit test. We can see after Alice calls mintRebasingOUSG()
, there are still some shares of rOUSG left in ousgInstantManager
.
function test_mintRebasingOUSG() public { // Set oracle price to a 123.45e18. oracleCheckHarnessOUSG.setPrice(123456789012345678900); deal(address(USDC), alice, 100_000e6); vm.startPrank(alice); USDC.approve(address(ousgInstantManager), 100_000e6); ousgInstantManager.mintRebasingOUSG(100_000e6); vm.stopPrank(); // rOUSGToken.sharesOf(address(ousgInstantManager))=47 console.log("rOUSGToken.sharesOf(address(ousgInstantManager))=", rOUSGToken.sharesOf(address(ousgInstantManager))); }
Foundry
- rousg.transfer(msg.sender, rousgAmountOut); + rousg.transferShares(msg.sender, ousgAmountOut * OUSG_TO_ROUSG_SHARES_MULTIPLIER);
Token-Transfer
#0 - 0xRobocop
2024-04-05T03:25:05Z
Report does not provide evidence for a non-trivial imprecision loss, it will take a lot of deposits to get a loss of at least 1 USD. Direct usage of transferShare seems to be better tough.
#1 - c4-pre-sort
2024-04-05T03:25:08Z
0xRobocop marked the issue as insufficient quality report
#2 - 3docSec
2024-04-09T09:45:42Z
Agree with presort
#3 - c4-judge
2024-04-09T09:45:45Z
3docSec changed the severity to QA (Quality Assurance)
#4 - c4-judge
2024-04-09T09:45:50Z
3docSec marked the issue as grade-b