Halborn Logo

Multiplyr - Affine DeFi


Prepared by:

Halborn Logo

HALBORN

Last Updated 04/25/2024

Date of Engagement by: September 19th, 2022 - October 7th, 2022

Summary

100% of all REPORTED Findings have been addressed

All findings

6

Critical

0

High

0

Medium

2

Low

2

Informational

2


1. INTRODUCTION

Affine DeFi engaged Halborn to conduct a security audit on their smart contracts beginning on 2022-09-19 and ending on 2022-10-07. The security assessment was scoped to the smart contracts provided to the Halborn team.

2. AUDIT SUMMARY

The team at Halborn was provided three weeks for the engagement and assigned a full-time security engineer to audit 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 audit is to:

    • Ensure that smart contract functions operate as intended

    • Identify potential security issues with the smart contracts

In summary, Halborn identified some security risks that were mostly addressed by the Affine DeFi team.

3. TEST APPROACH & METHODOLOGY

Halborn performed a combination of manual and automated security testing to balance efficiency, timeliness, practicality, and accuracy in regard to the scope of this audit. While manual testing is recommended to uncover flaws in logic, process, and implementation; automated testing techniques help enhance coverage of the bridge code 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 audit:

    • Research into architecture and purpose

    • Smart contract manual code review and walkthrough

    • Graphing out functionality and contract logic/connectivity/functions (solgraph)

    • Manual assessment of use and safety for the critical Solidity variables and functions in scope to identify any arithmetic related vulnerability classes

    • Manual testing by custom scripts

    • Scanning of solidity files for vulnerabilities, security hotspots or bugs. (MythX)

    • Static Analysis of security for scoped contract, and imported functions. (Slither)

    • Testnet deployment (Brownie, Remix IDE)

4. SCOPE

