As of 30 Oct 2026, Primex Finance has 5 audit reports by 2 security firms on Trustblock; the latest is by Resonance Security, dated 14 Oct 2024. Those reports list no open critical findings.
Record last updated . It holds audits and findings, contracts and incidents up to Oct 2026. Trustblock does not review code.
Newest first
| Audit | Security firm | Date | Scope | Critical findings | Report |
|---|---|---|---|---|---|
| Primex Finance | Security firm: Resonance Security | Date: | Scope: 401 addresses on Ethereum, Polygon (POS) and Arbitrum | No open critical findings | Open · IPFS |
| Primex Finance | Security firm: Resonance Security | Date: | Scope: 401 addresses on Ethereum, Polygon (POS) and Arbitrum | 1 critical finding, fixed | Open · IPFS |
| Primex Finance | Security firm: Resonance Security | Date: | Scope: 401 addresses on Ethereum, Polygon (POS) and Arbitrum | No open critical findings | Open · IPFS |
| Primex Finance | Security firm: Resonance Security | Date: | Scope: 401 addresses on Ethereum, Polygon (POS) and Arbitrum | 1 critical finding, fixed | Open · IPFS |
| Primex Finance | Security firm: Quantstamp | Date: | Scope: Files named in the report | No open critical findings | Open · certificate.quantstamp.com |
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 1 |
| High | 0 | 0 | 7 |
| Medium | 3 | 4 | 11 |
| Low | 9 | 11 | 46 |
The functions getAmountsIn(), getAmountsOut(), and getGas() do not implement the possibility of calculating beforehand the amount of tokens and gas that will be used to swap through the function _swapExactTokensForTokens(). Users will have to perform swaps by trial and error without previously knowing accurate calculation estimates.
Open the reportThe current implementation of the `Bucket` contract permits users to interact with the contract even after it has been delisted. This raises significant concerns, especially regarding the `Bucket.increaseDebt()` function, as it places additional responsibility and burden on the delisted bucket. Consequently, it becomes possible to acquire more debt through existing positions by reducing deposits using the `PositionManager.decreaseDeposit()` function. In this scenario, anyone can close these positions by specifying the close reason as `BUCKET_DELISTED`. It is important to note that the verification of the bucket status occurs in the `primexDNS.getBucketAddress()` function. This verification provides protection to the `Bucket.deposit()` function and the `PositionLibrary.createPosition()` functions implicitly, thereby limiting the attack factor to the `PositionManager.decreaseDeposit()` function.
Open the reportBoth keeper rewards and spot trading rewards are susceptible to being attacked through wash trading. The function `PositionManager.closePositionByCondition()` allows anyone to close an existing position as long as the close reason condition is satisfied in `PositionLibrary.closePosition()`. Unlike the function `PositionManager.closePosition()` where the reason is restricted to `CLOSE_BY_TRADER`, the `CloseReason` can be decided by the keeper where the reason can be `CLOSE_BY_TRADER`, `RISKY_POSITION`, `BUCKET_DELISTED`, or `LIMIT_CONDITION`. Therefore, in addition to close conditions, this function can be also used to close positions for liquidations, bucket deprecation, and traders. Since the keeper rewards are paid by the protocol's treasury, this close reason flexibility in the function and the ability to open and close positions in the same transaction, the `closePositionByCondition()` function allows anyone to steal keeper rewards by repeatedly opening and closing positions to drain treasury rewards through `CLOSE_BY_TRADER` or `LIMIT_CONDITION`. The malicious trader can close the existing position using `CLOSE_BY_TRADER` or setting the close condition where the newly created position can be closed in the same block. Similarly, the spot trading rewards can also be stolen through similar exploit scenarios by repeatedly opening and closing positions. When a user opens a position via `PositionManager.openPosition()`, their trading activity is recorded via `_updateTraderActivity()`. Crucially, the volume that they are treating in USD terms is added to their total trading activity in that period of time, which is a variable used to calculate the rewards they receive from the `SpotTradingRewardsDistributor` contract. However, when the position is closed by the user via `PositionManager.closePosition()`, the trading activity the user received before is not rescinded. Hence they can open and close positions multiple times in order to increase their trading activity without actually benefitting the protocol. In this scenario, rewards are determined using the formula: ``` reward += (periodInfo.totalReward * periodInfo.traderActivity[trader]) / periodInfo.totalActivity; ``` The formula added intricacy when evaluating the economic feasibility of the attack, as rewards are influenced by the overall trading activity of other users as well. This issue is mitigated by the fact that the protocol charges a protocol fee when opening the position. As long as the protocol fee is significantly larger than the sum of the rewards, the attack is mitigated because it would not be economically feasible. However, this also means that the deployment configuration value should be carefully vetted before launching the protocol, and some on-chain values need to be continuously monitored since some of the variants will not be under the protocol's control.
Open the reportThe function _swapWithArbitraryDex(), although private, can be called indirectly by users with malicious inputs that are note validated properly. One of the inputs is _params.dexRouter which is used as the target address of an external function call, making it possible for a malicious user to craft arbitrary transactions with input parameters that could damage the entire protocol.There are multiple attack vectors associated with having this functionality enabled, mostly relating to the fact that it is possible for an attacker to impersonate the vulnerable smart contract. More specifically, it may be possible to drain the contract of all its funds, native or tokenized, as well as perform unauthorized actions acting as the DexAdapter. By taking into consideration the permissions and authorized actions that can be taken as DexAdapter, the impact may be more or less severe.As an example, the provided proof of concept can be used to drain the contract DexAdapter of all its NATIVE_CURRENCY. Furthermore, by using a combination of calls, delegate calls and reentrancy, a malicious smart contract may abuse the doApprove() function to gain access to tokens owned by DexAdapter.
Open the reportProvided protocol specification, and specifically the recent changes, regarding Oracle contract's implementation state that if Oracle Route's length is equal to four, then Pyth and Chainlink Oracles cannot be neither the first nor the last step in the whole path. This requirement was formulated to assure only expected token conversions are permitted. However, it was observed that the implementation of such mechanism is not complete. Namely, it does verify that Pyth and Chainlink Oracles are not the first step in the four-step route, but they do not assert they are not the last. Hence, the scenarios that were supposed to be prevented by design are actually possible.
Open the reportThe function setProtocolParamsByAdmin() does not implement access control allowing any user to delegate calls to PositionManagerExtension.While this does not present an immediate threat since all administrative functions on PositionManagerExtension possess proper access control, current or future functions that do not have any access control implemented may be at risk.
Open the reportAfter EIP1559 the gas cost per transaction is split into a base fee and a priority fee. The priority fee is awarded to the validator of that block, whereas the base fee is burned. The priority fee can be set as high as the user wants, usually with the intention of increasing their likelihood of getting included in a block. In the scenario where a someone is both a keeper and a validator of a specific block, they have the opportunity to perform an attack which involves paying themselves an usually high priority fee in order to increase the rewards they receive as part of the gas refund. Since the rewards are given directly in terms of the value of the native token, such an attack could seriously harm the protocol's treasury. Note that, while such an attack is currently not possible on some L2s (such as Arbitrum) given their centralized sequencer model, it may be in the future if they transition to a more distributed architecture. The issue is mitigated by the fact that `KeeperRewardDistributor.updateReward()` function has a cap on the `maxGasPriceForReward` to be related to the gas price from the oracle. Additionally, the protocol charges a protocol fee when opening the position. As long as the protocol fee is significantly larger than the reward, the attack is mitigated because it would not be economically feasible. As a result, we lowered the severity for this issue.
Open the reportIt is crucial to validate inputs, even if the inputs come from trusted addresses, to avoid human error. A lack of robust input validation can only increase the likelihood and impact in the event of mistakes. Following is the list of places that can potentially benefit from stricter input validation: 1. qs-badge-fixed `Bucket.initialize()#L17`: the interface of `liquidityMiningRewardDistributor` should be validated. 2. qs-badge-acknowledged `Bucket.setMaxTotalDeposit()#L183`: the `_maxTotalDeposit` can be set lower than the current token supply. **Update:** The team clarified that this is a feature. 3. qs-badge-acknowledged `Bucket.setReserveRate()#L149`: add a validation on `_fee`. **Update:** the team clarified that the fee can be zero. 4. qs-badge-fixed `Bucket.setFeeBuffer()#156`: add a validation on `_feeBuffer`. **Update:** Added `_feeBuffer < WadRayMath.WAD + (WadRayMath.WAD / 100)` in the `Bucket` contract. 5. qs-badge-fixed `BucketsFactory.constructor()#L31`: the interface of `_bucketImplementation` should be validated. **Update:** Also, all the overwrote `upgradeTo()` functions the factories have the same check. 6. qs-badge-fixed `FeeExecutor.setTierBonus()#L44`: the `bonus[i].percent` should not be zero. The percent is verified in `_activateBonus` to confirm active status. 7. qs-badge-acknowledged `BonusExecutor.setMaxBonusCount()#L58`: the `_maxCount` should be set lower than the existing bonus count. **Update:** The team clarified that this is a feature. 8. qs-badge-fixed `PositionLibrary.increaseDeposit()#L211`: the `amountToTrader` should be greater than zero before updating the trader's balance in the trader balance vault. **Update:** Instead of adding a validation, a condition check is done before `traderBalanceVault.topUpAvailableBalance()`. 9. qs-badge-fixed `PositionLibrary.decreaseDeposit()#L288`: the `position.amount` should be greater than zero before updating the trader's balance in the trader balance vault. 10. qs-badge-fixed `PositionLibrary.closePosition()#L327`: the `vars.amountToReturn` should be greater than zero before updating the trader's balance in the trader balance vault. **Update:** Instead of adding a validation, a condition check is done before `traderBalanceVault.topUpAvailableBalance()`. 11. qs-badge-acknowledged `PositionLibrary.createPosition()#L555`: the price feed oracle should exist before creating the leveraged position. **Update:** The existence of the oracle is checked when adding a new asset to the bucket. 12. qs-badge-acknowledged `PositionLibrary.createPositionByOrder()#L631`: - the close condition parameters should be validated. **Update:** the team clarified that it is intended to not check the price of closing conditions. Other checks take place in the setCloseConditions in PositionLibrary. - check that `_params.order.depositedAsset != _params.order.positionAsset`. **Update:** the team clarified that the deposit asset of a margin position may be equal to the position asset. - make sure the token pair exists on DEX: `priceOracle.getPriceFeedsPair(_params.order.positionAsset, _params.order.depositedAsset)`. **Update:** the team clarified that adding new restrictions is unnecessary because the existence of the oracle between deposit and position assets will be checked on later stage in `PositionLibrary.openPosition()`. - if `!isSpot`, check that `position.bucket.allowedAssets(_params.order.positionAsset)`. **Update:** the team clarified that the allowedAssets is checked when creating an order. 13. qs-badge-acknowledged `PositionLibrary.openPosition()#L699`: the close condition parameters should be validated. **Update:** It is intended to not check the price of closing conditions. Other checks take place in the setCloseConditions in PositionLibrary. 14. qs-badge-fixed `PrimexPricingLibrary.getAmountIn()#L168`: the first token of `_params.routes.length` should be `_params.tokenA`. **Update:** Also, the team added the check that the last token is the tokenB. 15. qs-badge-fixed `PToken.unlockDeposit()#L88`: the `deposits` length should be greater than zero; otherwise, the function can unexpectedly revert. 16. qs-badge-fixed `PToken.depositProlongation()#L108`: the `deposits` length should be greater than zero; otherwise, it is possible to extend a non-existent locked deposit. **Update:** the `depositProlongation()` function no longer exists. 17. qs-badge-fixed `PositionManager._onlyExists()#L767`: the `positions` length should be greater than zero; otherwise, it is possible to return true for a non-existent position when the positions list is empty. 18. qs-badge-fixed `LimitOrderManager.orderExists()#L457`: the `orders[index]` length should be greater than zero; otherwise, it is possible to return true for a non-existent order when the `orders[index]` list is empty. 19. qs-badge-fixed `LimitOrderLibrary.createLimitOrder()#L357`: the deadline should be validated when creating limit orders. 20. qs-badge-acknowledged `LimitOrderLibrary.creat
Open the reportNumerous functions within the `BestDexLens` contract involve iterating through multiple dexes and conducting intricate calculations to determine the optimal route. This concern becomes particularly pronounced when an increasing count of dexes is integrated into the protocol, raising apprehensions about the computational load leading to potential out-of-gas errors. The central problematic function is `getBestMultipleDexes()`, which iteratively traverses a dex list. This function, in turn, is invoked by `getBestDexForOpenablePosition()`, `getBestDexByPosition()`, and `getBestDexByOrder()`. We consider the severity as low because, in the scope of this audit, the functions are only used in the `PrimexUpkeep.checkUpkeep()` function. From our understanding, the `checkUpkeep()` is only intended to be for a static call to simulate the order execution and retrieve a filtered result that can be performed. However, if any of the functions are used outside our knowledge unexpectedly, the severity can be higher.
Open the reportIf the owner renounces their ownership, all ownable contracts will be left without an owner. As a result, any function guarded by the `onlyOwner` modifier will no longer be able to be executed, such as upgrading the `UpgradeableBeacon` implementation contract. Furthermore, critical role transfers are not following a two-step pattern. The two-step pattern ensures that there is an additional layer of verification and authorization before transferring critical roles, reducing the risk of unauthorized access or malicious actions.
Open the reportThe protocol incorporates several privileged roles, and it is important to highlight the associated centralization risk to users interacting with the protocol. The following is a list of the privileged roles and their corresponding privileges: 1. VAULT_ACCESS_ROLE 1. qs-badge-acknowledged May call `TraderBalanceVault.withdrawFrom()` to withdraw any amount of tokens from any trader. 1. BIG_TIMELOCK_ADMIN 1. qs-badge-acknowledged May call `SpotTradingRewardDistributor.withdrawPmx()` to withdraw all the PMX. **Update**: To clarify, only the `undistributedPMX` (previously `availablePMX`) can be withdrawn. 1. qs-badge-fixed May cancel limit orders by calling `cancelLimitOrdersByAdmin()`. **Update:** The `LimitOrderManager._unlockAssetsAndDeleteOrder()` function has been modified to return the funds to the trader of the order, rather than to the `msg.sender`, which would be the admin if called through the `cancelLimitOrdersByAdmin()` function. 1. qs-badge-fixed May claim an arbitrary amount of `earlyPMX` and `PMX` tokens from the `Redeemer` contract by calling `adminClaimEarlyTokens()` and `adminClaimRegularTokens()` respectively. **Update:** Those functions have been removed. 1. qs-badge-acknowledged May change the implementation of any upgradeable contract calling `PrimexProxyAdmin.upgrade()` or `upgradeAndCall()`. **Update:** Please refer to PRI-48 for a detailed response regarding the upgradeability. 1. MEDIUM_TIMELOCK_ADMIN 1. qs-badge-fixed May transfer tokens as long from any bucket as long as they leave `transferRestrictions[].minAmountToBeLeft` tokens in the bucket, by calling `Reserve.transferToTreasury()`. However, the same admin can also change the `transferRestrictions[]` struct by calling `Reserve.setTransferRestriction()`. Hence, they have the ability to transfer the entire funds of any bucket to the protocol treasury. **Update:** The maximum amount is determined by the number of pTokens owned by the Reserve. This design enables the Reserve to receive a portion of the borrowing fee paid by traders. We have marked the status as "fixed" as this is not inherently an issue. 3. EMERGENCY_ADMIN 1. qs-badge-acknowledged May prevent all users from redeeming early `PMX` tokens by calling `Redeemer.pause()`. Note that the `SMALL_TIMELOCK_ADMIN` could then unpause. **Update:** The client states that the Redeemer will not be included in the first release. 1. qs-badge-acknowledged May decrease the amount of rewards by calling `SpotTradingRewardDistributor.decreaseRewardPerPeriod()`. **Update:** The team clarifies that this is the intended behavior, enabling the emergency role to respond to suspicious activity. This trade-off represents the team's decision between decentralization and operational security.
Open the reportThe reward distribution mechanism in `PToken` and `DebtToken` is designed to update rewards based on changes in the token supply. The reward per token is recalculated to reflect any changes in the token supply, ensuring that token holders continue to accumulate rewards at a rate proportionate to their holdings. However, when an error occurs during the update of reward information, the try/catch block causes this step to be skipped. This means that when a change in token supply occurs, the reward per token could potentially remain unchanged. As a result, token holders may continue to accumulate rewards at the previous rate, which may not be accurate given the new token supply. The implementation incorporates a try-catch pattern to isolate failures in the reward distribution contract from affecting the pToken and debt token contracts. However, there is uncertainty regarding the operational process to address the gap in missed rewards when an unexpected failure occurs. It is essential to establish a clear and defined procedure to rectify any missed rewards promptly.
Open the reportThe reports name a repository or files. Open a report to see its scope.
Is this your project? Show this record on your own website. The label states what the record holds, links to this page and updates by itself. It is free and needs no sign-in.
Automatic: the three strongest facts of this project’s own record (its last audit and two more), highlighted. They update with the record. Picking a fact turns Automatic off.
<head><script src="https://widget.trustblock.run/project-widget.min.js" defer></script></head><body><divdata-tb-project-widgetdata-tb-project-slug="primex-finance"></div></body>
Paste the code where the label should appear. It uses no API credit and updates by itself.
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