Null Solutions

NullV1 lets traders go long or short on an asset’s funding rate through paired receiver and payer pools. We reviewed the market, factory and router contracts, with a close look at how each interaction settles balances, fees and PnL.
report id
1028
category
DeFi & Yield
language
Solidity
final report
August 25, 2026
lines of code
1220
Content

View Audit Report

1

Executive Summary

This document presents the results of the security review conducted by Nethermind Security for Null Solutions's smart contracts for the NullV1 Protocol for perpetual derivative markets.

NullV1 is a derivatives protocol where the system creates isolated markets consisting of two counterparty pools. A Receiver pool and a Payer pool that accepts ERC-20 tokens as collateral. Participants take receiver (long) or payer (short) positions on the funding rate of an underlying asset price. All collateral is held in a single NullV1Market contract and accounted across two ERC-4626 vaults representing the receiver and payer pools.

NullV1Factory deploys EIP-1167 proxy clones for markets and paired pools keyed by unique collateral, oracle, and funding rate model. As the core settlement engine, NullV1Market holds all collateral tokens and enforces a balance invariant across the receiver pool, payer pool, and protocol fees. On every interaction, the Settlement library reconciles balances, accrues fees, updates PnL, and settles funding transfers.

The periphery uses NullV1Router as a stateless entry point handling user interactions, slippage protection, deadline checks, and collateral wrapping. Pricing data is sourced from Chainlink Data Streams oracles.

The audit comprises 1220 lines of Solidity code. The audit was performed using (a) manual analysis of the codebase, and (b) automated analysis tools.

Audit assessment
Audit TypeSecurity Review
Initial ReportAug 18, 2026
Final ReportAug 25, 2026
Initial Commit98887f0
Final Commit1f0562a
Documentation AssessmentHigh
Test Suite AssessmentHigh
Total findings
5
Fixed
5
Acknowledged
0
Mitigated
0
Unresolved
0

Findings by severity

  • Critical0
  • High0
  • Medium1
  • Low2
  • Undetermined0
  • Informational2
  • Best Practices0

4

System Overview

NullV1 is a decentralised financial protocol designed to create and settle perpetual derivative markets backed by counterparty ERC-4626 liquidity pools. The system creates isolated markets consisting of two counterparty pools: a Receiver pool and a Payer pool. Participants supply ERC-20 as collateral to either side to take exposure, earning returns or incurring losses based on asset price changes provided by oracles and funding rates calculated from pool asset imbalances.

4.1 Architecture

The system is organised into two subsystems, namely Core and Periphery.

4.1.1 Core

The Core smart contracts comprise:

  • NullV1Factory: Deploys markets via minimal proxy clones. Manages protocol-wide access control (whitelisting, pause, deposit blocking) and FRM enablement. Owned by the protocol admin.
  • NullV1Market: Central settlement engine for a single market. Holds the MarketState, orchestrates settlement on every interaction, applies fees, and queries the oracle and funding rate model. Every market has its own Receiver and Payer pool clones on initialisation.
  • NullV1Pool: ERC-4626 vault (two instances per market: Receiver and Payer). Users deposit collateral to receive non-transferable shares. On every deposit/withdraw, the pool triggers market settlement before executing the ERC-4626 operation.
  • SettlementLib: A library containing the core settlement logic for balance reconciliation, collateral fee accrual, price PnL computation, and funding application.
  • ConstantsLib: Protocol-wide constants
  • ErrorsLib: Custom error definitions

4.1.2 Periphery

The Periphery smart contracts comprise:

  • NullV1Router: User-facing entry point for deposits and redemptions. Provides slippage protection, deadline enforcement, and automatic ERC-4626 collateral wrapping/unwrapping.
  • NullV1ImbalanceFRM: Stateless funding rate model that computes a signed rate based on pool imbalance.
  • NullV1ChainlinkOracleV11: Price oracle backed by Chainlink Data Streams with multi-session support, staleness checks, and a 15-day stale price escape hatch.
  • NullV1ChainlinkOracleFactory: Deploys oracle clones via minimal proxy pattern.

