Thesauros logo

Thesauros Rebalancer and Provider Adapters Security Review Report

July 2026

Overview

This report covers the security review for Thesauros, a DeFi protocol for automated interest rebalancing. It supports ERC4626 and uses different liquidity providers to maximize yield. This engagement covered an iterative code update to the protocol. Our security assessment was a full review of the new code, spanning a total of 3 days. During our review, we did not identif y any major severity vulnerabilities. We did identif y some minor severity vulnerabilities and code optimizations. All reported issues were fixed by the development team and subsequently verified by us. We can confidently say that the overall security and code quality have increased af ter completion of our audit.

Scope

The analyzed resources are located on:

https://github.com/Thesauros/optimized-rebalancer-contracts/commit/ fd1c8747a01732448774ca3bfd6bc6cf2024b5fc

The issues described in this report were fixed in the following commit:

https://github.com/Thesauros/optimized-rebalancer-

Summary

Total number of findings
3

Weaknesses

This section contains the list of discovered weaknesses.

THES2-1 | FIXED-ORDER WITHDRAWALS MAY FAIL DESPITE AVAILABLE LIQUIDITY

Severity:

Low

Status:

Fixed

Path:

contracts/Rebalancer.sol#L447

Description:

The withdrawal flow treats each provider’s reported accounting balance as immediately withdrawable liquidity and processes providers in a fixed order. If an earlier provider cannot satisf y the requested amount and reverts, the transaction stops before later providers are considered. This can occur with MorphoProvider, where getDepositBalance() returns the asset value of the vault’s MetaMorpho shares. This value may remain positive while borrowing activity reduces the assets immediately available for withdrawal. Consequently, a withdrawal may revert even when a later provider holds sufficient liquidity.

uint256 assetsAtProvider = provider.getDepositBalance(
    address(this),
    this
);

uint256 amount = (assetsAtProvider >= assetsLeft)
    ? assetsLeft
    : assetsAtProvider;

_delegateActionToProvider(amount, "withdraw", provider);

MetaMorphoV1_1.sol

function _withdrawMorpho(uint256 assets) internal {
    for (uint256 i; i < withdrawQueue.length; ++i) {
        Id id = withdrawQueue[i];
        MarketParams memory marketParams = _marketParams(id);
        (uint256 supplyAssets,, Market memory market) = _accruedSupplyBalance(marketParams, id);

        uint256 toWithdraw = UtilsLib.min(
            _withdrawable(marketParams, market.totalSupplyAssets, market.totalBorrowAssets, supplyAssets), assets
        );


        if (toWithdraw > 0) {
            // Using try/catch to skip markets that revert.
            try MORPHO.withdraw(marketParams, toWithdraw, 0, address(this), address(this)) {
                assets -= toWithdraw;
            } catch {}
        }

        if (assets == 0) return;
    }

    if (assets != 0) revert ErrorsLib.NotEnoughLiquidity();
}

The failed transaction restores the burned shares and accounting state, but the user may remain unable to withdraw until liquidity returns or the provider order is changed.

Remediation:

  • Use immediately withdrawable liquidity instead of accounting balances when determining each provider’s withdrawal amount.
  • Isolate failed provider withdrawals and continue processing the remaining providers.
  • Calculate the remaining amount from the actual underlying tokens received from each provider.
  • Revert only af ter all providers have been attempted and the requested amount remains unavailable.

THES2-3 | PERSISTENT METAMORPHO `LOSTASSETS` MAY MISSTATE REBALANCER POSITION VALUE

Severity:

Low

Status:

Fixed

Path:

contracts/providers/AaveV3Provider.sol

Description:

MetaMorpho V1.1 records assets missing through realized bad debt or forced market removal as lostAssets and continues to include them in totalAssets(). The update rule only increases or preserves lostAssets; once positive, it cannot return to zero through normal vault operations.

  • MetaMorphoV1_1.sol
