Prepared by:
HALBORN
Last Updated 04/30/2025
Date of Engagement: March 21st, 2025 - April 3rd, 2025
100% of all REPORTED Findings have been addressed
All findings
4
Critical
0
High
0
Medium
0
Low
2
Informational
2
Molecula Protocol
engaged Halborn to conduct a security assessment on their smart contracts beginning on March 10th, 2025 and ending on April 3rd, 2025. The security assessment was scoped to the smart contracts provided to the Halborn team.
The team at Halborn was provided 19 days for the engagement and assigned a security engineer to evaluate the security of the smart contract.
The security engineer is a blockchain and smart-contract security expert with advanced penetration testing, smart-contract hacking, and deep knowledge of multiple blockchain protocols.
The purpose of this assessment is to:
Ensure that smart contract functions operate as intended.
Identify potential security issues with the smart contracts.
In summary, Halborn identified some improvements to reduce the likelihood and impact of risks, which were either acknowledged and solved by the Molecula Protocol team
, or marked as not applicable by Halborn after additional review. The main ones were the following:
Implement mechanisms to prevent front/back-running an oracle change.
Implement a 2-Step ownership pattern.
Halborn
performed a combination of manual review of the code and automated security testing to balance efficiency, timeliness, practicality, and accuracy in regard to the scope of the smart contract assessment. While manual testing is recommended to uncover flaws in logic, process, and implementation; automated testing techniques help enhance coverage of smart contracts and can quickly identify items that do not follow security best practices. The following phases and associated tools were used throughout the term of the assessment:
Research into the architecture, purpose, and use of the platform.
Smart contract manual code review and walkthrough to identify any logic issue.
Thorough assessment of safety and usage of critical Solidity variables and functions in scope that could lead to arithmetic related vulnerabilities.
Manual testing by custom scripts.
Graphing out functionality and contract logic/connectivity/functions (solgraph
).
Static Analysis of security for scoped contract, and imported functions. (Slither
,Aderyn
).
Local or public testnet deployment (Foundry
, Remix IDE
).ontent goes here.
EXPLOITABILITY METRIC () | METRIC VALUE | NUMERICAL VALUE |
---|---|---|
Attack Origin (AO) | Arbitrary (AO:A) Specific (AO:S) | 1 0.2 |
Attack Cost (AC) | Low (AC:L) Medium (AC:M) High (AC:H) | 1 0.67 0.33 |
Attack Complexity (AX) | Low (AX:L) Medium (AX:M) High (AX:H) | 1 0.67 0.33 |
IMPACT METRIC () | METRIC VALUE | NUMERICAL VALUE |
---|---|---|
Confidentiality (C) | None (I:N) Low (I:L) Medium (I:M) High (I:H) Critical (I:C) | 0 0.25 0.5 0.75 1 |
Integrity (I) | None (I:N) Low (I:L) Medium (I:M) High (I:H) Critical (I:C) | 0 0.25 0.5 0.75 1 |
Availability (A) | None (A:N) Low (A:L) Medium (A:M) High (A:H) Critical (A:C) | 0 0.25 0.5 0.75 1 |
Deposit (D) | None (D:N) Low (D:L) Medium (D:M) High (D:H) Critical (D:C) | 0 0.25 0.5 0.75 1 |
Yield (Y) | None (Y:N) Low (Y:L) Medium (Y:M) High (Y:H) Critical (Y:C) | 0 0.25 0.5 0.75 1 |
SEVERITY COEFFICIENT () | COEFFICIENT VALUE | NUMERICAL VALUE |
---|---|---|
Reversibility () | None (R:N) Partial (R:P) Full (R:F) | 1 0.5 0.25 |
Scope () | Changed (S:C) Unchanged (S:U) | 1.25 1 |
Severity | Score Value Range |
---|---|
Critical | 9 - 10 |
High | 7 - 8.9 |
Medium | 4.5 - 6.9 |
Low | 2 - 4.4 |
Informational | 0 - 1.9 |
Critical
0
High
0
Medium
0
Low
2
Informational
2
Security analysis | Risk level | Remediation Date |
---|---|---|
Incorrect Static Call in Migration Function Breaks Cross-Contract Migration | Low | Not Applicable - 04/09/2025 |
Sandwich Attack Opportunity in Oracle Price Updates | Low | Solved - 04/09/2025 |
Centralization of Privileges | Informational | Solved - 04/09/2025 |
Absence of Two-Step Ownership Transfer Pattern | Informational | Acknowledged - 04/09/2025 |
//
In the MoleculaPoolTreasury.migrate()
function, there's a issue when migrating from old MoleculaPoolTreasury contracts. The function calls valueToRedeem()
on the old contract using a static call:
bytes memory result = oldMoleculaPool.functionStaticCall(
abi.encodeWithSignature("valueToRedeem()")
);
// Get the old `valueToRedeem` in mUSD.
uint256 oldValueToRedeem = abi.decode(result, (uint256));
This approach only works with the original MoleculaPool
contract where valueToRedeem
is a state variable. In the newer MoleculaPoolTreasury
implementation, this variable no longer exists in the same form. Instead, the valueToRedeem
is stored within the token mapping:
// In MoleculaPoolTreasury
struct TokenInfo {
TokenType tokenType;
bool isBlocked;
int8 n;
uint32 arrayIndex;
uint256 valueToRedeem; // This is now per-token
}
mapping(address => TokenInfo) public poolMap;
When attempting to migrate from one MoleculaPoolTreasury
to another, the static call will fail or return 0, making the migration process incomplete.
It is recommended to make it clear that this function is designated only for MoleculaPool and/or create another migrate()
function specifically designed for new pool MoleculaPoolTreasury.
NOT APPLICABLE: Natspec comment in the interface mention that this function should only be used for old MoleculaPool to new one.
//
The RebaseERC20.sol
contract allows the owner to update the oracle address using the setOracle
function. When this function is called, it changes the source from which token price/share information is derived. This creates an opportunity for sandwich attacks where users can exploit the price difference between the old and new oracle by executing trades immediately before and after the oracle update:
function setOracle(address oracleAddress) public onlyOwner checkNotZero(oracleAddress) {
oracle = oracleAddress;
}
The issue arises because:
The setOracle
transaction is visible in the mempool before being executed
The token price calculation depends directly on the oracle address via convertToShares()
and convertToAssets()
There's no protection mechanism to prevent trades immediately before and after oracle updates
When the owner submits a transaction to update the oracle, an attacker observing the mempool can:
Submit a transaction with higher gas to execute before the oracle update
Submit another transaction to execute after the oracle update
Profit from any difference in valuations between the two oracles
It is recommended to stop the protocol when doing this , pausing mechanisms should be used before and after the price update (in separate transaction).
SOLVED: The Molecula team has pausing system in place to protect this from happening that will be used during such updates.
//
The Molecula Protocol suffers from excessive centralization of control, giving contract owners extensive powers that could compromise the security and integrity of the protocol. Multiple contracts in the system rely heavily on onlyOwner
access controls, allowing privileged accounts to manipulate critical protocol parameters, mint/burn tokens, and modify system states without restrictions.
Key centralization issues include:
In RebaseERC20.sol
, the owner can arbitrarily mint and burn shares to/from any account
In MoleculaPoolTreasury.sol
, the owner can add or remove tokens from the pool, block tokens, and manipulate the whitelist
In SupplyManager.sol
, the owner can set agents, distribute yield, and change critical parameters
It is recommended to implement decentralization measures/multisigs wallets to mitigate these centralization risks.
SOLVED: The Molecula team implements a decentralized MoleculaRouter to manage minting/burning operations, and owner privileges will be transitioned to DAO governance.
//
Multiple contracts including RebaseERC20.sol, MoleculaPoolTreasury.sol, and SupplyManager.sol inherit from OpenZeppelin's Ownable rather than Ownable2Step:
// MoleculaPoolTreasury.sol
contract MoleculaPoolTreasury is Ownable, IMoleculaPool, ZeroValueChecker {
// ...
}
// SupplyManager.sol
contract SupplyManager is Ownable, ISupplyManager, IOracle, ZeroValueChecker {
// ...
}
The single-step ownership transfer mechanism creates risk of permanently losing administrative control if the owner address is incorrectly specified during transfer.
It is recommended to implement OpenZeppelin's Ownable2Step pattern instead of the basic Ownable for all privileged contracts.
ACKNOWLEDGED: The Molecula team acknowledged the finding.
Halborn used automated testing techniques to enhance the coverage of certain areas of the smart contracts in scope. Among the tools used was Slither
, a Solidity static analysis framework.
After Halborn verified the smart contracts in the repository and was able to compile them correctly into their abis and binary format, Slither was run against the contracts. This tool can statically verify mathematical relationships between Solidity variables to detect invalid or inconsistent usage of the contracts' APIs across the entire code-base.
Halborn strongly recommends conducting a follow-up assessment of the project either within six months or immediately following any material changes to the codebase, whichever comes first. This approach is crucial for maintaining the project’s integrity and addressing potential vulnerabilities introduced by code modifications.
// Download the full report
Molecula Contracts
* Use Google Chrome for best results
** Check "Background Graphics" in the print settings if needed