Primex Finance

QuantstampFiles named in the report

Quantstamp reviewed Primex Finance on 19 Oct 2023: 59 findings, 0 critical; 36 marked fixed, 15 acknowledged, 8 open.

No open critical findings
Open the reportOpen Primex Finance’s security record

Report hosted on certificate.quantstamp.com.

Trustblock does not review code.

Findings59

Statuses are the security firm’s own, from its report.

SeverityOpenAcknowledgedFixed
Critical000
High003
Medium2410
Low61123
HighSlippage Vulnerability in PrimeX Protocol for Swap and Spot Trade PositionsFixed

In the PrimeX protocol, the keeper can attack orders using spot trades or swaps. The protocol includes slippage protection by comparing the DEX execution price with the oracle price data within a certain gap. However, this protection is not applied to swap and spot trade positions. As a result, if a limit order is created for a swap or spot trade, there can be high slippage without the oracle price gap protection. The limit orders do not have the slippage protection by `amountOutMin` as well.

HighTraders May Lose Entire Position if Asset Was Removed From BucketFixed

When an asset was removed from a bucket through `removeAsset()`, the assets of all open positions may be transferred to the treasury. The affected traders will lose their entire position. The issue arises from the fact that `health()` in `PositionLibrary` returns 0 when `position.positionAsset` is not an allowed asset in the bucket. This can only occur if the asset was removed, as this position could not exist otherwise. Because `health()` returns 0, those positions can be closed through `closePosition()` with `CloseReason.RISKY_POSITION`. Such closures will lead to the traders not receiving any profits (`topUpAvailableBalance()` will not be called) AND not receiving their deposit back, as `decreaseTraderDebt()` will be called with `params.primexDNS.treasury()` as a receiver. Furthermore, when a position is closed due to liquidation, the trader will not receive any funds back. The deposit and potential profit will be sent to the treasury in the `PositionLibrary.closePosition()`.

HighVolatility May Be Greater than OneFixed