IN-SCOPE: The security assessment was scoped to the following smart contracts:

    • src/ethereum/L1CompoundStrategy.sol

    • src/ethereum/L1Vault.sol

    • src/ethereum/L1WormholeRouter.sol

    • src/external/Multicall.sol

    • src/interfaces/*

    • src/polygon/Detailed.sol

    • src/polygon/EmergencyWithdrawalQueue.sol

    • src/polygon/ERC4626Router.sol

    • src/polygon/ERC4626RouterBase.sol

    • src/polygon/Forwarder.sol

    • src/polygon/L2AAVEStrategy.sol

    • src/polygon/L2Vault.sol

    • src/polygon/L2WormholeRouter.sol

    • src/polygon/Router.sol

    • src/polygon/TwoAssetBasket.sol

    • src/AffineGovernable.sol

    • src/BaseStrategy.sol

    • src/BaseVault.sol

    • src/BridgeEscrow.sol

    • src/Constants.sol

    • src/DollarMath.sol

    • src/WormholeRouter.sol

Commit ID: 30e93568ca0b0b458f8744bae1e62aaf1e132647

And the following smart contracts:

    • src/ethereum/CurveStrategy.sol

    • src/ethereum/ConvexStrategy.sol

    • src/polygon/DeltaNeutralLp.sol

Commit ID: 06d6bc37fa80f0fdf794a8cb93e8100288d065e0

Fixed Commit ID: 06d6bc37fa80f0fdf794a8cb93e8100288d065e0

And the following smart contracts:

    • src/BaseVault.sol

    • src/ethereum/L1Vault.sol

    • src/polygon/L2Vault.sol

Commit ID: 302ab4e2e54c2666d607be1b88861636fdee311d

5. RISK METHODOLOGY

Vulnerabilities or issues observed by Halborn are ranked based on the risk assessment methodology by measuring the LIKELIHOOD of a security incident and the IMPACT should an incident occur. This framework works for communicating the characteristics and impacts of technology vulnerabilities. The quantitative model ensures repeatable and accurate measurement while enabling users to see the underlying vulnerability characteristics that were used to generate the Risk scores. For every vulnerability, a risk level will be calculated on a scale of 5 to 1 with 5 being the highest likelihood or impact.
RISK SCALE - LIKELIHOOD
  • 5 - Almost certain an incident will occur.
  • 4 - High probability of an incident occurring.
  • 3 - Potential of a security incident in the long term.
  • 2 - Low probability of an incident occurring.
  • 1 - Very unlikely issue will cause an incident.
RISK SCALE - IMPACT
  • 5 - May cause devastating and unrecoverable impact or loss.
  • 4 - May cause a significant level of impact or loss.
  • 3 - May cause a partial impact or loss to many.
  • 2 - May cause temporary impact or loss.
  • 1 - May cause minimal or un-noticeable impact.
The risk level is then calculated using a sum of these two values, creating a value of 10 to 1 with 10 being the highest level of security risk.
Critical
High
Medium
Low
Informational
  • 10 - CRITICAL
  • 9 - 8 - HIGH
  • 7 - 6 - MEDIUM
  • 5 - 4 - LOW
  • 3 - 1 - VERY LOW AND INFORMATIONAL

6. SCOPE

Out-of-Scope: New features/implementations after the remediation commit IDs.

7. Assessment Summary & Findings Overview

Critical

0

High

0

Medium

2

Low

2

Informational

2

Impact x Likelihood

HAL-01

HAL-02

HAL-03

HAL-04

HAL-05

HAL-06

Security analysisRisk levelRemediation Date
IGNORE EXTERNAL CALL FEEMediumSolved - 10/26/2022
POSSIBLE LOSS OF FUNDSMediumSolved - 10/26/2022
POSSIBLE UNPREDICTABILITY BETWEEN L2 AND L1 RATIOSLowRisk Accepted
FUNCTION DOES NOT CHECK THE TOKEN BALANCE BEFORE AND AFTER A CALLLowSolved - 10/26/2022
LACK OF PROPER SLIPPAGE PROTECTIONInformationalSolved - 10/26/2022
POSSIBLE MISUSE OF CHAIN IDInformationalSolved - 10/26/2022

8. Findings & Tech Details

8.1 IGNORE EXTERNAL CALL FEE

// Medium

Description

The wormhole publishMessage function is payable. Currently, requires no fees, but that can be changed over time. If the wormhole decides to set a fee greater than 0, all those external calls within the protocol would fail. Hence, leaving the wormhole routers unable to perform their critical tasks.

Code Location

L2WormholeRouter.sol

function reportTransferredFund(uint256 amount) external {
    require(msg.sender == address(vault), "Only vault");
    bytes memory payload = abi.encode(Constants.L2_FUND_TRANSFER_REPORT, amount);
    uint64 sequence = wormhole.nextSequence(address(this));
    wormhole.publishMessage(uint32(sequence), payload, consistencyLevel);
}

L2WormholeRouter.sol

function requestFunds(uint256 amount) external {
    require(msg.sender == address(vault), "Only vault");
    bytes memory payload = abi.encode(Constants.L2_FUND_REQUEST, amount);
    uint64 sequence = wormhole.nextSequence(address(this));
    wormhole.publishMessage(uint32(sequence), payload, consistencyLevel);
}

L1WormholeRouter.sol

function reportTVL(uint256 tvl, bool received) external {
    require(msg.sender == address(vault), "Only vault");
    bytes memory payload = abi.encode(Constants.L1_TVL, tvl, received);
    // NOTE: We use the current tx count (to wormhole) of this contract
    // as a nonce when publishing messages
    // This casting is fine so long as we send less than 2 ** 32 - 1 (~ 4 billion) messages
    uint64 sequence = wormhole.nextSequence(address(this));

    wormhole.publishMessage(uint32(sequence), payload, consistencyLevel);
}

L1WormholeRouter.sol

function reportTransferredFund(uint256 amount) external {
    require(msg.sender == address(vault), "Only vault");
    bytes memory payload = abi.encode(Constants.L1_FUND_TRANSFER_REPORT, amount);
    uint64 sequence = wormhole.nextSequence(address(this));

    wormhole.publishMessage(uint32(sequence), payload, consistencyLevel);
}

  1. Wormhole publishMessage function increase its fee transaction
  2. Affine DeFi wormhole routers fail to publish messages due to not sending any fee on the transaction
  3. Affine DeFi overall protocol does not properly work
Score
Impact: 5
Likelihood: 1
Recommendation

SOLVED: The Affine DeFi team solved the issue in commit: 06d6bc37fa80f0fdf794a8cb93e8100288d065e0

8.2 POSSIBLE LOSS OF FUNDS

// Medium

Description

Wormhole does not fail if the destination chain ID is different from the one supposed to be. If the rebalancer bot calls this function directly with a different chain ID, it will not fail, so funds during the transactions can be lost.

You can check the Wormhole Chain IDs on each chain, which is not the same as the network chain ID and can be easily confused.

Code Location

WormholeRouter.sol

function _validateWormholeMessageEmitter(IWormhole.VM memory vm) internal view {
    require(vm.emitterAddress == bytes32(uint256(uint160(otherLayerRouter))), "Wrong emitter address");
    require(vm.emitterChainId == otherLayerChainId, "Wrong emitter chain");
    require(vm.nonce >= nextValidNonce, "Old transaction");
}

  1. Confuse wormhole chain ID with network chain ID
  2. Initialize the contract with a wrong wormhole chain ID
  3. Execute transactions on the protocol
  4. Validate wormhole message emitter does not work as intended
Score
Impact: 5
Likelihood: 1
Recommendation

SOLVED: The Affine DeFi team solved the issue in commit: 06d6bc37fa80f0fdf794a8cb93e8100288d065e0

8.3 POSSIBLE UNPREDICTABILITY BETWEEN L2 AND L1 RATIOS

// Low

Description

When setLayerRatios function is used to update the ratio between L1 and L2, an invalid total ratio can be set (more than 100%). Hence, the rebalancer bot could not properly work in those cases.

Code Location

L2Vault.sol

function setLayerRatios(uint256 _l1Ratio, uint256 _l2Ratio) external onlyGovernance {
    l1Ratio = _l1Ratio;
    l2Ratio = _l2Ratio;
}

Score
Impact: 3
Likelihood: 2
Recommendation

RISK ACCEPTED: The Affine DeFi team accepted the risk of this finding.

8.4 FUNCTION DOES NOT CHECK THE TOKEN BALANCE BEFORE AND AFTER A CALL

// Low

Description

Whenever the exit function is used, the contract should check the token balance before and after the call. So, the exact amount of tokens sent can be properly checked.

Code Location

BridgeEscrow.sol

function l1ClearFund(uint256 amount, bytes calldata exitProof) external {
    require(msg.sender == wormholeRouter, "Only wormhole router");

    // Exit tokens, after that the withdrawn tokens from L2 will be reflected in L1 BridgeEscrow.
    rootChainManager.exit(exitProof);

    // Transfer exited tokens to L1 Vault.
    uint256 balance = token.balanceOf(address(this));
    require(balance >= amount, "Funds not received");

    IL1Vault l1Vault = IL1Vault(vault);
    token.safeTransfer(address(l1Vault), balance);

    l1Vault.afterReceive();
}

Score
Impact: 2
Likelihood: 2
Recommendation

SOLVED: The Affine DeFi team solved the issue in commit: 06d6bc37fa80f0fdf794a8cb93e8100288d065e0

8.5 LACK OF PROPER SLIPPAGE PROTECTION

// Informational

Description

Within the _claimAndSellRewards function, the slippage protection of the transaction is set to zero. Hence, if there is tiny liquidity, there is a high risk of losing part of the investment.

Code Location

L1CompoundStrategy.sol

function _claimAndSellRewards() internal {
    comptroller.claimComp(address(this));
    if (rewardToken != address(cToken)) {
        uint256 rewardTokenBalance = balanceOfRewardToken();
        if (rewardTokenBalance >= minRewardToSell) {
            _sellRewardTokenForWant(rewardTokenBalance, 0);
        }
    }
    return;
}

Score
Impact: 1
Likelihood: 1
Recommendation

SOLVED: The Affine DeFi team solved the issue in commit: 06d6bc37fa80f0fdf794a8cb93e8100288d065e0

8.6 POSSIBLE MISUSE OF CHAIN ID

// Informational

Description

When initializing the wormhole router, the wormhole chain ID can be misused. As can be wrongly set due to confusion with the different deployed chain IDs.

As mentioned on HAL02, you can check the Wormhole Chain IDs on each chain, which is not the same as the network chain ID and can be easily confused.

Code Location

L2WormholeRouter.sol

function initialize(IWormhole _wormhole, L2Vault _vault, address _otherLayerRouter, uint16 _otherLayerChainId)
    external
    initializer
{
    wormhole = _wormhole;
    vault = _vault;
    governance = vault.governance();
    otherLayerRouter = _otherLayerRouter;
    otherLayerChainId = _otherLayerChainId;
}

Score
Impact: 1
Likelihood: 1
Recommendation

SOLVED: The Affine DeFi team solved the issue in commit: 06d6bc37fa80f0fdf794a8cb93e8100288d065e0

9. Automated Testing

STATIC ANALYSIS REPORT

Description

Halborn used automated testing techniques to enhance the coverage of certain areas of the scoped contracts. Among the tools used was Slither, a Solidity static analysis framework. After Halborn verified all the contracts in the repository and was able to compile them correctly into their ABI and binary formats, Slither was run on the all-scoped 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.

Slither results

src/ethereum/L1CompoundStrategy.sol

src/ethereum/L1Vault.sol src/ethereum/L1WormholeRouter.sol src/external/Multicall.sol src/polygon/Detailed.sol

src/polygon/EmergencyWithdrawalQueue.sol

src/polygon/ERC4626Router.sol src/polygon/ERC4626RouterBase.sol src/polygon/Forwarder.sol src/polygon/L2AAVEStrategy.sol

src/polygon/L2Vault.sol

src/polygon/L2WormholeRouter.sol src/AffineGovernable.sol src/BaseStrategy.sol src/BaseVault.sol src/BridgeEscrow.sol src/Constants.sol src/DollarMath.sol

src/WormholeRouter.sol

  • As a result of the tests carried out with the Slither tool, some results were obtained and reviewed by Halborn. Based on the results reviewed, some vulnerabilities were determined to be false positives. The actual vulnerabilities found by Slither are already included in the report findings.

AUTOMATED SECURITY SCAN

Description

Halborn used automated security scanners to assist with detection of well-known security issues, and to identify low-hanging fruits on the targets for this engagement. Among the tools used was MythX, a security analysis service for Ethereum smart contracts. MythX performed a scan on all the contracts and sent the compiled results to the analyzers to locate any vulnerabilities.

MythX results

src/ethereum/L1CompoundStrategy.sol src/ethereum/L1Vault.sol src/ethereum/L1WormholeRouter.sol src/external/Multicall.sol

src/polygon/EmergencyWithdrawalQueue.sol src/polygon/ERC4626Router.sol src/polygon/ERC4626RouterBase.sol src/polygon/Forwarder.sol src/polygon/L2AAVEStrategy.sol

src/polygon/L2Vault.sol src/polygon/L2WormholeRouter.sol src/AffineGovernable.sol src/BaseStrategy.sol src/BaseVault.sol src/BridgeEscrow.sol src/Constants.sol src/DollarMath.sol src/WormholeRouter.sol

  • No major issues found by Mythx. The floating pragma flagged by MythX is a false positive, as the pragma is set in the hardhat.config.ts file to the 0.8.16 version.

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.