Smart Contract Audit for EagleFi

Management Summary

EagleFi contacted Sayfer to perform a security audit on smart contracts associated with their platform, EagleFi, in March 2025.

This report documents the research carried out by Sayfer targeting the selected resources defined under the research scope. Particularly, this report displays the security posture review for the EagleFi smart contracts.

Over the research period of 3 weeks, we discovered 11 vulnerabilities in the contract.

Several fixes should be implemented following the report, to ensure the system’s security posture is competent.

After a review by the Sayfer team, we certify that all the security issues mentioned in this report have been addressed by the EagleFi team.

Risk Methodology

At Sayfer, we are committed to delivering the highest quality smart contract audits to our clients. That’s why we have implemented a comprehensive risk assessment model to evaluate the severity of our findings and provide our clients with the best possible recommendations for mitigation.

Our risk assessment model is based on two key factors: IMPACT and LIKELIHOOD. Impact refers to the potential harm that could result from an issue, such as financial loss, reputational damage, or a non-operational system. Likelihood refers to the probability that an issue will occur, taking into account factors such as the complexity of the contract and the number of potential attackers.

By combining these two factors, we can create a comprehensive understanding of the risk posed by a particular issue and provide our clients with a clear and actionable assessment of the severity of the issue. This approach allows us to prioritize our recommendations and ensure that our clients receive the best possible advice on how to protect their smart contracts.

Risk is defined as follows:

Vulnerabilities by Risk

High – Direct threat to key business processes.
Medium – Indirect threat to key business processes or partial threat to business processes.
Low – No direct threat exists. The vulnerability may be exploited using other vulnerabilities.
Informational – This finding does not indicate vulnerability, but states a comment that notifies about design flaws and improper implementation that might cause a problem in the long run.

Severity
# of issues
Low
3
Medium
4
High
0
Critical
0
Informational
4

Approach

Introduction

EagleFi contacted Sayfer to perform a security audit on their smart contracts.

This report documents the research carried out by Sayfer targeting the selected resources defined under the research scope. Particularly, this report displays the security posture review for the aforementioned contracts.

Scope Overview

Together with the client team we defined the following contract as the scope of the project.
Commit hash: 55592c9b82fd08d47c5741aca76e97a5673b3061

Contract Sha-256
contracts/basicPool.ts c2438a5b9ca3d10645e190981b9a42ae7e4ec07c6d44076dbb8b8ec5e4afa131
contracts/registry.ts b0c43e4a098c3226279b59b7c6968829b5c0af8d5397f0e091fff0655e835402
contracts/swapRouter.ts 3b796c027857c75750fa7e5ff6337c61fa4e09e16c2683fc11daa3912d6ddc46
contracts/token.ts 66e75916569fe05de328ad9d0a072a494dba81c7dc699e4daad3caa8dd292c12
contracts/tokenDeployer.ts 06398fe9834720033a0cfd2b46da1746202ea38aef2feea95f7079a50a67c4b0
utils/constants.ts eb013192c26d461238e8e4e756f6e306d35908dbe0e1d887dd22f0f53e0a84ff
utils/index.ts a341f8f88e4795497d7247f9611f540dd6c19d6ea1cfbd71fe3de97483025871
utils/ownership-internal.ts 05d909a580f8e7382fca7d96e79a67a324a17d1ab20f9f318ff3431ec98a32b9
utils/ownership.ts 91f2faf9f03376cdc420f3ab4f80021757275b9de430099bb0a14064fd5ed2e1

Our tests were performed from 09/03/2025 to 30/03/2025.

Don’t let it be too late!

Start your audit with Sayfer

Scope Validation

We began by ensuring that the scope defined to us by the client was technically logical.

Deciding what scope is right for a given system is part of the initial discussion.

Threat Model

We defined that the largest current threat to the system is the ability of malicious users to steal funds from the contract.

Don’t let it be too late!

Start your audit with Sayfer

Protocol Overview

Protocol Introduction

