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