Download audit report
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.
Findings by severity
| # | Contract | LoC | Comments | Ratio | Blank | Total |
| 1 | src/utils/SwapModule.sol | 244 | 33 | 13.5% | 35 | 312 |
| Total | 244 | 33 | 13.5% | 35 | 312 |
| # | Finding | Severity | Update |
| 1 | The checkParams(...) function does not check for zero minAmountOut | Best Practices | Acknowledged |
| 2 | The initialize(...) function lacks a check for equal array lengths of holders and roles | Best Practices | Acknowledged |
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.
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.The SwapModule is designed as a transitory contract. The movement of assets is strictly controlled by the subvault contract.
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.tokenOut (or any remaining tokenIn) is held by the SwapModule. The subvault can then call pullAssets() to retrieve these tokens.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:
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.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.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.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.swap() function verifies that the amountOut of tokens received is greater than or equal to the minAmountOut.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.
CALLER_ROLE calls createLimitOrder(), providing the Params struct, a GPv2Order.Data struct, and a unique orderUid.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.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.cowswapVaultRelayer (which must be approved via setCowswapApproval()) pulls the tokenIn from the SwapModule and delivers the tokenOut upon successful settlement.CALLER_ROLE can also call invalidateOrder() at any time to cancel a pending pre-signed order.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.
checkParams(...) function does not check for zero minAmountOut| File | Fix commit | Type | Status |
src/utils/SwapModule.sol | — | Best Practices | Acknowledged |
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.
initialize(...) function lacks a check for equal array lengths of holders and roles| File | Fix commit | Type | Status |
src/utils/SwapModule.sol | — | Best Practices | Acknowledged |
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.
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.
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.
| 10Tests 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