function _accruedFeeAndAssets()
    internal
    view
    returns (uint256 feeShares, uint256 newTotalAssets, uint256 newLostAssets)
{
    uint256 realTotalAssets;
    for (uint256 i; i < withdrawQueue.length; ++i) {
        realTotalAssets += MORPHO.expectedSupplyAssets(_marketParams(withdrawQueue[i]), address(this));
    }

    if (realTotalAssets < lastTotalAssets - lostAssets) newLostAssets = lastTotalAssets - realTotalAssets;
    else newLostAssets = lostAssets;

    ...
}

Morpho ’ s documentation e xplains that V1.1 does not immediately allocate realized bad debt across all depositors. U nless the loss is covered, the remaining depositors may eventually face the li quidity shortfall. (R ef, Managing Bad Debt: How-to Guide )

MorphoProvider uses MetaMorpho ’s book conversion as the reported position balance. I ts rate calculation w eights the real assets supplied to Morpho markets but uses the same lost A ssets - inclusive book value as its denominator.

function getDepositBalance(
    address user,
    IRebalancer
) external view returns (uint256 balance) {
    uint256 shares = _metaMorpho.balanceOf(user);
    balance = _metaMorpho.convertToAssets(shares);
}

function getDepositRate(
    IRebalancer
) external view returns (uint256 rate) {
    IMorpho morpho = _getMorpho();

    uint256 ratio;
    uint256 queueLength = _metaMorpho.withdrawQueueLength();

    uint256 totalDeposits = _metaMorpho.totalAssets();

    for (uint256 i; i < queueLength; i++) {
        Id idMarket = _metaMorpho.withdrawQueue(i);

        MarketParams memory marketParams = morpho.idToMarketParams(
            idMarket
        );
        Market memory market = morpho.market(idMarket);

        uint256 marketRate = _getMarketRate(marketParams, market);
        uint256 assetsInMarket = morpho.expectedSupplyAssets(
            marketParams,
            address(_metaMorpho)
        );
        ratio += marketRate.wMulDown(assetsInMarket);
    }

    rate =
        ratio.mulDivDown(1e18 - _metaMorpho.fee(), totalDeposits) *
        10 ** 9;
}

Following a material realized loss that has not been sufficiently covered, the Rebalancer may value its MetaMorpho position above the recoverable backing. If sufficient liquidity remains in MetaMorpho or another listed provider, an earlier redemption may be settled at the book valuation, leaving more of the loss to later shareholders.

Remediation:

  • Implement a shared accounting routine that derives the Rebalancer ’s recoverable MetaMorpho position from current real assets and exercisable share claims.
  • Use the same accounting basis in get DepositBalance() and getDepositRate().
  • Ensure Rebalancer share conversions, fee calculations, and withdrawals consume the adjusted provider balance.

THES2-2 | PROVIDER ADAPTERS HARDCODE ARBITRUM PROTOCOL ADDRESSES

Severity:

Informational

Status:

Fixed

Path:

contracts/providers/AaveV3Provider.sol#L42 contracts/providers/DolomiteProvider.sol#L49 contracts/providers/RevertProvider.sol#L33

Description:

The Aave V3, Dolomite, and Revert providers embed Arbitrum protocol addresses directly in their implementations. These addresses are used without verif ying the current chain or whether the targets contain the expected contract code.

// AaveV3Provider.sol
return IPoolAddressesProvider(
    0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb
);

// DolomiteProvider.sol
return IDolomiteMargin(
    0x6Bd780E7fDf01D77e4d475c821f1e7AE05409072
);
return IDolomiteGetter(
    0x9381942De7A66fdB4741272EaB4fc0A362F7a16a
);
return IDepositWithdrawalProxy(
    0xAdB9D68c613df4AA363B42161E1282117C7B9594
);

// RevertProvider.sol
return IV3Vault(
    0x74E6AFeF5705BEb126C6d3Bf46f8fad8F3e07825
);

When the adapters are used on Base, these addresses do not resolve to the expected protocol contracts. Provider registration does not validate the source bytecode or interface before granting approval and storing the provider list.

Remediation:

  • Replace embedded protocol addresses with validated chain-specific configuration.
  • Validate target bytecode and expected interface responses before storing providers or granting approvals.
  • Complete validation of all providers before committing a provider-list update, and revoke approvals for removed sources.

Table of contents