Dopex - Cosine'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: 88/189

Findings: 1

Award: $96.33

🌟 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#L269-L276 https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/perp-vault/PerpetualAtlanticVault.sol#L335

Vulnerability details

Summary

There are multiple problems in the process of buying and executing options that prevent them from working, if either the rDPX price drops to a certain degree or, the ETH price rises to a certain degree.

Vulnerability Details

There are three mathematical problems that lead to broken options.

The calculation of the required collateral to purchase an option works as followed:

  1. The contract gets the current rDPX price in ETH from an oracle:
uint256 currentPrice = getUnderlyingPrice();
  1. The strike price is calculated by calling the roundUp function with the currentPrice - (currentPrice / 4):
uint256 strike = roundUp(currentPrice - (currentPrice / 4));

The roundUp function works as followed:

uint256 public roundingPrecision = 1e6;

...

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

Here we have the first problem. As the roundingPrecision is equal to 1e6 and there is no way to update it in the contract the returned strike price is not able to fall below 1e6. Here is a calculation that proves it:

Current price of rDPX in ETH = 1e6 - 100 wei

roundUp is therefore called with:

roundUp(currentPrice - (currentPrice / 4)) =
roundUp((1e6 - 100) - ((1e6 - 100) / 4)) = roundUp(749.925)

Then the remainder is calculated:

remainder = _strike % roundingPrecision = 749.925 % 1e6 = 749.925

As the remainder is not equal to zero the returned strike price is calculated like that:

strike = _strike - remainder + roundingPrecision = 749.925 - 749.925 + 1e6 = 1e6

This means the strike price of the put option 1e6 is bigger than the current price (1e6 - 100) and therefore the bought option is directly in the money and if executed right after purchasing it the option writer would lose 100 wei * amountOfOptions bought and the buyer would win the given amount.

  1. Back to the purchase function we take the calculated strike price from the roundUp function and calculate the requiredCollateral to buy the option with it:
uint256 requiredCollateral = (amount * strike) / 1e8;

Here we have the second problem. The amount of options is multiplied with the strike price and divided by 1e8. If the amount of options times the strike price falls below 1e8 the result of the formula will be smaller than one and therefore rounded down to zero by solidity. Which basically means if the strike price is below 1e8 it is possible to buy options for free. The following calculation shows it in detail:

amount = 1
strike = 1e8 - 1

requiredCollateral = (amount * strike) / 1e8 =
(1 * (1e8 - 1)) / 1e8 = 0.99999999... = 0

The third problem is equal to the second one and lies in the process of executing an option with the settle function. This is the formula to calculate the ETH amount which the owner of the option (the core contract) receives:

ethAmount += (amount * strike) / 1e8;

As we can see it is the same formula as used to calculate the required collateral and therefore the same problem that if the strike price is below 1e8 the ETH amount will be zero arises. Therefore if the strike price is below 1e8 at execution of the option, the owner will not receive any funds.

Impact

If the price ratio between rDPX and ETH becomes too large, options will stop working and therefore dpxETH can no longer be pegged 1:1 to ETH . The reason for that is that dpxETH is backed by 75% ETH and 25% rDPX which should be backed by the perpetual puts. But as soon as the put options stop working there is no longer a reason for the dpxETH price to stay above 75% ETH. Which will lead to a loss for all dpxETH holders.

Tools Used

Manual Review, Foundry, VSCode

Assessed type

Math

#0 - c4-pre-sort

2023-09-10T15:27:22Z

bytes032 marked the issue as duplicate of #2083

#1 - c4-pre-sort

2023-09-12T04:43:53Z

bytes032 marked the issue as sufficient quality report

#2 - c4-judge

2023-10-20T14:13:45Z

GalloDaSballo marked the issue as satisfactory

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