Mellow Finance
Core

The core protocol contracts were in scope: Vault, Subvault, deposit/redeem queues, Oracle, Verifier, ShareManager, FeeManager, and RiskManager.
report id
0587
category
DeFi & Yield
language
Solidity
final report
September 3, 2025
lines of code
7516
Content

Download audit report

1

Executive Summary

This document presents the results of the security review conducted by Nethermind Security for Mellow Finance. The Mellow protocol provides a sophisticated and flexible infrastructure for on-chain asset management, enabling curators to design and deploy structured financial products with a high degree of security and control.

The system is built around a central Vault contract that orchestrates user deposits, withdrawals, and the delegation of assets to external yield-generating strategies via Subvaults. The protocol's architecture is designed to mitigate common DeFi risks such as front-running and price manipulation through time-buffered queues, while providing granular, function-level access control for all strategy-related operations via a novel Verifier mechanism.

This audit focused on the end-to-end review of the smart contracts that implement the protocol's core logic, including: Vault, Subvault, DepositQueue, RedeemQueue, Oracle, Verifier, ShareManager, FeeManager, and RiskManager.

The audit comprises 4217 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
Final ReportSeptember 03, 2025
Repositorymellow-finance/flexible-vaults
Initial Commit69413d545f
Final Commit72f689f965
DocumentationMellow Core Vaults & Flexible Vaults Architecture
Documentation AssessmentHigh
Test Suite AssessmentHigh
Total findings
5
Fixed
4
Acknowledged
1
Mitigated
0
Unresolved
0

Findings by severity

  • Critical0
  • High2
  • Medium0
  • Low0
  • Informational3
  • Best Practices0

2

Audited Files

