As of 14 Oct 2026, ICHI has 1 audit report on Trustblock, by Quantstamp, dated 24 May 2021. That report lists no open critical findings.
Record last updated . It holds audits and findings, contracts and incidents up to Oct 2026. Statuses are the security firms’ own. Trustblock does not review code.
Newest first
| Audit | Security firm | Date | Scope | Critical findings | Report |
|---|---|---|---|---|---|
| Ichi | 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 | 0 |
| High | 0 | 0 | 4 |
| Medium | 0 | 1 | 3 |
| Low | 0 | 7 | 14 |
The function `mint` adds a fee to the collateral USD value on L116: `collateralUSDValue = collateralUSDValue.add(collateralUSDValue.mul(mintingFee).div(PRECISION));`. If the member token allowance is too low, the `collateralUSDValue` is recomputed in L125-130 based on the amount of member tokens that the user can provide. However, the fee is never re-applied to this new amount. Rational minters would always skip paying the fee by providing an allowance of 1 wei less than the required member token value.
Open the reportThe `redeem` function updates the `collateral` oracle price but does not consult it for a price. Instead, collateral is redeemed 1-to-1 but collateral could be off-peg. As `mint` consults the collateral oracle price, but `redeem` treats them as pegged, there is arbitrage potential and the collateral reserve could be emptied.
Open the reportThe `redeem` function takes an `amount` parameter reflecting oneTokens (18 decimals) to be redeemed. This amount is added to the user's **collateral** balance but the collateral token could have a different number of decimals, like USDT/USDC (6 decimals), netting a 10^12 profit and allowing to withdraw the whole reserve. ```solidity uint netTokens = amount.sub(amount.mul(redemptionFee).div(PRECISION)); // div by precision because of fee // increases collateral with oneToken amount increaseUserBalance(msg.sender, collateral, netTokens); ```
Open the reportIt's not clear what precision the `amountUsd` argument of the `amountRequired` function is supposed to be. For `ICHIPeggedOracle`, it always returns the same precision as `amountUsd` which leads to a bug when used in this `OneTokenV1.mint` call: ```solidity (uint collateralTokensReq, /* volatility */) = IOracle(assets[collateralToken].oracle).amountRequired(collateralToken, collateralUSDValue); ``` `collateralUSDValue` is in `oneToken` precision (18 decimals) but when used with another stablecoin like USDT or USDC (6 decimals) the pegged oracle returns 10^12 times the stable coin amount actually required. The same issue exists for `UniswapOracleSimple.amountRequired` where it seems like `amountUsd` should be in `indexToken` precision to return the correct amount in `token`.
Open the reportThere is no call to `closeStrategy(token)` in `removeStrategy`, which may cause tokens to be locked.
Open the reportThe function appears to be copied from `ICHIPeggedOracle.sol`, however a 1:1 ratio between input and output tokens is not guaranteed in a composite oracle.
Open the report`UniswapOracleSimple.update` does not check if the pair has been initialized and will store wrong prices as the average prices will be computed since 1970. (time elapsed equals block timestamp.) The `pairInfo` function will return this wrong price, reading the price through `read`/`consult` will fail though.
Open the report`Incremental` allows changing settings for several oneTokens. However, its events don't indicate the `oneToken` the settings were changed for which makes it hard for a backend to associate settings changes with the `oneToken`.
Open the reportThe `mintingRatio` might never reach its min or max ratio as it is only changed by `stepSize` and not clamped, see `Incremental.getMintingRatio4`.
Open the reportThe function always sets the allowance to `INFINITE`: `IStrategy(strategy).setAllowance(token, INFINITE);`, regardless of the `allowance` parameter.
Open the reportAlthough the function is only intended to make a small step to the ratio value, the function can be called repeatedly (even in the same transaction). This could potentially be used if an arbitrage attack could benefit from changing the ratio significantly beyond the step size.
Open the report`OneTokenV1Base.closeStrategy` should revoke the approval of an old strategy (similar to how `setStrategy` sets approval) to shut down vulnerable strategies from causing havoc. The `@dev` comment talks about resetting the approval but it's not done: > @dev strategy remains assigned the asset with allowance set to 0.
Open the reportThe contract `OneTokenV1.sol` appears to exceed 24576 bytes, and may have issues during deployment without further optimization.
Open the reportThe documentation mentions using stable coins as collateral but some common ERC20 stablecoins, like USDT, do not correctly implement the ERC20 specification as the functions don't return a boolean. This will lead to reverts when interacting with them, for instance here in `OneTokenV1.mint`: ```solidity IERC20(collateralToken).transferFrom ```
Open the reportThe function `setMinRatio` has the constraint `minRatio <= p.maxRatio`, whereas `setMaxRatio` uses `maxRatio > p.minRatio`. It is unclear from these two functions whether `maxRatio == p.minRatio` should be allowed.
Open the reportIn `mint` there is a comment saying: ```solidity // this will also update the member token oracle price history (uint mintingRatio, uint maxOrderVolume) = updateMintingRatio(collateralToken); ``` This is not true unless `MintMaster.oneTokenOracles[oneToken]` always equals the member token oracle defined in the version contract `OneTokenV1.assets[memberToken].oracle)`. The oracle is not updated before performing the `IOracle(assets[memberToken].oracle).amountRequired(memberToken, memberTokensUSDValue)` call which could allow minting undercollateralized oneTokens and redeeming them for a profit.
Open the reportThe minting fee in `mint` is only paid on the collateral value, not on the member token value, i.e., not on the whole oneToken value.
Open the reportIn `OneTokenFactory.sol`, the function `assignOracle` invokes the Oracle's `init` function followed by `update`. However, `admitForeignToken` only invokes `init`. A similar pattern occurs in `OneTokenV1Base.sol`, where `changeMintMaster` does not invoke `update` unlike in `init`.
Open the reportThe function `setStepSize` ensures that it "must be smaller than the difference of min and max". However, `setMinRatio` and `setMaxRatio` do not have similar checks. If the difference is set too small, this invariant will no longer hold.
Open the reportThe following functions should perform additional checks on their arguments: 1. `OracleCommon.constructor` should check that `oneTokenFactory_` and `indexToken_` are non-zero. 1. In `UniswapOracleSimple.constructor`, `indexToken_` and `period_` should be non-zero. This would also handle the same missing check in `ICHICompositeOracle.constructor`. 1. `ICHIModuleCommon.updateDescription` does not check if `description` length is non-zero, but the constructor does. 1. `Incremental._updateMintingRatio` should check `p.set`. 1. `OneTokenV1Base.setStrategyAllowance` should check if `assets[token].strategy` exists. Otherwise, a null strategy is approved. 1. `OneTokenFactory.assignOracle` does not check `isModule(oracle)` but `admitForeignToken` does.
Open the report`StrategyCommon` declares a virtual function `execute`, whereas `Arbitrary` declares `executeTransaction`. It is further unclear how `Arbitrary.executeTransaction` relates to `OneTokenV1Base.executeStrategy`, which invokes `IStrategy(strategy).execute();`. If the strategy is of type `Arbitrary`, it appears that this function call will do nothing. Note that `OneTokenV1Base.executeStrategy` also emits a `StrategyExecuted` event, so there may be duplicate events emitted. While the `Arbitrary.executeTransaction` function does not emit a `StrategyExecuted` event, the inherited `execute` function does.
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="ichi"></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