4.2 Life cycle

4.2.1 Market Creation

  • Protocol owner calls createMarket function.
  • Factory validates the FRM is enabled, and the market does not already exist.
  • Factory creates market by cloning the NullV1Market implementation and initialises it.
  • Market creates two pool instances, one each for Receiver and Payer, reads the initial oracle price, and calls the FRM to initialise any stateful models.

4.2.2 User Interaction

  • User calls depositMarket or redeemMarket() via Router contract.
  • Router optionally updates the oracle with fresh Chainlink report data.
  • Router checks the deadline has not passed.
  • For each pool the user is interacting with:
    • Deposit: Router pulls collateral from the user (wrapping into ERC-4626 if needed), deposits into the pool, and verifies shares received meet the minimum slippage bound.
    • Redeem: Router redeems shares from the pool, unwraps collateral if needed, and verifies assets received meet the minimum slippage bound.

4.2.3 Settlement

Every pool operation (deposit, mint, withdraw, redeem) triggers settlement.

  • Reentrancy guard acquired : (transient storage, EIP-1153).
  • Balance reconciliation: Actual collateral balance is reconciled against accounting. Surpluses are distributed pro-rata to pools; deficits are deducted pro-rata.
  • Collateral fee accrual: Annualised fee charged on pool assets and is credited to protocol fees, but is currently disabled.
  • Oracle price fetch: Market reads the latest price from the Chainlink oracle.
  • Funding rate computation: Market queries the FRM with the current state.
  • Price PnL and funding application:
    - Price PnL transfers value between pools proportional to the price change and the receiver's settleable notional.
    - Funding transfers value based on the signed funding rate, elapsed time, and settleable balance. - Both transfers are clamped so neither pool falls below minimum assets.
  • Circuit breaker: If the price move exceeds 20%, PnL is skipped, and the reference price jumps to the new level.
  • Reentrancy guard released.

4.3 Settlement Mechanics

4.3.1 MarketState

MarketState {
lastUpdate: uint256 // Timestamp of last settlement
receiverPool: uint256 // Gross receiver pool assets (includes MIN_POOL_ASSETS floor)
payerPool: uint256 // Gross payer pool assets (includes MIN_POOL_ASSETS floor)
protocolFees: uint256 // Accumulated protocol fees
price: uint256 // Last settled oracle price
}

4.3.2 Settleable Balance

Settlement only applies to the portion of each pool above the minimum asset pool floor. This floor prevents zero-size settlement and ensures a minimum dust balance remains for accounting integrity.

4.3.3 Price PnL

  • Positive PnL (price up): transferred from payer to receiver.
  • Negative PnL (price down): transferred from receiver to payer.
  • Transfer clamped to the losing pool's settleable balance.

4.3.4 Funding

  • Positive funding rate: receivers pay payers (reduces net transfer to receiver).
  • Negative funding rate: payers pay receivers (increases net transfer to receiver).
  • Combined with price PnL into a single net transfer before clamping.

4.3.5 Circuit breaker

If the price difference is greater than 20%, settlement skips PnL entirely and resets the reference price. This prevents exploitation from oracle discontinuities.

4.4 Funding Rate Model

f = mu * (c * R - P) / (c * R + P)

Where:

  • mu (sensitivity): Maximum absolute funding rate, WAD-scaled.
  • c (targetCoverage): Payer-to-receiver ratio at equilibrium, WAD-scaled.
  • R = receiver pool balance
  • P = payer pool balance

4.5 Oracle

Pricing is sourced from the Chainlink oracle. The protocol supports 3 feed types.

  • Regular feed
  • Extended feed
  • Overnight feed