EagleFi is a highly configurable decentralized exchange (DEX) built on the innovative Massa blockchain. As the Front Page of the Massa Ecosystem, EagleFi is committed to breaking barriers in global DeFi adoption by offering an unparalleled trading and data-driven experience.

With a focus on functionality and convenience, EagleFi provides crypto enthusiasts with a user-friendly platform that consolidates key project data to simplify their DeFi journey. It continues to deliver cutting-edge features while staying true to its mission of breaking DeFi barriers.

Security Evaluation

The following test cases were the guideline while auditing the system. This checklist is a modified version of the SCSVS v1.2, with improved grammar, clarity, conciseness, and additional criteria. Where there is a gap in the numbering, an original criterion was removed. Criteria that are marked with an asterisk were added by us.

Architecture, Design and Threat Modeling

Architecture, Design and Threat Modeling Test Name
G1.2 Every introduced design change is preceded by threat modeling.
G1.3 The documentation clearly and precisely defines all trust boundaries in the contract (trusted relations with other contracts and significant data flows).
G1.4 The SCSVS, security requirements or policy is available to all developers and testers.
G1.5 The events for the (state changing/crucial for business) operations are defined.
G1.6 The project includes a mechanism that can temporarily stop sensitive functionalities in case of an attack. This mechanism should not block users’ access to their assets (e.g. tokens).
G1.7 The amount of unused cryptocurrencies kept on the contract is controlled and at the minimum acceptable level so as not to become a potential target of an attack.
G1.8 If the fallback function can be called by anyone, it is included in the threat model.
G1.9 Business logic is consistent. Important changes in the logic should be applied in all contracts.
G1.10 Automatic code analysis tools are employed to detect vulnerabilities.
G1.11 The latest major release of Solidity is used.
G1.12 When using an external implementation of a contract, the most recent version is used.
G1.13 When functions are overridden to extend functionality, the super keyword is used to maintain previous functionality.
G1.14 The order of inheritance is carefully specified.
G1.15 There is a component that monitors contract activity using events.
G1.16 The threat model includes whale transactions.
G1.17 The leakage of one private key does not compromise the security of the entire project.

Policies and Procedures

Policies and Procedures Test Name
G2.2 The system’s security is under constant monitoring (e.g. the expected level of funds).
G2.3 There is a policy to track new security vulnerabilities and to update libraries to the latest secure version.
G2.4 The security department can be publicly contacted and that the procedure for handling reported bugs (e.g., thorough bug bounty) is well-defined.
G2.5 The process of adding new components to the system is well defined.
G2.6 The process of major system changes involves threat modeling by an external company.
G2.7 The process of adding and updating components to the system includes a security audit by an external company.
G2.8 In the event of a hack, there’s a clear and well known mitigation procedure in place.
G2.9 The procedure in the event of a hack clearly defines which persons are to execute the required actions.
G2.10 The procedure includes alarming other projects about the hack through trusted channels.
G2.11 A private key leak mitigation procedure is defined.

Upgradability

Upgradability Test Name
G3.2 Before upgrading, an emulation is made in a fork of the main network and everything works as expected on the local copy.
G3.3 The upgrade process is executed by a multisig contract where more than one person must approve the operation.
G3.4 Timelocks are used for important operations so that the users have time to observe upcoming changes (please note that removing potential vulnerabilities in this case may be more difficult).
G3.5 initialize() can only be called once.
G3.6 initialize() can only be called by an authorized role through appropriate modifiers (e.g. initializer, onlyOwner).
G3.7 The update process is done in a single transaction so that no one can front-run it.
G3.8 Upgradeable contracts have reserved gap on slots to prevent overwriting.
G3.9 The number of reserved (as a gap) slots has been reduced appropriately if new variables have been added.
G3.10 There are no changes in the order in which the contract state variables are declared, nor their types.
G3.11 New values returned by the functions are the same as in previous versions of the contract (e.g. owner(), balanceOf(address)).
G3.12 The implementation is initialized.
G3.13 The implementation can’t be destroyed.

 

Business Logic