#ContractLoCCommentsRatioBlankTotal
1src/permissions/BitmaskVerifier.sol373697.3%376
2src/permissions/Consensus.sol1001414.0%21135
3src/permissions/Verifier.sol1372115.3%27185
4src/permissions/MellowACL.sol45613.3%1364
5src/permissions/protocols/EigenLayerVerifier.sol12221.6%8132
6src/permissions/protocols/OwnedCustomVerifier.sol2328.7%530
7src/permissions/protocols/ERC20Verifier.sol3625.6%644
8src/permissions/protocols/SymbioticVerifier.sol7822.6%888
9src/strategies/SymbioticStrategy.sol5423.7%965
10src/queues/SignatureDepositQueue.sol1815.6%524
11src/queues/RedeemQueue.sol201126.0%34247
12src/queues/SignatureQueue.sol1121715.2%24153
13src/queues/Queue.sol48714.6%1570
14src/queues/DepositQueue.sol172127.0%30214
15src/queues/SignatureRedeemQueue.sol2214.5%730
16src/managers/TokenizedShareManager.sol51713.7%1876
17src/managers/RiskManager.sol2062411.7%34264
18src/managers/ShareManager.sol1912814.7%37256
19src/managers/FeeManager.sol1312015.3%26177
20src/managers/BasicShareManager.sol51713.7%1472
21src/hooks/LidoDepositHook.sol4012.5%849
22src/hooks/RedirectingDepositHook.sol2414.2%227
23src/hooks/BasicRedeemHook.sol4812.1%453
24src/libraries/TransferLibrary.sol312374.2%761
25src/libraries/ShareManagerFlagLibrary.sol2633126.9%867
26src/libraries/SlotLibrary.sol10990.0%120
27src/libraries/FenwickTreeLibrary.sol665380.3%11130
28src/interfaces/permissions/IMellowACL.sol1213108.3%732
29src/interfaces/permissions/IConsensus.sol3745121.6%23105
30src/interfaces/permissions/IVerifier.sol745067.6%32156
31src/interfaces/permissions/ICustomVerifier.sol1011110.0%122
32src/interfaces/queues/IQueue.sol2124114.3%1156
33src/interfaces/queues/IRedeemQueue.sol44103234.1%14161
34src/interfaces/queues/IDepositQueue.sol3181261.3%17129
35src/interfaces/queues/ISignatureQueue.sol5479146.3%27160
36src/interfaces/managers/IRiskManager.sol614472.1%40145
37src/interfaces/managers/ITokenizedShareManager.sol68133.3%115
38src/interfaces/managers/IShareManager.sol706288.6%47179
39src/interfaces/managers/IFeeManager.sol423992.9%24105
40src/interfaces/hooks/IHook.sol49225.0%114
41src/interfaces/hooks/IRedeemHook.sol57140.0%214
42src/interfaces/factories/IFactory.sol343397.1%2390
43src/interfaces/factories/IFactoryEntity.sol56120.0%213
44src/interfaces/oracles/IOracle.sol6580123.1%38183
45src/interfaces/external/symbiotic/ISymbioticRegistry.sol4125.0%16
46src/interfaces/external/symbiotic/ISymbioticVault.sol8112.5%312
47src/interfaces/external/symbiotic/ISymbioticStakerRewards.sol7114.3%412
48src/interfaces/external/eigen-layer/IRewardsCoordinator.sol9011.1%43134
49src/interfaces/external/eigen-layer/IDelegationManager.sol8611.2%33120
50src/interfaces/external/eigen-layer/IAllocationManager.sol11810.8%45164
51src/interfaces/external/eigen-layer/ISignatureUtils.sol1317.7%317
52src/interfaces/external/eigen-layer/IStrategy.sol1616.2%1330
53src/interfaces/external/eigen-layer/IStrategyManager.sol3912.6%1858
54src/interfaces/external/tokens/IWSTETH.sol6116.7%29
55src/interfaces/external/tokens/IWETH.sol4125.0%16
56src/interfaces/modules/IVerifierModule.sol10770.0%421
57src/interfaces/modules/IBaseModule.sol8675.0%216
58src/interfaces/modules/IShareModule.sol765268.4%49177
59src/interfaces/modules/IACLModule.sol7457.1%314
60src/interfaces/modules/IVaultModule.sol4062155.0%29131
61src/interfaces/modules/ISubvaultModule.sol1119172.7%636
62src/interfaces/modules/ICallModule.sol910111.1%221
63src/factories/Factory.sol921718.5%22131
#ContractLoCCommentsRatioBlankTotal
64src/oracles/OracleHelper.sol1221915.6%8149
65src/oracles/Oracle.sol1952311.8%30248
66src/vaults/Subvault.sol1616.2%320
67src/vaults/VaultConfigurator.sol6523.1%774
68src/vaults/Vault.sol4912.0%555
69src/modules/VerifierModule.sol25416.0%1039
70src/modules/SubvaultModule.sol31619.4%1249
71src/modules/ACLModule.sol14214.3%622
72src/modules/CallModule.sol13323.1%420
73src/modules/ShareModule.sol2473915.8%41327
74src/modules/VaultModule.sol1242217.7%26172
75src/modules/BaseModule.sol17635.3%932
Total4217135432.1%11396710

4

System Overview

The Mellow protocol introduces a modular and upgradeable vault architecture designed for curators and asset managers to create, deploy, and manage structured on-chain financial products. The system aims to abstract the complexities of DeFi, providing a standardized framework for trustless execution of institutional-grade strategies while maintaining transparency and composability.

The architecture is centered around a main Vault contract that serves as the primary user entry point and orchestrates interactions between several specialized modules and peripheral contracts.

4.1 Core Components

