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
- Access Control: Only owner can manage strategies and rebalance
- Vault-Only Withdrawals: Only vault can request fund withdrawals
- Strategy Validation: Checks for zero addresses
- 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