Mellow Finance
Swap Module

Examined the SwapModule contract, which executes permissioned token swaps for Mellow subvaults via DEX aggregators or CoW Protocol.
report id
0735
category
DeFi & Yield
language
Solidity
final report
November 19, 2025
lines of code
244
Content

Download audit report

1

Executive Summary

This document presents the security review performed by Nethermind Security for Mellow Protocol smart contracts. The scope of this audit was specifically focused on the newly developed SwapModule contract.

The Mellow Protocol's SwapModule is a smart contract designed to provide secure, flexible, and permissioned trade execution for the protocol's vaults (referred to as subvaults). It acts as a specialized utility layer that isolates trading operations from the core vault logic.

The module's primary function is to execute token swaps on behalf of a subvault through two distinct mechanisms. First, it facilitates immediate, on-chain swaps by interfacing with a whitelist of approved external DEX aggregators and routers. Second, it supports gasless, off-chain limit orders through a native integration with CoW Protocol, allowing trades to be settled by off-chain solvers when specific price targets are met.

The audit comprises 244 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 ReportNovember 19, 2025
Final ReportNovember 19, 2025
Initial Commit688382e
Final Commit688382e
Documentation AssessmentLow
Test Suite AssessmentHigh
Total findings
2
Fixed
0
Acknowledged
2
Mitigated
0
Unresolved
0

Findings by severity

  • Critical0
  • High0
  • Medium0
  • Low0
  • Informational0
  • Best Practices2

2

Audited Files

#ContractLoCCommentsRatioBlankTotal
1src/utils/SwapModule.sol2443313.5%35312
Total2443313.5%35312

4

System Overview

For a comprehensive understanding of the entire Mellow Protocol system, please refer to the official audit report: NM0587-FINAL_Mellow. This section focuses exclusively on the newly added SwapModule contract.

The Mellow Protocol's SwapModule is a permissioned utility contract designed to execute token swaps on behalf of a designated subvault contract. It functions as a secure swapping layer, enabling the protocol to trade assets through two distinct mechanisms: immediate on-chain transactions via whitelisted external routers, and gasless off-chain limit orders using CoW Protocol. The module's design emphasizes security through a strict, role-based access control system and mandatory on-chain oracle price checks for slippage protection.

4.1 Access Control and Roles

The contract is built upon the MellowACL (Access Control List) system, which defines a set of specific roles to manage permissions. All primary functions are restricted to addresses holding these roles, ensuring that only authorized entities can perform sensitive operations.

  • subvault: This is the address of the associated vault contract that "owns" the assets. The subvault is the *only* entity permitted to push assets into or pull assets from the SwapModule.
  • CALLER_ROLE: This role is granted to trusted operators, such as off-chain keepers or managers. Holders of this role are responsible for initiating all trading operations, including both on-chain swaps (swap()) and the creation or invalidation of CoW Protocol orders.
  • ROUTER_ROLE: This serves as a whitelist for external smart contracts (e.g., Uniswap V3's router, 1inch's aggregator) that are permitted to be used for executing on-chain swaps.
  • TOKEN_IN_ROLE / TOKEN_OUT_ROLE: These roles create whitelists for assets that are approved for trading through the module.
  • SET_SLIPPAGE_ROLE: This administrative role has permission to set the defaultMultiplier and customMultiplier values, which control the acceptable slippage tolerance for all trades.
  • DEFAULT_ADMIN_ROLE: This role has full administrative privileges to manage all other roles and can update critical parameters like the oracle address.

4.2 Asset Management Flow

The SwapModule is designed as a transitory contract. The movement of assets is strictly controlled by the subvault contract.

  • Pushing Assets: Before a swap can be initiated, the subvault must first transfer the required tokenIn to the SwapModule by calling the pushAssets() function. This function ensures that only the designated subvault can fund the module for a trade.
  • Pulling Assets: After a swap is completed, the resulting tokenOut (or any remaining tokenIn) is held by the SwapModule. The subvault can then call pullAssets() to retrieve these tokens.

4.3 On-Chain Swap Execution

For immediate, on-chain trades, an address with the CALLER_ROLE invokes the swap() function. This flow interacts directly with a whitelisted external DEX aggregator or router.

The process is as follows:

  • 1. Initiation: A CALLER_ROLE submits the trade details via the Params struct, which includes tokenIn, tokenOut, amountIn, minAmountOut, and a deadline. The call must also specify a router address and the data for the external call.
  • 2. Validation: The swap() function first performs a series of checks via checkParams(). It verifies that the tokens are whitelisted, the deadline is valid, and the module holds sufficient amountIn.
  • 3. Oracle Price Check: checkParams() queries the configured IAaveOracle (via the evaluate() function) to get the current market price of tokenIn versus tokenOut. It then applies the configured multiplier (slippage tolerance) to calculate an oracleMinAmount. The transaction is reverted if the caller's specified minAmountOut is less than this oracle-derived minimum, protecting against poor execution or oracle deviation.
  • 4. Execution: If all checks pass, the module verifies the router has the ROUTER_ROLE. It then approves the router to spend the amountIn and executes the swap by calling the router with the provided data.
  • 5. Final Check: After the external call, the swap() function verifies that the amountOut of tokens received is greater than or equal to the minAmountOut.

