Onyx Protocol security record

As of 21 Oct 2026, Onyx Protocol has 9 audit reports by 2 security firms on Trustblock; the latest is by PeckShield, dated 21 Sep 2025. Those reports list no open critical findings. It also holds 1 deployed contract on 1 chain and 1 incident (latest 31 Oct 2023).

Critical findings
No open critical findings
Across 9 reports.
Incident
Incident 31 Oct 2023
Source: De.Fi incident database. 3 audits have been published since. See the incident
Open the latest report
Your label, live
Get the label for your site

Record last updated . It holds audits and findings, contracts and incidents up to Oct 2026. Trustblock does not review code.

Audits9

Newest first

AuditSecurity firmDateScopeCritical findingsReport
Onyx: Onyx DAO Registry auditSecurity firm: PeckShieldDate: Scope: Not recorded (see the report)No open critical findingsOpen · GitHub
Onyx auditSecurity firm: PeckShieldDate: Scope: Not recorded (see the report)No open critical findingsOpen · GitHub
XCN Token (Base) - AuditSecurity firm: CertiKDate: Scope: Not recorded (see the report)No open critical findingsOpen · skynet.certik.com
Onyx Protocol - AuditSecurity firm: CertiKDate: Scope: Files named in the reportNo open critical findingsOpen · skynet.certik.com
XCN Swap - AuditSecurity firm: CertiKDate: Scope: Files named in the reportNo open critical findingsOpen · skynet.certik.com
Chain StakingSecurity firm: CertiKDate: Scope: Files named in the reportNo open critical findingsOpen · skynet.certik.com
Chain - XCN TokenSecurity firm: CertiKDate: Scope: Files named in the reportNo open critical findingsOpen · skynet.certik.com
Chain Team Treasury TimelockSecurity firm: CertiKDate: Scope: Files named in the reportNo open critical findingsOpen · skynet.certik.com
Chain TokenSecurity firm: CertiKDate: Scope: 1 address on EthereumNo open critical findingsOpen · skynet.certik.com

Findings62

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

SeverityOpenAcknowledgedFixed
Critical000
High724
Medium404
Low21317
High`_addReserves` function is not disabled in OErc7Onyx Protocol - Audit · CertiK · 28 Feb 2023Fixed

The `_addReserves()` function accrues interest and adds reserves by transferring assets from the sender. The formula for the calculation of `exchangeRate` is as follows: $$ exchangeRate = {totalCash + totalBorrows - totalReserves \over totalSupply } $$ ```solidity=342 function exchangeRateStoredInternal() internal view returns (MathError, uint) { uint _totalSupply = totalSupply; if (_totalSupply == 0) { /* * If there are no tokens minted: * exchangeRate = initialExchangeRate */ return (MathError.NO_ERROR, initialExchangeRateMantissa); } else { /* * Otherwise: * exchangeRate = (totalCash + totalBorrows - totalReserves) / totalSupply */ uint totalCash = getCashPrior(); uint cashPlusBorrowsMinusReserves; Exp memory exchangeRate; MathError mathErr; (mathErr, cashPlusBorrowsMinusReserves) = addThenSubUInt(totalCash, totalBorrows, totalReserves); if (mathErr != MathError.NO_ERROR) { return (mathErr, 0); } (mathErr, exchangeRate) = getExp(cashPlusBorrowsMinusReserves, _totalSupply); if (mathErr != MathError.NO_ERROR) { return (mathErr, 0); } return (MathError.NO_ERROR, exchangeRate.mantissa); } } ``` The `getCashPrior()` function returns `totalSupply`, the NFT can not be borrowed, so the `totalBorrows` is always 0. Based on the logic of functions `mintFresh()` and `redeemFreshOToken()`, it can be inferred that the exchange rate between the underlying asset and the token is 1:1. If the `_addReserves()` function is not disabled, the exchange rate will decrease, the return value `redeemAmount` at line 633 in the `OTokenEx` contract may be 0 when the sender redeems underlying asset using the function `redeem()`/`redeems()`. Worse, if `totalReserves` equals `totalCash`, the exchange rate will be 0 and the sender will not be able to mint/redeem.