Business Logic Test Name
G4.2 The contract logic and protocol parameters implementation corresponds to the documentation.
G4.3 The business logic proceeds in a sequential step order and it is not possible to skip steps or to do it in a different order than designed.
G4.4 The contract has correctly enforced business limits.
G4.5 The business logic does not rely on the values retrieved from untrusted contracts (especially when there are multiple calls to the same contract in a single flow).
G4.6 The business logic does not rely on the contract’s balance (e.g., balance == 0).
G4.7 Sensitive operations do not depend on block data (e.g., block hash, timestamp).
G4.8 The contract uses mechanisms that mitigate transaction-ordering (front-running) attacks (e.g. pre-commit schemes).
G4.9 The contract does not send funds automatically, but lets users withdraw funds in separate transactions instead.

Access Control

Access Control Test Name
G5.2 The principle of the least privilege is upheld. Other contracts should only be able to access functions and data for which they possess specific authorization.
G5.3 New contracts with access to the audited contract adhere to the principle of minimum rights by default. Contracts should have a minimal or no permissions until access to the new features is explicitly granted.
G5.4 The creator of the contract complies with the principle of the least privilege and their rights strictly follow those outlined in the documentation.
G5.5 The contract enforces the access control rules specified in a trusted contract, especially if the dApp client-side access control is present and could be bypassed.
G5.6 Calls to external contracts are only allowed if necessary.
G5.7 Modifier code is clear and simple. The logic should not contain external calls to untrusted contracts.
G5.8 All user and data attributes used by access controls are kept in trusted contracts and cannot be manipulated by other contracts unless specifically authorized.
G5.9 The access controls fail securely, including when a revert occurs.
G5.10 If the input (function parameters) is validated, the positive validation approach (whitelisting) is used where possible.

Communication

Communication Test Name
G6.2 Libraries that are not part of the application (but the smart contract relies on to operate) are identified.
G6.3 Delegate call is not used with untrusted contracts.
G6.4 Third party contracts do not shadow special functions (e.g. revert).
G6.5 The contract does not check whether the address is a contract using extcodesize opcode.
G6.6 Re-entrancy attacks are mitigated by blocking recursive calls from other contracts and following the Check-Effects-Interactions pattern. Do not use the send function unless it is a must.
G6.7 The result of low-level function calls (e.g. send, delegatecall, call) from other contracts is checked.
G6.8 Contract relies on the data provided by the right sender and does not rely on tx.origin value.

Arithmetic

Arithmetic Test Name
G7.2 The values and math operations are resistant to integer overflows. Use SafeMath library for arithmetic operations before solidity 0.8.*.
G7.3 The unchecked code snippets from Solidity ≥ 0.8.* do not introduce integer under/overflows.
G7.4 Extreme values (e.g. maximum and minimum values of the variable type) are considered and do not change the logic flow of the contract
G7.5 Non-strict inequality is used for balance equality.
G7.6 Correct orders of magnitude are used in the calculations.
G7.7 In calculations, multiplication is performed before division for accuracy.
G7.8 The contract does not assume fixed-point precision and uses a multiplier or store both the numerator and denominator.

Denial of Service

Denial of Service Test Name
G8.2 The contract does not iterate over unbound loops.
G8.3 Self-destruct functionality is used only if necessary. If it is included in the contract, it should be clearly described in the documentation.
G8.4 The business logic isn’t blocked if an actor (e.g. contract, account, oracle) is absent.
G8.5 The business logic does not disincentivize users to use contracts (e.g. the cost of transaction is higher than the profit).
G8.6 Expressions of functions assert or require have a passing variant.
G8.7 If the fallback function is not callable by anyone, it is not blocking contract functionalities.
G8.8 There are no costly operations in a loop.
G8.9 There are no calls to untrusted contracts in a loop.
G8.10 If there is a possibility of suspending the operation of the contract, it is also possible to resume it.
G8.11 If whitelists and blacklists are used, they do not interfere with normal operation of the system.
G8.12 There is no DoS caused by overflows and underflows.

Blockchain Data

