Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Rohit-KK15/MetaVault-AI/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The StrategyRouter manages fund allocation across multiple DeFi strategies, handles rebalancing, and coordinates strategy operations like harvesting and deleveraging.

Contract Architecture

contract StrategyRouter is Ownable {
    // Vault that holds the tokens
    IVault public immutable vault;
    
    // Strategy list
    address[] public strategies;
    
    // Allocation targets in basis points (10000 = 100%)
    mapping(address => uint256) public targetBps;
}
Source: contracts/strategy/StrategyRouter.sol:18-27

Constructor

constructor(address _vault, address _owner) Ownable(_owner) {
    require(_vault != address(0), "vault=0");
    vault = IVault(_vault);
}
Source: contracts/strategy/StrategyRouter.sol:36-39

Strategy Management

Set Strategies

Configure strategies and their target allocations:
function setStrategies(
    address[] calldata _strats,
    uint256[] calldata _bps
) external onlyOwner {
    require(_strats.length == _bps.length, "len mismatch");
    
    delete strategies;
    
    uint256 total;
    for (uint256 i = 0; i < _strats.length; i++) {
        require(_strats[i] != address(0), "zero strat");
        strategies.push(_strats[i]);
        targetBps[_strats[i]] = _bps[i];
        total += _bps[i];
    }
    
    require(total == 10000, "targets must sum 10000");
    
    emit StrategiesUpdated();
}
Source: contracts/strategy/StrategyRouter.sol:45-64
Target allocations must sum to exactly 10000 basis points (100%).

Get Strategy Stats

function getStrategyStats(address strat)
    external view
    returns (
        uint256 balance,
        uint256 target,
        uint256 actualPct
    )
{
    balance = IStrategy(strat).strategyBalance();
    target = targetBps[strat];
    uint256 total = _computeTotalManaged();
    actualPct = total == 0 ? 0 : (balance * 10000) / total;
}
Source: contracts/strategy/StrategyRouter.sol:70-82

Get Portfolio State

function getPortfolioState()
    external view
    returns (
        address[] memory strats,
        uint256[] memory balances,
        uint256[] memory targets
    )
{
    strats = strategies;
    balances = new uint256[](strats.length);
    targets = new uint256[](strats.length);
    
    for (uint256 i = 0; i < strats.length; i++) {
        balances[i] = IStrategy(strats[i]).strategyBalance();
        targets[i] = targetBps[strats[i]];
    }
}
Source: contracts/strategy/StrategyRouter.sol:84-96

Fund Management

Move Funds to Strategy

function moveFundsToStrategy(address strat, uint256 amount) external onlyOwner {
    require(amount > 0, "zero amount");
    
    // vault → strategy
    vault.moveToStrategy(strat, amount);
    
    // strategy invests (with error handling)
    try IStrategy(strat).invest(amount) {
        // success
    } catch {
        emit StrategyWithdrawFailed(strat, "invest failed");
    }
}
Source: contracts/strategy/StrategyRouter.sol:105-120

Withdraw from Strategies

Called by vault when liquidity is needed:
function withdrawFromStrategies(uint256 amount) external returns (uint256) {
    require(msg.sender == address(vault), "not vault");
    require(amount > 0, "zero amount");
    
    IERC20 asset = IERC20(vault.asset());
    uint256 pulled = 0;
    uint256 requested = amount;
    
    for (uint256 i = 0; i < strategies.length && pulled < requested; i++) {
        address strat = strategies[i];
        uint256 need = requested - pulled;
        
        uint256 before = asset.balanceOf(address(vault));
        
        // Call withdraw on strategy with error handling
        try IStrategy(strat).withdrawToVault(need) {
            // success
        } catch (bytes memory reason) {
            string memory msgStr = _decodeRevertReason(reason);
            emit StrategyWithdrawFailed(strat, msgStr);
            continue;
        }
        
        uint256 afterBal = asset.balanceOf(address(vault));
        uint256 got = 0;
        if (afterBal > before) got = afterBal - before;
        pulled += got;
    }
    
    emit WithdrawnFromStrategies(requested, pulled);
    require(pulled >= requested, "strategies insufficient");
    
    return pulled;
}
Source: contracts/strategy/StrategyRouter.sol:135-170

Rebalancing