Open the report
HighCentralization Related RisksXCN Swap - Audit · CertiK · 18 Aug 2022Fixed

In the contract `Swap.sol`, the role `Owner` has authority over the following functions: - withdraw() - setPrice() Any compromise to the `Owner` account may allow a hacker to take advantage of this authority and withdraw all the MDT tokens and modify the critical configuration in the contract.

Open the report
HighMissing Moving Delegates in Function `emergencyWiChain Staking · CertiK · 28 Mar 2022Fixed

Function `emergencyWithdraw()` does not trigger the `_moveDelegates()` function like the `withdraw()` function does.

Open the report
HighProposal FailureChain Staking · CertiK · 28 Mar 2022Fixed

The function `getPriorVotes()` contains a `require()` statement that the input `blockNumber` is smaller than the current block number. ```solidity=310 function getPriorVotes(uint256 _pid, address account, uint blockNumber) public view returns (uint256) { require(blockNumber < block.number, "Comp::getPriorVotes: not yet determined"); ... ``` However, in the `propose()` function in the `CHNGovernance` contract, the following lines of code call the `getPriorVotes()` function with `blockNumber` set as `block.number`, which would cause the aforementioned `require()` statement to fail. ```solidity function getVotingPower(address user, uint blockNumber) public view returns (uint256) { return chnTokenStaking.getPriorVotes(poolId, user, blockNumber); } function propose(address[] memory targets, uint[] memory values, string[] memory signatures, bytes[] memory calldatas, string memory description) public returns (uint) { require(getVotingPower(msg.sender, block.number) >= proposalThreshold, "GovernorAlpha::propose: proposer votes below proposal threshold"); ... ```

Open the report
MediumArray Index Not CheckOnyx Protocol - Audit · CertiK · 28 Feb 2023Fixed

The input parameter `tokenIndex` is not checked for a valid array index. If the `tokenIndex` is not correct, the `doTransferOut()` function will revert and the user can not redeem the underlying asset.

Open the report
MediumLack Of Access ControlOnyx Protocol - Audit · CertiK · 28 Feb 2023Fixed

The functions `setUnderlyingPrice()` and `setDirectPrice()` can be called by anyone as it has no access restriction. This enables anyone to call this and update the `prices` configuration.

Open the report
MediumPotential Reentrancy AttackOnyx Protocol - Audit · CertiK · 28 Feb 2023Fixed

The following codes in the function `redeemFresh()/borrowFresh()` do not meet the Checks-Effects-Interactions pattern. ```solidity=787 doTransferOut(borrower, borrowAmount); /* We write the previously calculated values into storage */ accountBorrows[borrower].principal = vars.accountBorrowsNew; accountBorrows[borrower].interestIndex = borrowIndex; totalBorrows = vars.totalBorrowsNew; ``` It only has a reentrancy lock as there is no lock at the Controller level, only the OToken level. If the `OToken` is an ERC777/ERC677 protocol, the reentrancy can happen in function levels of an ERC777/ERC677-based contract, i.e. multiple function calls that are triggered by the hook mechanism of ERC777/ERC677. This issue is possible to happen with all compound forks, but Compound is not affected as they do not list tokens with callback functionality.

Open the report
MediumPotential Loss of Pool RewardsChain Staking · CertiK · 28 Mar 2022Fixed

In the `add()` and `set()` functions, the flag `_withUpdate` determines if all the pools will be updated. This reliance might lead to significant loss of the reward. For illustration, assume we have only one pool with `pool.allocPoint == 50` and `totalAllocPoint == 50` at the beginning. Now we want to add another pool with `pool.allocPoint == 50`. There will be two scenarios on calculating the pool reward, Case 1: `_withUpdate` is set to `true`. - distribute the reward and update the pool. - add the given pool information Case 2: `_withUpdate` is set to `false`. - add the given pool information (Note: While we focused on the `add()` function, both the `add()` and `set()` functions update `totalAllocPoint`, which is used in calculation of pool rewards in the function `updatePool()` * In Case 1, reward for the first pool is updated in the call to `updatePool()` where `reward = multiplier.mul(rewardPerBlock).mul(pool.allocPoint).div(totalAllocPoint)`. * In Case 2, an update `totalAllocPoint = totalAllocPoint.add(_allocPoint)` is done first. Then `updatePool()` calculates the reward for the first pool: `reward = multiplier.mul(rewardPerBlock).mul(pool.allocPoint).div(totalAllocPoint);` Because the second pool is sharing rewards with the first one, the amount of reward for the first pool becomes half as much as that in the first case.

Open the report
Low`totalReserves` Update errorOnyx Protocol - Audit · CertiK · 28 Feb 2023Fixed

The `_reduceReservesFresh()` function reduces reserves by transferring to admin. However, the second argument of `doTransferOut()` function is the index of the token, not the reduction amount. Additionally, the `doTransferOut()` function can only transfer one NFT at a time. As a result, the reduction amount should always be 1, and the admin can only transfer their own tokens.

Open the report
LowInclude unlisted tokensOnyx Protocol - Audit · CertiK · 28 Feb 2023Fixed

The `claimXcn()` function of the `Comptroller` contract requires that the token must be listed. However, the `pendingXcn()` function does not exclude the unlisted token. The same issue exists with the function `getDailyXCN()`.

Open the report
Show 10 more
LowInconsistent with the logic of the `distributeSupOnyx Protocol - Audit · CertiK · 28 Feb 2023Fixed

In the `distributeSupplierXcn()` function of the `Comptroller` contract, line 1276 covers the case where users supplied tokens before the market's supply state index was set. Rewards the user with `xcnInitialIndex` accrued from the start of when supplier rewards were first set for the market. So the `distributeSupplierXcn()` function of the `OnyxLens` contract should use the same comparison conditions.

Open the report
LowMissing Zero Address ValidationOnyx Protocol - Audit · CertiK · 28 Feb 2023Fixed

Addresses should be checked before assignment or external call to make sure they are not zero addresses. ```solidity=51 admin = admin_; ``` - `admin_` is not zero-checked before being used. --- ```solidity=68 implementation = implementation_; ``` - `implementation_` is not zero-checked before being used. --- ```solidity=52 admin = admin_; ``` - `admin_` is not zero-checked before being used. --- ```solidity=69 implementation = implementation_; ``` - `implementation_` is not zero-checked before being used. --- ```solidity=36 target = target_; ``` - `target_` is not zero-checked before being used. --- ```solidity=415 MINT_BASE_TOKEN = mintBaseToken; ``` - `mintBaseToken` is not zero-checked before being used.

Open the report
LowRedundant CodeOnyx Protocol - Audit · CertiK · 28 Feb 2023Fixed

In the `oErc721` contract, the sender cannot borrow the assets, so the aforementioned lines are redundant since the `totalBorrows` is always 0.

Open the report
LowReturn a different XCN token addressOnyx Protocol - Audit · CertiK · 28 Feb 2023Fixed

In the `Comptroller` contract, the function `getXcnAddress()` returns the address `0xc03Fa5e041BE9C8deD030D6dec7936cE67D78ae8` as the XCN token address. However, in the `ComptrollerG3`, `ComptrollerG4`, and `ComptrollerG5` contracts, this function returns the address `0xA2cd3D43c775978A96BdBf12d733D5A1ED94fb18`.

Open the report
LowUnimplemented function `_setMigration()`Onyx Protocol - Audit · CertiK · 28 Feb 2023Fixed

The function `_setMigration()` is not implemented in the implementation contract `OErc721`.

Open the report
LowInconsistent exchange ratio descriptionXCN Swap - Audit · CertiK · 18 Aug 2022Fixed

The comment in line 18 and the formula in line 38 show exchange ratio for XCN and MDT is 1 to 1. ```sol= uint256 public mdtPrice = 1000000000000000000; // 1 XCN = 1 MDT ... uint256 _xcnAmount = _amount.mul(mdtPrice).div(10**PRICE_DECIMALS); ``` However, the comment in line 38 shows the ratio is `MDT = 0.5 XCN`. ```sol uint256 _xcnAmount = _amount.mul(mdtPrice).div(10**PRICE_DECIMALS); // Same decimals, and MDT = 0.5 XCN ``` The exchange ratio is inconsistent across the codebase.

Open the report
LowToo Many DigitsXCN Swap - Audit · CertiK · 18 Aug 2022Fixed

Literals with many digits are difficult to read and review.

Open the report
LowUnnecessary Use of SafeMathXCN Swap - Audit · CertiK · 18 Aug 2022Fixed

The `SafeMath` library is used unnecessarily. With Solidity compiler versions 0.8.0 or newer, arithmetic operations will automatically revert in case of integer overflow or underflow.

Open the report
LowFunction `add()` NOT RestrictedChain Staking · CertiK · 28 Mar 2022Fixed

The comments above the `add()` function express the documented constraint that the same stake token should not be added more than once with the `add()` function. The current implementation is relying on the `owner` to avoid repeatedly adding the same stake token to the pool, as the function will only be called by the owner."

Open the report
LowIncorrect Error MessageChain Staking · CertiK · 28 Mar 2022Fixed

```solidity=80 require(_rewardPerBlock < _rewardToken.totalSupply(), "Reward per block less than token total supply"); ``` The error message above is incorrect.

Open the report
Show 5 more
LowMissing Explicit Pool Validity ChecksChain Staking · CertiK · 28 Mar 2022Fixed

There are no checks to validate if a `_pid` exists in the following functions: - `set()` - `pendingReward()` - `updatePool()` - `stake()` - `withdraw()` - `emergencyWithdraw()` - `claimRewardFromVault()`

Open the report
LowPotential Wrong Return Values of Function `getMulChain Staking · CertiK · 28 Mar 2022Fixed

```solidity=112 function getMultiplier(uint256 _from, uint256 _to) public view returns (uint256) { if (_to <= bonusEndBlock) { return _to.sub(_from).mul(BONUS_MULTIPLIER); } else if (_from >= bonusEndBlock) { return _to.sub(_from); } else { return bonusEndBlock.sub(_from).mul(BONUS_MULTIPLIER).add( _to.sub(bonusEndBlock) ); } } ``` Function call of `getMultiplier()` may return a wrong result if inputted block number, `_from`, is smaller than `startBlock`. This does NOT affect the core functions because they only use block numbers that are bigger than `startBlock` when calling function `getMultiplier()`. But wrong return values of function `getMultiplier()` may still misguide users.

Open the report
LowFork LeftoversChain - XCN Token · CertiK · 6 Mar 2022Fixed

The linked error messages use `COMP` as the project name, which is fork leftover. ```solidity=554 require(signatory != address(0), "Comp::delegateBySig: invalid signature"); require(nonce == nonces[signatory]++, "Comp::delegateBySig: invalid nonce"); require(now <= expiry, "Comp::delegateBySig: signature expired"); ```

Open the report
LowStrict ConditionalChain - XCN Token · CertiK · 6 Mar 2022Fixed

The linked conditional conducts a strict comparison between `value` and ChainToken balance. However, the check should be passed when `balanceOf(msg.sender)` = `value`.

Open the report
LowUnlocked Compiler VersionChain - XCN Token · CertiK · 6 Mar 2022Fixed

The contract has unlocked compiler version. An unlocked compiler version in the source code of the contract permits the user to compile it at or above a particular version. This, in turn, leads to differences in the generated bytecode between compilations due to different compiler versions. This can lead to an ambiguity when debugging as compiler specific bugs may occur in the codebase that would be hard to identify over a span of multiple compiler versions rather than a specific one.

Open the report

Incidents1

Attached through a shared contract address. Our incident data ends in Oct 2026.

Incident

Incident,

29 ETH.

Source: De.Fi incident database.

Audits before it
6.
Audits since
3, the latest dated 21 Sep 2025.

Contracts1

AddressChainSource code
0x41c37a4683d6a05adb31c39d71348a8403b13ca9Chain: EthereumSource code: Verified source

All 1 address and the files the reports name

Show this on your site

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.

Content

Facts

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.

Fixed as
Language

Style

Layout
Theme
Accent
Corners

Format

Size
Date
Embed as
HTML
<head>
<script src="https://widget.trustblock.run/project-widget.min.js" defer></script>
</head>
<body>
<div
data-tb-project-widget
data-tb-project-slug="onyx-protocol"
></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