4.4 CoW Protocol Limit Order Flow

As an alternative to immediate on-chain swaps, the module can create gasless, off-chain limit orders using CoW Protocol (GPv2Settlement).

This flow allows the protocol to set a specific price target without committing gas upfront.

  • 1. Creation: A CALLER_ROLE calls createLimitOrder(), providing the Params struct, a GPv2Order.Data struct, and a unique orderUid.
  • 2. Validation: The function performs the same critical checkParams() validation as an on-chain swap, including the oracle-based minimum output check. It then uses checkCowswapOrder() to ensure all parameters in the CoW Protocol order (e.g., tokens, amounts, deadlines) are consistent with the Params.
  • 3. Pre-Signature: Upon successful validation, the SwapModule calls setPreSignature(orderUid, true) on the GPv2Settlement contract. This "pre-signs" the order on-chain, authorizing CoW Protocol's solvers to execute the swap on the module's behalf if and when the order's limit price (order.buyAmount) is met.
  • 4. Execution: The order is executed off-chain by CoW Protocol's solvers. The cowswapVaultRelayer (which must be approved via setCowswapApproval()) pulls the tokenIn from the SwapModule and delivers the tokenOut upon successful settlement.
  • 5. Invalidation: A CALLER_ROLE can also call invalidateOrder() at any time to cancel a pending pre-signed order.

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 The checkParams(...) function does not check for zero minAmountOut

FileFix commitTypeStatus
src/utils/SwapModule.sol—Best PracticesAcknowledged

The checkParams(...) function is called by both swap(...) and createLimitOrder(...) to validate swap parameters before execution. This function performs several important sanity checks, including correctly verifying that params.amountIn is not 0.

However, the function fails to implement a similar check to ensure that params.minAmountOut is also greater than 0. This makes it possible for a swap or limit order to be processed with an expected minimum output of 0.

This is particularly relevant for createLimitOrder(...), which creates CoW Protocol orders. In this context, params.minAmountOut maps to the order's buyAmount. The CoW Protocol documentation explicitly advises against signing orders where buyAmount is 0.

// src/utils/SwapModule.sol
function checkParams(Params calldata params) public view {
// ...
// @audit This check correctly ensures amountIn is not zero.
if (TransferLibrary.balanceOf(params.tokenIn, address(this)) < params.amountIn || params.amountIn == 0) {
revert Forbidden("amountIn");
}
SwapModuleStorage storage $ = _swapModuleStorage();
// ...
uint256 oracleMinAmount =
Math.mulDiv(
evaluate(params.tokenIn, params.tokenOut, params.amountIn),
multiplier,
BASE_MULTIPLIER
);

// @audit-issue A check to ensure `params.minAmountOut > 0` is missing.
if (params.minAmountOut < oracleMinAmount) {
revert Forbidden("minAmountOut < oracleMinAmount");
}
if (params.deadline < block.timestamp) {
// ...
}
}

Recommendation

Consider adding a validation check in the checkParams(...) function to ensure that params.minAmountOut is strictly greater than 0.

6.2 The initialize(...) function lacks a check for equal array lengths of holders and roles

FileFix commitTypeStatus
src/utils/SwapModule.sol—Best PracticesAcknowledged

The initialize(...) function in the SwapModule contract decodes initialization parameters from a bytes calldata data argument. This includes a holders array and a roles array, which are then used to grant permissions by looping over them.

The problem is that the function does not validate that holders.length is equal to roles.length. The loop iterates based on holders.length.

This means that if an administrator provides arrays of mismatched lengths (e.g., holders.length is less than roles.length) due to an off-chain error, the transaction will succeed silently. It will only grant roles up to the shorter holders.length, leading to a partially configured contract where expected permissions are not set. This violates the "fail-fast" principle and can make debugging misconfigurations difficult.

/// @inheritdoc IFactoryEntity
function initialize(bytes calldata data) external initializer {
(
address admin,
address subvault_,
address oracle_,
uint256 defaultMultiplier_,
address[] memory holders,
bytes32[] memory roles
) = abi.decode(data, (address, address, address, uint256, address[], bytes32[]));
if (admin == address(0) || subvault_ == address(0) || oracle_ == address(0)) {
revert ZeroValue();
}
checkMultiplier(defaultMultiplier_);
SwapModuleStorage storage $ = _swapModuleStorage();

_grantRole(DEFAULT_ADMIN_ROLE, admin);
$.subvault = subvault_;
$.oracle = oracle_;
$.defaultMultiplier = defaultMultiplier_;
// @audit-issue This loop iterates based on `holders.length` but does not
// @audit-issue validate that `holders.length` equals `roles.length`.
for (uint256 i = 0; i < holders.length; i++) {
if (holders[i] == address(0) || roles[i] == bytes32(0)) {
revert ZeroValue();
}
_grantRole(roles[i], holders[i]);
}
emit Initialized(data);
}

Recommendation

Consider adding a validation check at the beginning of the initialize(...) function to require that holders.length == roles.length. The function should revert if the lengths are mismatched.

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

No documentation for the SwapModule contract was provided, but Mellow's team addressed all the questions and concerns raised by the Nethermind Security team, providing valuable insights and a comprehensive understanding of the project's technical aspects.

Test suite
10Tests 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.