The system's functionality is segregated into a collection of distinct, interconnected smart contracts:

  • Vault: The central contract that aggregates core logic and serves as the facade for the entire system. It coordinates asset management, share issuance, and interactions with subvaults and queues. It is composed of four main modules: ACLModule, ShareModule, VaultModule, and BaseModule.
  • Subvault: An isolated contract responsible for holding assets and executing delegated yield-generating strategies. Curators interact with external protocols through subvaults, with all actions subject to validation by an associated Verifier.
  • DepositQueue & RedeemQueue: These contracts manage user deposits and withdrawals through a time-buffered queuing system. This mechanism introduces a mandatory delay (depositInterval and redeemInterval) between a user's request and its execution, which is designed to protect against front-running and price manipulation attacks.
  • SignatureDepositQueue & SignatureRedeemQueue: Stateless, alternative queues that allow for instant, gas-efficient deposits and redemptions. They bypass the standard time-delay by processing EIP-712 compliant orders signed by a trusted off-chain consensus group.
  • Oracle: A permissioned price oracle responsible for providing secure and validated price reports for vault assets. It is a critical component for converting assets to shares and vice versa. It includes mechanisms to reject or flag reports based on configurable deviation thresholds.
  • Verifier: A crucial access control component paired with each Subvault. It enforces granular, call-level permissions, ensuring that curators can only execute pre-approved interactions with external contracts.
  • Share Manager: An upgradeable contract that handles all logic related to the vault's ERC20 receipt tokens (shares), including issuance, transfers, lockups, and whitelisting.
  • Fee Manager: A centralized module that calculates and directs four types of fees: deposit, redeem, performance, and protocol fees. Fees are typically collected in vault shares.
  • Risk Manager: Enforces configurable deposit limits at both the global Vault level and for each individual Subvault. It tracks asset balances (denominated in shares) to prevent excessive exposure.
  • MellowACL: A system-wide, role-based access control layer built upon OpenZeppelin's AccessControl. It manages permissions for administrative and operational functions.

4.2 Key Workflows

4.2.1 User Deposit Workflow

  1. A user initiates a deposit by calling deposit() on the DepositQueue, which registers a request containing the asset amount and a timestamp. Each user is limited to one pending deposit request at a time.
  2. The request remains pending until a trusted operator submits a price report to the Oracle.
  3. An oracle report can only process deposit requests that are older than the configured depositInterval. This delay mitigates front-running risks.
  4. Upon processing, the system calculates the corresponding number of shares based on the reported price, less any applicable deposit fees. The shares are allocated to the user but not yet minted.
  5. The user (or any other address) can then call claim() to mint the allocated shares to their account.

4.2.2 User Withdrawal Workflow

  1. A user initiates a withdrawal by calling redeem() on the RedeemQueue, which burns their vault shares and creates a redemption request. To prevent yield-griefing, these requests are non-cancellable.
  2. The vault curator is responsible for ensuring sufficient liquidity is available in the main Vault to fulfill pending redemptions, potentially by pulling assets from various Subvault strategies.
  3. A valid Oracle report, submitted after the redeemInterval, prices the redemption requests.
  4. The curator calls handleBatches() to process the priced requests, which allocates the underlying assets to the queue for withdrawal.
  5. Finally, the user calls claim() to receive their underlying assets.

4.2.3 Delegated Strategy Execution

  1. A curator, holding the CALLER_ROLE, executes an external call (e.g., to an external DeFi protocol) through a Subvault.
  2. The Subvault forwards the call data to its designated Verifier contract for authorization.
  3. The Verifier validates the call against a set of predefined rules based on its verification type:
    • ONCHAIN_COMPACT: Checks if a hash of the call's components (who, where, selector) exists in an on-chain allowlist.
    • MERKLE_COMPACT / MERKLE_EXTENDED: Validates the call against a Merkle root, allowing for a large set of approved calls with minimal on-chain footprint.
    • CUSTOM_VERIFIER: Delegates the validation logic to an external contract, enabling complex, state-dependent rules.
  4. If the Verifier approves the call, the Subvault executes it. Otherwise, the transaction reverts.

4.3 Security Model

The security of the Mellow Vaults relies on a defense-in-depth approach, combining robust access control, strict price validation, and architectural safeguards.

  • Granular Access Control: The system employs a two-tiered access control model. High-level administrative powers are managed by MellowACL, while low-level curator actions are strictly controlled by the Verifier contract, ensuring curators can only perform whitelisted operations.
  • Oracle Security: The Oracle is permissioned, accepting reports only from trusted actors. All submitted prices are validated against configurable absolute and relative deviation thresholds. Reports that are valid but exceed a `suspicious` threshold are queued for explicit approval by an admin, preventing the system from acting on potentially manipulative price data.
  • Front-Running Mitigation: The time-delayed nature of the DepositQueue and RedeemQueue ensures that user actions are priced by future, unknown oracle reports, neutralizing the advantage of front-runners who might otherwise exploit stale prices.
  • Risk Management: The Risk Manager contract enforces system-wide and per-subvault deposit limits, preventing unbounded asset exposure and providing a safeguard against unforeseen protocol risks or economic exploits.

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 Inconsistent Price Conversion Logic Leads to Incorrect Oracle Prices