The Chainlink Volatility Feed measures the [realized volatility](https://docs.chain.link/data-feeds/rates-feeds#realized-volatility) of one asset against another. Note that realized volatility can be greater than or equal to 100% (see: [formula here](https://www.wallstreetmojo.com/realized-volatility/#h-realized-volatility-formula)), and such an instance would create one of two problems: * In the case where the volatility is exactly 100% any user holding this asset would have a health of 0 and hence be able to liquidate. * In the case where the volatility is over 100% the calculation of a user's health will fail due to overflow and hence no one will be able to liquidate.

Medium`ECDSA` Library Vulnerable to Signature MalleabilityFixed

The protocol currently utilizes OpenZeppelin's `ECDSAUpgradeable` version `4.3.1` in contracts `PMXBonusNFT`, `ReferralProgram`, and `WhiteBlackListReferral` for validating registration using `ECDSAUpgradeable.recover(bytes, bytes)`. However, it is important to note that version `4.7.3` introduced a high-severity patch to address a [vulnerability related to signature malleability](https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-4h98-2769-gh6h) in the `ECDSA.recover()` and `ECDSA.tryRecover()` functions. These functions were found to be vulnerable due to accepting EIP-2098 compact signatures in addition to the traditional 65-byte signature format. This vulnerability affected the functions that accept a single bytes argument, not the ones that take `r, v, s` or `r, vs` as separate arguments. As a result, the `PMXBonusNFT`, `ReferralProgram`, and `WhiteBlackListReferral` contracts are impacted since they utilize the single bytes argument version of `ECDSA`, and version `4.3.1` is affected by this vulnerability.

MediumAssumption of Singular Bucket Updates in `_searchApproxIndex()` FunctionFixed

In the `FeeExecutor`, lists `updatedTimestamps` and `indexes` are used to track all index and timestamp updates for a bucket. The function `FeeExecutor._searchApproxIndex()` is designed to return an approximate index corresponding to a given bonus deadline and the current index. However, its implementation, which employs a binary search approach, assumes that only one bucket updates are recorded in the `updatedTimestamps` and `indexes` lists. This assumption is incorrect because multiple buckets can update the lists using the function `FeeExecutor._updateIndex()`. Based on `FeeDecreaser` and `InterestIncreaser` functions, the `_updateIndex()` function pushes different bucket indexes which can be different based on the bucket's specific `bar` rate. This incorrect assumption can lead to inaccurate results returned by the `_searchApproxIndex()` function.

MediumBuggy `BestDexLens` ImplementationFixed

The `BestDexLens.getBestMultipleDexes()` function filters out DEX results and attempts to optimize share splits across DEX results to return the best DEX routing path for the amount requested. However, several concerns arise from the current implementation: 1. **Overwriting the Negative Value**: Within the loop, a condition checks if `amount` equals 0 and assigns `VERY_NEGATIVE_VALUE` to `amountByDexByShare`. However, in the subsequent lines, this value is immediately overwritten without any related checks. 1. **Unsafe Gas Estimation**: The gas, represented in ETH units (`vars.gas`), is directly added or subtracted to/from the `amount`. Mixing gas estimations in ETH with an amount that might represent another token or asset unit could lead to inaccurate calculations or misrepresentations of value. 1. **Unsafe Casting to `int256`**: The `amount`, an unsigned integer, is directly cast to `int256`. Without ensuring the `amount` is within safe bounds, this could introduce potential overflows or unexpected negative values. 1. **Arbitrarily Large Negative Value Concern**: A value `VERY_NEGATIVE_VALUE` defined as `-1e72` is used to indicate an undesirable amount obtained from the DEX. The problem arises when `currentValue` is assigned this large negative value and subsequently compared to other values using the conditional `_params.isAmountToBuy ? (currentValue < bestValue || bestValue == 0) : currentValue > bestValue`. If `_params.isAmountToBuy` is `true`, then a `currentValue` of `VERY_NEGATIVE_VALUE` will almost always be considered smaller than `bestValue`, even if `bestValue` represents a more desirable outcome. This bug can lead to incomplete or suboptimal solutions.

MediumClose Conditions May Be Incorrectly Set in `BatchManager`Fixed

When batch closing positions through `BatchManager.closeBatchPositions()`, each position listed in the array `_ids` is validated. If the position does not exist through `PositionManager.getPosition()`, the function will catch the error and continue to the next position id in the `_ids` array. Therefore the `i` variable may not track to `vars.length` and `vars.length` can be less than `i`. ```solidity if ( _closeReason == PositionLibrary.CloseReason.BATCH_STOP_LOSS || _closeReason == PositionLibrary.CloseReason.BATCH_TAKE_PROFIT ) vars.closeConditions[vars.length] = positionManager.getCloseCondition(vars.ids[i], _conditionIndexes[i]); ``` Therefore, the variable `i` used to obtain the position close condition can be incorrect. Since `i` can be ahead of `vars` the position id can be zero. This will return the close condition from position id `0` for the current position `_ids[i]` and can incorrectly close the current position being evaluated.

MediumDeployers Can Upgrade Factory Deployed ContractsFixed

The architecture graph suggests that exclusive roles should possess substantial privileges, which should be meticulously time-locked for enhanced security. The factories inherit the `UpgradeableBeacon` contract for beacon upgrades. However, the `UpgradeableBeacon` contract inherits the `Ownable` contract, making the deployer the owner and granting them the ability to upgrade implementations through the `UpgradeableBeacon.upgradeTo()` function (see: [code](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/4d4a509b1f8e521583d82c042ef4bce919fe8e12/contracts/proxy/beacon/UpgradeableBeacon.sol#L52C14-L52C23)). As a result, the deployer holds a significant privilege to upgrade all contracts, highlighting the need to secure this role and its associated responsibilities.

MediumInsufficient Reward upon PMX WithdrawalFixed

The `ActivityRewardDistributor.withdrawPmx()` function enables the admin to withdraw unused PMX tokens from the contract. During the execution of this function, the `bucketInfo.totalReward` is reduced by the withdrawal amount. However, the `bucketInfo.rewardPerDay` and `bucketInfo.endTimestamp` remain unchanged. As a result, there may not be enough PMX tokens available to cover the reward until the end timestamp. Additionally, the `lastUpdatedRewardTimestamp` is not updated as expected. As a consequence, the time span since this timestamp was last refreshed is inaccurately accounted for during subsequent reward accumulation. Specifically, when rewards are withdrawn, the `bucketInfo.fixedReward` value increases based on the duration since the `lastUpdatedRewardTimestamp`. Failure to update the `lastUpdatedRewardTimestamp` can result in the period being double-counted if it is not properly updated.

MediumInterest Accumulation Prior to Bucket LaunchFixed

According to the [documentation](https://docs.primex.finance/guides/credit-buckets/liquidity-mining), it states that "During the stabilization period, Traders can now borrow funds from the Credit Bucket, and Lenders start earning interest on the liquidity they provide for margin trading." Based on this information, we assume that lenders only start earning interest after moving to the stabilization period, which occurs when the bucket launches. Additionally, the implementation of `_depositLM()` does not update indexes and rates. However, the `withdraw()` function allows lenders to withdraw funds before the bucket launches. Within this function, both `_updateIndexes()` and `_updateRates()` are called, causing the index to be updated and start accumulating before the bucket launches.

Show 10 more
MediumOverflow Blocking `ActivityRewardDistributor` From FunctioningFixed

In both the `setupBucket()` and `decreaseRewardPerDay()` functions of the `ActivityRewardDistributor` contract, if `rewardPerDay` is equal to zero, the `bucketInfo.endTimestamp` is overridden to `type(uint256).max`. Additionally, the `getBucketAccumulatedReward()` and `_updateBucketInfo()` functions have the logic of `endTimestamp += unusedTime` when `rewardPerDay == 0`. The line `endTimestamp += unusedTime` will overflow if the `endTimestamp` is set to `type(uint256).max`. Consequently, almost all functions relying on `getBucketAccumulatedReward()` and `_updateBucketInfo()` will fail. From our discussion with the team, the `rewardPerDay` can be set to zero as emergency mitigation in case there is a bug to stop the accrual of incorrect rewards. Despite the likelihood of this occurring being low, it would cause extra chaos and worsen the emergency situation, which is why it is a medium-severity issue.

MediumReserve May Fall Below the Minimum Percentage of Total SupplyFixed

The `Reserve` currently uses `pToken.scaledTotalSupply()` instead of actual token supply `pToken.totalSupply()`, when validating the minimum percentage of token supply in the `Reserve` during the function `transferToTreasury()`. This methodology could result in inaccuracies in the calculated minimum percentage, which can lead to a lower than expected minimum percentage in the reserve. ``` _require( reserveBalance >= (restristions.minAmountToBeLeft + amount) && reserveBalance >= (pToken.scaledTotalSupply().wmul(restristions.minPercentOfTotalSupplyToBeLeft) + amount), Errors.NOT_SUFFICIENT_RESERVE_BALANCE.selector ); ```

MediumStrict Oracle Tolerance in Spot Trade Can Prevent Traders From Closing PositionsFixed

When closing a spot trade position (or more specifically, on `vars.borrowedAmountIsNotZero` in `PositionManager._closePosition()#L682-690`) through `PositionManager._closePosition()` the `oracleTolerableLimit` is set to zero between `depositAsset` and `positionAsset`. This means when closing the position through `PositionLibrary.closePosition()`, the swapped amount returned by the DEX in the `PrimexPricingLibrary.multiSwap()` must be equivalent to or better than the price provided by the price oracle. This check is validated in the `multiSwap()` where the `getOracleAmountsOut()` must be greater or equal to the amount obtained from the DEX because `_needOracleTolerableLimitCheck` is set to `true` in the `PositionLibrary.closePosition()` function. This strict requirement can prevent spot traders from closing their positions.

Low`depositAndLock()` Not Directly Tied to a DepositFixed

The `TraderBalanceVault.depositAndLock()` function allows the `VAULT_ACCESS_ROLE` to add any amount of any asset to any trader's locked balance. However, this function does not actually require that a deposit takes place, contrary to what the function name suggests.

Low`increaseDeposit()` May Revert if No Swap Is PerformedFixed

When increasing the deposit of a position through `PositionLibrary.increaseDeposit()`, the following check is performed: ``` _require(depositedAmountInBorrowed >= params.amountOutMin, ...) ``` However, this check will also be performed in case `params.asset == borrowedAsset`, meaning that no swap is executed. Therefore, this swap may revert if the user has provided `_amountOutMin` in `increaseDeposit(..)` of `PositionManager.sol` anyway.

Low`orderExist` Modifier May Incorrectly Return TrueFixed

The `limitOrderManager.orderExists` modifier may run into a problem when `orders.length == 0`. In this case, if it is called with input `0` then it will return true since the check `orders[index].id == _orderId` essentially amounts to a check that `0 == 0`.

Low`PositionManager` and `BatchManager` do not need `NO_FEE_ROLE`Fixed

The deployment process grants `NO_FEE_ROLE` to `PositionManager` and `BatchManager` in addition to `LimitOrderManager` and `Bucket`. The `NO_FEE_ROLE` is given to contracts to avoid protocol fees when swapping with `SwapManager`. The `PositionManager` and `BatchManager` do not need `NO_FEE_ROLE` because the contracts do not use `SwapManager` to conduct swaps.

LowApplication Monitoring Can Be Improved by Emitting More EventsFixed

In order to validate the proper deployment and initialization of the contracts, it is a good practice to emit events. Also, any important state transitions can be logged, which is beneficial for monitoring the contract, and also tracking eventual bugs or hacks. Below we present a non-exhaustive list of events that could be emitted to improve application management: 1. `Bucket.setReserveRate()` 1. `Bucket.setWithdrawalFee()` 1. `Bucket.setInterestRateStrategy()` 1. `PositionManager.setKeeperRewardDistributor()` 1. `PositionManager.setMinPositionSize()` 1. `PositionManager.setOracleTolerableLimitMultiplier()` 1. `Reserve.setTransferRestrictions()` 1. `PrimexDNS.setConditionalManager()` 1. `PrimexDNS.setPMX()` 1. `PrimexDNS.setProtocolRate` 1. `PrimexDNS.setProtocolRateInPMX` 1. `PrimexDNS.setAavePool()` 1. `PrimexDNS.activateBucket()` 1. `PrimexDNS.freezeBucket()` 1. `PrimexDNS.setDexAdapter()` 1. `PrimexDNS.activateDEX()` 1. `PrimexDNS.freezeDEX()` 1. `PriceOracle.updatePriceFeed()` 1. `PriceOracle.updateVolatilityFeed()` 1. `PriceOracle.setGasPriceFeed()` 1. `KeeperRewardDistributor.setDefaultMaxGasPrice()` 1. `KeeperRewardDistributor.setOracleGasPriceTolerance()` 1. `KeeperRewardDistributor.setMaxGasAmount` 1. `KeeperRewardDistributor.setPmxPartInReward()` 1. `KeeperRewardDistributor.setNativePartInReward()` 1. `KeeperRewardDistributor.setPositionSizeCoefficients()` 1. `KeeperRewardDistributor.setAdditionalGas()` 1. `SpotTradingRewardDistributor.decreaseRewardPerPeriod()` 1. `SpotTradingRewardDistributor.setRewardPerPeriod()` 1. `SpotTradingRewardDistributor.topUpAvailablePmxBalance()` 1. `SpotTradingRewardDistributor.withdrawPmx()` 1. `Treasury.setMaxSpendingLimit()` 1. `Treasury.decreaseLimits()` 1. `ReferralProgram.setReferrals()` 1. `DexAdapter.setQuoter()` 1. `DexAdapter.setDexType()` 1. `Redeemer.changeRate()` 1. `Redeemer.adminClaimEarlyTokens()` 1. `Redeemer.adminClaimRegularTokens()` 1. `SwapManager.setSwapRate()` 1. `SwapManager.setSwapRateInPmx()` 1. `PMXBonusNFT.setExecutor()` 1. `PMXBonusNFT.blockNFT()` 1. `PMXBonusNFT.unblockNFT()`

LowDuplications and Inconsistencies in Referrers List Due to Absence of Validations in `setReferrers()` and `setReferrals()` FunctionsFixed

The `setReferrers()` function permits the configuration of multiple referrers. However, it currently does not verify the existence of a referrer in the `referrers` list prior to the addition of a new referrer. This can result in the same referrer address being duplicated in the `referrers` list. Furthermore, the `setReferrals()` function neglects to validate the existence of referrals or referrers before updating the `referralsOf` and `referrerOf` mappings. It also fails to add new referrers to the `referrers` list. This can lead to inconsistencies in the tracking of referrers and referrals.

LowInconsistency and Validation Gaps in Treasury Spending Limit ManagementFixed

The current structure of the `Treasury.setMaxSpendingLimit()` and `Treasury.decreaseLimits()` functions, crucial for managing the treasury spending, exhibit inconsistencies and lack the necessary validation checks. The `setMaxSpendingLimit()` function allows the `Treasury` to add a new spender address with spending limits or update existing limits. This function does not validate the `_newSpendingLimits` used and but the function is locked behind a `MEDIUM_TIMELOCK_ADMIN`. Although the function name implies modifying the maximum spending limits for a spender, the implementation allows any data in `SpendingInfo.limits` to be updated for new and existing spenders. The `Treasury.decreaseLimits()` function allows `Treasury` to update the spending limits of existing spenders. This function validates that the new limits are decreased and time durations are increased. However, the function does not validate the existence of the spender address. This lack of validation could allow for unintentional increases in the time duration for non-existent spenders in the `spenders` mapping. The following input validations are recommended, but not limited to: 1. When adding or updating spending limits for a spender address, 1. `maxTotalAmount` should exceed zero, 2. `maxAmountPerTransfer` should exceed zero, 3. `maxPercentPerTransfer` should be less than `WAD` or reasonably bounded, 4. `minTimeBetweenTransfers` should exceed zero or reasonably bounded, 5. `maxAmountDuringTimeFrame` should exceed zero or reasonably bounded. 2. When decreasing spending limits for a spender address, 1. `isSpenderExist` should be validated,

Show 10 more
LowInconsistency in Pause Management when Opening PositionFixed

If `PositionManager` contract is paused, it is still possible to open new positions via the `LimitOrderManager` contract through the `LimitOrderManager.openPositionByOrder()` function. However, this can be mitigated once the `LimitOrderManager` contract is paused as well.

LowIneffective Conditions Can Be Attached to OrdersFixed

An order for a swap does not require, and should not have, a close condition attached to it. This is because a swap order does not open a position, so there is no need for a way to close it. The `LimitOrderLibrary.setCloseConditions()` function includes a check on `_order.shouldOpenPosition` to prevent this situation. However, it is important to note that this check can be bypassed if a user changes a spot trade order to a swap order using the `LimitOrder.updateOrder()` function. In such cases, the originally attached closing condition will remain attached, potentially leading to unexpected behavior.

LowInfinite Token ApprovalFixed

Several contracts approves token infinitely for another contract. While we do not find any exploit from this, and the risk is mitigated as it is only approved to another contract within the protocol, it increase the risk surface as a vulnerability can cause the token on the approved contracts to be all taken away. We found the pattern in the following places: 1. `ActivityRewardDistributor.initialize()` 2. `Bucket.initialize()` 3. `LiquidityMiningRewardDistributor.initialize()` 4. `SpotTradingRewardDistributor.initialize()`

LowLoss of Points Due to Insufficient PrecisionFixed

The `getLenderInfo()` function calculates the percentage of funds a lender holds with the formula: ``` (lenderInfo.points * 100) / buckets[_bucketName].totalPoints; ``` This calculation can lead to a loss of precision. For example, a lender with 1 point in a 1000-point bucket would be represented as holding 0% rather than the correct 0.1%. As the total points accumulate in `totalPoints`, the inaccuracy magnifies.

LowMissing `GUARDIAN_ADMIN` roleFixed

During `Registry` deployment, if `guardianAddress` is undefined from the `addresses.json` configuration, the role `GUARDIAN_ADMIN` will not be added to the `Registry`. The `guardianAddress` is not specified in the configuration. The guardian role is needed in PrimexTimelock to pause and unpause functionalities.

LowMissing Access Control in `PToken.setBucket()`Fixed

Any user can call the `PToken.setBucket()` function and change the bucket corresponding to the `PToken` as long as they chose an address conforming to a valid PrimeX bucket interface. However, once this bucket is set it cannot be changed. Thus, this attack has a very small window of opportunity but can be disruptive if successfully executed. Additionally, this issue is mitigated in the `BucketsFactory.createBucket()` function. Essentially, the pToken is deployed/created, and `setBucket()` is immediately called, ensuring that this scenario would not occur in practice due to their deployment mechanism. Nonetheless, if the `PToken` contract is ever used independently, this issue should be a concern.

LowMissing Authorization Check for `PTokensFactory.createPToken()`Fixed

The `PTokensFactory.createPToken()` function seems only needed for the `BucketFactory.createBucket()` function. However, there is no authorization check for the `PTokensFactory.createPToken()` function. In other words, anyone can call the function and deploy a new pToken. We do not find any exploit due to this, but if any of the off-chain components are to listen to the `PTokenCreated` event, they might be wrongly notified and have unexpected behavior.

LowProposers Incorrectly Set to ExecutorsFixed

During the deployment process of `PrimexTimelock`, the proposers are incorrectly parsed from executors. Therefore the proposers are set as the executors regardless of the proposers provided in the task parameters.

LowPTokens Can Be Transfered to Blacklisted AddressFixed

In the `transfer()` function of `PToken`, there is no check to ensure that the recipient is not blacklisted. In accordance with these checks in other places, blacklisted addresses should not be able to receive any pTokens. **Note:** We discussed this with the team during the audit, and the team has clarified that this is the intended behavior.

LowSpot Trade or Swap Order with the `depositedAsset` Being the Same as `positionAsset`Fixed

An order for a swap or spot trade should not have the `depositedAsset` being the same as the `positionAsset`, as there is no need for them to be identical. The `LimitOrderLibrary.createLimitOrder()` function includes validation on the assets when `vars.isSpot` to prevent this scenario. However, it is important to note that when using the `LimitOrderManager.updateOrder()` function, one can change the `depositedAsset` without the same validation. As a result, it is possible to create a spot trade or swap order with the `depositedAsset` being the same as the `positionAsset`, which may lead to unexpected behavior or unnecessary operations.

Show 6 more
LowUnable to Clean Up Legacy OrdersFixed

The `LimitOrderManager` contract currently only allows the trader to cancel an order with the `cancelLimitOrder()` function, unless the bucket has been delisted. However, if the `deadline` of the order has passed, it may be kept in the contract indefinitely unless the trader actively cancels it. These legacy orders will be stored in the contract, including arrays such as `traderOrderIds` and `bucketOrderIds`. When there are too many legacy orders, it can potentially block certain features, such as the `getBucketOrders()` function, from executing due to running out of gas. Additionally, directly accessing the public array may also raise gas concerns, although this can be configurable through the node (refer to the gas limit on `eth_call` discussion [here](https://community.infura.io/t/gas-limit-on-eth-call/1115)).

LowUnclear Whether Native Currency Should Be Supported for SwappingFixed

When swapping from the wallet through the `swap()` function, the input token `params.tokenA` can be `NATIVE_CURRENCY`. The current implementation does not accept `NATIVE_CURRENCY` when swapping directly from the wallet through the `TokenTransfersLibrary.doTransferFromTo()` function. However, the implementation accepts `NATIVE_CURRENCY` when using funds from the `traderBalanceVault.lockAsset()`. Nonetheless, the call to `PrimexPricingLibrary.multiSwap()` will eventually fail. However, it might be great to clarify whether native token should be supported or not and add the corresponding validation.

LowUninitialized Implementation ContractFixed

Leaving an implementation contract uninitialized poses a security risk. An uninitialized implementation contract can be exploited by attackers, potentially compromising the associated proxy. To mitigate this risk, it is recommended to invoke the `_disableInitializers()` function in the constructor during deployment (see: [OpenZeppelin doc](https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#initializing_the_implementation_contract)). This action will automatically lock the implementation contract, preventing any unauthorized usage. The following is the list of impacted contracts found with the `grep` command: ```sh ▶ grep -rl --include="*.sol" --exclude="I*.sol" --exclude-dir="mocks" "initializer" . ./SpotTradingRewardDistributor/SpotTradingRewardDistributor.sol ./TraderBalanceVault/TraderBalanceVault.sol ./PMXBonusNFT/PMXBonusNFT.sol ./ActivityRewardDistributor/ActivityRewardDistributor.sol ./PToken/PToken.sol ./PriceOracle/PriceOracle.sol ./WhiteBlackList/WhiteBlackListReferral/WhiteBlackListReferral.sol ./WhiteBlackList/WhiteBlackList/WhiteBlackList.sol ./LimitOrderManager/LimitOrderManager.sol ./KeeperRewardDistributor/KeeperRewardDistributor.sol ./Bucket/Bucket.sol ./LiquidityMiningRewardDistributor/LiquidityMiningRewardDistributor.sol ./DebtToken/DebtToken.sol ./Reserve/Reserve.sol ./ReferralProgram/ReferralProgram.sol ./PrimexDNS/PrimexDNS.sol ./BonusExecutor/FeeDecreaser.sol ./Treasury/Treasury.sol ./PositionManager/PositionManager.sol ```

LowUnlocked Pragma and Risk of Unsupported Opcode in the Latest Solidity VersionFixed

Every Solidity file specifies in the header a version number of the format `pragma solidity (^)0.8.*`. The caret (`^`) before the version number implies an unlocked pragma, meaning that the compiler will use the specified version _and above_, hence the term "unlocked". Note that Solidity `0.8.20` might only work on Ethereum Mainnet at this moment as it introduced a new OP Code that other EVM-compatible chains might not support yet (see: [this article](https://medium.com/coinmonks/push0-opcode-a-significant-update-in-the-latest-solidity-version-0-8-20-ea028668028a) for more information).

LowUnresolved `TODO`Fixed

The `PrimexPricingLibrary.getLiquidiationPriceByOrder()` function has an unresolved `TODO` stating: "This function is only used in tests. Maybe it can be removed."

LowUsing Deprecated FunctionFixed

The function `AccessControl._setupRole()` has been [deprecated](https://docs.openzeppelin.com/contracts/4.x/api/access#AccessControl-_setupRole-bytes32-address-) in favor of `AccessControl._grantRole()`, which performs the same operations.

Contracts in scope

No deployed address is recorded for this audit.

The report names a repository or files. Open the report to see its scope.

Repository and files the report names (13)

  • src/contracts/SwapManager.sol
  • src/contracts/InterestRateStrategy.sol
  • src/contracts/PrimexTimelock.sol
  • src/contracts/EPMXToken.sol
  • src/contracts/PMXToken.sol
  • src/contracts/BatchManager.sol
  • src/contracts/PrimexProxyAdmin.sol
  • src/contracts/PrimexUpkeep.sol
  • src/contracts/PrimexRegistry.sol
  • src/contracts/Redeemer.sol
  • src/contracts/PMXPriceFeed.sol
  • src/contracts/Constants.sol
  • src/contracts/DexAdapter.sol

Part of Primex Finance’s security record

Primex Finance
Audits5 audits by 2 firmsLast audit Oct 2024, by Resonance Security.
Critical findingsNo open critical findingsAcross the 5 reports, 2 critical findings were reported. Each is marked fixed by the firm that reported it.
Open Primex Finance’s security record

Security firm

Quantstamp

236 projects reviewed · 332 reports · latest Oct 2026

See Quantstamp’s audits

Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.

Tell us