4.6 Design decisions

  • Minimal proxy (Clones) pattern: Markets, pools, and oracles are all deployed as EIP-1167 clones to minimise deployment costs.
  • Non-transferable shares: Pool shares cannot be transferred (transfer/transferFrom return false), making positions non-transferable and simplifying the accounting model.
  • Interaction-driven settlement: State is only updated when someone interacts with the market.
  • Stateless router: The router holds no state, cannot receive ETH, and never custodies funds beyond a single transaction. -
  • ERC-4626 collateral wrapping: The router transparently handles collateral that is itself an ERC-4626 vault, pulling the underlying asset from users and wrapping it before deposit.
  • Settlement logics and calculations are based on the receiver assets.
  • PnL and funding are sized off the receiver notional, so a small payer pool's upside on a favorable move — and its funding accrual, which requires no price move at all — is counterparty-sized while its downside is bounded by its own settleable balance.

5

Risk Rating Methodology

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:

  1. High: the issue is trivial to exploit and has no specific conditions that need to be met;
  2. Medium: the issue is moderately complex and may have some conditions that need to be met;
  3. Low: the issue is very complex and requires very specific conditions to be met.

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:

  1. High: the issue can cause significant damage, such as loss of funds or the protocol entering an unrecoverable state;
  2. Medium: the issue can cause moderate damage, such as impacts that only affect a small group of users or only a particular part of the protocol;
  3. Low: the issue can cause little to no damage, such as bugs that are easily recoverable or cause unexpected interactions that cause minor inconveniences.

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 / LikelihoodLowMediumHigh
HighMediumHighCritical
MediumLowMediumHigh
LowInfo / Best PracticesLowMedium
UndeterminedUndeterminedUndeterminedUndetermined

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.

  1. Informational findings do not pose any risk to the application, but they carry some information that the audit team intends to pass to the client formally;
  2. Best Practice findings are used when some piece of code does not conform with smart contract development best practices;
  3. Undetermined findings are used when we cannot predict the impact or likelihood of the issue.

6

Issues

6.1 Shares are calculated before fee recomputation on mint, causing depositors to lose a portion of their assets

FileFix commitTypeStatus
/src/core/NullV1Pool.sol—MediumFixed

NullV1Pool charges its entry and exit fees on notional — the size of the bet a deposit represents — rather than on the deposit amount itself.

The fee is computed by one of two helpers, depending on the flow:

  • feeOnTotal — the caller handed over a fixed sum of collateral, so the fee has to be taken out of it;
  • feeOnRawNotional — the caller asked for a fixed position size(shares), so the fee has to be added on top;
    function feeOnTotal(uint256 assets, uint256 feeBasisPoints) internal pure returns (uint256) { return Math.mulDiv(assets, feeBasisPoints, feeBasisPoints + MAX_BASIS_POINTS, Math.Rounding.Ceil); }<br>function feeOnRawNotional(uint256 notional, uint256 assets, uint256 feeBasisPoints) internal pure returns (uint256) { if (notional == 0 || feeBasisPoints == 0) return 0; uint256 denom = assets * (feeBasisPoints + MAX_BASIS_POINTS); if (notional * feeBasisPoints >= denom) revert ErrorsLib.FeeExceedsAssets(); return Math.mulDiv(notional, assets * feeBasisPoints, denom - notional * feeBasisPoints, Math.Rounding.Ceil); }

The two functions are meant to be mirror implementations: feeOnRawNotional quotes the fee the user will pay, and feeOnTotal should pull exactly that same fee back out. This equivalence holds only if the fee rate stays constant regardless of deposit size

_notional places the depositor's own asset amount into the denominator:

function _notional(uint256 assets, bool depositing) private view returns (uint256) {
(, uint256 receiverAssets, uint256 payerAssets,,) = _market.state();
uint256 ownAssets =
(poolKind == NullV1PoolKind.Receiver ? receiverAssets : payerAssets) + (depositing ? assets : 0);
if (depositing && poolKind == NullV1PoolKind.Receiver) receiverAssets += assets;
uint256 settleableR = SettlementLib._settleableAssets(receiverAssets);
return ownAssets == 0 || settleableR == 0 || (depositing && SettlementLib._settleableAssets(payerAssets) == 0)
? 0
@==> : assets.mulDiv(settleableR, ownAssets);
}