FileFix commitTypeStatus
src/oracles/OracleHelper.sol—HighFixed

The OracleHelper contract is responsible for calculating the prices of assets held within a vault, which are then reported to an oracle. The price of each asset is defined relative to a "base asset" through the priceD18 parameter, as explained in the comments of the AssetPrice struct. According to these comments, to convert an amount from a specific asset to the base asset, one should multiply by priceD18.

The getPricesD18(...) function performs two key calculations using priceD18 that are logically inconsistent with each other.

First, when calculating the totalRedeemDemand (the total value of withdrawal demands in terms of the base asset), the code divides the demand_ by assetPrice.priceD18. This contradicts the logic described in the comments.

// ...
// @audit This comment explains that priceD18 is a multiplier.
// @audit - If priceD18 = 2e18, it means that 1 asset = 2 base assets
/**
* Price of the asset expressed via the base asset.
// ...
*/

// ...
$.unprocessedShares += shares_;
if (assetPrice.priceD18 == 0) {
$.totalRedeemDemand += demand_;
} else {
// @audit-issue The demand is divided by the price, not multiplied.
// @audit-issue This should be Math.mulDiv(demand_, assetPrice.priceD18, 1 ether).
$.totalRedeemDemand += Math.mulDiv(demand_, 1 ether, assetPrice.priceD18);
}
// ...

Later in the same function, when calculating the final prices of the other assets, the logic correctly multiplies the base asset's price by assetPrices[i].priceD18. This second calculation is consistent with the comments.

