As of 3 Nov 2026, YFX - has 2 audit reports by 1 security firm on Trustblock; the latest is by CertiK, dated 1 May 2024. Those reports list no open critical findings.
Record last updated . It holds audits, findings and contracts up to Nov 2026, and incidents up to Oct 2026. Trustblock does not review code.
Newest first
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 0 | 5 | 2 |
| Medium | 0 | 2 | 12 |
| Low | 6 | 5 | 34 |
Delegate into a loop can allow double spending attack. The `msg.value` is not reset after each delegate call. So if the loop is big enough, the `msg.value` can drain the contract balance.
Open the reportIn commit 49b6ebb9d89ddde763c04e91b7ea6b0fe561290f, the implementation has divided `Router.sol` into `Router.sol` and `Executor.sol`, introducing a potential for reentrancy attacks. For instance, an attacker could reentrantly call `Executor.executePoolOrder()` during a `Router.cancelPoolOrder()` operation, before `Order.updatePoolOrder()` effectively cancels the order. This could allow execution of an order that should have been cancelled, potentially leading to unauthorized withdrawal or manipulation of funds.
Open the reportThe Chainlink API (latestAnswer) used in the `MarketPriceFeed` contract is deprecated: This API is deprecated. Please see API Reference for the latest Price Feed API. Chainlink Docs [https://web.archive.org/web/20210304160150/https://docs.chain.link/docs/deprecated-aggregatorinterface-api-reference](https://web.archive.org/web/20210304160150/https://docs.chain.link/docs/deprecated-aggregatorinterface-api-reference). This function does not error if no answer has been reached but returns 0. Besides, the latestAnswer is reported with 18 decimals for crypto quotes but 8 decimals for FX quotes (See Chainlink FAQ for more details). A best practice is to get the decimals from the oracles instead of hard-coding them in the contract.
Open the reportThe function ` _shouldExecute` contains a conditional check where `block.timestamp` is expected to be greater than `_createTs.add(IManager(manager).communityExecuteOrderDelay())`. This logic is intended to ensure that a certain amount of time has passed before an action can be executed, but the implementation use incorrect operator.
Open the reportIn the `trade()` function of the `MarketLogic` contract, there's a line that checks the validity of `iParams.price`. This line incorrectly uses `||` (logical OR) to link two conditions that should both hold true, potentially allowing invalid transactions to be processed. ```solidity=124 require((order.takerOpenPriceMin > 0 ? iParams.price >= order.takerOpenPriceMin : true) || (order.takerOpenPriceMax > 0 ? iParams.price <= order.takerOpenPriceMax : true), "MT2"); ```
Open the reportThe function `IWrappedCoin(WETH).withdraw()` unwraps WETH and transfers ETH to the current contract. So please ensure that the function `receive()` is declared in the `Order` contract to successfully receive the ETHs, also ensure the function `_fallback()` is not called in the function `receive()` to avoid the risk of gas inefficiency. ```solidity receive () external payable virtual { // no _fallback(); } ```
Open the reportThe project allows any users to create pool orders with a fee. The `params.maker` parameter is taken from user input, allowing any user to initiate a liquidity removal order that affects another user's liquidity position. This vulnerability could be exploited by a malicious actor to remove liquidity on behalf of another user without their consent, leading to unintended loss of positions.
Open the reportIf the value of the `researveRate` state variable in the `Pool` contract is greater than zero, it indicates that a portion of the funds in the pool needs to be reserved and not lent out. In this scenario, when calculating the maximum lendable funds for a market, this reserved amount should be excluded; otherwise, it would result in an inflated value compared to the actual lendable funds. At line 82 of `FundingLogic.sol`, the code calls the `getMarketAmount()` function to obtain the specified market's maximum lendable funds. Based on the logic of this function, we can infer that the value of the exposure limit `openLimitByRatio` is calculated based on `balance + allMakerFreeze`, without considering the case where `reserveRate` is greater than zero. This leads to an inflated value of `openLimitByRatio` compared to the actual maximum lendable funds in the market. Consequently, if `openLimitByRatio` is smaller than `args.openLimit`, it ultimately results in an underestimated calculation of the `Funding Rate` (refer to line 95 in `FundingLogic.sol`).
Open the reportAt line 83 of the code, the `positionModes` state variable of the `Market` contract is accessed to obtain the current position mode of the taker of the order. However, there is no assignment operation for the local variable `order` before this line of code, so the return value of `order.taker` is `address(0x0)`. As a result, `IMarket(msg.sender).positionModes(order.taker);` always returns the default value, which is the `OneWay` position mode. This causes all takers of executed trading orders to be treated as using the `OneWay` position mode. This is unreasonable.
Open the reportThe passed-in memory data `order` is not updated after executing orders, so the `order.status` is still `Open`. The execution fee can never be charged successfully. ```solidity=269 function _executeOrder(MarketDataStructure.Order memory order, address to) internal { if (order.status == MarketDataStructure.OrderStatus.Open) { (,uint256 positionId) = IMarket(order.market).executeOrder(order.id); EnumerableSet.remove(notExecuteOrderIds[order.taker][order.market], order.id); if (order.status == MarketDataStructure.OrderStatus.Opened || order.status == MarketDataStructure.OrderStatus.OpenFail) { TransferHelper.safeTransferETH(to, order.executeFee); } emit Open(order.market, positionId, order.id); } } ```
Open the reportBased on the logic of the pointed out codes, it can be inferred that the business logic of the code utilizes a voting mechanism to determine the activation or deactivation of the price provided by the off-chain centralized program. However, the functions `disableFastPrice()` and `enableFastPrice()` can only be accessed by the `controller` account, resulting in the state variable `disableFastPriceVotes` only recording the voting status of the `controller` account, and the state variable `disableFastPriceVoteCount` only accumulating the number of votes from the `controller` account. According to the given description, even though the state variable `minAuthorizations` is greater than one, the provided code does not implement a multi-account voting mechanism. Instead, it relies solely on the `controller` account rather than multiple accounts to determine whether to use the price provided by the off-chain centralized program.
Open the reportWhen the native token sent by the user in these functions is greater than the expected value, the remaining part will not be returned to the user.
Open the reportIn the `Pool` contract, if the `params.isOutETH` is true, it means the `Vault` should send ETH to the `RiskFunding` contract. ```solidity=144 _transfer(IManager(manager).riskFunding(), params.toRiskFund, params.isOutETH); ``` However, the `RiskFunding` contract is unable to receive ETH.
Open the reportThe returned value, which tells whether the order is executed successfully, has not been used. If the order execution fails, it is unreasonable to still execute the subsequent logic.
Open the reportThe `Router` contract's functions `setTPSLPrice()`, `switchPositionMode()`, and `setMakerTPSLPrice()` lack the `nonReentrant` modifier, a standard security practice to prevent reentrancy attacks.
Open the reportThe function `takerOpen` allows user to place an open-position order. The comment should be `@notice place an open-position order, margined by both erc20 and eth`, instead of `margined by erc20 tokens`.
Open the report**Router.sol** ```solidity=506 function setMakerTPSLPrice(address _pool, uint256 _positionId, uint256 _profitPrice, uint256 _stopLossPrice) external payable validatePool(_pool) { IPool(_pool).setTPSLPrice(msg.sender, _positionId, _profitPrice, _stopLossPrice); emit SetMakerTPSLPrice(_pool, _positionId, _profitPrice, _stopLossPrice); } ``` **Pool.sol** ```solidity=404 function setTPSLPrice(address maker, uint256 positionId, uint256 tp, uint256 sl) external _onlyRouter { IPool.Position storage position = makerPositions[positionId]; require(!clearAll && position.maker == maker && position.liquidity > 0, "PS0"); position.makerStopLossPrice = sl; position.makerProfitPrice = tp; } ``` In the given contract setup, the `setMakerTPSLPrice()` function in `Router.sol` allows setting TP and SL prices for a maker's position in the `Pool.sol` contract. This setup is crucial for enabling the `PriceProvider` to remove the maker's liquidity based on specified price conditions. However, there is a critical oversight: the absence of validation checks for the TP and SL prices set by the maker. Without these checks, it's possible to set a TP price that is equal to or lower than the SL price. In such cases, given the liquidity removal conditions (`g.sharePrice >= position.makerProfitPrice || g.sharePrice <= position.makerStopLossPrice`), the `PriceProvider` can trigger liquidity removal at any time, potentially leading to unintended liquidity removals. **Router.sol** ```solidity=453 function removeLiquidityBySystem(address _pool, address _maker, bool isReceiveETH) external payable onlyPriceProvider { _removeLiquidity(0, _pool, _maker, type(uint256).max, true, isReceiveETH, true); } ``` **Pool.sol** ```solidity=295 if (isSystem) { vars.isTP = position.makerProfitPrice > 0 ? g.sharePrice >= position.makerProfitPrice : false; vars.aType = vars.isTP ? 1 : 2; require((g.sharePrice <= position.makerStopLossPrice) || vars.isTP, "PRL4"); } ```
Open the reportIn the `SwapMath` library, although the `SafeMath` library is included for secure `uint256` calculations, specific lines fail to utilize the `add()` and `sub()` functions provided by `SafeMath`. This oversight could result in arithmetic overflows and underflows, posing significant security risks.
Open the reportThe smart contract contains a function `_globalHf` that is intended to calculate the pool health status. The pool health factor is a critical measure that indicates whether the pool is safe. If the pool is deemed unsafe, a "clear all" action is triggered to mitigate risks. The current implementation of the `_globalHf` function sets the pool's health status to safe based on the condition `totalAvailableFund > 0`.
Open the reportThe `orderId` starts from 0, and the `triggerOrderID` starts from max of type `uint32`(that is 4294967295). Both market orders and trigger orders are stored in the same order list. As the number of market orders increases, it results in overwritten data for trigger orders within the order list.
Open the reportInvoke the function `setTPSLPrice()` in `Router.sol` to configure the take-profit and stop-loss prices for a particular position. The `liquidator` role account utilizes the function `liquidate()` in `Router.sol` to execute the take-profit or stop-loss action based on the value of the `action` parameter. If the underlying asset's market price meets the criteria for either take-profit or stop-loss, it will generate and execute a market order with `MarketDataStructure.OrderType` set to `Liquidate`, resulting in the user's position quantity becoming zero. There are three questions below: 1. Which function helps the `liquidator` to determine which position has set take-profit and stop-loss prices? 2. How does the `liquidator` role execute take-profit and stop-loss orders for users? Assuming the knowledge of a position with configured take-profit and stop-loss prices, if the price does not meet the take-profit condition and `liquidate()` fails to execute the take-profit, will it proceed to execute the stop-loss by calling `liquidate()` again?
Open the reportThe following code will invoke the `_updateBorrowIG()` function in `Pool` contract to update the long/short borrowing rates of the `Pool` contract. 1. Line 446 of the `_executeLiquidityOrder()` function in the `Router` contract, 2. Line 214 of the `executeAddLiquidityOrder()` function in the `Pool` contract. 3. Line 288 of the `executeRmLiquidityOrder()` function in the `Pool` contract. If it is an add liquidity operation, then steps 1 and 2 are executed, as the code between these two function calls does not modify the contract state, resulting in a redundant update of the long/short borrowing rates. If it is a remove liquidity operation, then steps 1 and 3 are executed, as the code between these two function calls does not modify the contract state, resulting in a redundant update of the long/short borrowing rates.
Open the reportThe assignment of the state variable `sharePrice` at line 226 is redundant code and will be overwritten, as the code at line 233 calculates the latest `baseAsset` total assets and LP token quantity to determine the `sharePrice` of the `Pool` contract after adding liquidity.
Open the reportIt is expected that calls of the form `transfer(recipient, amount)` fail if the recipient address is the zero address.
Open the reportIt is expected that calls of the form `transfer(recipient, amount)` fail if the recipient address is the zero address.
Open the reportIt is expected that calls of the form `transfer(recipient, amount)` fail if the recipient address is the zero address.
Open the reportIt is expected that calls of the form `transferFrom(from, dest, amount)` fail if the `dest` address is zero.
Open the reportIt is expected that calls of the form `transferFrom(from, dest, amount)` fail if the `dest` address is zero.
Open the reportIt is expected that calls of the form `transferFrom(from, dest, amount)` fail if the `dest` address is zero.
Open the reportAccording to the description of the Maintenance Margin, when the maximum leverage in the market is 100x, the maintenance margin rate is set at $0.5\%$. Similarly, when the maximum leverage is 50x, the maintenance margin rate becomes $1\%$. From this information, we can deduce that the calculation formula for the market's maintenance margin rate is $\frac{1}{MaxLeverage} * \frac{1}{2}$. Because initial margin rate equals $\frac{1}{MaxLeverage}$, the initial margin is greater than the maintenance margin, and preventing the liquidation of user positions immediately after opening. However, it seems that the code in line 608 only restricts the market's maintenance margin rate to be greater than zero and less than `1e6`. In this case, the market's maintenance margin rate may not necessarily be equal to $\frac{1}{MaxLeverage} * \frac{1}{2}$, which does not guarantee the prevention of immediate liquidation upon position opening by users.
Open the reportAccording to the code at line 47 and lines 49-52, we can infer that: 1. If the off-chain price has not been updated for longer than `priceDuration`, it is considered an error in the off-chain price feed service, and the `Chainlink` price needs to be adjusted up or down by $2‰$. 2. If the off-chain price has not been updated for longer than `maxPriceUpdateDelay`, it is considered that the off-chain price feed service is inactive, and the `Chainlink` price needs to be adjusted up or down by $5\\%$. However, based on the logic and comments in the `getPrice()` function, we can see the opposite design: 1. If the off-chain price has not been updated for longer than `priceDuration`, the `Chainlink` price is adjusted by $5\\%$. 2. If the off-chain price has not been updated for longer than `maxPriceUpdateDelay`, the `Chainlink` price is adjusted by $2‰$.
Open the reportThe attribute `longMakerFreeze` in the `DataByMarket` data structure represents long position assets, while the attribute `shortMakerFreeze` represents short position assets. However, the comments and variable names express different meanings.
Open the reportThe assignment of the state variable `marketType` occurs only after the `initialize()` function of the `Market` contract is called. Therefore, the validation of the market type through the `checkoutConfig()` function of the `MarketLogic` contract is only effective after the initialization of the `Market` contract. However, in the `createPair()` function of the `Manager` contract, the logic in lines 299 to 301 validates the `Market` contract configuration before the initialization of the `Market` contract. This results in the `marketType` state variable always being zero, causing the validation to fail in line 601 of the `checkoutConfig()` function.
Open the report```solidity=62 require(!marketConfig.updateMarginPaused, "Market:Set price paused"); ``` The correct error message should be `Market: Update margin paused`. --- ```solidity=310 require(position.amount > 0, "Market:order not exist"); ``` According to the logic of the code at lines 309-310, we can infer that its purpose is to verify whether the specified position exists. Therefore, if the boolean expression `position.amount > 0` returns false, the error message should be `Market: position does not exist`.
Open the reportThe `asset` parameter in the `createPair()` function represents the collateral token of the market and should align with the value of the `baseAsset` state variable in the `Pool` contract. However, the current code logic lacks validation for the values of `asset` and `baseAsset`.
Open the reportAddresses should be checked before assignment or external call to make sure they are not zero addresses. ```solidity=29 manager = _manager; ``` - `_manager` is not zero-checked before being used. --- ```solidity=59 manager = _manager; ``` - `_manager` is not zero-checked before being used. --- ```solidity=93 tradeToken = _tradeToken; ``` - `_tradeToken` is not zero-checked before being used. --- ```solidity=99 inviteToken = _inviteToken; ``` - `_inviteToken` is not zero-checked before being used. --- ```solidity=33 fundingLogic = _fundingLogic; ``` - `_fundingLogic` is not zero-checked before being used. --- ```solidity=74 marginAsset = _marginAsset; ``` - `_marginAsset` is not zero-checked before being used. --- ```solidity=75 pool = _pool; ``` - `_pool` is not zero-checked before being used. --- ```solidity=92 fundingLogic = _fundingLogic; ``` - `_fundingLogic` is not zero-checked before being used. --- ```solidity=32 manager = _manager; ``` - `_manager` is not zero-checked before being used. --- ```solidity=37 marketPriceFeed = _marketPriceFeed; ``` - `_marketPriceFeed` is not zero-checked before being used. --- ```solidity=43 WETH = _WETH; ``` - `_WETH` is not zero-checked before being used. --- ```solidity=44 vault = IManager(_manager).vault(); ``` - `_manager` is not zero-checked before being used. --- ```solidity=458 marketPriceFeed = _marketPriceFeed; ``` - `_marketPriceFeed` is not zero-checked before being used. --- ```solidity=22 manager = _manager; ``` - `_manager` is not zero-checked before being used. --- ```solidity=36 rewardAsset = _rewardAsset; ``` - `_rewardAsset` is not zero-checked before being used. --- ```solidity=42 manager = _manager; ``` - `_manager` is not zero-checked before being used. --- ```solidity=43 WETH = _WETH; ``` - `_WETH` is not zero-checked before being used. --- ```solidity=103 manager = _manager; ``` - `_manager` is not zero-checked before being used. --- ```solidity=113 marketPriceFeed = _marketPriceFeed; ``` - `_marketPriceFeed` is not zero-checked before being used. --- ```solidity=58 manager = _manager; ``` - `_manager` is not zero-checked before being used. --- ```solidity=67 chainlinkFlags = _chainlinkFlags; ``` - `_chainlinkFlags` is not zero-checked before being used. --- ```solidity=72 priceHelper = _priceHelper; ``` - `_priceHelper` is not zero-checked before being used. --- ```solidity=94 secondaryPriceFeed = _secondaryPriceFeed; ``` - `_secondaryPriceFeed` is not zero-checked before being used. --- ```solidity=17 manager = _manager; ``` - `_manager` is not zero-checked before being used.
Open the reportThe `removeLiquidity()` function of the `Pool` contract includes the maker address information in its return value. However, the caller of this function, the `Router` contract, only uses the function's return value at lines 464 and 482, without utilizing the `maker` address information. Therefore, the return value can be safely removed.
Open the reportSuch a judgment and requirement is strange and useless. ```solidity=88 if (fundingLogic != address(0)) { require(fundingLogic != address(0), "Market: invalid funding logic contract address"); } ```
Open the reportAccording to the code logic, we know that when the value of `block.timestamp` surpasses the `deadline`, the creation of open/close position orders is prohibited, resulting in the failure of invoking the `takerOpen()/takerClose()/takerOpenETH()` functions. We want to know more information about the `deadline` variable.
Open the reportThe state variable `sharePrice` represents the value of an `LP` token in terms of the `baseAsset`. In the following two scenarios, `totalSupply` is zero, but the value of `sharePrice` is different. 1. When the state variable `sharePrice` is initialized to zero, it indicates that the `Pool` contract has been deployed and `totalSupply` is also zero. 2. In the `executeRmLiquidityOrder()` function, lines 313-317, when liquidity is removed and `totalSupply` becomes zero, the value of the state variable `sharePrice` is set to `1e10`. This may result in erroneous display of interface data.
Open the report```solidity mapping(int8 => IPool.InterestData) public interestDate; ``` It seems the variable `interestDate` is more suitable to be named as `interestData`.
Open the reportThe return value of the transfer()/transferFrom() call is not checked. ```solidity=195 if (params.order.freezeMargin > 0) IERC20(marginAsset).transfer(IManager(manager).vault(), params.order.freezeMargin); ``` --- ```solidity=435 if (order.freezeMargin > 0) IERC20(marginAsset).transfer(msg.sender, order.freezeMargin); ```
Open the report```solidity _transfer(params.inviter, params.feeToInviter, baseAsset == WETH ? true : false); ``` Using the conditional operator to set true or false is unnecessary.
Open the report```solidity=216 uint256 amount = _liquidity.mul(uint256(int256(allMakerFreeze).add(int256(balance)).add(totalUnPNL).add(allMarketPos.makerFundingPayment))).div(totalSupply); ``` - The type conversion `int256(allMakerFreeze)` from type uint256 to type int256 may flip the value's sign. --- ```solidity=209 (, int256 _p, , ,) = priceFeed.getRoundData(uint80(roundId - i)); ``` - The type conversion `uint80(roundId - i)` from larger type uint256 to smaller type uint80 may truncate data.
Open the reportAll markets adopt a uniform order execution fee. The parameter `_market` is not utilized in the function `getExecuteOrderFee()`.
Open the reportSome events are never emitted, which can lead to confusion and code maintainability issues. ```solidity=47 event SetReferrerDiscountShare(address referrer, uint256 discountShare); ``` - `SetReferrerDiscountShare` is declared in `InviteManager` but never emitted. --- ```solidity=43 event SetPaused(bool _open, bool _set, bool _openTrigger); ``` - `SetPaused` is declared in `MarketStorage` but never emitted. --- ```solidity=76 event SetValidationLogic(address validationLogic); ``` - `SetValidationLogic` is declared in `PoolStorage` but never emitted. --- ```solidity=32 event SetManager(address _manager); ``` - `SetManager` is declared in `Vault` but never emitted. --- ```solidity=72 event SetIndexMaxDeviationBasisPoints(uint256 _indexMaxDeviationBasisPoints); ``` - `SetIndexMaxDeviationBasisPoints` is declared in `FastPriceFeed` but never emitted.
Open the reportThese functions can be replaced by the modifier `onlyController`, for a more consistent coding style.
Open the reportThe name of a function or state field with `public` or `external` visibility should not start with an underscore, "`_`".
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="yfx"></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