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: 29/189
Findings: 4
Award: $666.81
🌟 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
Whenever proceeds is sent to PerpetualAtlanticVaultLP
, the function PerpetualAtlanticVaultLP::addProceeds is called to update the stored _totalCollateral
value.
When called, the function ensures that the contract collateral token balance is greater than or equal to the sum of the stored _totalCollateral
value plus the proceeds, but on settlement PerpetualAtlanticVault::settle function makes an external call to PerpetualAtlanticVaultLP::subtractLoss function, this function updates the variable _totalCollateral
by subtracting the realized loss.
The problem here is, this function requires that the PerpetualAtlanticVaultLP
collateral balance matches the stored totalCollateral
minus the realized loss. This implementation can easily be abused by an attacker, by sending a small amount of collateral token directly to the PerpetualAtlanticVaultLP
contract address. This would allow the addition of proceeds, but it would make settling options impossible because the subtractLoss
function would consistently fail at the require check
Manual Review
consider making the following change to the subtractLoss function:
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-09T09:53:34Z
bytes032 marked the issue as duplicate of #619
#1 - c4-pre-sort
2023-09-11T16:14:14Z
bytes032 marked the issue as sufficient quality report
#2 - c4-judge
2023-10-21T07:14:40Z
GalloDaSballo marked the issue as satisfactory
🌟 Selected for report: said
Also found by: 0Kage, 0xCiphky, 0xkazim, 836541, AkshaySrivastav, Evo, HChang26, HHK, KrisApostolov, Neon2835, QiuhaoLi, Tendency, Toshii, bart1e, bin2chen, carrotsmuggler, chaduke, etherhood, gjaldon, glcanvas, josephdara, lanrebayode77, mahdikarimi, max10afternoon, nobody2018, peakbolt, qpzm, rvierdiiev, sces60107, tapir, ubermensch, volodya
17.313 USDC - $17.31
https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/perp-vault/PerpetualAtlanticVaultLP.sol#L118-L136 https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/perp-vault/PerpetualAtlanticVaultLP.sol#L274-L284
When a writer deposits via PerpetualAtlanticVaultLP::deposit function, the function previewDeposit is called internally, this function further calls convertToShares function, this function returns the amount of shares a user will receive for a given amount of assets.
The problem here is, the computation in convertToShares
function makes use of an outdated _totalCollateral
variable, which results in wrongly calculating user shares.
function deposit( uint256 assets, address receiver ) public virtual returns (uint256 shares) { // Check for rounding error since we round down in previewDeposit. require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES"); perpetualAtlanticVault.updateFunding(); // <--- Here // Need to transfer before minting or ERC777s could reenter. collateral.transferFrom(msg.sender, address(this), assets); _mint(receiver, shares); _totalCollateral += assets; emit Deposit(msg.sender, receiver, assets, shares); }
Notice that perpetualAtlanticVault::updateFunding
function is called after conversion from asset to shares.
Note that, perpetualAtlanticVault::updateFunding function, transfers funding into the perpetualAtlanticVaultLP in a drip-vested manner.
Whenever perpetualAtlanticVault::updateFunding
function is called to update and transfer funding, perpetualAtlanticVaultLP::addProceeds
function is also called, addProceeds
function updates the stored _totalCollateral
value based off the amount sent from the vault to the perpetualAtlanticVaultLP
, hence, it is important to ensure the right _totalCollateral
value is used in computation by first updating funding, since this value is constantly changing.
As confirmed by a sponsor(psytama), funding was meant to be updated before calling the function previewDeposit, as rightly done in perpetualAtlanticVaultLP::redeem
function.
Manual Review
Context
#0 - c4-pre-sort
2023-09-10T09:40:38Z
bytes032 marked the issue as duplicate of #867
#1 - c4-pre-sort
2023-09-11T09:08:44Z
bytes032 marked the issue as sufficient quality report
#2 - c4-judge
2023-10-20T19:56:37Z
GalloDaSballo changed the severity to 3 (High Risk)
#3 - c4-judge
2023-10-20T19:56:45Z
GalloDaSballo marked the issue as satisfactory
🌟 Selected for report: said
Also found by: 0xWaitress, Nyx, RED-LOTUS-REACH, Tendency, __141345__, carrotsmuggler, nirlin, peakbolt, qpzm, wallstreetvilkas
158.2271 USDC - $158.23
When a user wishes to bond via RdpxV2Core::bond or RdpxV2Core::bondWithDelegate, the function calculateBondCost
is called, this function calculates and returns how much weth
and rDPX
token is required to bond.
Assuming the user transaction is executed at a block.timestamp
equal to the next funding payment timestamp, timeToExpiry
variable in RdpxV2Core::calculateBondCost
will be zero
function calculateBondCost( uint256 _amount, uint256 _rdpxBondId ) public view returns (uint256 rdpxRequired, uint256 wethRequired) { ############# uint256 timeToExpiry = IPerpetualAtlanticVault( addresses.perpetualAtlanticVault ).nextFundingPaymentTimestamp() - block.timestamp; if (putOptionsRequired) { wethRequired += IPerpetualAtlanticVault( addresses.perpetualAtlanticVault ).calculatePremium(strike, rdpxRequired, timeToExpiry, 0); } }
This variable is used in calculating premium via a call to PerpetualAtlanticVault::calculatePremium
.
The problem here is, time expiry will be calculated using a not up to date next funding payment timestamp, thereby making the calculated premium, different from the premium calculated in PerpetualAtlanticVault::purchase function, this is possible since before calculating and updating wethRequired
in RdpxV2Core::calculateBondCost function, PerpetualAtlanticVault::updateFunding function isn't called, when called this function will update latestFundingPaymentPointer given current timestamp equals or is greater than nextFundingPaymentTimestamp
, while In PerpetualAtlanticVault::purchase
function, updateFunding
is called, which then updates the latest funding payment.
Hence the premium calculated here, will differ from the premium user deposited, making the contract pay up user premium under such conditions.
function purchase( uint256 amount, address to ) external nonReentrant onlyRole(RDPXV2CORE_ROLE) returns (uint256 premium, uint256 tokenId) { _whenNotPaused(); _validate(amount > 0, 2); //SNIP, Took out outer logic for easy viewing updateFunding(); uint256 timeToExpiry = nextFundingPaymentTimestamp() - block.timestamp; // Get total premium for all options being purchased premium = calculatePremium(strike, amount, timeToExpiry, 0); // Transfer premium from msg.sender to PerpetualAtlantics vault collateralToken.safeTransferFrom(msg.sender, address(this), premium); perpetualAtlanticVaultLp.lockCollateral(requiredCollateral); _updateFundingRate(premium); emit Purchase(strike, amount, premium, to, msg.sender); }
Assuming funding duration is 7 days, where the genesis timestamp is set to 1694127600
, when latestFundingPaymentPointer
was previously zero(i.e. First epoch), latestFundingPaymentPointer
will be 1 after PerpetualAtlanticVault::updateFunding function is called nextFundingPaymentTimestamp
will be = 1694127600 + (1 * 7 days)
==> 1694732400
The difference between current timestamp and nextFundingPaymentTimestamp
is then used to calculate the expiry.
This creates a disparity between the amount user paid and the amount PerpetualAtlanticVault transfers from RdpxV2Core contract.
PS: This Report is written based off current scope, if IOptionPricing::getOptionPrice
, which according to a sponsor isn't yet deployed reverts on 0 time expiry, this will mean a DoS to users bonding at a time when block.timestamp == nextFundingPaymentTimestamp
, which will still need to be fixed.
Manual Review
update funding payment pointer before calculating bond cost
IPerpetualAtlanticVault(addresses.perpetualAtlanticVault).updateFundingPaymentPointer();
DoS
#0 - c4-pre-sort
2023-09-09T16:49:13Z
bytes032 marked the issue as primary issue
#1 - c4-pre-sort
2023-09-09T16:49:23Z
bytes032 marked the issue as sufficient quality report
#2 - c4-pre-sort
2023-09-09T16:50:51Z
bytes032 marked the issue as duplicate of #237
#3 - c4-pre-sort
2023-09-14T09:35:06Z
bytes032 marked the issue as duplicate of #761
#4 - c4-judge
2023-10-20T11:57:12Z
GalloDaSballo marked the issue as satisfactory
#5 - c4-judge
2023-10-20T15:37:09Z
GalloDaSballo removed the grade
#6 - c4-judge
2023-10-20T15:37:16Z
GalloDaSballo marked the issue as satisfactory
#7 - c4-judge
2023-10-21T07:54:55Z
GalloDaSballo changed the severity to 2 (Med Risk)
491.258 USDC - $491.26
Currently, the redeem
function in RdpxV2Core
allows users to withdraw DSC from a matured bond without checking for the contract's paused state.
In a case where the admin wants to stop all activity, e.g. In the event of a hack or compromise, they will be unable to stop the transfer of receipt tokens to users via RdpxV2Core::redeem
function.
In the rise of such events, being able to halt vital functionalities is important to mitigate further risk/fund loss
Add the whenNotPaused
modifier to RdpxV2Core::redeem function
Context
#0 - c4-pre-sort
2023-09-10T07:02:11Z
bytes032 marked the issue as duplicate of #1809
#1 - c4-pre-sort
2023-09-11T12:11:34Z
bytes032 marked the issue as duplicate of #6
#2 - c4-pre-sort
2023-09-11T12:12:17Z
bytes032 marked the issue as sufficient quality report
#3 - c4-judge
2023-10-20T19:59:10Z
GalloDaSballo marked the issue as satisfactory