// ...
for (uint256 i = 0; i < assetPrices.length; i++) {
if (i != $.baseAssetIndex) {
// @audit This calculation correctly uses priceD18 as a multiplier.
pricesD18[i] = Math.mulDiv(
pricesD18[$.baseAssetIndex], assetPrices[i].priceD18, 1 ether
);
}
// ...

The incorrect calculation of totalRedeemDemand will lead to a miscalculation of the base asset's price. Since all other asset prices are derived from the base asset price, the entire set of prices returned by the function will be incorrect. This can severely compromise the protocol's economic security, leading to incorrect vault valuations, unfair distribution of funds upon withdrawal, and potential financial exploits.

Recommendation

Review the intended formula for converting asset amounts to their base asset equivalents. Assuming the comments and the final price calculation logic are correct, the calculation for totalRedeemDemand should be changed from division to multiplication.

6.2 Off-chain price calculation can result in an infinite loop

The OracleHelper contract includes the getPricesD18(...) function, which is designed for off-chain execution by keepers to calculate a vault's share price. This calculation is iterative, using a while(true) loop to find a stable price that correctly accounts for performance and protocol fees. The loop is expected to terminate once the calculated price converges.

The performance fee calculation, handled by the FeeManager contract, is conditional. It is applied only when the share price surpasses its previous high water mark, which corresponds to when priceD18 is less than minPriceD18. This conditional logic can create a scenario where the calculated price oscillates between two values, preventing convergence and causing the loop to run indefinitely.

This issue arises when an iteration calculates a price ( P1) low enough to trigger the performance fee. In the next iteration, the inclusion of this fee results in a higher calculated price ( P2). If P2 is now above the high-water mark, the performance fee is no longer applied in the subsequent iteration, causing the calculated price to revert to P1. This cycle of price oscillation between P1 and P2 means the loop's exit condition is never met.

The core of the issue lies in the while loop in the OracleHelper contract.

// src/oracles/OracleHelper.sol

// ...
uint256 baseAssetPriceD18 = vault.oracle().getReport($.baseAsset).priceD18;
// @audit The loop lacks a circuit breaker and can run indefinitely under certain conditions.
while (true) {
uint256 feeShares = totalShares > recipientShares
? feeManager.calculateFee(
address(vault), $.baseAsset, baseAssetPriceD18, totalShares - recipientShares
)
: 0;
uint256 newBaseAssetPriceD18 = Math.mulDiv(
totalShares + $.unprocessedShares + feeShares,
1 ether,
totalAssets - $.totalRedeemDemand
);
// @audit-issue This equality check may never be true if the price oscillates.
if (newBaseAssetPriceD18 == baseAssetPriceD18) {
break;
}
baseAssetPriceD18 = newBaseAssetPriceD18;
}
// ...

The conditional logic in the FeeManager contract.

// src/managers/FeeManager.sol
function calculateFee(address vault, address asset, uint256 priceD18, uint256 totalShares)
public
view
returns (uint256 shares)
{
// ...
if (asset == $.baseAsset[vault]) {
uint256 minPriceD18_ = $.minPriceD18[vault];
// @audit Performance fees are only added if the price is below the high-water mark.
if (priceD18 < minPriceD18_ && minPriceD18_ != 0) {
shares = Math.mulDiv(
minPriceD18_ - priceD18,
$.performanceFeeD6 * totalShares,
priceD18 * 1e6
);
}
// ...
}
}

An infinite loop in the off-chain price calculation script will cause it to fail, preventing keepers from submitting updated price reports. This leads to stale prices on-chain, which can halt critical vault operations or be exploited by attackers, resulting in a Denial of Service for the price update mechanism.

Recommendation

Consider re-architecting the iterative price calculation to guarantee convergence.

6.3 Incorrect Natspec for CUSTOM_VERIFIER verification data

FileFix commitTypeStatus
src/permissions/Verifier.sol—InfoFixed

The getVerificationResult(...) function in the Verifier contract handles the authorization of delegated function calls based on different verification types. One such type is CUSTOM_VERIFIER, which allows for verification logic to be delegated to an external contract.

The natspec documentation for the VerificationPayload struct provides instructions on how to encode the verificationData for each verification type. For CUSTOM_VERIFIER, it incorrectly suggests using abi.encodePacked(address customVerifier, customVerifierSpecificData).

/// @notice Struct containing all inputs required to verify a delegated function call.
struct VerificationPayload {
/// @dev The method used to verify the delegated call.
VerificationType verificationType;
/// @dev Encoded payload to be verified, varies by verification type:
/// - MERKLE_COMPACT: abi.encode(who, where, selector)
/// - MERKLE_EXTENDED: abi.encode(who, where, value, callData)
// @audit-issue Natspec suggests abi.encodePacked, which is incompatible with the implementation.
/// - CUSTOM_VERIFIER: abi.encodePacked(address customVerifier, customVerifierSpecificData)
bytes verificationData;
/// @dev Merkle proof used to validate the `verificationType` and `verificationData` for MERKLE_COMPACT,
/// MERKLE_EXTENDED, and CUSTOM_VERIFIER types.
bytes32[] proof;
}

The issue arises in the getVerificationResult(...) function, where the verifier address is extracted from verificationData using an assembly block with the calldataload instruction. This instruction reads a full 32-byte word.

    function getVerificationResult(...) public view returns (bool) {
// ...
} else if (payload.verificationType == VerificationType.CUSTOM_VERIFIER) {
address verifier;
assembly {
// @audit This instruction reads a full 32-byte word from calldata.
verifier := calldataload(verificationData.offset)
}
return ICustomVerifier(verifier).verifyCall(who, where, value, data, verificationData[0x20:]);
// ...
}

If a developer follows the natspec and uses abi.encodePacked, the 20-byte verifier address will be tightly packed with customVerifierSpecificData. The calldataload instruction would then read the address plus the first 12 bytes of the custom data, resulting in a corrupted address and causing the verification to always fail. The correct encoding should pad the address to 32 bytes, for example, by using abi.encode(...).

This discrepancy between the documentation and the implementation can mislead developers, causing them to create invalid payloads and breaking the CUSTOM_VERIFIER functionality for anyone who relies on the natspec.

Recommendation

Consider updating the natspec comment for verificationData in the VerificationPayload struct. Instead of abi.encodePacked(...), it should specify abi.encode(...) or another encoding method that ensures the customVerifier address is padded to a full 32-byte word to align with the implementation.

6.4 Incorrect natspec for ClaimableRequestExists error

The cancelDepositRequest(...) function in the DepositQueue contract allows a user to cancel their pending deposit request. The function checks if the request has become claimable by comparing the request's timestamp with the latest price checkpoint timestamp. If the request is claimable, the function reverts with the ClaimableRequestExists error.

// src/queues/DepositQueue.sol

function cancelDepositRequest() external nonReentrant {
// ...
(bool exists, uint32 timestamp,) = $.prices.latestCheckpoint();
// @audit The function reverts if a user tries to cancel a request that is already claimable.
if (exists && timestamp >= request._key) {
revert ClaimableRequestExists();
}
// ...
}

However, the natspec documentation for the ClaimableRequestExists error in the IDepositQueue interface is incorrect. It states that the error is thrown when a user tries to deposit again, which is not the case.

// src/interfaces/queues/IDepositQueue.sol

// @audit-issue The natspec incorrectly states the error is for depositing again.
/// @notice Thrown when a user tries to deposit again while a claimable request exists.
error ClaimableRequestExists();

This discrepancy can mislead developers, auditors, and integrators about the actual behavior of the cancelDepositRequest(...) function, potentially causing confusion when interacting with the contract.

Recommendation

Consider updating the natspec for the ClaimableRequestExists error to accurately reflect its usage. The comment should clarify that the error is thrown when a user attempts to cancel a deposit request that has already become claimable.

6.5 Use of abi.encodePacked with dynamic types could lead to storage slot collisions

FileFix commitTypeStatus
src/libraries/SlotLibrary.sol—InfoAcknowledged

The SlotLibrary contract provides a helper function, getSlot(...), to compute deterministic storage slots for various modules based on a contractName, an instance name, and a version. This mechanism is used by contracts such as VerifierModule and Verifier to derive their unique storage locations.

The implementation of getSlot(...) uses abi.encodePacked(...) to concatenate multiple dynamic string arguments. This function is known to be unsafe when used with multiple dynamic types, as it can lead to hash collisions. For example, the following two different sets of inputs would produce the same output:

  • abi.encodePacked("VerifierModule", "example", 1);
  • abi.encodePacked("Verifier", "Moduleexample", 1);

This is because abi.encodePacked directly concatenates the byte representations of the strings without any padding or delimiters.

function getSlot(string memory contractName, string memory name, uint256 version)
internal
pure
returns (bytes32)
{
// @audit-issue Use of abi.encodePacked with multiple dynamic types can lead to collisions.
return keccak256(
abi.encode(
uint256(
keccak256(
abi.encodePacked(
"mellow.flexible-vaults.storage.",
contractName,
name,
version
)
)
) - 1
)
) & ~bytes32(uint256(0xff));
}

This vulnerability could allow two different modules, for instance, a VerifierModule with name set to "example" and a Verifier with name set to "Moduleexample", to be assigned the same storage slot if they share the same version. This would cause the storage of one module to overwrite the other, leading to state corruption and unpredictable behavior.

Recommendation

Consider revisiting the slot generation logic in light of the potential for hash collisions with abi.encodePacked. While the risk may be low, it is important to be aware of this behavior. Adding explanatory comments to the function could also serve to inform future developers of this subtlety.

Resolution

Acknowledged, not applicable. This issue is mitigated by the current assumptions: contractName always corresponds to the actual contract name, and name is static, so storage collisions are impossible.

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 Mellow Protocol team provided an overview of the main system components during the kick-off call with a detailed explanation of the intended functionalities. They also supplied Notion documentation detailing the contracts' inner workings and flow: Mellow Core Vaults & Flexible Vaults Architecture. Moreover, the team addressed all questions and concerns from the Nethermind Security team, offering valuable insights into the project's technical aspects.

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