As a result, a larger deposit enlarges the payer pool, which reduces the depositor's effective leverage, which in turn shrinks their fee ( fee = assets × settleableReceiver ÷ (payerPool + assets)) and vice versa.

The mint entry point is where this dependency is not handled correctly. mint first derives the fee from the net asset amount and passes the resulting gross amount to _deposit (step 1):

function previewDeposit(uint256 assets) public view override(IERC4626, ERC4626Upgradeable) returns (uint256) {
uint256 fee = Math.min(assets, feeOnTotal(_notional(assets, true), _market.entryFee()));
return super.previewDeposit(assets - fee);
}

function deposit(uint256 assets, address receiver) public virtual returns (uint256) {
// --SNIP
uint256 shares = previewDeposit(assets);
_deposit(_msgSender(), receiver, assets, shares);
return shares;
}

_deposit then recomputes the fee from the gross amount (step 2):

function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual override {
// --SNIP
@==> uint256 fee = Math.min(assets, feeOnTotal(_notional(assets, true), _market.entryFee()));
IERC20(asset()).safeTransferFrom(caller, address(_market), assets);
_market.recordDeposit(assets, fee, poolKind);
_mint(receiver, shares);
emit Deposit(caller, receiver, assets, shares);
}

The fee is thus calculated twice, and the two calculations are expected to agree. In the mint flow, however, they do not, because each step computes the notional from a different amount:

  • previewMint (step 1) calculates the fee from the smaller, net amount, which corresponds to a higher notional ratio and therefore a higher fee rate. It quotes the user a gross amount to pay based on this fee;
  • _deposit (step 2) discards that fee and recomputes it from the larger, gross amount, which corresponds to a lower fee rate (because of smaller notional) and therefore a smaller fee;

Because the fee actually charged is smaller than the one quoted when calculating the gross amount, more capital ends up in the pool than the minted shares were priced for. Critically, the share count is fixed in step 1 — before _deposit is called — and nothing subsequently adjusts it once the fee is recalculated from the gross amount. The resulting surplus capital sits in the payer pool with no shares attached to it, and is effectively distributed among all existing payer-shareholders. The depositor thereby loses a portion of the assets they paid in, without receiving any shares in return.

Consider the following example, which illustrates the issue: Pre-state: the payer pool holds 5,000 assets, the receiver pool holds 1,000,000 assets, and the entry fee is 0.25%.

  • Bob wants a payer position worth 10,000 USDC. The notional for this position is 666,666, and the share count is fixed at this point. Bob calls mint, which calls previewMint to calculate the fee:;
  • The fee quoted by previewMint is 1,994.015.
  • The gross amount Bob must pay is 11,994.015.
  • Bob pays 11,994.015 USDC against the shares requested, and this gross amount is passed to _deposit;
  • _deposit recalculates the fee from this gross amount:;
  • The notional of 11,994.015 is 705,778.076
  • The recalculated fee is: 1,760.045

The fee actually taken ( 1,760.045) is therefore 233.97 USDC less than the fee originally quoted to Bob. The pool receives 10,233.97 USDC, but the shares minted for Bob remain fixed at the step-1 priced at 10,000 USDC. As a result, Bob effectively loses 233.97 USDC, since no shares back this amount — it is instead attributed to the existing payer-shareholders.

NOTE: Same issue exists in the withdraw flow.

Recommendation

Ensure the fee used to determine the minted share count and the fee actually recorded/transferred in _deposit are computed from the same basis. Concretely, either:

  • Compute the fee once (e.g., in mint/ previewMint) and pass it through to _deposit instead of recomputing it there from the gross amount, or;
  • Recompute the shares in _deposit after the fee is finalised, so that the minted shares always match the fee actually charged;

6.2 _deposit does not revert on zero shares

FileFix commitTypeStatus
/src/core/NullV1Pool.sol1f0562aLowFixed