Automatic portfolio rebalancing to target allocations:
function rebalance() external onlyOwner {
    // Calculate initial total managed assets
    uint256 totalManaged = _computeTotalManaged();
    
    // Pull excess from overweight strategies
    for (uint256 i = 0; i < strategies.length; i++) {
        address strat = strategies[i];
        uint256 current = IStrategy(strat).strategyBalance();
        uint256 desired = (totalManaged * targetBps[strat]) / 10000;
        
        if (current > desired) {
            uint256 excess = current - desired;
            
            try IStrategy(strat).withdrawToVault(excess) returns (uint256 withdrawn) {
                if (withdrawn > 0) {
                    vault.receiveFromStrategy(withdrawn);
                }
            } catch (bytes memory reason) {
                emit StrategyWithdrawFailed(strat, _decodeRevertReason(reason));
            }
        }
    }
    
    // Recalculate after withdrawals
    totalManaged = _computeTotalManaged();
    uint256 vaultBal = vault.totalAssets();
    
    // Push to underweight strategies
    for (uint256 i = 0; i < strategies.length; i++) {
        address strat = strategies[i];
        uint256 current = IStrategy(strat).strategyBalance();
        uint256 desired = (totalManaged * targetBps[strat]) / 10000;
        
        if (current < desired && vaultBal > 0) {
            uint256 need = desired - current;
            uint256 amt = need <= vaultBal ? need : vaultBal;
            
            if (amt > 0) {
                vault.moveToStrategy(strat, amt);
                
                try IStrategy(strat).invest(amt) {
                    vaultBal -= amt;
                } catch (bytes memory reason) {
                    emit StrategyWithdrawFailed(strat, _decodeRevertReason(reason));
                }
            }
        }
    }
    
    emit Rebalanced(_computeTotalManaged());
}
Source: contracts/strategy/StrategyRouter.sol:176-232

Harvesting

Collect profits from all strategies:
function harvestAll() external onlyOwner {
    address[] memory list = strategies;
    
    for (uint256 i = 0; i < list.length; i++) {
        address strat = list[i];
        
        uint256 beforeBal = vault.totalAssets();
        
        // Trigger strategy harvest (tolerates failures)
        try IStrategy(strat).harvest() {
        } catch (bytes memory reason) {
            emit StrategyWithdrawFailed(strat, _decodeRevertReason(reason));
            continue;
        }
        
        uint256 afterBal = vault.totalAssets();
        
        if (afterBal > beforeBal) {
            uint256 profit = afterBal - beforeBal;
            // Vault will apply the fee internally
            vault.handleHarvestProfit(profit);
            
            emit Harvested(strat, profit);
        }
    }
}
Source: contracts/strategy/StrategyRouter.sol:273-298

Deleveraging

Trigger leverage unwinding for specific strategies:
function triggerDeleverage(address strat, uint256 maxLoops) external onlyOwner {
    require(strat != address(0), "zero strat");
    require(maxLoops > 0, "maxLoops must be > 0");
    
    try IStrategy(strat).deleverageAll(maxLoops) {
        emit DeleverageTriggered(strat, maxLoops);
    } catch (bytes memory reason) {
        emit StrategyWithdrawFailed(strat, _decodeRevertReason(reason));
    }
}
Source: contracts/strategy/StrategyRouter.sol:246-267

Helper Functions

Compute Total Managed

function _computeTotalManaged() internal view returns (uint256 total) {
    total = vault.totalAssets();
    for (uint256 i = 0; i < strategies.length; i++) {
        total += IStrategy(strategies[i]).strategyBalance();
    }
}
Source: contracts/strategy/StrategyRouter.sol:238-243

Decode Revert Reason

function _decodeRevertReason(bytes memory reason) internal pure returns (string memory) {
    if (reason.length >= 4) {
        bytes4 selector;
        assembly { selector := mload(add(reason, 32)) }
        if (selector == 0x08c379a0) {
            // decode Error(string)
            // ... assembly code to extract string
        }
    }
    return "revert";
}
Source: contracts/strategy/StrategyRouter.sol:302-332

Events

event StrategiesUpdated();
event Rebalanced(uint256 totalManaged);
event Harvested(address indexed strat, uint256 profit);
event WithdrawnFromStrategies(uint256 requested, uint256 pulled);
event DeleverageTriggered(address indexed strat, uint256 maxLoops);
event StrategyWithdrawFailed(address indexed strat, string reason);
Source: contracts/strategy/StrategyRouter.sol:29-34

Vault Interface

interface IVault {
    function asset() external view returns (address);
    function totalAssets() external view returns (uint256);
    function moveToStrategy(address strategy, uint256 amount) external;
    function receiveFromStrategy(uint256 amount) external;
    function handleHarvestProfit(uint256 profit) external;
}
Source: contracts/strategy/StrategyRouter.sol:9-16

Error Handling

The router includes comprehensive error handling:
  • Try-Catch Blocks: All strategy interactions wrapped in try-catch
  • Revert Decoding: Extracts readable error messages from reverts
  • Graceful Degradation: Continues operations even if one strategy fails
  • Event Emissions: Logs all failures for monitoring

Security Features

  1. Access Control: Only owner can manage strategies and rebalance
  2. Vault-Only Withdrawals: Only vault can request fund withdrawals
  3. Strategy Validation: Checks for zero addresses
  4. Allocation Validation: Ensures targets sum to 100%

Integration Example

// Deploy router
StrategyRouter router = new StrategyRouter(vaultAddress, ownerAddress);

// Set strategies with allocations
address[] memory strats = new address[](2);
strats[0] = aaveStrategyAddress;
strats[1] = leverageStrategyAddress;

uint256[] memory bps = new uint256[](2);
bps[0] = 7000; // 70% to Aave
bps[1] = 3000; // 30% to Leverage

router.setStrategies(strats, bps);

// Connect vault to router
vault.setRouter(address(router));

Next Steps