View Audit Report
This document presents the results of the security review conducted by Nethermind Security for Nuxaris LLC. The focus of the engagement has been the CBNBRouter and CBNBVault contracts deployed on BNB Smart Chain, which hold the BNB backing cBNB, a 1:1 representation of BNB issued on the Canton Network. On BSC mainnet, CBNBRouter is deployed at 0x74B4052AE403E0787DfC1dA8a1247dD950755C28 and CBNBVault at 0x38c147415e59196aEBf3010dd0BAA9A7e5Bf9196.
Whitelisted depositors lock BNB on BSC, an off-chain relayer mints the same amount of cBNB on Canton after the deposit is finalized, and on redemption the relayer burns the cBNB on Canton before a multisig releases the BNB on BSC.
The audit comprises 398 lines of Solidity code. The audit was performed using (a) manual analysis of the codebase, and (b) automated analysis tools.
Findings by severity
| # | Contract | LoC | Comments | Ratio | Blank | Total |
| 1 | src/CBNBRouter.sol | 252 | 84 | 33% | 65 | 400 |
| 2 | src/CBNBVault.sol | 146 | 49 | 33% | 35 | 230 |
| Total | 398 | 133 | 33.4% | 100 | 630 |
| # | Finding | Severity | Update |
| 1 | Deposits have no depositor-side bound on fees, so a concurrent fee update can silently consume part of the deposit | Low | Acknowledged |
| 2 | Deposit message IDs are only unique within a single vault deployment | Info | Acknowledged |
The system consists of two contracts on BNB Smart Chain, CBNBRouter and CBNBVault, operated by three Safe multisigs: the Admin Safe, which holds the admin role on both contracts, the Manager Safe, which holds the manager role on the router, and the Relayer Safe, which holds the relayer role on the router. On BSC mainnet, CBNBRouter is deployed at 0x74B4052AE403E0787DfC1dA8a1247dD950755C28 and CBNBVault at 0x38c147415e59196aEBf3010dd0BAA9A7e5Bf9196.
CBNBRouter is the user-facing entry point. It restricts deposits to a whitelist that the Manager Safe maintains, deducts a fixed fee and a proportional fee capped at 10%, enforces a minimum net amount, and forwards the remainder to the vault. The fees stay in the router as a tracked balance that only the Admin Safe can collect, and the native-BNB sweep refuses any amount that would dip into it. The router also forwards release requests from the Relayer Safe to the vault, holds the destination chain and token configuration, and exposes separate pause flags for deposits and withdrawals that the Manager can set but only the Admin can clear.
CBNBVault is the custody contract and accepts calls only from the router the Admin Safe has configured. On each deposit it derives a messageId from the depositor, chain ID, an incrementing nonce, the net amount, the destination and the recipient, and emits it for the relayer. On each release it marks the messageId as processed so no request can be paid twice, enforces an optional per-UTC-day release limit, and transfers the BNB. Its sweep function moves ERC-20 tokens only and cannot move native BNB.
The router's reference to the vault is immutable. The vault's reference to the router is a storage variable that only the vault admin can change.
CBNBRouter.deposit(recipient) with BNB as msg.value. recipient is the buyer's Canton party ID. The router requires the caller to be whitelisted, deposits to be unpaused, and the destination to be configured.cryptoFee and the proportional protocolFeeBps (capped at 10%), requires the remainder to be at least minAmount, adds the fees to feeBalance, and forwards the remainder to CBNBVault.deposit.messageId from the depositor, chain ID, an incrementing nonce, the net amount, the destination and the recipient, and emits Deposit.cbnb-redeem party with the request ID as memo.finalizeWithdraw(messageId, destination, amount) with messageId = keccak256("cbnb-redeem:" + requestId). Two Relayer Safe owners sign and execute it.RELAYER_ROLE, then calls CBNBVault.release.messageId as processed (reverting if already seen), enforces the per-UTC-day release limit if it is non-zero, transfers the BNB and emits Withdraw.The risk rating methodology used by Nethermind Security follows the principles established by the OWASP Foundation. The severity of each finding is determined by two factors: Likelihood and Impact.
Likelihood measures how likely the finding is to be uncovered and exploited by an attacker. This factor will be one of the following values:
When defining the likelihood of a finding, other factors are also considered. These can include but are not limited to motive, opportunity, exploit accessibility, ease of discovery, and ease of exploit.
Impact is a measure of the damage that may be caused if an attacker exploits the finding. This factor will be one of the following values:
When defining the impact of a finding, other factors are also considered. These can include but are not limited to data and state integrity, loss of availability, financial loss, and reputation damage. After defining the likelihood and impact of an issue, the severity is determined according to the table below.
| Impact / Likelihood | Low | Medium | High |
| High | Medium | High | Critical |
| Medium | Low | Medium | High |
| Low | Info / Best Practices | Low | Medium |
| Undetermined | Undetermined | Undetermined | Undetermined |
To address issues that do not fit a High, Medium or Low severity, Nethermind Security also uses three more finding severities: Informational, Best Practices, and Undetermined.
| File | Fix commit | Type | Status |
src/CBNBRouter.sol | — | Low | Acknowledged |
Whitelisted depositors lock BNB by calling the deposit(...) function of the CBNBRouter contract. They send the BNB as msg.value, and the router deducts two fees whose values are read from storage at execution time: the fixed cryptoFee and the proportional protocolFeeBps. The router forwards the remainder to the vault. The relayer mints exactly this remainder as cBNB on Canton. The fees stay in the router as feeBalance, which only the admin can collect.
function deposit(bytes calldata _recipient)
// ...
{
// ...
if (msg.value <= cryptoFee) {
revert InvalidCryptoFeeAmount();
}
// @audit-issue The fee values are read at execution time, and the depositor cannot bound them.
uint256 depositAmount = msg.value - cryptoFee;
uint256 protocolFee = _calculateFee(depositAmount);
uint256 amountAfterFees = depositAmount - protocolFee;
// @audit The only check on the net amount is the protocol-controlled minimum.
if (amountAfterFees < minAmount) {
revert AmountBelowMinimum();
}
uint256 totalFees = cryptoFee + protocolFee;
if (totalFees > 0) {
feeBalance += totalFees;
}
messageId = vault.deposit{value: amountAfterFees}(/* ... */);
}The Manager Safe or the Admin Safe can change both fees at any time through the setFees(...) function. The change takes effect immediately, with no delay. Only protocolFeeBps has an upper bound (10%); cryptoFee has none.
function setFees(uint256 _newCryptoFee, uint16 _newProtocolFeeBps)
external
onlyManagerOrAdmin
{
if (_newProtocolFeeBps > MAX_PROTOCOL_FEE_BPS) {
revert FeeTooHigh();
}
// @audit-issue There is no upper bound on the fixed fee and no delay before it applies.
cryptoFee = _newCryptoFee;
protocolFeeBps = _newProtocolFeeBps;
emit FeesUpdated(_newCryptoFee, _newProtocolFeeBps);
}The deposit(...) function does not let the depositor state the fees they agreed to or the minimum amount they expect to have bridged. The only check on the net amount is minAmount, and the protocol sets it, not the depositor. If a fee update is executed before a pending deposit, whether in the same block or while the deposit is waiting to be mined, the deposit still succeeds. It is charged the new fees as long as the remainder stays at or above minAmount. This needs no malicious intent: any routine fee change, such as the first move away from the current 0 / 0 configuration, applies to every deposit that was signed under the old fees and is mined after the update.
Recommendation
Consider giving depositors a way to make sure that a deposit is only charged the fees they agreed to, for example by binding each deposit to an expected fee or a minimum bridged amount. If the deployed contracts are to stay as they are, consider an operational procedure that keeps fee updates from overlapping with pending deposits (for example, pausing deposits, waiting until pending deposits are mined or dropped, and only then updating the fees), and communicating fee changes to whitelisted depositors before they apply. Also consider placing an upper bound on cryptoFee.
Resolution
Only Nuxaris-controlled addresses are whitelisted depositors, therefore this cannot affect external depositors.
| File | Fix commit | Type | Status |
src/CBNBVault.sol | — | Info | Acknowledged |
Whitelisted depositors bridge BNB to Canton by calling the deposit(...) function of the CBNBRouter contract, which forwards the net amount to the deposit(...) function of the CBNBVault contract. The vault derives a messageId for every deposit and emits it in the Deposit event. The off-chain relayer watches these events and, once the block is finalized, mints the same amount of cBNB on Canton. The NatSpec describes messageId as the identifier "used for idempotent processing", and the README states that it "is unique per deposit, because it includes the vault nonce".
/// @return messageId Unique message identifier used for idempotent processing
function deposit(...)
external
payable
onlyRouter
returns (bytes32 messageId)
{
// ...
messageId = _nextMessageId(_sender, msg.value, _dstChainID, _dstOutputToken, _recipient);
// @audit The relayer reads the messageId from this event.
emit Deposit(messageId, /* ... */);
}The _nextMessageId(...) function computes the messageId as the hash of the depositor, the chain ID, the vault's nonce, the net amount, the destination and the recipient. The nonce is incremented on every deposit, so two deposits into the same vault always receive different IDs, even when all their other parameters are identical.
function _nextMessageId(
address _sender,
uint256 _amount,
uint256 _dstChainID,
bytes32 _dstOutputToken,
bytes memory _recipient
) internal returns (bytes32) {
// @audit-issue The nonce is vault storage and starts at 0 in every new vault.
uint256 currentNonce = nonce++;
// @audit-issue None of the hashed fields identifies the vault instance.
return keccak256(
abi.encodePacked(
_sender,
block.chainid,
currentNonce,
_amount,
_dstChainID,
_dstOutputToken,
_recipient
)
);
}However, this uniqueness only holds within a single vault instance. The nonce is a storage variable of the vault and starts at 0 in every newly deployed vault, and none of the hashed fields identifies the vault itself. If the vault is redeployed on the same chain for any reason, its nonce does not continue from the previous vault's nonce. The k-th deposit into the new vault then receives exactly the same messageId as the k-th deposit into the old vault whenever both have the same depositor, net amount, destination and recipient. This breaks the uniqueness and idempotency guarantee of messageId, so any off-chain infrastructure or process that relies on it, such as the relayer, would be working on a wrong assumption.
Recommendation
Consider accounting for this limitation in any future vault deployment or migration, so that deposit message IDs remain unique across all vault instances.
Resolution
Only Nuxaris-controlled addresses are whitelisted depositors, therefore this cannot affect external depositors.
Software documentation refers to the written or visual information that describes the functionality, architecture, design, and implementation of software. It provides a comprehensive overview of the software system and helps users, developers, and stakeholders understand how the software works, how to use it, and how to maintain it. Software documentation can take different forms, such as user manuals, system manuals, technical specifications, requirements documents, design documents, and code comments. Software documentation is critical in software development, enabling effective communication between developers, testers, users, and other stakeholders. It helps to ensure that everyone involved in the development process has a shared understanding of the software system and its functionality. Moreover, software documentation can improve software maintenance by providing a clear and complete understanding of the software system, making it easier for developers to maintain, modify, and update the software over time. Smart contracts can use various types of software documentation. Some of the most common types include:
These types of documentation are essential for smart contract development and maintenance. They help ensure that the contract is properly designed, implemented, and tested, and they provide a reference for developers who need to modify or maintain the contract in the future.
The codebase contains detailed Readme that explains the expected flows in the system, along with trust assumptions and known code limitations. Additionally, the development team provided answers to questions raised by the Nethermind Security team during the sync call.
The code is also accompanied with comprehensive Natspec documentation describing the purpose, inputs, and outputs for each function. Functions with more complex logic also have inline comments to improve code readability.
| 49Tests passed | 0Failed |
The complete test output is in the full report.
As auditors, we recommend that any changes or updates made to the audited codebase undergo a re-audit or security review to address potential vulnerabilities or risks introduced by the modifications. By conducting a re-audit or security review of the modified codebase, you can significantly enhance the overall security of your system and reduce the likelihood of exploitation. However, we do not possess the authority or right to impose obligations or restrictions on our clients regarding codebase updates, modifications, or subsequent audits. Accordingly, the decision to seek a re-audit or security review lies solely with you.
This report is based on the scope of materials and documentation provided by you to Nethermind in order that Nethermind could conduct the security review outlined in 1. Executive Summary and 2. Audited Files. The results set out in this report may not be complete nor inclusive of all vulnerabilities. Nethermind has provided the review and this report on an as-is, where-is, and as-available basis. You agree that your access and/or use, including but not limited to any associated services, products, protocols, platforms, content, and materials, will be at your sole risk. Blockchain technology remains under development and is subject to unknown risks and flaws. The review does not extend to the compiler layer, or any other areas beyond the programming language, or other programming aspects that could present security risks. This report does not indicate the endorsement of any particular project or team, nor guarantee its security. No third party should rely on this report in any way, including for the purpose of making any decisions to buy or sell a product, service or any other asset. To the fullest extent permitted by law, Nethermind disclaims any liability in connection with this report, its content, and any related services and products and your use thereof, including, without limitation, the implied warranties of merchantability, fitness for a particular purpose, and non-infringement. Nethermind does not warrant, endorse, guarantee, or assume responsibility for any product or service advertised or offered by a third party through the product, any open source or third-party software, code, libraries, materials, or information linked to, called by, referenced by or accessible through the report, its content, and the related services and products, any hyperlinked websites, any websites or mobile applications appearing on any advertising, and Nethermind will not be a party to or in any way be responsible for monitoring any transaction between you and any third-party providers of products or services. As with the purchase or use of a product or service through any medium or in any environment, you should use your best judgment and exercise caution where appropriate. FOR AVOIDANCE OF DOUBT, THE REPORT, ITS CONTENT, ACCESS, AND/OR USAGE THEREOF, INCLUDING ANY ASSOCIATED SERVICES OR MATERIALS, SHALL NOT BE CONSIDERED OR RELIED UPON AS ANY FORM OF FINANCIAL, INVESTMENT, TAX, LEGAL, REGULATORY, OR OTHER ADVICE.
The Leading Engineers of Blockchain Infrastructure