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: 51/189
Findings: 3
Award: $271.48
๐ 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
subtractLoss()
is a public function in the LP vault contract, only called through settle()
in the vault contract. Its purpose is to subtract the amount of WETH the vault withdraws from the LP vault when settling options, and given the LP vault uses an internal logic to account to distinguish how many tokens are in the contract and how much collateral is available, more collateral is required to be available in the contract than it's attempted to be withdrawn, or else the function correctly reverts. The problem is that this function is making a strict equality check in the validation shown below.
require( collateral.balanceOf(address(this)) == _totalCollateral - loss, "Not enough collateral was sent out" );
Any extra funds that are added to it and aren't accounted for by _totalCollateral
causes the function to revert, even though it technically has more collateral than necessary. It's important to notice that neither the LP vault nor the permissioned contracts have the means to withdraw this extra amount, and since there's no way to synchronize this extra collateral to the total available, all collateral will be stuck in the contract forever.
A transfer of a value as low as 1 wei in collateral is enough to disrupt the function, disabling option settlement as an ultimate consequence.
This simple PoC will showcase how transferring collateral to the contract reverts the function.
function testProtocolBricked() public { rdpxV2Core.bond(1 * 1e18, 0, address(this)); weth.transfer(address(vaultLp), 1); //transfer dust, protocol has slightly more than enough to cover loss now uint256[] memory _ids = new uint256[](1); vault.addToContractWhitelist(address(rdpxV2Core)); rdpxPriceOracle.updateRdpxPrice(1e7); vm.expectRevert( "Not enough collateral was sent out" ); rdpxV2Core.settle(_ids); }
Solmate, manual code review
Change the strict comparison (==
) to higher or equal than (>=
), as shown below.
function subtractLoss(uint256 loss) public onlyPerpVault { require( collateral.balanceOf(address(this)) >= _totalCollateral - loss, "Not enough collateral was sent out" ); _totalCollateral -= loss; }
DoS
#0 - c4-pre-sort
2023-09-09T10:02:49Z
bytes032 marked the issue as duplicate of #619
#1 - c4-pre-sort
2023-09-11T16:15:18Z
bytes032 marked the issue as sufficient quality report
#2 - c4-judge
2023-10-20T19:34:51Z
GalloDaSballo marked the issue as satisfactory
๐ Selected for report: bart1e
Also found by: 0x3b, 0xCiphky, Aymen0909, HHK, Inspex, bin2chen, chaduke, gkrastenov, jasonxiale, josephdara, kodyvim, peakbolt, pep7siup, rokinot, rvierdiiev, tapir
181.367 USDC - $181.37
Judge has assessed an item in Issue #839 as 3 risk. The relevant finding follows:
UniV3LiquidityAMO: recoverERC721() does not, in fact, recovers them The function to recover ERC721โs (found here) sends them to the rDPX V2 core contract, however said contract has no function to retrieve them, rendering the function useless and losing the token forever. If we take a closer look at emergencyWithdraw() shown below, weโll see it cannot properly retrieve ERC721โs as they only function with ERC20โs.
function emergencyWithdraw( address[] calldata tokens ) external onlyRole(DEFAULT_ADMIN_ROLE) { _whenPaused(); IERC20WithBurn token;
for (uint256 i = 0; i < tokens.length; i++) { token = IERC20WithBurn(tokens[i]); token.safeTransfer(msg.sender, token.balanceOf(address(this))); } emit LogEmergencyWithdraw(msg.sender, tokens);
} The comment below can be found in the recovery function:
// Only the owner address can ever receive the recovery withdrawal
Which leaves me to assume this was an accident. The V2 core contract inherits ERC721Holder which only includes the onERC721Received() function. The transferring to the core contract will succeed but thatโs as far as it goes. In order to fix this, consider sending this recovered asset to an ownerโs EOA address instead.
#0 - c4-judge
2023-10-24T07:10:57Z
GalloDaSballo marked the issue as duplicate of #935
#1 - c4-judge
2023-10-24T07:11:07Z
GalloDaSballo marked the issue as satisfactory
๐ Selected for report: catellatech
Also found by: 0xSmartContract, 0xnev, K42, LokiThe5th, RED-LOTUS-REACH, Sathish9098, __141345__, bitsurfer, hunter_w3b, mark0v, nirlin, rjs, rokinot
90.0998 USDC - $90.10
Dopex is a decentralized options protocol, as the initials suggest. The protocol offers classic european options as well as a novel "atlantic" model, a mix between american and european where the collateral can be used for other purposes, leading to a more capital efficient model.
The protocol centers around two main tokens: DPX, the governance token, and rDPX, the rebate token, with an elastic supply and multitude of uses such as collateral on synthetics, lockup for boosted fees and so on. The new rDPX V2 model aims to introduce a new usage of the later, and was the object of this code review.
This is the first and foremost aspect that was taken into consideration. This is important to consider throughout the whole auditing process, as certain patterns can be found in the network that won't be found on Ethereum mainnet, and vice versa. One example of such, the gas limit on Arbitrum is of 1.125 quadrillion, this means any form of DoS by reaching the block gas limit is left off the table, as this would be equivalent. Another example is any vulnerability regarding front running being invalid, as the network's centralized sequencer and low latency make it impossible to front run transactions (with certain exceptions, for example, contract calls that are scheduled to happen at a fixed frequency).
My approach to reviewing the codebase is to choose a smart contract to begin with and branch off to the next ones from there, based on their dependencies and such. For this contest I began reviewing the AMO contracts instead of the core; some time ago I found that beginning an audit through more peripheral contracts can be a more comfortable experience. The documentation provided in order to understand the new mechanism was somewhat dry, as many details had to be often asked to the developers directly through discord, though it is straightforward.
The more relevant findings are more related to internal accounting rather than systemic risks or mishandling of user funds. Final impressions that were left were more that the protocol's own funds are at risk rather than users' funds.
I was a dopex user in the past, so I had a general overview of the protocol before the audit was even announced. rDPX V2 is a feature which is long awaited by the dopex community, having suffered a complete revamp from it's previous dpxUSD design. These new contracts which are being introduced however have a completely different design from what I previously used, so past experience could not be leveraged in this review.
Based on the style of code writing, some contracts were either not written by the same individual or reviewed by a third party before the audit began. The use of custom errors in RdpxV2Core compared to the use of require statements elsewhere is one parameter that leaves this impression, there were also some comments spelling mistakes and inadequate code discrepancies between core and peripheric contracts as well. The different usage of transferFrom()
and safeTransferFrom()
is another factor.
The code is quite intertwined, the core contract requires a lot of vault functions. For a few examples of this:
Both vault and vaultLP can update the funding when necessary. An updated funding will lead the core contract to change it's bond behavior.
Funds can be transferred from the users to core, from backing to core, from reLP to core and from AMO to core. From there on, they can be further moved to the vault LP when settling options, or the perpetual vault when purchasing options.
Pausing the vault contract means the core contract cannot create bonds or settle options anymore (the latter if purchasing APP are required). The core contract is also unable to update the funding.
Overall, a proficient smart contract developer was required in order to write an extensive smart contract as rDPX V2 core, as well as deploying other contracts which are deeply dependent on one another.
As it is becoming a more common design pattern in the crypto ecosystem, the administrator has the power to compromise the entire protocol by withdrawing all funds. For this project, the core contract, the AMOs and the perpetual vault all have functions that enable the administrator to withdraw any token whatsoever, these functions being emergencyWithdraw()
and recoverERC20()
.
Beyond funds being at risk, there is also the risk of the administrator mishandling atlantic put options. Those are bought when a user is bonding, and sold at the admin's whims. According to a comment from the developer, these options will be settled when the backing reserve is high in rDPX and low in WETH backing. As to how much should be considered high or low, it's up for them to decide.
Arbitrum's sequencer has gone offline in the past, which essentially shuts down the network. This was something that was considered in the auditing process but even after taking this into account I was unable to find any vulnerability pertaining this matter. So assuming no bug was left behind, the protocol should be able to handle these down times.
Matters pertaining oracles are outside the scope of this contest, so nothing can be said about it.
I sent one of the vulnerabilities found to be low risk, more specifically the one pertaining to the recovery of ERC721s tokens. I did it so because, based on past contest experiences, judges seem to have a tendency to reduce the gravity of vulnerabilities that pertain to user error (in this case, accidentally sending ERC721 tokens to the core contract). There may be a case to be made about changing this to a medium risk, as it is a function not working as intended even though I sent this as a low risk finding. It's ultimately left for the judge to decide what severity it should be given.
25 hours
#0 - c4-pre-sort
2023-09-15T15:57:20Z
bytes032 marked the issue as low quality report
#1 - c4-judge
2023-10-20T10:12:14Z
GalloDaSballo marked the issue as grade-b
#2 - GalloDaSballo
2023-10-20T10:12:18Z
A bit generic but has a couple of points worth reading