Blockchain Data Test Name
G9.2 Any saved data in contracts is not considered secure or private (even private variables).
G9.3 No confidential data is stored in the blockchain (passwords, personal data, token etc.).
G9.4 Contracts do not use string literals as keys for mappings. Global constants are used instead to prevent Homoglyph attack.
G9.5 Contract does not trivially generate pseudorandom numbers based on the information from blockchain (e.g. seeding with the block number).

Gas Usage and Limitations

Gas Usage and Limitations Test Name
G10.2 Gas usage is anticipated, defined and has clear limitations that cannot be exceeded. Both code structure and malicious input should not cause gas exhaustion.
G10.3 Function execution and functionality does not depend on hard-coded gas fees (they are bound to vary).

Clarity and Readability

Clarity and Readability Test Name
G11.2 The logic is clear and modularized in multiple simple contracts and functions.
G11.3 Each contract has a short 1-2 sentence comment that explains its purpose and functionality.
G11.4 Off-the-shelf implementations are used, this is made clear in comment. If these implementations have been modified, the modifications are noted throughout the contract.
G11.5 The inheritance order is taken into account in contracts that use multiple inheritance and shadow functions.
G11.6 Where possible, contracts use existing tested code (e.g. token contracts or mechanisms like ownable) instead of implementing their own.
G11.7 Consistent naming patterns are followed throughout the project.
G11.8 Variables have distinctive names.
G11.9 All storage variables are initialized.
G11.10 Functions with specified return type return a value of that type.
G11.11 All functions and variables are used.
G11.12 require is used instead of revert in if statements.
G11.13 The assert function is used to test for internal errors and the require function is used to ensure a valid condition in input from users and external contracts.
G11.14 Assembly code is only used if necessary.

Test Coverage

Test Coverage Test Name
G12.2 Abuse narratives detailed in the threat model are covered by unit tests
G12.3 Sensitive functions in verified contracts are covered with tests in the development phase.
G12.4 Implementation of verified contracts has been checked for security vulnerabilities using both static and dynamic analysis.
G12.5 Contract specification has been formally verified
G12.6 The specification and results of the formal verification is included in the documentation.

Decentralized Finance

Decentralized Finance Test Name
G14.1 The lender’s contract does not assume its balance (used to confirm loan repayment) to be changed only with its own functions.
G14.2 Functions that change lenders’ balance and/or lend cryptocurrency are non-re-entrant if the smart contract allows to borrow the main platform’s cryptocurrency (e.g. Ethereum). It blocks the attacks that update the borrower’s balance during the flash loan execution.
G14.3 Flash loan functions can only call predefined functions on the receiving contract. If it is possible, define a trusted subset of contracts to be called. Usually, the sending (borrowing) contract is the one to be called back.
G14.4 If it includes potentially dangerous operations (e.g. sending back more ETH/tokens than borrowed), the receiver’s function that handles borrowed ETH or tokens can be called only by the pool and within a process initiated by the receiving contract’s owner or another trusted source (e.g. multisig).
G14.5 Calculations of liquidity pool share are performed with the highest possible precision (e.g. if the contribution is calculated for ETH it should be done with 18 digit precision – for Wei, not Ether). The dividend must be multiplied by the 10 to the power of the number of decimal digits (e.g. dividend * 10^18 / divisor).
G14.6 Rewards cannot be calculated and distributed within the same function call that deposits tokens (it should also be defined as non-re-entrant). This protects from momentary fluctuations in shares.
G14.7 Governance contracts are protected from flash loan attacks. One possible mitigation technique is to require the process of depositing governance tokens and proposing a change to be executed in different transactions included in different blocks.
G14.8 When using on-chain oracles, contracts are able to pause operations based on the oracles’ result (in case of a compromised oracle).
G14.9 External contracts (even trusted ones) that are allowed to change the attributes of a project contract (e.g. token price) have the following limitations implemented: thresholds for the change (e.g. no more/less than 5%) and a limit of updates (e.g. one update per day).
G14.10 Contract attributes that can be updated by the external contracts (even trusted ones) are monitored (e.g. using events) and an incident response procedure is implemented (e.g. during an ongoing attack).
G14.11 Complex math operations that consist of both multiplication and division operations first perform multiplications and then division.
G14.12 When calculating exchange prices (e.g. ETH to token or vise versa), the numerator and denominator are multiplied by the reserves (see the getInputPrice function in the UniswapExchange contract).

 