When depositing into an underfunded pool, leverage is high, and the fee charged against the deposited assets can saturate to the full asset amount. As a result, a depositor can be charged a 100% fee on their assets:

function previewDeposit(uint256 assets) public view override(IERC4626, ERC4626Upgradeable) returns (uint256) {
uint256 fee = Math.min(assets, feeOnTotal(_notional(assets, true), _market.entryFee()));
return super.previewDeposit(assets - fee);
}

In this case, the shares minted for the depositor will be zero. The issue is that _deposit does not guard against this:

function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual override {
if (NullV1Factory(_market.factory()).isDepositsPaused()) {
revert ErrorsLib.DepositsPaused();
}
_checkAllowed(caller, true);
_checkAllowed(receiver, true);
IERC20(asset()).safeTransferFrom(caller, address(_market), assets);
uint256 fee = Math.min(assets, feeOnTotal(_notional(assets, true), _market.entryFee()));
_market.recordDeposit(assets, fee, poolKind);
_mint(receiver, shares);
emit Deposit(caller, receiver, assets, shares);
}

This allows the depositor to pay a 100% fee while receiving zero shares, resulting in a loss of funds.

Recommendation

Add a check in _deposit that reverts if shares is zero.

6.3 deposit flow is cheaper than mint flow, causing protocol to lose portion of fees

FileFix commitTypeStatus
/src/core/NullV1Pool.sol9a57c6aLowFixed

Both payer and receiver pools charge entry fees against the notional, which is the leverage the deposit is making ( leverage = settleableReceiver / (payerPool + your deposit)). The higher the deposit is, the less the leverage is:

function _notional(uint256 assets, bool depositing) private view returns (uint256) {
(, uint256 receiverAssets, uint256 payerAssets,,) = _market.state();
uint256 ownAssets =
(poolKind == NullV1PoolKind.Receiver ? receiverAssets : payerAssets) + (depositing ? assets : 0);
if (depositing && poolKind == NullV1PoolKind.Receiver) receiverAssets += assets;
uint256 settleableR = SettlementLib._settleableAssets(receiverAssets);
return ownAssets == 0 || settleableR == 0 || (depositing && SettlementLib._settleableAssets(payerAssets) == 0)
? 0
@==> : assets.mulDiv(settleableR, ownAssets);
}

When making deposits into the pool, there are two flows: mint which spends the assets based on the requested shares, and deposit, which mints shares based on the requested assets. However, if we take a look at how both functions charge the fees: 1. previewMint:

 function previewMint(uint256 shares) public view override(IERC4626, ERC4626Upgradeable) returns (uint256) {
uint256 assets = super.previewMint(shares);
return assets + feeOnRawNotional(_notional(assets, true), assets, _market.entryFee());
}

2. previewDeposit:

function previewDeposit(uint256 assets) public view override(IERC4626, ERC4626Upgradeable) returns (uint256) {
uint256 fee = Math.min(assets, feeOnTotal(_notional(assets, true), _market.entryFee()));
return super.previewDeposit(assets - fee);
}

previewMint calculates the leverage in _notional based on the net asset the user will deposit into the pool, while previewDeposit measures the leverage against a gross deposit that includes the fee that is about to be charged. That fee doesn't go into the pool, but still is included in the leverage calculation, which makes it less and thus gives higher shares. So, for depositing the same amount of assets, the deposit flow will be cheaper than mint flow, in which the former will charge fewer fees than the latter.

Consider the following example demonstrating the issue:

  • Pre-state: The receiver pool has 1_000_000 assets and the payer pool has 5_000 assets;
  • mint flow:
    • - To deposit a net amount of 10_000, you need spend 11_994,015555 and request shares of 100000000000000000000
    • The fee charged: 11994015555
    • Minted shares: 100000000000000000000
  • deposit flow:
    • amount spent: 11994015555
    • The fee charged: 1760045072
    • Minted shares: 102339704830000000000

