Template repository for building ERC-4626 vaults that integrate with Steer Protocol infrastructure.
IVaultManaged.sol- Core vault interface with manager execution functionIVaultInitializable.sol- Required initialization signatureIVaultRegistry.sol- Registry interface
MockOrchestrator.sol- Simulates Steer orchestratorMockRegistry.sol- Simulates vault registryMockERC20.sol- Test token
MinimalVault.sol- Complete working example showing integration points- Note: This is a REFERENCE IMPLEMENTATION - you should implement your own strategy
- Complete test suite showing integration requirements
- Tests for ERC-4626 compliance
- Tests for manager functions
- Tests for governance functions
# Clone this template
git clone <this-repo>
cd vault-template
# Install dependencies
forge install OpenZeppelin/openzeppelin-contracts@v5.0.0
forge install OpenZeppelin/openzeppelin-contracts-upgradeable@v5.0.0
# Build
forge build
# Run tests
forge test# Should see all tests passing
forge test -vv
# Expected output:
# [PASS] test_Deposit_Success()
# [PASS] test_Tend_OnlyManager()
# [PASS] test_Initialize_Success()
# etc...Read the integration guide: ../docs/STEER-VAULT-INTEGRATION-GUIDE.md
Required:
- β
Implement
IVaultManaged(ERC-4626 + tend function) - β
Implement
IVaultInitializable(standard initialization) - β Manager-only access control on tend()
Optional but Recommended:
- Pausable functionality
- Governance functions
- Monitoring view functions
# Create your vault contract
cp src/examples/MinimalVault.sol src/YourVault.solEdit YourVault.sol:
contract YourVault is
Initializable,
ERC4626Upgradeable,
PausableUpgradeable,
IVaultManaged,
IVaultInitializable
{
// 1. Customize initialization parameters
function initialize(
address _manager,
address _orchestrator,
address _governance,
bytes memory _params
) public initializer {
// Decode YOUR parameters
(address _asset, /* your params */) = abi.decode(_params, (/*...*/));
// Your init logic
}
// 2. Implement YOUR strategy in tend()
function tend(bytes calldata data) external override onlyManager {
// Decode YOUR strategy parameters
(/* your params */) = abi.decode(data, (/*...*/));
// Execute YOUR strategy logic
// - Rebalance positions
// - Harvest rewards
// - Compound yields
// - Whatever your strategy does
}
// 3. Override totalAssets() if deploying externally
function totalAssets() public view override returns (uint256) {
// Return: idle + deployed to external protocols
return IERC20(asset()).balanceOf(address(this)) + /* deployed */;
}
}# Create test file
cp test/MinimalVault.t.sol test/YourVault.t.solEdit YourVault.t.sol:
- Import your vault
- Test your specific strategy logic
- Verify integration requirements still met
# Run all tests
forge test
# Run specific test
forge test --match-contract YourVaultTest
# Run with gas reporting
forge test --gas-report
# Run with coverage
forge coverageThese tests verify your vault meets Steer requirements:
function test_Initialize_CorrectSignature() public {
// Verify initialize() has correct signature
vault.initialize(manager, orchestrator, governance, params);
}
function test_Tend_OnlyManager() public {
// Verify only manager can call tend
vm.prank(notManager);
vm.expectRevert();
vault.tend(data);
}
function test_ERC4626_Compliant() public {
// Verify ERC-4626 functions work
vault.deposit(assets, receiver);
vault.withdraw(assets, receiver, owner);
}
function test_Tend_DoesNotBrickWithdrawals() public {
// Deposit -> Tend -> Withdraw should work
vault.deposit(1000, user);
vault.tend(data);
vault.withdraw(500, user, user); // Should succeed
}Add tests specific to your strategy:
function test_YourStrategy_Rebalancing() public {
// Test your rebalancing logic
}
function test_YourStrategy_Harvesting() public {
// Test your reward harvesting
}
function test_YourStrategy_EdgeCases() public {
// Test edge cases specific to your strategy
}function _handleTend(bytes calldata data) internal {
(address lendingPool, uint256 targetAllocation) =
abi.decode(data, (address, uint256));
uint256 idle = IERC20(asset()).balanceOf(address(this));
if (idle > targetAllocation) {
// Deposit excess to lending pool
uint256 toDeposit = idle - targetAllocation;
IERC20(asset()).approve(lendingPool, toDeposit);
ILendingPool(lendingPool).deposit(toDeposit);
}
}
function totalAssets() public view override returns (uint256) {
uint256 idle = IERC20(asset()).balanceOf(address(this));
uint256 lent = ILendingPool(lendingPool).balanceOf(address(this));
return idle + lent;
}function _handleTend(bytes calldata data) internal {
(address[] memory pools, uint256[] memory allocations) =
abi.decode(data, (address[], uint256[]));
// Withdraw from all pools
for (uint i = 0; i < currentPools.length; i++) {
IPool(currentPools[i]).withdraw(type(uint256).max);
}
// Deposit to new allocations
for (uint i = 0; i < pools.length; i++) {
IERC20(asset()).approve(pools[i], allocations[i]);
IPool(pools[i]).deposit(allocations[i]);
}
}function _handleTend(bytes calldata data) internal {
(bytes memory swapRoute, uint256 minOutput) =
abi.decode(data, (bytes, uint256));
// Claim rewards
IProtocol(protocol).claimRewards();
// Swap rewards to asset
uint256 rewards = IERC20(rewardToken).balanceOf(address(this));
IERC20(rewardToken).approve(swapper, rewards);
uint256 assets = ISwapper(swapper).swap(swapRoute, rewards, minOutput);
// Reinvest
IERC20(asset()).approve(protocol, assets);
IProtocol(protocol).deposit(assets);
}Customize what you need in initialize():
// Simple
abi.encode(address asset, uint256 minDeposit)
// With protocol integration
abi.encode(address asset, address protocol, uint256 minDeposit)
// With multi-pool
abi.encode(address asset, address[] pools, uint256[] weights)
// With helper pattern
abi.encode(address asset, address helper, bytes config)Customize what orchestrator sends in tend():
// Simple rebalancing
abi.encode(address[] pools, uint256[] amounts)
// Action-based
abi.encode(uint8 action, bytes actionData)
// Complex routes
abi.encode(bytes[] swapRoutes, uint256[] minOutputs, address[] targets)Add monitoring functions for your strategy:
function getPositionValue() external view returns (uint256);
function getAllocations() external view returns (address[] memory, uint256[] memory);
function pendingRewards() external view returns (uint256);Add events for monitoring:
event PositionRebalanced(address[] oldPools, address[] newPools);
event RewardsHarvested(uint256 amount, uint256 compounded);
event AllocationUpdated(address pool, uint256 oldAmount, uint256 newAmount);- β Keep tend() idempotent (safe to call multiple times)
- β Add comprehensive events for monitoring
- β Implement pausable for emergencies
- β Test with MockOrchestrator before mainnet
- β Document your parameter encoding clearly
- β Add view functions for position tracking
- β Handle edge cases (zero balances, failed swaps, etc.)
- β Use try-catch for external protocol calls
- β Add reentrancy guards where needed
- β Don't change initialize() signature
- β Don't make tend() callable by anyone
- β Don't brick withdrawals in tend()
- β Don't forget to update totalAssets() if deploying externally
- β Don't hardcode addresses (use params)
- β Don't skip error handling
- β Don't assume external calls succeed
Before submitting to Steer, verify:
- Implements
IVaultManagedinterface - Implements
IVaultInitializableinterface - Initialize has correct signature
- tend() is manager-only
- ERC-4626 functions work correctly
- totalAssets() returns idle + deployed
- All tests pass:
forge test - Documented parameter encoding
- Documented strategy in 2-3 sentences
- Tested with MockOrchestrator
- Tested with MockRegistry
- Code is clean and commented
- π Integration Guide:
../docs/STEER-VAULT-INTEGRATION-GUIDE.md - π Minimal Requirements:
../docs/steer-vault-builder-integration-requirements.md - π Reference Architecture:
../docs/reference-stability-vault-porting-guide.md
- π¬ Discord: Steer Protocol Discord - #vault-builders
- π§ Email: integrations@steer.finance
- π GitHub: Open an issue
vault-template/
βββ src/
β βββ interfaces/ # Required interfaces
β β βββ IVaultManaged.sol
β β βββ IVaultInitializable.sol
β β βββ IVaultRegistry.sol
β βββ mocks/ # Testing infrastructure
β β βββ MockOrchestrator.sol
β β βββ MockRegistry.sol
β β βββ MockERC20.sol
β βββ examples/ # Reference implementations
β βββ MinimalVault.sol
βββ test/
β βββ MinimalVault.t.sol # Example tests
βββ lib/ # Dependencies (gitignored)
βββ foundry.toml # Foundry configuration
βββ remappings.txt # Import remappings
βββ README.md # This file
- Understand Requirements: Read
STEER-VAULT-INTEGRATION-GUIDE.md - Install Dependencies: Run
forge install - Study Example: Review
MinimalVault.sol - Build Your Vault: Implement your strategy
- Test Thoroughly: Run
forge test - Submit: Contact integrations@steer.finance
MIT License - See LICENSE file
Ready to build? Start with src/examples/MinimalVault.sol and customize for your strategy!