Order audit from Sayfer

    This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

    Security Assessment Findings

    Missing Minimum Liquidity Lock Mechanism

    ID SAY-01
    Status Fixed
    Risk Medium
    Business Impact Without a minimum liquidity mechanism, the platform is vulnerable to first depositor attacks, where an attacker can extract disproportionate value from subsequent liquidity providers. By adding minimal liquidity and then directly transferring tokens to the pool, the attacker creates a situation where new depositors receive fewer LP tokens than they should, effectively transferring value to the first depositor. The risk of the issue was reduced to medium as the registry allows for pools with initial liquidity creation as well.
    Location – contracts/basicPool.ts; _getAddLiquidityData(u256, u256, u256, u256)

    Description

    _getAddLiquidityData( ) lacks the minimum liquidity protection mechanism that other AMMs like Uniswap V2 implement.

    • basicPool.ts:1506-1515
    let fnalAmountA = amountA;
    let fnalAmountB = amountB;
    let liquidity: u256;
    if (reserveA u256.Zero reserveB u256.Zero) {
    Initial liquidity: liquidity = sqrt(amountA * amountB)
    const product = SafeMath256.mul(amountA, amountB);
    liquidity = sqrt(product)
    liquidity = SafeMath256.sqrt(product);
    }

    Unlike Uniswap V2, which permanently locks a minimum amount of liquidity tokens
    (MINIMUM_LIQUIDITY) by sending them to address(0), EagleFi allows the first
    depositor to receive all of the initial LP tokens with no minimum requirement.
    This creates a significant economic vulnerability where an attacker can manipulate
    the pool’s exchange rate to extract value from subsequent liquidity providers:

    1. The attacker adds minimal liquidity (e.g., 1 token A and 1 token B) and receives 1 LP token representing 100% ownership of the pool
    2. They bypass the addLiquidity function and directly transfer a large amount of tokens (e.g., 1,000,000 of each token) to the pool contract
    3. The pool now contains 1,000,001 of each token, but only 1 LP token exists in circulation
    4. When legitimate users add liquidity, they receive dramatically fewer LP tokens than they should because the pool ratio is skewed.
    5. The attacker can then remove their liquidity with a much higher share of the pool than their actual contribution

    Mitigation

    Implement a minimum liquidity lock mechanism similar to Uniswap V2. When the
    first liquidity is provided, mint a small fixed amount of LP tokens (e.g., 1000) and
    send them to a burn address (address(0)). This ensures a minimum liquidity
    baseline that cannot be manipulated by the first depositor.

     

    Missing K-Value Invariant Check When Swapping

    ID SAY-02
    Status Fixed
    Risk Medium
    Business Impact The absence of a K-value invariant check may allow an attacker to exploit minor rounding errors and carefully crafted input amounts to gradually reduce pool value. This could result in economic value extraction from the pool over multiple swap operations.
    Location – contracts/basicPool.ts; swap(StaticArray)

    Description

    swap(StaticArray) currently verifies that the contract’s balance for the incoming token is sufficient by ensuring that it is not less than the sum of the previous reserve and the deposit amount. However, this measure solely confirms the adequacy of token transfer and does not assess whether the product of both token reserves—critical for maintaining pool balance—is preserved after the swap.

    • basicPool.ts:461-465
    // Ensure that the balance of the smart contract of tokenIn is greater
    than or equal to the reserveIn + amountIn
    assert(
    contractInBalance SafeMath256.add(reserveIn, amountIn),
    'SWAP INSUFFICIENT_IN_SEND',
    );

    As a result, any slight deviations caused by rounding errors or imprecise fee handling are not caught, leaving the protocol open to cumulative economic attacks that can subtly reduce the pool’s value over time. This differs from the implementation of flash loans in flashLoan(StaticArray), where K value is explicitly checked.

    • basicPool.ts:931-932
    // Ensure that the new pool K value is greater than or equal to the
    old pool K value
    assert(newPoolK poolK, 'FLASH_ERROR INVALID_POOL_K_VALUE'); 

    Mitigation

    Add a final invariant check at the end of the swap process to validate that the fee-adjusted product of the reserves is maintained or increased. Extensive testing under extreme conditions is advised to ensure the invariant holds in all scenarios.

     

    Missing Decimal Normalization

    ID SAY-03
    Status Fixed
    Risk Medium
    Business Impact The lack of decimal normalization causes severe economic risks when tokens with different decimal places interact, leading to unfair LP token distribution and skewed exchange rates that can be exploited by sophisticated traders. Users providing tokens with lower decimal places receive disproportionately fewer LP tokens relative to their economic contribution, creating significant losses for less-informed participants until arbitrage balances the pool.
    Location – Protocolwide

    Description

    EagleFi does not implement explicit normalization for tokens with different decimal places throughout its pool operations. When performing critical calculations such as determining LP token shares, swap amounts, and price updates, the protocol operates directly on the raw token amounts without adjusting for decimal differences.

    When tokens with significantly different decimals (e.g., USDC with 6 and WMAS with 18) are added to a pool, their relative contribution appears unbalanced. For example, 1 USDC (10 6 base units) paired with 1 WMAS (10 18 base units) would create a pool that values WMAS at a much higher price than USDC, requiring arbitrage to correct.

    While token.ts properly stores decimal information during construction, this information is never utilized when tokens interact in a pool.

    • token.ts:62, 84
    const decimals = args.nextU8().expect('Invalid decimals');
    [...]
    Storage.set(DECIMALS_KEY, [decimals]);

    It is also worth mentioning that a comment in basicPool.ts claims that _addLiquidity( ) normalizes decimals, which it does not actually do.

    • basicPool.ts:1116
    * - Normalizes the token amounts to default decimals.  

    Mitigation

    The protocol should implement proper decimal normalization throughout its codebase to ensure accurate economic representation:

    1. During pool creation, record the decimal places of both tokens as part of the pool’s configuration.
    2. Create utility functions that adjust token amounts to a common decimal base before performing calculations.
    3. Modify key functions like addLiquidity( ), swap( ), getAmountOut( ), and price oracle updates to normalize token amounts before performing calculations.
    4. Implement validation during pool creation to prevent pairing tokens with extreme decimal differences (e.g., more than 12 decimal places apart) which could lead to precision issues.
    5. Clearly document how the protocol handles tokens with different decimal places to ensure users and integrators understand the behavior.

    Single-Step Ownership Transfer

    ID SAY-04
    Status Fixed
    Risk Medium
    Business Impact The single-step ownership transfer mechanism presents a significant risk of permanently locking admin functionality if ownership is accidentally transferred to an incorrect or inaccessible address. Since the function only validates that the address is syntactically valid, but doesn’t verify that the address is actually controlled by the intended recipient, a simple typographical error could render the protocol’s administrative functions permanently inaccessible. This could prevent future protocol upgrades, parameter adjustments, or emergency interventions.
    Location – utils/ownership.ts; transferOwnership(StaticArray<u8>)

    Description

    transferOwnership(StaticArray<u8>) implements a single-step transfer without confirmation from the new owner.

    • utils/ownership.ts:22-33
    export function transferOwnership(binaryArgs: StaticArray): void {
    const args = new Args(binaryArgs);
    const newOwner = args.nextString().expect('Invalid new owner');
    _onlyOwner();
    
    assert(validateAddress(newOwner), 'INVALID_OWNER_ADDRESS');
    
    // Set the new owner
    _setOwner(newOwner);
    }

    Mitigation

    Implement a two-step ownership transfer process where the new owner must accept ownership through a separate transaction, mitigating the risk of accidental transfers to invalid addresses.

     

    Unbounded Swap Route in swapRouter.ts

    ID SAY-05
    Status Fixed
    Risk Low
    Business Impact The transactions with excessively long routes could consume large amounts of gas, potentially exceeding block gas limits and causing the entire transaction to fail after partial execution.
    Location – contracts/swapRouter.ts; swap(StaticArray)

    Description

    swapRouter’s swap(StaticArray) allows execution of multi-hop routes without a maximum limit.

    • swapRouter.ts:71-93
    if (swapRouteLength > 1) {
    // Add support for multiple swaps
    for (let i = 0; i < swapRouteLength; i ) {
    const swapPath = swapPathArray[i];
    [...]
    }
    }

    Without an upper bound on swapRouteLength, excessively long routes could consume excessive gas, potentially causing reverts.

    Mitigation

    Implement a reasonable maximum route length (e.g., 3-4 hops) to prevent excessive gas consumption and potential out-of-gas reverts.

     

    Restrictive Syncing Permissions

    ID SAY-06
    Status Acknowledged
    Risk Low
    Business Impact The restrictive permission model could cause pool reserves to remain out of sync with actual token balances if direct token transfers to the pool occur (bypassing the protocol’s functions) and the owner is unavailable or unresponsive. This could lead to incorrect price calculations, unfavorable trades for users, and oracle data corruption until the owner manually synchronizes the reserves.
    Location – contracts/basicPool.ts; syncReserves()

    Description

    syncReserves() can only be called by the registry contract’s owner.

    • basicPool.ts:734
    export function syncReserves(): void {
    [...]
    // only owner of registery contract can call this function
    _onlyOwner();
    [...]
    }

    This differs from Uniswap’s approach. While both mechanisms update oracles, EagleFi’s approach is more centralized because syncReserves() is restricted to the owner. This creates greater risk during emergencies where prices need correction but the owner is unavailable. A similar vulnerability was identified in other protocols where centralized oracle controls led to exploitable delays in price updates.

    Mitigation

    Consider making syncReserves() permissionless to allow anyone to synchronize the contract’s state with actual token balances, improving the system’s reliability.

     

    Pool Construction Allows Duplicates

    ID SAY-07
    Status Fixed
    Risk Low
    Business Impact The ability to create pools with arbitrarily similar fee rates could lead to significant liquidity fragmentation across the protocol. Users might unknowingly provide liquidity to different pools with essentially identical economics (e.g., 0.300% vs 0.301% fee), reducing capital efficiency and trading volume per pool. This fragmentation would result in higher slippage for traders, reduced fee income for liquidity providers, and overall diminished protocol utility.
    Location – utils/index.ts; buildPoolKey(string, string, u64, string)

    Description

    buildPoolKey( ) constructs pool keys using token addresses and fee rates.

    • utils/index.ts:21-39
    export function _buildPoolKey(
    tokenA: string,
    tokenB: string,
    inputFeeRate: u64,
    wmasAddress: string,
    ): string {
    // sort the addresses to ensure that the key of the pool is always
    the same
    // Ensure WMAS if exists, it is always tokenB
    const sortedTokens = sortPoolTokenAddresses(tokenA, tokenB,
    wmasAddress);
    const sortedTokenA = sortedTokens[0];
    const sortedTokenB = sortedTokens[1];
    const key =
    `${sortedTokenA}-${sortedTokenB}-${inputFeeRate.toString()}`;
    generateEvent(`Built pool key: ${key}`);
    return key;
    }

    This implementation allows creation of multiple pools with nearly identical fee rates (e.g., 3000 vs 3001), fragmenting liquidity and potentially confusing users, since these would represent effectively the same trading tier.

    Mitigation

    Implement a fee tier system with predefined values (like Uniswap V3’s 0.05%, 0.3%, 1% tiers) rather than allowing arbitrary fee values, or add governance controls to restrict fee values.

     

    Documentation-Implementation Mismatches

    ID SAY-08
    Status Fixed
    Risk Informational
    Business Impact Documentation-Implementation mismatches can cause confusion about the intended operation of the protocols, both for the developers themselves and for third-parties that rely on it.
    Location – contracts/registry.ts:51
    – contracts/basicPool.ts:108-110
    – interfaces/IBasicPool.ts:38
    – utils/index.ts:162-166
    – contracts/basicPool.ts:1299-1303

    Description

    Case 1:

    A comment in registry.ts indicates that flashLoanFee should be a value between 0 and 1.

    • registry.ts:51
     Storage key containning the flash loan fee value of the pool. value is between 0 and 1 

    However, the actual check is if the value is between 0 and 10%:

    • registry.ts:86-89
    assert(
    isBetweenZeroAndTenPercent(flashLoanFeeInput),
    'Fash loan fee must be between 0 and 10%',
    );

    Case 2:

    The documentation for the constructor in basicPool.ts does not match the actual implementation. The JSDoc comment lists parameters but omits flashLoanFeeInput which is used in the function. The same applies to the init function defined in IBasicPool.ts.

    Case 3:

    The comments for wrapMasToWMAS(u256, address) and _wrapMasToWMAS(u256), in utils/index.ts and contracts/basicPool.ts respectively, incorrectly describe how these functions behave.

    • index.ts:162-166 & basicPool.ts:1299-1303
    //Ensure bAmount is equal to MAS coins transferred
    assert(
    u256.fromU64(transferredCoins) amountToWrap,
    'INSUFFICIENT MAS COINS TRANSFERRED',
    );

    The comment states that the assertion ensures that the values are equal, but the code actually checks if transferredCoins is greater than or equal to amountToWrap, allowing excess tokens to be transferred without warning

    Mitigation

    Update either the documentation or the implementation to match the other side of the equation.

     

    Unused Code

    ID SAY-09
    Status Fixed
    Risk Informational
    Business Impact This finding is purely informational and does not pose a risk.
    Location – contracts/basicPool.ts:34
    – lib/basicPoolMath.ts:89-102

    Description

    • The NATIVE_MAS_COIN_ADDRESS is imported in basicPool.ts but never used.
    • getAmountIn(u256, u256, u256) defined in lib/basicPoolmath.ts is never used.

    Mitigation

    Review the unused code and consider removing it if no justification for its continued presence in the codebase can be found.

     

    Magic numbers in tokenDeployer

    ID SAY-10
    Status Fixed
    Risk Informational
    Business Impact The use of “magic numbers” reduces code maintainability and increases the risk of introducing bugs during future updates. If the actual gas costs on the Massa blockchain change over time, developers might not update this hardcoded value appropriately because its purpose and origin aren’t clear.
    Location – tokenDeployer.ts; createNewToken(StaticArray<u8>)

    Description

    tokenDeployer.ts uses unexplained “magic numbers” for default gas costs without clear documentation.

    • tokenDeployer.ts:87
    coinsToUseOnDeploy = u64(5 * 10 7); 

    The value 5 * 10 ** 7 lacks context explaining how it was determined or what it represents, making future maintenance difficult.

    Mitigation

    Define this value as a named constant with clear documentation explaining its purpose and calculation, improving code readability and maintainability.

     

    Typos

    ID SAY-11
    Status Fixed
    Risk Informational
    Business Impact This finding is purely informational and does not pose a risk.
    Location – smartcontracts/tests/classes/swapPath.ts:11
    – contracts/basicPool.ts:50, 52, 54, 56, 58, 60, 62, 64, 66
    – contracts/registry.ts:48, 51, 53

    Description

    • At swapPath.ts:11; isTranferFrom -> isTransferFrom
    • At the specifed locations in basicPool.ts and registry.ts;
      containning -> containing

    Mitigation

    Correct the specified typos.

    You can find more information about it on our Blog

    Sayfer’s blog focuses on web3, security, and vulnerability research. We believe that in the cybersecurity industry it’s crucial to stay up to date on the latest trends and advancements. Currently, our team of experienced researchers enjoys researching cutting-edge blockchain and web3 technologies.
    Contact us

    Keep In Touch

    Location
    Tel Aviv, Israel
    Messengers:
    Please feel free to contact us, we will be happy to respond!

      This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.
      Skip to content