So, through the deposit flow, the position's value has more 233,97 USDC than the mint flow, for the same spent amount.

Recommendation

Consider disabling one of the flows. It is tricky to fix the issue while keeping both flows given the notional logic.

6.4 Inconsistent reentrancy guard implementations across contracts

Reentrancy protection is implemented across the protocol to prevent untrusted external calls from re-entering state-modifying functions.

However, three distinct versions of reentrancy guards are used across the codebase rather than standardising on the custom NullV1ReentrancyGuard contract.

  • NullV1Market uses NullV1ReentrancyGuard;
  • NullV1Factory uses ReentrancyGuard which is storage based;
  • NullV1Router uses ReentrancyGuardTransient from OZ library;

This inconsistency creates architectural fragmentation and maintenance overhead across the codebase. Additionally, using persistent storage in NullV1Factory incurs higher execution gas costs than transient storage implementations.

Recommendation

Consider standardising all contracts across the project to use NullV1ReentrancyGuard as the single transient storage-based reentrancy guard implementation.

6.5 No mechanism to prevent deployment of new markets using whitelisted account, if compromised

FileFix commitTypeStatus
/src/core/NullV1Factory.sol—InfoFixed

In the NullV1Factory contract, the whitelistAdmin grants market creation privileges to specific addresses by calling the addToWhitelist(...) function. Whitelisted users can then call the createMarket(...) function to instantiate new markets with specific parameters, such as collateral tokens, oracle feeds, and funding rate models.

However, the NullV1Factory contract does not provide a removeFromWhitelist(...) function or any other mechanism to revoke an address's whitelisted status once it has been granted. While deposit functionality can be independently restricted using the setDepositsBlocked(...) function in the NullV1Pool contract via _checkAllowed(...), the createMarket(...) function relies solely on the isWhitelisted mapping without any secondary blocklist control.

function createMarket(...) external noDelegateCall nonReentrant returns (INullV1Market market) {
// ...
// @audit-issue The contract checks whitelist status, but there is no mechanism to revoke it.
if (!isWhitelisted[msg.sender]) {
revert ErrorsLib.NotAllowed(msg.sender);
}
// ...
}

This means that if a whitelisted address is compromised or should otherwise be restricted, the whitelistAdmin has no way to revoke its ability to create new markets. The compromised account can continue calling createMarket(...) indefinitely to deploy markets with arbitrary or malicious oracle parameters and funding rate models into the protocol.

Recommendation

Consider extending the existing blocklist check to also gate the createMarket(...) function.

7

Documentation Evaluation

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:

  • Technical whitepaper: A technical whitepaper is a comprehensive document describing the smart contract’s design and technical details. It includes information about the purpose of the contract, its architecture, its components, and how they interact with each other;
  • User manual: A user manual is a document that provides information about how to use the smart contract. It includes step-by-step instructions on how to perform various tasks and explains the different features and functionalities of the contract;
  • Code documentation: Code documentation is a document that provides details about the code of the smart contract. It includes information about the functions, variables, and classes used in the code, as well as explanations of how they work;
  • API documentation: API documentation is a document that provides information about the API (Application Programming Interface) of the smart contract. It includes details about the methods, parameters, and responses that can be used to interact with the contract;
  • Testing documentation: Testing documentation is a document that provides information about how the smart contract was tested. It includes details about the test cases that were used, the results of the tests, and any issues that were identified during testing;
  • Audit documentation: Audit documentation includes reports, notes, and other materials related to the security audit of the smart contract. This type of documentation is critical in ensuring that the smart contract is secure and free from vulnerabilities.

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.

8

Remarks & Test Suite

The Null Solution team provided a comprehensive overview of the main system components during the kick-off call, including a detailed explanation of the intended functionalities and a written specification for the different contracts. Additionally, the team addressed all questions and concerns raised by the Nethermind Security team, providing valuable insights and an in-depth understanding of the project's technical aspects.

Test suite
447Tests passed0Failed

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.