From 6892f3231b0afc45433cfe0e1b0494c164737d6c Mon Sep 17 00:00:00 2001 From: Takeshi <152513888+0xhokugava@users.noreply.github.com> Date: Fri, 7 Feb 2025 01:07:56 +0630 Subject: [PATCH] silo strategy initial implementation --- chains/SonicLib.sol | 3 + src/strategies/SiloFarmStrategy.sol | 4 +- src/strategies/SiloStrategy.sol | 117 +++++++++++++++++++ src/strategies/base/ERC4626StrategyBase.sol | 7 +- src/strategies/libs/StrategyDeveloperLib.sol | 3 + src/strategies/libs/StrategyIdLib.sol | 1 + test/strategies/SILO.Sonic.t.sol | 27 +++++ 7 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 src/strategies/SiloStrategy.sol create mode 100644 test/strategies/SILO.Sonic.t.sol diff --git a/chains/SonicLib.sol b/chains/SonicLib.sol index 688cc2a8..dcd8bf06 100644 --- a/chains/SonicLib.sol +++ b/chains/SonicLib.sol @@ -25,6 +25,7 @@ import {ALMPositionNameLib} from "../src/strategies/libs/ALMPositionNameLib.sol" import {ALMLib} from "../src/strategies/libs/ALMLib.sol"; import {GammaUniswapV3MerklFarmStrategy} from "../src/strategies/GammaUniswapV3MerklFarmStrategy.sol"; import {SiloFarmStrategy} from "../src/strategies/SiloFarmStrategy.sol"; +import {SiloStrategy} from "../src/strategies/SiloStrategy.sol"; import {ISiloIncentivesController} from "../src/integrations/silo/ISiloIncentivesController.sol"; import {IGaugeV3} from "../src/integrations/shadow/IGaugeV3.sol"; import {ALMShadowFarmStrategy} from "../src/strategies/ALMShadowFarmStrategy.sol"; @@ -224,6 +225,7 @@ library SonicLib { // Silo address public constant SILO_GAUGE_wS_008 = 0x0dd368Cd6D8869F2b21BA3Cb4fd7bA107a2e3752; address public constant SILO_GAUGE_wS_020 = 0x2D3d269334485d2D876df7363e1A50b13220a7D8; + address public constant SILO_VAULT_USDC_20 = 0x322e1d5384aa4ED66AeCa770B95686271de61dc3; // Gamma address public constant GAMMA_UNISWAPV3_UNIPROXY = 0xcD5A60eb030300661cAf97244aE98e1D5A70f2c8; @@ -342,6 +344,7 @@ library SonicLib { _addStrategyLogic(factory, StrategyIdLib.GAMMA_UNISWAPV3_MERKL_FARM, address(new GammaUniswapV3MerklFarmStrategy()), true); _addStrategyLogic(factory, StrategyIdLib.SILO_FARM, address(new SiloFarmStrategy()), true); _addStrategyLogic(factory, StrategyIdLib.ALM_SHADOW_FARM, address(new ALMShadowFarmStrategy()), true); + _addStrategyLogic(factory, StrategyIdLib.SILO, address(new SiloStrategy()), false); LogDeployLib.logDeployStrategies(platform, showLog); //endregion diff --git a/src/strategies/SiloFarmStrategy.sol b/src/strategies/SiloFarmStrategy.sol index 3ac6acbd..9a5ba4a5 100644 --- a/src/strategies/SiloFarmStrategy.sol +++ b/src/strategies/SiloFarmStrategy.sol @@ -18,7 +18,7 @@ contract SiloFarmStrategy is FarmingStrategyBase { /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @inheritdoc IControllable - string public constant VERSION = "1.0.1"; + string public constant VERSION = "1.0.2"; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* INITIALIZATION */ @@ -127,7 +127,7 @@ contract SiloFarmStrategy is FarmingStrategyBase { /// @inheritdoc IFarmingStrategy function farmMechanics() external pure returns (string memory) { - return FarmMechanicsLib.CLASSIC; + return FarmMechanicsLib.AUTO; } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ diff --git a/src/strategies/SiloStrategy.sol b/src/strategies/SiloStrategy.sol new file mode 100644 index 00000000..3ae857a2 --- /dev/null +++ b/src/strategies/SiloStrategy.sol @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import "./base/ERC4626StrategyBase.sol"; +import "./libs/StrategyIdLib.sol"; +import "../integrations/silo/ISiloIncentivesController.sol"; +import "../integrations/silo/ISilo.sol"; + +/// @title Earns APR by lending assets on Silo V2 +/// @author 0xhokugava (https://github.com/0xhokugava) +contract SiloStrategy is ERC4626StrategyBase { + using SafeERC20 for IERC20; + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTANTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @inheritdoc IControllable + string public constant VERSION = "1.0.0"; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* INITIALIZATION */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @inheritdoc IStrategy + function initialize(address[] memory addresses, uint[] memory nums, int24[] memory ticks) public initializer { + if (addresses.length != 3 || nums.length != 0 || ticks.length != 0) { + revert IControllable.IncorrectInitParams(); + } + __ERC4626StrategyBase_init(StrategyIdLib.SILO, addresses[0], addresses[1], addresses[2]); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* VIEW FUNCTIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @inheritdoc IStrategy + function strategyLogicId() public pure override returns (string memory) { + return StrategyIdLib.SILO; + } + + /// @inheritdoc IStrategy + function extra() external pure returns (bytes32) { + return CommonLib.bytesToBytes32(abi.encodePacked(bytes3(0x00d395), bytes3(0x000000))); + } + + /// @inheritdoc IStrategy + function description() external view returns (string memory) { + StrategyBaseStorage storage $base = _getStrategyBaseStorage(); + return _genDesc($base._underlying); + } + + /// @inheritdoc IStrategy + function getSpecificName() external pure override returns (string memory, bool) { + return ("", false); + } + + /// @inheritdoc IStrategy + function supportedVaultTypes() external pure override returns (string[] memory types) { + types = new string[](1); + types[0] = VaultTypeLib.COMPOUNDING; + } + + /// @inheritdoc IStrategy + function initVariants(address platform_) + public + view + returns (string[] memory variants, address[] memory addresses, uint[] memory nums, int24[] memory ticks) + {} + + /// @inheritdoc IStrategy + function isHardWorkOnDepositAllowed() external pure returns (bool) { + return true; + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* STRATEGY BASE */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @inheritdoc ERC4626StrategyBase + //slither-disable-next-line unused-return + function _depositAssets(uint[] memory amounts, bool) internal override returns (uint value) { + StrategyBaseStorage storage $base = _getStrategyBaseStorage(); + address u = $base._underlying; + ERC4626StrategyBaseStorage storage $ = _getERC4626StrategyBaseStorage(); + if ($.lastSharePrice == 0) { + $.lastSharePrice = _getSharePrice(u); + } + ISilo siloVault = ISilo(u); + value = siloVault.deposit(amounts[0], address(this), ISilo.CollateralType.Collateral); + } + + /// @inheritdoc ERC4626StrategyBase + //slither-disable-next-line unused-return + function _withdrawAssets( + address[] memory, + uint value, + address receiver + ) internal virtual override returns (uint[] memory amountsOut) { + amountsOut = new uint[](1); + StrategyBaseStorage storage $base = _getStrategyBaseStorage(); + amountsOut[0] = ISilo($base._underlying).withdraw(value, receiver, address(this), ISilo.CollateralType.Collateral); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* INTERNAL LOGIC */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + function _genDesc(address silo) internal view returns (string memory) { + return string.concat( + "Earn ", + // CommonLib.implode(CommonLib.getSymbols(silo), ", "), + " and supply APR by lending ", + IERC20Metadata(ISilo(silo).asset()).symbol(), + " to Silo V2" + ); + } +} diff --git a/src/strategies/base/ERC4626StrategyBase.sol b/src/strategies/base/ERC4626StrategyBase.sol index 4861adbb..ab462929 100644 --- a/src/strategies/base/ERC4626StrategyBase.sol +++ b/src/strategies/base/ERC4626StrategyBase.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.23; import "@openzeppelin/contracts/interfaces/IERC4626.sol"; import "./StrategyBase.sol"; +import {console} from "forge-std/console.sol"; /// @notice Hold ERC4626 vault shares, emit APR and collect fees /// @author Alien Deployer (https://github.com/a17) @@ -14,7 +15,7 @@ abstract contract ERC4626StrategyBase is StrategyBase { /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Version of ERC4626StrategyBase implementation - string public constant VERSION_ERC4626_STRATEGY_BASE = "1.0.0"; + string public constant VERSION_ERC4626_STRATEGY_BASE = "1.0.1"; // keccak256(abi.encode(uint256(keccak256("erc7201:stability.ERC4626StrategyBase")) - 1)) & ~bytes32(uint256(0xff)); bytes32 private constant ERC4626_STRATEGY_BASE_STORAGE_LOCATION = @@ -93,7 +94,7 @@ abstract contract ERC4626StrategyBase is StrategyBase { /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @inheritdoc StrategyBase - function _depositAssets(uint[] memory amounts, bool) internal override returns (uint value) { + function _depositAssets(uint[] memory amounts, bool) internal virtual override returns (uint value) { StrategyBaseStorage storage $base = _getStrategyBaseStorage(); address u = $base._underlying; value = IERC4626(u).deposit(amounts[0], address(this)); @@ -168,7 +169,7 @@ abstract contract ERC4626StrategyBase is StrategyBase { address[] memory, uint value, address receiver - ) internal override returns (uint[] memory amountsOut) { + ) internal virtual override returns (uint[] memory amountsOut) { amountsOut = new uint[](1); StrategyBaseStorage storage __$__ = _getStrategyBaseStorage(); amountsOut[0] = IERC4626(__$__._underlying).redeem(value, receiver, address(this)); diff --git a/src/strategies/libs/StrategyDeveloperLib.sol b/src/strategies/libs/StrategyDeveloperLib.sol index a316f982..761df1c7 100644 --- a/src/strategies/libs/StrategyDeveloperLib.sol +++ b/src/strategies/libs/StrategyDeveloperLib.sol @@ -65,6 +65,9 @@ library StrategyDeveloperLib { if (CommonLib.eq(strategyId, StrategyIdLib.ALM_SHADOW_FARM)) { return 0x88888887C3ebD4a33E34a15Db4254C74C75E5D4A; } + if (CommonLib.eq(strategyId, StrategyIdLib.SILO)) { + return 0xa12C4Bbe4D6eD65285f05328Bca4462Bf4808E53; + } return address(0); } } diff --git a/src/strategies/libs/StrategyIdLib.sol b/src/strategies/libs/StrategyIdLib.sol index af1d805a..4dc51fdf 100644 --- a/src/strategies/libs/StrategyIdLib.sol +++ b/src/strategies/libs/StrategyIdLib.sol @@ -23,4 +23,5 @@ library StrategyIdLib { string internal constant SWAPX_FARM = "SwapX Farm"; string internal constant SILO_FARM = "Silo Farm"; string internal constant ALM_SHADOW_FARM = "ALM Shadow Farm"; + string internal constant SILO = "Silo"; } diff --git a/test/strategies/SILO.Sonic.t.sol b/test/strategies/SILO.Sonic.t.sol new file mode 100644 index 00000000..7f588698 --- /dev/null +++ b/test/strategies/SILO.Sonic.t.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import "../base/chains/SonicSetup.sol"; +import "../base/UniversalTest.sol"; + +contract SiloStrategyTest is SonicSetup, UniversalTest { + constructor() { + vm.selectFork(vm.createFork(vm.envString("SONIC_RPC_URL"))); + vm.rollFork(6548098); // Feb-04-2025 03:31:56 PM +UTC + } + + function testSiFSonic() public universalTest { + _addStrategy(SonicLib.SILO_VAULT_USDC_20); + } + + function _addStrategy(address siloVault) internal { + strategies.push( + Strategy({ + id: StrategyIdLib.SILO, + pool: address(0), + farmId: type(uint).max, + underlying: siloVault + }) + ); + } +}