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: 39/189
Findings: 3
Award: $499.11
🌟 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
https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/core/RdpxV2Core.sol#L764 https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/perp-vault/PerpetualAtlanticVault.sol#L315 https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/perp-vault/PerpetualAtlanticVault.sol#L359 https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/perp-vault/PerpetualAtlanticVaultLP.sol#L199-L205
Options can not be settled.
From RdpxV2Core
, admin would call the settle function to settle the options and send the collateral and rdpx to the respective contracts.
while traversing the array of options in RdpxV2Core, the function settle of PerpetualAtlanticVault
would be called.
(amountOfWeth, rdpxAmount) = IPerpetualAtlanticVault( addresses.perpetualAtlanticVault ).settle(optionIds);
function settle( uint256[] memory optionIds ) external nonReentrant onlyRole(RDPXV2CORE_ROLE) returns (uint256 ethAmount, uint256 rdpxAmount) { _whenNotPaused(); _isEligibleSender(); updateFunding(); for (uint256 i = 0; i < optionIds.length; i++) { uint256 strike = optionPositions[optionIds[i]].strike; uint256 amount = optionPositions[optionIds[i]].amount; // check if strike is ITM _validate(strike >= getUnderlyingPrice(), 7); ethAmount += (amount * strike) / 1e8; rdpxAmount += amount; optionsPerStrike[strike] -= amount; totalActiveOptions -= amount; // Burn option tokens from user _burn(optionIds[i]); optionPositions[optionIds[i]].strike = 0; } // Transfer collateral token from perpetual vault to rdpx rdpxV2Core collateralToken.safeTransferFrom( addresses.perpetualAtlanticVaultLP, addresses.rdpxV2Core, ethAmount ); // Transfer rdpx from rdpx rdpxV2Core to perpetual vault IERC20WithBurn(addresses.rdpx).safeTransferFrom( addresses.rdpxV2Core, addresses.perpetualAtlanticVaultLP, rdpxAmount ); IPerpetualAtlanticVaultLP(addresses.perpetualAtlanticVaultLP).subtractLoss( ------------>> audit find, follow. ethAmount ); IPerpetualAtlanticVaultLP(addresses.perpetualAtlanticVaultLP) .unlockLiquidity(ethAmount); IPerpetualAtlanticVaultLP(addresses.perpetualAtlanticVaultLP).addRdpx( rdpxAmount ); emit Settle(ethAmount, rdpxAmount, optionIds); }
in above settle function, the options array is traversed and ethAmount
and rdpxAmount
amounts are called.
collateral is sent from perpetualAtlanticVaultLP
to rdpxV2Core
.
collateralToken.safeTransferFrom( addresses.perpetualAtlanticVaultLP, addresses.rdpxV2Core, ethAmount );
Transfer rdpx from rdpx rdpxV2Core to perpetual vault
// Transfer rdpx from rdpx rdpxV2Core to perpetual vault IERC20WithBurn(addresses.rdpx).safeTransferFrom( addresses.rdpxV2Core, addresses.perpetualAtlanticVaultLP, rdpxAmount
After transferring the collateral amount, the loss of this would be updated in perpetualAtlanticVaultLP
by calling the subtractloss
function subtractLoss(uint256 loss) public onlyPerpVault { require( collateral.balanceOf(address(this)) == _totalCollateral - loss, --------->>> @@audit find - strict equality. "Not enough collateral was sent out" ); _totalCollateral -= loss; }
in above call, the total collateral value after deducting the loss is checked strictly with the contract balance.
above condition can be break, by simply donating small amount of find to the PerpetualAtlanticVaultLP
Manual review.
Update the condition check in
function subtractLoss(uint256 loss) public onlyPerpVault { require( collateral.balanceOf(address(this)) >= _totalCollateral - loss, --->> add collateral.balanceOf(address(this)) == _totalCollateral - loss, ---->> remove "Not enough collateral was sent out" ); _totalCollateral -= loss; }
DoS
#0 - c4-pre-sort
2023-09-09T09:54:12Z
bytes032 marked the issue as duplicate of #619
#1 - c4-pre-sort
2023-09-11T16:14:19Z
bytes032 marked the issue as sufficient quality report
#2 - c4-judge
2023-10-21T07:16:04Z
GalloDaSballo marked the issue as satisfactory
🌟 Selected for report: Vagner
Also found by: 0Kage, 0xCiphky, 0xnev, ABAIKUNANBAEV, Aymen0909, Evo, KmanOfficial, MohammedRizwan, T1MOH, Viktor_Cortess, Yanchuan, ak1, alexzoid, bin2chen, codegpt, hals, ladboy233, mrudenko, nemveer, oakcobalt, peakbolt, pep7siup, qbs, said, savi0ur, tapir, wintermute, zaevlad, zzebra83
7.8372 USDC - $7.84
https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/amo/UniV2LiquidityAmo.sol#L238 https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/amo/UniV2LiquidityAmo.sol#L286 https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/amo/UniV2LiquidityAmo.sol#L346 https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/amo/UniV2LiquidityAmo.sol#L160-L178
Whenever any interaction is done with UniSwapV2, _sendTokensToRdpxV2Core is called to send the asset to the rdxV2Core. Once the asset is sent, the reserve of respective asset should be updated by calling the syc. This is done in UniSwap V3 flow, but not in the uniswap v2.
we can see the _sendTokensToRdpxV2Core function calling in UniV2LiquidityAmo contract in following places.
At the end of above function call, the tokens are sent to rdpxV2Core contract.
Manual review.
call the rdxV2Core's sync function in _sendTokensToRdpxV2Core function.
Token-Transfer
#0 - c4-pre-sort
2023-09-09T03:54:05Z
bytes032 marked the issue as duplicate of #798
#1 - c4-pre-sort
2023-09-09T04:09:35Z
bytes032 marked the issue as duplicate of #269
#2 - c4-pre-sort
2023-09-11T11:58:43Z
bytes032 marked the issue as sufficient quality report
#3 - c4-judge
2023-10-20T19:39:07Z
GalloDaSballo changed the severity to 2 (Med Risk)
#4 - c4-judge
2023-10-20T19:39:13Z
GalloDaSballo marked the issue as satisfactory
🌟 Selected for report: Vagner
Also found by: 0Kage, 0xCiphky, 0xnev, ABAIKUNANBAEV, Aymen0909, Evo, KmanOfficial, MohammedRizwan, T1MOH, Viktor_Cortess, Yanchuan, ak1, alexzoid, bin2chen, codegpt, hals, ladboy233, mrudenko, nemveer, oakcobalt, peakbolt, pep7siup, qbs, said, savi0ur, tapir, wintermute, zaevlad, zzebra83
7.8372 USDC - $7.84
Missing of the reserve asset update in rdpxV2Core.
Lack of asset update would impact the functions which depend the asset reserve.
For example, one place would be lowerDepeg
where one of the reserve asset is use to swap for other and one is burned.
function collectFees() external onlyRole(DEFAULT_ADMIN_ROLE) { for (uint i = 0; i < positions_array.length; i++) { Position memory current_position = positions_array[i]; INonfungiblePositionManager.CollectParams memory collect_params = INonfungiblePositionManager.CollectParams( current_position.token_id, rdpxV2Core, type(uint128).max, type(uint128).max ); // Send to custodian address univ3_positions.collect(collect_params); } }
collectFees
would be called to collect the fee and this fee would be sent to rdpxCore.
But the sync
of rdpxCore' is missed.
One of the place where it is implemented correctly is removeLiquidity, in this function call, at the end of the function after fee collection, _sendTokensToRdpxV2Core
is called. this function would call the rdpxV2Core's sync.
Manual review.
Update the collectfee function as shown below.
function collectFees() external onlyRole(DEFAULT_ADMIN_ROLE) { for (uint i = 0; i < positions_array.length; i++) { Position memory current_position = positions_array[i]; INonfungiblePositionManager.CollectParams memory collect_params = INonfungiblePositionManager.CollectParams( current_position.token_id, rdpxV2Core, type(uint128).max, type(uint128).max ); // Send to custodian address univ3_positions.collect(collect_params); } IRdpxV2Core(rdpxV2Core).sync(); +++++++ audit update }
Token-Transfer
#0 - c4-pre-sort
2023-09-09T04:12:17Z
bytes032 marked the issue as duplicate of #269
#1 - c4-pre-sort
2023-09-11T11:58:49Z
bytes032 marked the issue as sufficient quality report
#2 - c4-judge
2023-10-20T19:58:27Z
GalloDaSballo marked the issue as satisfactory
491.258 USDC - $491.26
Allowing the redemption of bonds would lead to transfer of receipet tokens to the user.
This receiptTokenAmount
would be used to convert into ETH and dpxEth.
when any one of the asset depreciates, redeeming the other one would lead to lowering the value of the depreciating asset further.
function redeem( uint256 id, address to ) external returns (uint256 receiptTokenAmount) { // Validate bond ID _validate(bonds[id].timestamp > 0, 6); // Validate if bond has matured _validate(block.timestamp > bonds[id].maturity, 7); _validate( msg.sender == RdpxV2Bond(addresses.receiptTokenBonds).ownerOf(id), 9 );
Above redeem of bond does not have whenNotPaused protection.
when we look at the bond issue method which is done through bond. But, the redeem call does not have this pause protection.
Manual review.
use whenNotPause
modifier for redeem call also.
Access Control
#0 - c4-pre-sort
2023-09-10T07:01:43Z
bytes032 marked the issue as duplicate of #1809
#1 - c4-pre-sort
2023-09-10T07:02:57Z
bytes032 marked the issue as duplicate of #6
#2 - c4-pre-sort
2023-09-11T12:10:00Z
bytes032 marked the issue as sufficient quality report
#3 - c4-judge
2023-10-20T19:59:49Z
GalloDaSballo marked the issue as satisfactory