Nexus Mutual Tokenomics Smart Contract Audit

# 1. Introduction

iosiro was commissioned by Nexus Mutual to conduct a smart contract audit of their Tokenomics update. The audit was performed by 2 auditors between 21 October and 1 November 2023, using 16 resource days. A final review of changes and fixes was completed on 20 November 2023.

This report is organized into the following sections.

- **[Section 2 - Executive summary:](#section-2)** A high-level description of the findings of the audit.
- **[Section 3 - Audit details:](#section-3)** A description of the scope and methodology of the audit.
- **[Section 4 - Design specification:](#section-4)** An outline of the intended functionality of the smart contracts.
- **[Section 5 - Open findings:](#section-5)** Detailed descriptions of audit findings that remained present at the conclusion of the audit.
- **[Section 6 - Closed findings:](#section-6)** Detailed descriptions of audit findings that were addressed during the audit.

The information in this report should be used to understand the smart contracts' risk exposure better and as a guide to improving the security posture of the smart contracts by remediating the issues identified. The results of this audit reflect the in-scope source code reviewed at the time of the audit.

The purpose of this audit was to achieve the following:

- Identify potential security flaws.
- Ensure that the smart contracts function according to the documentation provided.

Assessing the off-chain functionality associated with the contracts, for example, backend web application code, was outside of the scope of this audit.

Due to the unregulated nature and ease of transfer of cryptocurrencies, operations that store or interact with these assets are considered high risk from cyber attacks. As such, the highest level of security should be observed when interacting with these assets. This requires a forward-thinking approach, which takes into account the new and experimental nature of blockchain technologies. Strategies that should be used to encourage secure code development include:

- Security should be integrated into the development lifecycle, and the level of perceived security should not be limited to a single code audit.
- Defensive programming should be employed to account for unforeseen circumstances.
- Current best practices should be followed where possible.

<a name="section-2"></a>
# 2. Executive summary

This report presents the findings of an audit performed by iosiro of Nexus Mutual's Tokenomics smart contract updates. This includes a review of Nexus Mutual's Virtual Ratcheting Automated Market Maker (vRAMM), updates to how the protocol's Minimum Capital Requirements work, and other changes to accommodate the vRAMM integration.

### Audit findings

The audit uncovered one medium-risk issue, one low-risk issue and a few informational issues. The medium risk issue allowed non-members to obtain NXM, which was prohibited in other parts of the protocol. Nexus Mutual addressed this issue to ensure that only members of the mutual can exchange Ether for NXM tokens.

A price manipulation attack was identified during the audit, but this was rated as low-risk as the impact was considered negligible and could not be exploited in a manner that would negatively impact the protocol or its users. Changes were introduced to further reduce the risk and severely limit the extent to which the internal price can be manipulated.

### Recommendations

The following actions should be taken to ensure that the security posture of the Ratcheting AMM remains robust and in line with security best practices:

- Additional audits on the vRAMM and all other protocol components should be performed regularly, as security best practices, tools, and knowledge change over time. Additional audits throughout the project's lifespan ensure the longevity of the codebase.
- Maintain and expand the existing bug bounty program to encourage the responsible disclosure of security vulnerabilities in the system.

<a name="section-3"></a>

# 3. Audit details

## 3.1 Scope

The source code considered in-scope for the assessment is described below. Code from all other files was considered to be out-of-scope. Out-of-scope code that interacts with in-scope code was assumed to function as intended and not introduce any functional or security vulnerabilities for the purposes of this audit.

### 3.1.1 Smart contracts

- **Project name:** Tokenomics
- **Commits:** [ac1b25c](, [bde664](
- **Final Commit:** [d5a7d87](
- **Files:** `Ramm.sol`, `TokenController.sol`, `LegacyPool.sol`, `LegacyGateway.sol`, `LegacyPooledStaking.sol`, `Cover.sol`, `Assessment.sol`, `YieldTokenIncidents.sol`, `IndividualClaims.sol`, `Pool.sol`, `MCR.sol`

## 3.2 Methodology

The audit was conducted using a variety of techniques described below.

### 3.2.1 Code review

The source code was manually inspected to identify potential security flaws. Code review is a useful approach for detecting security flaws, discrepancies between the specification and implementation, design improvements, and high-risk areas of the system.

### 3.2.2 Dynamic analysis

The contracts were compiled, deployed, and tested in a test environment, both manually and through the test suite provided. Manual analysis was used to confirm the code's functionality and discover security issues that could be exploited.

### 3.2.3 Automated analysis

Tools were used to automatically detect the presence of several types of security vulnerabilities, including reentrancy, timestamp dependency bugs, and transaction-ordering dependency bugs. Static analysis results were reviewed manually, and any false positives were removed. Any true positive results are included in this report.

Static analysis tools commonly used include Slither, Securify, and MythX. Tools such as the Remix IDE, compilation output, and linters could also identify potential areas of concern.

## 3.3 Summary of findings

The table below provides an overview of the audit's findings. Detailed write-ups are provided in [Section 5](#section-5) and [Section 6](#section-6).

<table><thead><tr><th>#</th><th>Issue</th><th>Risk</th><th>Status</th></tr></thead><tbody><tr><td><a href="#issue-5-4-1">5.4.1</a></td><td>Design comments</td><td class="risk-informational">Informational</td><td class="status-open">Open</td></tr><tr><td><a href="#issue-6-2-1">6.2.1</a></td><td>Non-members allowed to swap</td><td class="risk-medium">Medium</td><td class="status-closed">Closed</td></tr><tr><td><a href="#issue-6-3-1">6.3.1</a></td><td>Atomic manipulation of internal price</td><td class="risk-low">Low</td><td class="status-closed">Closed</td></tr><tr><td><a href="#issue-6-3-2">6.3.2</a></td><td>Temporary denial of service</td><td class="risk-low">Low</td><td class="status-closed">Closed</td></tr><tr><td><a href="#issue-6-4-1">6.4.1</a></td><td>Storage layout</td><td class="risk-informational">Informational</td><td class="status-closed">Closed</td></tr><tr><td><a href="#issue-6-4-2">6.4.2</a></td><td>Design comments</td><td class="risk-informational">Informational</td><td class="status-closed">Closed</td></tr></tbody></table>Each issue identified during the audit has been assigned a risk rating. The rating is determined based on the criteria outlined below.

- **High risk**: The issue could result in a loss of funds for the contract owner or system users.
- **Medium risk**: The issue resulted in the code specification being implemented incorrectly.
- **Low risk**: A best practice or design issue that could affect the security of the contract.
- **Informational**: A lapse in best practice or a suboptimal design pattern that has a minimal risk of affecting the security of the contract.

In addition to a risk rating, each issue is assigned a status:

- **Open**: The issue remained in the code as of the final commit reviewed and may still pose a risk.
- **Closed**: The issue was identified during the audit and has since been satisfactorily addressed, removing the risk it posed.

<a name="section-4"></a>

# 4. Design specification

The following section outlines the system's intended functionality at a high level. This specification is based on the implementation in the codebase. Any perceived points of conflict should be highlighted with the auditing team to determine the source of the discrepancy.

Nexus Mutual has introduced an integrated virtual Automated Market Maker (vRAMM) to the protocol, which includes a ratcheting mechanism for price adjustment. The objectives of the ratcheting AMM are as follows:

- Bring the system price of NXM closer to that of the open market.
- Enable users to exit directly from the protocol at a reasonable price.
- Allow the protocol to capture capital when users wish to provide it.
- Create positive book value for long-term aligned members.

### AMM Liquidity

The AMM's liquidity is drawn from the Nexus Mutual capital pool and attempts to maintain a hardcoded liquidity target in Ether. When the AMM's liquidity goes above the target, due to NXM tokens purchased with Ether, the AMM will reduce its liquidity. Similarly, if liquidity is below the target, additional liquidity is injected based on the time elapsed since the last swap.

The total amount of liquidity added to the AMM is constrained by the protocol's Minimum Capital Requirement (MCR), ensuring that the buying and selling of NXM tokens do not negatively impact the protocol's cover policy commitments.

### Ratcheting Price Adjustment

NXM trading is suspended when prices fall below or rise above the book value, which prompts the need for a price ratcheting system. This system sets the AMM's target price at 1% higher or lower than the protocol's book value. Consequently, the purchase price for NXM gravitates to 1% above the book value, while the selling price trends towards 1% below the book value.

### Time-Weighted Average Pricing (TWAP)

Staking assignments and available capacity for cover are expressed in NXM. Without an external oracle that can deliver a current and manipulation-proof exchange rate, the price has to be determined internally. The internal exchange rate is calculated by averaging the difference between the higher and lower pools relative to the token's book value over the past three days.

### Fast and Normal Speed

Upon its initial launch, liquidity will be injected at a substantially increased rate, which will remain in place until a predefined quota of liquidity--known as the budget--is exhausted. The ratcheting mechanism will also apply steeper corrections to the asking price during this time.

This approach is facilitated by the surplus of Ether presently held within the Nexus Mutual protocol. Once the allocated budget is depleted, there will be a deceleration in both the provision of liquidity and the pace of price adjustments through the ratcheting mechanism. The Ratcheting AMM will then proceed to operate with its normal parameters.

### MCR Update

The Minimum Capital Requirement (MCR) floor value has been removed. Instead, the MCR now exclusively tracks the geared ratio of the total cover underwritten by the mutual over time.

### Circuit-breakers

The RAMM implements circuit breakers on the total accumulated Ether and token outflows as an additional safety measure. The circuit breakers ensure that the impact can be contained to sensible and safe values in the event of a price manipulation attack.

The thresholds can be set, updated or removed by the emergency administrators at any point in time.

<a name="section-5"></a>

# 5. Open findings

The following section details the findings of the audit, which remained open at the conclusion of the final review.

## 5.1 High risk

No high-risk issues were open at the conclusion of the review.

## 5.2 Medium risk

No medium-risk issues were open at the conclusion of the review.

## 5.3 Low risk

No low-risk issues were open at the conclusion of the review.

## 5.4 Informational

<a name="issue-5-4-1"></a>
### 5.4.1 Design comments

Actions to improve the functionality and readability of the codebase are outlined below.

#### Superfluous variables


The return variables and events for `injected` and `extracted` can be consolidated only to return a single `int` and emit a single event, e.g. `emit EthLiquidityUpdated(int adjustment)`.

#### Potential revert due to underflow


The statement `if ( - ethOut < context.mcr)` can be rewritten as `if ( < context.mcr + ethOut)`, ensuring that an underflow cannot occur and that a suitable revert message is always returned.

#### Observation events


* The events emitted when updating the TWAP observations should be ordered according to timestamp. Applying the same modulo addition logic employed in `_updateTwap()` should result in the correct order.
* As a gas optimization, events should only be emitted when the TWAP observations are updated. Further gas savings can be achieved by excluding the current observation, which changes on each block.

<a name="section-6"></a>
# 6. Closed findings

The following section details the findings reported and addressed during the audit.

## 6.1 High risk

No high-risk issues were identified during the audit.

## 6.2 Medium risk

<a name="issue-6-2-1"></a>
### 6.2.1 Non-members allowed to swap

*[Ramm.sol#L191](, [Ramm.sol#L243](*

#### Description

The NXM token contract disallows non-whitelisted addresses from owning NXM by using the `canTransfer()` modifier on transfer functions, intending to restrict NXM token transfers to only members of the mutual. However, this restriction could be bypassed through the swapping functionality in the new `Ramm.sol` contract. NXM is either minted to or burned from the `msg.sender`, which does not enforce the same restrictions as when transferring  NXM tokens.

#### Recommendation

If it is a strict requirement that NXM should never be minted to non-members across the entire Nexus Mutual system, the `mint()` and `burn()` functions in `NXMToken.sol` could also make use of the `canTransfer()` modifier. Alternatively, the `Ramm.sol` contract could leverage the existing controls in the following way:

- When buying NXM, the `Ramm.sol` contract first mints NXM to itself and then transfers the NXM to the buyer.
- When selling NXM, the `Ramm.sol` contract first performs a `transferFrom()` from the trader and then burns the NXM from itself.

#### Update

This issue was remediated in commit [d782256]( by adding a check to `TokenController`’s internal `_mint()` function and updating all external `mint` functions to use the internal function.

During the final review, it was noted that the `TokenController` was not whitelisted as an NXM member and calls to `mintStakingPoolNXMRewards` would revert. The logic was updated to exempt `TokenController` from the membership requirement in commit [a22c1ab](

## 6.3 Low risk

<a name="issue-6-3-1"></a>
### 6.3.1 Atomic manipulation of internal price


#### Description

It is possible to atomically affect the internal price of NXM by modifying the total supply of NXM or the Pool's capital without swapping. Miner Extracted Value (MEV) searchers and other malicious parties could use this opportunity to sandwich transactions and extract profit from the protocol. However, a profitable scenario could not be identified during the audit due to numerous mitigating factors.

The issue's root lies in including the token's instantaneous book value. Ideally, the calculation should be based on the book value at the end of the previous block. However, this is not feasible from an implementation perspective as ERC20 token transfers and ETH transfers via `selfdestruct` cannot be accurately accounted for.

The scenarios below were considered to ascertain the risk associated with atomic manipulation of the internal price:

**Withdrawing membership or burning tokens**

Any member can affect the total supply of NXM by burning their tokens or renouncing their membership. At the time of the audit, the Pool's capital was approximately 146800 Ether, and the total supply of NXM was 6.6 million tokens. To cause a 50 bps (0.5%) change to the internal price, 33165 NXM would need to be burned. At the time of this audit, such an amount had a market value of at least $1m.

No mechanism to profit directly by withdrawing membership or simply burying tokens was identified during the audit due to the minimal impact on the internal price.

**Minting and burning of NXM staking rewards**

The total amount of NXM distributed to stakers has historically been insignificant compared to the Pool's capital and had a negligible effect on the book value. Therefore, the benefit of sandwiching transactions that buy, edit or expire cover was minimal.

**Cover payout**

Since cover payouts should proportionally reduce the total supply of NXM (burned from stakers) and the Pool's capital, its impact on the internal price should be minimal. Yet again, the amount of NXM burned, and capital outflows when claiming cover has been small relative to the total token supply and pool capital.

#### Recommendation

Modify the internal price calculation so that it is not dependent on the current book value. Instead, it can be calculated as the arithmetic mean of the two underlying TWAP prices (above and below book value). It should be noted that this will result in the internal price not tracking the booking value during periods without swapping. To mitigate this, `getInternalPriceAndUpdateTwap()` will need to be called instead of `getInternalPrice()`, requiring changes to the state mutability of various other functions.

Alternatively, implement a best-effort approach to track the Pool's capital and NXM token supply for the current and previous blocks. Adding hooks to all mint and burn functions and the default receiver and transfer functions of the Pool would ensure that the TWAP is updated under most circumstances. Sample code for these changes is provided below:


uint256[] internal capitalHistory;
uint256[] internal supplyHistory;
uint256 internal lastUpdated;

function _onBookValueChange() onlyPoolOrTokenController external {
   if (lastUpdated != block.timestamp) {
     lastUpdated = block.timestamp;

// add hook to swap and _updateTwap


receive() external payable {

// Add hook to sendEth, transferAsset, addAsset, etc.


// ...
internalPrice = PriceA + PriceB - 1 ether * capitalHistory[capitalHistory.length - 1] / supplyHistory[supplyHistory.length - 1];

#### Update

It was concluded that the risk of sandwiching the internal price was minimal and did not necessitate significant changes to the protocol or the internal price calculation. However, all references to `getInternalPrice()` in state modifying functions were updated to call `getInternalPriceAndUpdateTwap()` instead, limiting the extent to which the order of transactions in the same block can influence the `internalPrice`.

<a name="issue-6-3-2"></a>
### 6.3.2 Temporary denial of service


#### Description

The circuit-breaker mechanism could result in a temporary denial of service if an attacker purposefully performed repeated swaps. However, simulations showed that an attack of this nature would be costly to complete, with no direct profit, and could be quickly remediated by increasing the limits.

More specifically, the simulations revealed that:
- The liquidity injection mechanism and circuit-breaker limits prevent triggering the circuit-breakers within a single block. Slippage incurred on each atomic swap makes exploitation infeasible.
- Swapping over time would require spending over 500 ETH over 30 days to trigger the circuit-breaker purposefully as a sole trader. This would provide sufficient time to increase the limits and avoid any disruptions to the protocol.

#### Recommendation

The protocol's emergency admins can increase the limits when the circuit-breaker has been triggered. This circuit-breaker mechanism could be enhanced to mitigate the over-reliance on the emergency admin multisig through the following means:

- Use an accumulator to limit the outflow of NXM or ETH within a time window.
- Enforce maximum and minimum slippage tolerances on trades proportional to book value.

Another enhancement to consider is to validate whether either of the limits has almost been reached and if the margin on either limit is too low to pause swapping in both directions. This prevents swapping only being permitted in a single direction, which could artificially skew the price.

#### Update

As the limits can be increased or removed at any time, Nexus Mutual decided not to increase the complexity of the circuit-breaker mechanism unnecessarily. Instead, efforts will be made to monitor and preemptively increase the limits on an ongoing basis.

## 6.4 Informational

<a name="issue-6-4-1"></a>
### 6.4.1 Storage layout


#### Description

A detailed review of the RAMM smart contract's source code and its actual storage layout, obtained via `forge inspect Ramm storageLayout --pretty`, revealed discrepancies between the anticipated and actual storage allocation.

These discrepancies arose from misunderstandings about how the Solidity compiler handles fixed-size arrays, structs, and variable packing.

Incorrect assumptions regarding storage layout in upgradeable contracts can lead to state corruption during future upgrades. Ineffective packing of variables also increases the gas footprint instead of reducing it.

The source code and the resulting storage layout are given below for reference:
 // slot 2 & 3
 // 160 * 3 = 480 bits
 Observation[3] internal observations;
 uint24 public ratchetSpeedB;

 // emergency pause
 bool public swapPaused;

| Name               | Type                                            | Slot | Offset | Bytes | Contract                |
|---------------|--------------------------------|-- --|-------|--------|------------------|
| master             | contract INXMMaster                 | 0    | 0         | 20        | Ramm.sol:Ramm |
| internalContracts | mapping(uint256 => address payable) | 1    | 0      | 32    | Ramm.sol:Ramm |
| _status           | uint256                             | 2    | 0      | 32    | Ramm.sol:Ramm |
| slot0             | struct Slot0                        | 0    | 0      | 32    | Ramm.sol:Ramm |
| slot1             | struct Slot1                        | 1    | 0      | 32    | Ramm.sol:Ramm |
| observations      | struct Observation[3]               | 2    | 0      | 96    | Ramm.sol:Ramm |
| ratchetSpeedB     | uint24                              | 5    | 0      | 3     | Ramm.sol:Ramm |
| swapPaused        | bool                                | 5    | 3      | 1     | Ramm.sol:Ramm |
| ethReleased       | uint96                              | 5    | 4      | 12    | Ramm.sol:Ramm |
| ethLimit          | uint32                              | 5    | 16     | 4     | Ramm.sol:Ramm |
| nxmReleased       | uint96                              | 5    | 20     | 12    | Ramm.sol:Ramm |
| nxmLimit          | uint32                              | 6    | 0      | 4     | Ramm.sol:Ramm |

As shown in the output provided, variables were not tightly packed as initially assumed. Specifically:
`ratchetSpeedB` and `swapPaused` occupy the next slot instead of being tightly packed alongside the final observation. Similarly, `nxmLimit` consumes an additional slot and is not packed tightly with the other circuit breaker variables.
* The `observations` array consumes more storage slots than anticipated, with each element starting in a new slot.

The Solidity compiler's documentation states that arrays are never tightly packed, and each element will start in a new slot. Furthermore, variables are never tightly packed alongside structs and are always placed in the next slot.

#### Recommendation

The following changes could be made to improve the storage layout of the smart contract:

* **Inline `ratchetSpeedB`:**  Implement `ratchetSpeedB` as an internal pure function or a ternary expression that returns the ratchet speed based on the `budget` state variable. This change will calculate its value on the fly, eliminating the need for a dedicated storage slot.
* **Optimize `swapPaused` storage:** Modify the budget field within the `Slot1` struct to create space for `swapPaused` and integrate it into `Slot1`. Replace the `whenSwapNotPaused` modifier with inline checks to minimize the additional loading of the `Slot1` struct.

#### Update

The storage layout of the contract was revised in line with the recommendations above. The review of commit [d5a7d87]( included these changes.

<a name="issue-6-4-2"></a>
### 6.4.2 Design comments

Actions to improve the functionality and readability of the codebase are outlined below.

#### Code refactoring

Large portions of the source code can be refactored to improve readability and minimize code duplication. Examples of possible improvements are provided in the Github gist linked [here](

**Update:** Several reusable `internal` functions and code blocks were introduced. Overall, the changes significantly improved the readability and maintainability of the code base. The review of commit [d5a7d87]( included these changes.

#### Reuse function return values

To reduce bytecode size and runtime gas costs, the values of `mcrValue`, `capital,` `supply`, `tokenController` and `pool` should be passed from `swap()` to `swapEthForNxm()` and `swapNxmForEth()`. To avoid significantly expanding the parameters for the functions, the properties can be bundled into a `Context` struct passed as an argument.

**Update:** The addresses and values were added to a struct per the recommendation. The review of commit [d5a7d87]( included these changes.

#### Document and use constant for accumulator precision

The accumulator uses 9-decimal precision, and the codebase repeatedly scales values using `1e9`. To improve readability, the value `1e9` should be replaced with a constant such as `ACCUMULATOR_PRECISION`, and its intention and implications should be documented.

**Update:** Due to the contract's storage layout changes, storing the accumulator values with reduced precision is no longer required. Accumulator scaling was removed from the contract, and all values were updated to be stored using 112 bits of precision, sufficient to ensure that the values do not overflow for two or more consecutive sample windows. The changes were included in the review of commit [d5a7d87](

#### Constants used only during initialization should be marked internal


Exposing constants using the keyword `public` unnecessarily increases the contract's bytecode size, especially if these constants are not included in the interface definition. Constants should be marked as `internal` or `private` instead of `public`.

**Update:** All constants were updated to be `internal` in commit [255cbe4](

Secure your system.
Request a service
Start Now