Dopex - Juntao's results

A rebate system for option writers in the Dopex Protocol.

General Information

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

Dopex

Findings Distribution

Researcher Performance

Rank: 60/189

Findings: 3

Award: $192.67

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

96.3292 USDC - $96.33

Labels

bug
3 (High Risk)
satisfactory
sufficient quality report
edited-by-warden
duplicate-2083

External Links

Lines of code

https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/perp-vault/PerpetualAtlanticVault.sol#L270

Vulnerability details

Impact

PUTS Options may be created with strike price being higher than the current price, this leads to more funds required by vault and may cause insolvency.

Proof of Concept

Users call bond(...) or bondWithDelegate(...) to do bonding, both of the functions will call _purchaseOptions(...) to purchase rdpx put options from PerpetualAtlanticVault if putOptionsRequired is true. In PerpetualAtlanticVault, purchase(...) will then be called to create the put option, the strike price is determined as below:

uint256 currentPrice = getUnderlyingPrice(); // price of underlying wrt collateralToken uint256 strike = roundUp(currentPrice - (currentPrice / 4)); // 25% below the current price

Let's assume rdpx price is 10u and ETH price is 2000u, then currentPrice is 0.005e8, strike is calculated and rounded up by roundUp(0.005e8 - (0.005e8 / 4)):

function roundUp(uint256 _strike) public view returns (uint256 strike) { uint256 remainder = _strike % roundingPrecision; if (remainder == 0) { return _strike; } else { return _strike - remainder + roundingPrecision; } }

The strike price is 0.01e8, higher than currentPrice.

The root cause is price is in 1e8 decimals and roundingPrecision is defined as 1e6, which means any price less than 1% of the ETH price will be rounded up to 1% of the ETH price, which is 20u in the above case.

Tools Used

Manual Review

roundingPrecision should be defined as 1e4 or even smaller, 0.01% of the ETH price.

Assessed type

Decimal

#0 - c4-pre-sort

2023-09-09T05:29:28Z

bytes032 marked the issue as duplicate of #2083

#1 - c4-pre-sort

2023-09-12T04:43:44Z

bytes032 marked the issue as sufficient quality report

#2 - c4-judge

2023-10-20T14:17:21Z

GalloDaSballo marked the issue as satisfactory

Lines of code

https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/perp-vault/PerpetualAtlanticVaultLP.sol#L200-L203

Vulnerability details

Impact

Malicious user can grief attack to prevent from option being settled.

Proof of Concept

When an option is settled, protocol will transfer collateral from PerpetualAtlanticVaultLP:

collateralToken.safeTransferFrom( addresses.perpetualAtlanticVaultLP, addresses.rdpxV2Core, ethAmount );

and subtract loss from total available assets from PerpetualAtlanticVaultLP:

IPerpetualAtlanticVaultLP(addresses.perpetualAtlanticVaultLP).subtractLoss( ethAmount );

The [subtractLoss(...)] will check if the transferred collateral balance is equal to _totalCollateral - loss:

require( collateral.balanceOf(address(this)) == _totalCollateral - loss, "Not enough collateral was sent out" );

A malicious user can simply transfer 1 wei collateral token to PerpetualAtlanticVaultLP, then the settle transaction will be reverted due to mismatch.

Please copy below test codes to test/rdpxV2-core/Unit.t.sol to verify:

function testAuditGriefAttackSettle() public { rdpxV2Core.bond(1 ether, 0, address(this)); vault.addToContractWhitelist(address(rdpxV2Core)); uint256[] memory _ids = new uint256[](1); _ids[0] = 0; rdpxPriceOracle.updateRdpxPrice(1e7); // attacker sends 1 wei to vaultLP address attacker = address(uint160(uint(keccak256(abi.encodePacked("attacker"))))); deal(address(vaultLp.collateral()), attacker, 1 wei); vm.prank(attacker); vaultLp.collateral().transfer(address(vaultLp), 1 wei); vm.expectRevert("Not enough collateral was sent out"); rdpxV2Core.settle(_ids); }

Tools Used

Manual Review

Add sync() function to PerpetualAtlanticVaultLP:

function sync() public { _totalCollateral = collateral.balanceOf(address(this)); }

Call sync() function before transferring collateral token from PerpetualAtlanticVaultLP.

Assessed type

DoS

#0 - c4-pre-sort

2023-09-09T09:58:03Z

bytes032 marked the issue as duplicate of #619

#1 - c4-pre-sort

2023-09-11T16:15:07Z

bytes032 marked the issue as sufficient quality report

#2 - c4-judge

2023-10-20T19:34:34Z

GalloDaSballo marked the issue as satisfactory

Findings Information

Awards

96.3292 USDC - $96.33

Labels

bug
3 (High Risk)
satisfactory
upgraded by judge
sufficient quality report
duplicate-549

External Links

Lines of code

https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/core/RdpxV2Core.sol#L1238-L1242 https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/perp-vault/PerpetualAtlanticVault.sol#L529-L531 https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/reLP/ReLPContract.sol#L221-L222

Vulnerability details

Impact

Protocol expects oracle returns price in 8 decimals, while the oracle returns price in 18 decimals, this mismatch will cause huge loss to the protocol.

Proof of Concept

In RdpxV2Core contract, getRdpxPrice() function calls IRdpxEthOracle.getRdpxPriceInEth() to get the price of rDPX against ETH:

function getRdpxPrice() public view returns (uint256) { return IRdpxEthOracle(pricingOracleAddresses.rdpxPriceOracle) .getRdpxPriceInEth(); }

In PerpetualAtlanticVault contract, getUnderlyingPrice() calls function calls IRdpxEthOracle.getRdpxPriceInEth() to get the price of rDPX against ETH:

function getUnderlyingPrice() public view returns (uint256) { return IRdpxEthOracle(addresses.assetPriceOracle).getRdpxPriceInEth(); }

In ReLPContract contract, IRdpxEthOracle.getRdpxPriceInEth() is also called in reLP(...) to get rdpx price:

tokenAInfo.tokenAPrice = IRdpxEthOracle(addresses.rdpxOracle) .getRdpxPriceInEth();

The protocol expects the price to be in 8 decimals, however, the implementation of IRdpxEthOracle.getRdpxPriceInEth() in fact returns in 18 decimals:

/// @notice Returns the price of rDPX in ETH /// @return price price of rDPX in ETH in 1e18 decimals function getRdpxPriceInEth() external view override returns (uint price) { require( blockTimestampLast + timePeriod + nonUpdateTolerance > block.timestamp, "RdpxEthOracle: UPDATE_TOLERANCE_EXCEEDED" ); price = consult(token0, 1e18); require(price > 0, "RdpxEthOracle: PRICE_ZERO"); }

Tools Used

Manual Review

Price data returned by IRdpxEthOracle.getRdpxPriceInEth() should be divided by 1e10 to convert the data to be in 1e8 decimals.

Assessed type

Oracle

#0 - c4-pre-sort

2023-09-09T05:07:29Z

bytes032 marked the issue as duplicate of #549

#1 - c4-pre-sort

2023-09-12T05:17:10Z

bytes032 marked the issue as sufficient quality report

#2 - c4-judge

2023-10-20T18:27:53Z

GalloDaSballo marked the issue as satisfactory

#3 - c4-judge

2023-10-20T18:28:12Z

GalloDaSballo changed the severity to 2 (Med Risk)

#4 - c4-judge

2023-10-20T18:28:21Z

GalloDaSballo changed the severity to 3 (High Risk)

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter