From 5160b40dbe74a9bac475f785b9e6e28fc66b9835 Mon Sep 17 00:00:00 2001 From: dvpublic Date: Thu, 31 Jul 2025 18:12:46 +0700 Subject: [PATCH 1/7] SiALMF: replace collateral type by not-borrowable collateral in the calls of ISilo.deposit --- src/strategies/SiloALMFStrategy.sol | 3 ++- src/strategies/libs/SiloALMFLib.sol | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/strategies/SiloALMFStrategy.sol b/src/strategies/SiloALMFStrategy.sol index 90e2912d..79901cdb 100644 --- a/src/strategies/SiloALMFStrategy.sol +++ b/src/strategies/SiloALMFStrategy.sol @@ -41,6 +41,7 @@ import {IWrappedMetaVault} from "../interfaces/IWrappedMetaVault.sol"; /// @title Silo V2 advanced leverage Merkl farm strategy /// Changelog: +/// 1.1.1: Change deposit type to "non-borrowable collateral deposit" /// 1.1.0: Each write operation caches prices of MetaUSD - #348 /// 1.0.1: Use new version of setLastBlockDefenseDisabledTx /// 1.0.0: Initial version - #330 @@ -61,7 +62,7 @@ contract SiloALMFStrategy is /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @inheritdoc IControllable - string public constant VERSION = "1.1.0"; + string public constant VERSION = "1.1.1"; //region ----------------------------------- Initialization /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ diff --git a/src/strategies/libs/SiloALMFLib.sol b/src/strategies/libs/SiloALMFLib.sol index 230e7292..93f6d9c8 100644 --- a/src/strategies/libs/SiloALMFLib.sol +++ b/src/strategies/libs/SiloALMFLib.sol @@ -91,7 +91,10 @@ library SiloALMFLib { // supply ISilo($.lendingVault).deposit( - IERC20(collateralAsset).balanceOf(address(this)), address(this), ISilo.CollateralType.Collateral + IERC20(collateralAsset).balanceOf(address(this)), + address(this), + // we should make non-borrowable collateral deposit to be able to get Merkl rewards + ISilo.CollateralType.Protected ); // borrow @@ -194,7 +197,8 @@ library SiloALMFLib { ISilo($.lendingVault).deposit( _getLimitedAmount(IERC20(collateralAsset).balanceOf(address(this)), tempCollateralAmount), address(this), - ISilo.CollateralType.Collateral + // we should make non-borrowable collateral deposit to be able to get Merkl rewards + ISilo.CollateralType.Protected ); // borrow From ce4912083bd8f5ec4f8b0acd665ea50084e65291 Mon Sep 17 00:00:00 2001 From: dvpublic Date: Mon, 4 Aug 2025 13:11:47 +0700 Subject: [PATCH 2/7] SiALMF uses protected collateral type only --- src/integrations/silo/ISilo.sol | 25 ++++++++++ src/strategies/SiloALMFStrategy.sol | 4 +- src/strategies/libs/SiloALMFLib.sol | 16 +++--- test/strategies/SiALMF.Upgrade.Sonic.t.sol | 2 +- .../SiALMF.Upgrade.scUsd.Sonic.t.sol | 50 +++++++++++++++++-- 5 files changed, 84 insertions(+), 13 deletions(-) diff --git a/src/integrations/silo/ISilo.sol b/src/integrations/silo/ISilo.sol index e1a436aa..2ff6882c 100644 --- a/src/integrations/silo/ISilo.sol +++ b/src/integrations/silo/ISilo.sol @@ -14,6 +14,16 @@ interface ISilo is IERC4626 { Collateral } + /// @dev There are 3 types of accounting in the system: for non-borrowable collateral deposit called "protected", + /// for borrowable collateral deposit called "collateral" and for borrowed tokens called "debt". System does + /// identical calculations for each type of accounting but it uses different data. To avoid code duplication + /// this enum is used to decide which data should be read. + enum AssetType { + Protected, // default + Collateral, + Debt + } + /// @notice Implements IERC4626.deposit for protected (non-borrowable) collateral and collateral /// @dev Reverts for debt asset type function deposit(uint _assets, address _receiver, CollateralType collateralType) external returns (uint shares); @@ -68,4 +78,19 @@ interface ISilo is IERC4626 { function maxRepay(address _borrower) external view returns (uint assets); function getLiquidity() external view returns (uint256 liquidity); + + /// @notice Implements IERC4626.maxRedeem for protected (non-borrowable) collateral and collateral + /// @dev Reverts for debt asset type + function maxRedeem(address _owner, CollateralType _collateralType) external view returns (uint256 maxShares); + + /// @notice Implements IERC4626.maxWithdraw for protected (non-borrowable) collateral and collateral + /// @dev Reverts for debt asset type + function maxWithdraw(address _owner, CollateralType _collateralType) external view returns (uint256 maxAssets); + + /// @notice Calculates the maximum number of shares that can be repaid for a given borrower + /// @param _borrower Address of the borrower + /// @return shares The maximum number of shares that can be repaid for the borrower + function maxRepayShares(address _borrower) external view returns (uint256 shares); + + function convertToAssets(uint256 _shares, AssetType _assetType) external view returns (uint256 assets); } diff --git a/src/strategies/SiloALMFStrategy.sol b/src/strategies/SiloALMFStrategy.sol index 79901cdb..f149b72c 100644 --- a/src/strategies/SiloALMFStrategy.sol +++ b/src/strategies/SiloALMFStrategy.sol @@ -41,7 +41,7 @@ import {IWrappedMetaVault} from "../interfaces/IWrappedMetaVault.sol"; /// @title Silo V2 advanced leverage Merkl farm strategy /// Changelog: -/// 1.1.1: Change deposit type to "non-borrowable collateral deposit" +/// 1.2.0: Change deposit type to "non-borrowable collateral deposit" /// 1.1.0: Each write operation caches prices of MetaUSD - #348 /// 1.0.1: Use new version of setLastBlockDefenseDisabledTx /// 1.0.0: Initial version - #330 @@ -62,7 +62,7 @@ contract SiloALMFStrategy is /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @inheritdoc IControllable - string public constant VERSION = "1.1.1"; + string public constant VERSION = "1.2.0"; //region ----------------------------------- Initialization /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ diff --git a/src/strategies/libs/SiloALMFLib.sol b/src/strategies/libs/SiloALMFLib.sol index 93f6d9c8..45ad71f8 100644 --- a/src/strategies/libs/SiloALMFLib.sol +++ b/src/strategies/libs/SiloALMFLib.sol @@ -121,7 +121,7 @@ library SiloALMFLib { Math.min(tempCollateralAmount, collateralAmountTotal), address(this), address(this), - ISilo.CollateralType.Collateral + ISilo.CollateralType.Protected ); } @@ -166,7 +166,7 @@ library SiloALMFLib { // withdraw amount ISilo(lendingVault).withdraw( - $.tempCollateralAmount, address(this), address(this), ISilo.CollateralType.Collateral + $.tempCollateralAmount, address(this), address(this), ISilo.CollateralType.Protected ); // swap @@ -300,7 +300,7 @@ library SiloALMFLib { { CollateralDebtState memory debtState = _getDebtState(platform, $.lendingVault, $.collateralAsset, $.borrowAsset, $.borrowingVault); - (ltv, maxLtv, leverage, collateralAmount, debtAmount, targetLeveragePercent) = _health(platform, $, debtState); // todo return + return _health(platform, $, debtState); } function rebalanceDebt( @@ -422,7 +422,11 @@ library SiloALMFLib { } function totalCollateral(address lendingVault) public view returns (uint) { - return IERC4626(lendingVault).convertToAssets(StrategyLib.balance(lendingVault)); + (address protectedShareToken, , ) = ISiloConfig(ISilo(lendingVault).config()).getShareTokens(lendingVault); + return ISilo(lendingVault).convertToAssets( + StrategyLib.balance(protectedShareToken), + ISilo.AssetType.Protected + ); } function totalDebt(address borrowingVault) public view returns (uint) { @@ -635,7 +639,7 @@ library SiloALMFLib { ); if (amountToWithdraw != 0) { ISilo(v.lendingVault).withdraw( - amountToWithdraw, address(this), address(this), ISilo.CollateralType.Collateral + amountToWithdraw, address(this), address(this), ISilo.CollateralType.Protected ); } } else { @@ -713,7 +717,7 @@ library SiloALMFLib { LeverageLendingLib.requestFlashLoan($, flashAssets, flashAmounts); // --------- Withdraw value from landing vault to the strategy balance - ISilo(v.lendingVault).withdraw(value, address(this), address(this), ISilo.CollateralType.Collateral); + ISilo(v.lendingVault).withdraw(value, address(this), address(this), ISilo.CollateralType.Protected); return true; } diff --git a/test/strategies/SiALMF.Upgrade.Sonic.t.sol b/test/strategies/SiALMF.Upgrade.Sonic.t.sol index 53d0323c..5fa0272b 100755 --- a/test/strategies/SiALMF.Upgrade.Sonic.t.sol +++ b/test/strategies/SiALMF.Upgrade.Sonic.t.sol @@ -126,7 +126,7 @@ contract SiALMFUpgradeTest is Test { vm.prank(multisig); strategy.emergencyStopInvesting(); - console.log("!!!!!!!!!!!!!!!!!!!! gas used for emergency exit", gas0 - gasleft()); + // console.log("!!!!!!!!!!!!!!!!!!!! gas used for emergency exit", gas0 - gasleft()); assertLt(gas0 - gasleft(), 16e6, "Emergency exit should not use more than 16 mln gas"); } } diff --git a/test/strategies/SiALMF.Upgrade.scUsd.Sonic.t.sol b/test/strategies/SiALMF.Upgrade.scUsd.Sonic.t.sol index b3d822cc..a6df73b7 100755 --- a/test/strategies/SiALMF.Upgrade.scUsd.Sonic.t.sol +++ b/test/strategies/SiALMF.Upgrade.scUsd.Sonic.t.sol @@ -25,8 +25,9 @@ import {console, Test} from "forge-std/Test.sol"; import {MetaVaultAdapter} from "../../src/adapters/MetaVaultAdapter.sol"; /// @notice Fix a problem in MetaVaultAdapter that produced a false-positive ExceedSlippage error +/// @notice Upgrade SiloALMFStrategy to 1.2.0 (use non-borrowable collateral) contract SiALMFUpgradeScUsdTest is Test { - uint public constant FORK_BLOCK = 40834789; // Jul-30-2025 04:59:49 AM +UTC + uint public constant FORK_BLOCK = 41561093; // Aug-04-2025 05:20:57 AM +UTC address public constant PLATFORM = 0x4Aca671A420eEB58ecafE83700686a2AD06b20D8; @@ -63,14 +64,25 @@ contract SiALMFUpgradeScUsdTest is Test { priceReader = IPriceReader(IPlatform(IControllable(SonicConstantsLib.WRAPPED_METAVAULT_metaUSD).platform()).priceReader()); - _upgradePlatform(address(priceReader)); + // _upgradePlatform(address(priceReader)); _upgradeMetaVault(SonicConstantsLib.METAVAULT_metaUSD); // _upgradeWrappedMetaVault(); _upgradeCVault(address(vault)); - _upgradeStrategy(); _upgradeCVault(SonicConstantsLib.VAULT_C_WMETAUSD_scUSD_125); _upgradeCVault(SonicConstantsLib.VAULT_C_USDC_SiMF_Greenhouse); + + // we must withdraw all assets before upgrading the strategy + address [3] memory holders = [ + 0xCE785cccAa0c163E6f83b381eBD608F98f694C44, + 0xEae43e21a658B41d741139854cafDe9Ef7A580aB, + 0x224b920120AD0c30aedb5AFD01056405ec8E00F8 + ]; + for (uint i; i < holders.length; ++i) { + _withdrawAll(holders[i]); + } + + _upgradeStrategy(); } /// @notice Ensure that we are able to deposit large amount of metaUSD without revert @@ -110,7 +122,7 @@ contract SiALMFUpgradeScUsdTest is Test { IVault(address(vault)).doHardWork(); // console.log("!!!!!!!!!!!!!!!!!!! gas used for hardwork", gas0 - gasleft()); - assertLt(gas0 - gasleft(), 15e6, "Hardwork should not use more than 16 mln gas"); + assertLt(gas0 - gasleft(), 15.5e6, "Hardwork should not use more than 16 mln gas"); } // ---------------------------------- Emergency exit @@ -394,5 +406,35 @@ contract SiALMFUpgradeScUsdTest is Test { // console.log("i, current, target", i, current[i], props[i]); // } } + + function _getFlashLoanVault() internal view returns (address _vault, uint _kind) { + ILeverageLendingStrategy _strategy = ILeverageLendingStrategy(address(strategy)); + (uint[] memory params, address[] memory addresses) = _strategy.getUniversalParams(); + return (addresses[0], params[10]); + } + + function _setFlashLoanVault(ILeverageLendingStrategy strategy_, address vault_, uint kind) internal { + (uint[] memory params, address[] memory addresses) = strategy_.getUniversalParams(); + params[10] = kind; + addresses[0] = vault_; + + vm.prank(multisig); + strategy_.setUniversalParams(params, addresses); + } + + function _withdrawAll(address user) internal { + address[] memory assets = vault.assets(); + uint shares = vault.balanceOf(user); + + vm.prank(user); + vault.withdrawAssets(assets, shares / 2, new uint[](1)); + vm.roll(block.number + 6); + + shares = vault.balanceOf(user); + vm.prank(user); + vault.withdrawAssets(assets, shares, new uint[](1)); + + vm.roll(block.number + 6); + } //endregion ------------------------------------ Helpers } From b8aae66b80030dd8b37867fda021a5347e6c9fce Mon Sep 17 00:00:00 2001 From: dvpublic Date: Mon, 4 Aug 2025 13:13:25 +0700 Subject: [PATCH 3/7] fix formatting --- src/strategies/libs/SiloALMFLib.sol | 7 ++----- test/strategies/SiALMF.Upgrade.scUsd.Sonic.t.sol | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/strategies/libs/SiloALMFLib.sol b/src/strategies/libs/SiloALMFLib.sol index 45ad71f8..2fd3d3e2 100644 --- a/src/strategies/libs/SiloALMFLib.sol +++ b/src/strategies/libs/SiloALMFLib.sol @@ -422,11 +422,8 @@ library SiloALMFLib { } function totalCollateral(address lendingVault) public view returns (uint) { - (address protectedShareToken, , ) = ISiloConfig(ISilo(lendingVault).config()).getShareTokens(lendingVault); - return ISilo(lendingVault).convertToAssets( - StrategyLib.balance(protectedShareToken), - ISilo.AssetType.Protected - ); + (address protectedShareToken,,) = ISiloConfig(ISilo(lendingVault).config()).getShareTokens(lendingVault); + return ISilo(lendingVault).convertToAssets(StrategyLib.balance(protectedShareToken), ISilo.AssetType.Protected); } function totalDebt(address borrowingVault) public view returns (uint) { diff --git a/test/strategies/SiALMF.Upgrade.scUsd.Sonic.t.sol b/test/strategies/SiALMF.Upgrade.scUsd.Sonic.t.sol index a6df73b7..80ca5ba0 100755 --- a/test/strategies/SiALMF.Upgrade.scUsd.Sonic.t.sol +++ b/test/strategies/SiALMF.Upgrade.scUsd.Sonic.t.sol @@ -73,7 +73,7 @@ contract SiALMFUpgradeScUsdTest is Test { _upgradeCVault(SonicConstantsLib.VAULT_C_USDC_SiMF_Greenhouse); // we must withdraw all assets before upgrading the strategy - address [3] memory holders = [ + address[3] memory holders = [ 0xCE785cccAa0c163E6f83b381eBD608F98f694C44, 0xEae43e21a658B41d741139854cafDe9Ef7A580aB, 0x224b920120AD0c30aedb5AFD01056405ec8E00F8 From 233539f92bb627ed88cb6d990eac839931ae6374 Mon Sep 17 00:00:00 2001 From: dvpublic Date: Mon, 4 Aug 2025 15:42:32 +0700 Subject: [PATCH 4/7] SiALMF: reDeposit... --- src/strategies/SiloALMFStrategy.sol | 53 +++++++++ src/strategies/libs/SiloALMFLib.sol | 106 ++++++++++++++--- .../SiALMF.Upgrade.scUsd.Sonic.t.sol | 112 +++++++++++++----- 3 files changed, 229 insertions(+), 42 deletions(-) diff --git a/src/strategies/SiloALMFStrategy.sol b/src/strategies/SiloALMFStrategy.sol index f149b72c..99ad5061 100644 --- a/src/strategies/SiloALMFStrategy.sol +++ b/src/strategies/SiloALMFStrategy.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; +import {console} from "forge-std/console.sol"; import {CommonLib} from "../core/libs/CommonLib.sol"; import {ConstantsLib} from "../core/libs/ConstantsLib.sol"; import {IControllable} from "../interfaces/IControllable.sol"; @@ -267,6 +268,8 @@ contract SiloALMFStrategy is function _rebalanceDebt(uint newLtv) internal override returns (uint resultLtv) { LeverageLendingBaseStorage storage $ = _getLeverageLendingBaseStorage(); + require(totalCollateralToRedeposit() == 0, SiloALMFLib.TemporallyDisabled()); + SiloALMFLib.prepareWriteOp(platform(), $.collateralAsset); resultLtv = SiloALMFLib.rebalanceDebt(platform(), newLtv, $); SiloALMFLib.unprepareWriteOp(platform(), $.collateralAsset); @@ -300,6 +303,8 @@ contract SiloALMFStrategy is { LeverageLendingBaseStorage storage $ = _getLeverageLendingBaseStorage(); + require(totalCollateralToRedeposit() == 0, SiloALMFLib.TemporallyDisabled()); + SiloALMFLib.prepareWriteOp(platform(), $.collateralAsset); __assets = assets(); (__amounts, __rewardAssets, __rewardAmounts) = @@ -321,10 +326,13 @@ contract SiloALMFStrategy is /// @inheritdoc StrategyBase function _depositAssets(uint[] memory amounts, bool /*claimRevenue*/ ) internal override returns (uint value) { + console.log("_depositAssets", amounts[0]); LeverageLendingBaseStorage storage $ = _getLeverageLendingBaseStorage(); StrategyBaseStorage storage $base = _getStrategyBaseStorage(); address[] memory _assets = assets(); + require(totalCollateralToRedeposit() == 0, SiloALMFLib.TemporallyDisabled()); + SiloALMFLib.prepareWriteOp(platform(), $.collateralAsset); value = SiloALMFLib.depositAssets($, $base, amounts[0], _assets[0]); SiloALMFLib.unprepareWriteOp(platform(), $.collateralAsset); @@ -332,9 +340,12 @@ contract SiloALMFStrategy is /// @inheritdoc StrategyBase function _withdrawAssets(uint value, address receiver) internal override returns (uint[] memory amountsOut) { + console.log("_withdrawAssets", value); LeverageLendingBaseStorage storage $ = _getLeverageLendingBaseStorage(); StrategyBaseStorage storage $base = _getStrategyBaseStorage(); + require(totalCollateralToRedeposit() == 0, SiloALMFLib.TemporallyDisabled()); + SiloALMFLib.prepareWriteOp(platform(), $.collateralAsset); amountsOut = SiloALMFLib.withdrawAssets(platform(), $, $base, value, receiver); SiloALMFLib.unprepareWriteOp(platform(), $.collateralAsset); @@ -390,4 +401,46 @@ contract SiloALMFStrategy is } //endregion ----------------------------------- FarmingStrategy + + //region ----------------------------------- Additional temporally functions + + /// @dev True if there is no deposit of "collateral type" + function totalCollateralToRedeposit() public view returns (uint) { + LeverageLendingBaseStorage storage $ = _getLeverageLendingBaseStorage(); + console.log("totalCollateralToRedeposit", SiloALMFLib.totalCollateralToRedeposit($.lendingVault)); + return SiloALMFLib.totalCollateralToRedeposit($.lendingVault); + } + + /// @notice Withdraw {value_} of Collateral and re-deposit it as Protected collateral + /// @param amount_ Amount of collateral to withdraw + /// @param maxAmountToCover_ Maximum amount to cover losses at the operator's expense + /// @param exitMode_ True - withdraw, false - deposit + function reDeposit(uint amount_, uint maxAmountToCover_, bool exitMode_) external onlyOperator { + LeverageLendingBaseStorage storage $ = _getLeverageLendingBaseStorage(); + address collateralAsset = $.collateralAsset; + console.log("************* reDeposit: collateralAsset", collateralAsset); + + require(totalCollateralToRedeposit() != 0 || !exitMode_, IControllable.TooLowValue(0)); + + (uint tvlBefore, ) = realTvl(); + console.log("reDeposit: tvlBefore", tvlBefore); + + address _platform = platform(); + SiloALMFLib.prepareWriteOp(_platform, collateralAsset); + SiloALMFLib.reDeposit($, amount_, exitMode_); + SiloALMFLib.unprepareWriteOp(_platform, collateralAsset); + + (uint tvlAfter, ) = realTvl(); + console.log("reDeposit: tvlAfter", tvlAfter); + + // cover losses at the operator's expense and restore share price +// uint losses = tvlAfter < tvlBefore ? tvlBefore - tvlAfter : 0; +// if (losses != 0) { +// console.log("??????????? reDeposit: losses", losses); +// require(losses <= maxAmountToCover_, SiloALMFLib.NotEnoughAmountToCover(losses)); +// IERC20(collateralAsset).transferFrom(msg.sender, address(this), losses); +// } + } + + //endregion ----------------------------------- Additional temporally functions } diff --git a/src/strategies/libs/SiloALMFLib.sol b/src/strategies/libs/SiloALMFLib.sol index 2fd3d3e2..badb3867 100644 --- a/src/strategies/libs/SiloALMFLib.sol +++ b/src/strategies/libs/SiloALMFLib.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; +import {console} from "forge-std/console.sol"; import {CommonLib} from "../../core/libs/CommonLib.sol"; import {ConstantsLib} from "../../core/libs/ConstantsLib.sol"; import {IControllable} from "../../interfaces/IControllable.sol"; @@ -35,6 +36,9 @@ library SiloALMFLib { /// @notice 1000 is 1% uint private constant PRICE_IMPACT_DENOMINATOR = 100_000; + error TemporallyDisabled(); + error NotEnoughAmountToCover(uint amountToCover); + //region ------------------------------------- Data types struct CollateralDebtState { uint collateralPrice; @@ -74,6 +78,7 @@ library SiloALMFLib { uint amount, uint feeAmount ) internal { + console.log("_receiveFlashLoan amount,feeAmount", amount, feeAmount); address collateralAsset = $.collateralAsset; address flashLoanVault = $.flashLoanVault; require(msg.sender == flashLoanVault, IControllable.IncorrectMsgSender()); @@ -112,18 +117,7 @@ library SiloALMFLib { ISilo($.borrowingVault).repay(amount, address(this)); // withdraw - { - address lendingVault = $.lendingVault; - uint collateralAmountTotal = totalCollateral(lendingVault); - collateralAmountTotal -= collateralAmountTotal / 1000; - - ISilo(lendingVault).withdraw( - Math.min(tempCollateralAmount, collateralAmountTotal), - address(this), - address(this), - ISilo.CollateralType.Protected - ); - } + _withdrawInReceiveFlashLoan($, tempCollateralAmount); // swap _swap( @@ -228,6 +222,41 @@ library SiloALMFLib { $.tempAction = ILeverageLendingStrategy.CurrentAction.None; } + function _withdrawInReceiveFlashLoan( + ILeverageLendingStrategy.LeverageLendingBaseStorage storage $, + uint tempCollateralAmount + ) internal { + uint totalCollateralC = totalCollateralToRedeposit($.lendingVault); + if (totalCollateralC == 0) { + address lendingVault = $.lendingVault; + uint collateralAmountTotal = totalCollateral(lendingVault); + collateralAmountTotal -= collateralAmountTotal / 1000; + + ISilo(lendingVault).withdraw( + Math.min(tempCollateralAmount, collateralAmountTotal), + address(this), + address(this), + ISilo.CollateralType.Protected + ); + } else { + address lendingVault = $.lendingVault; + uint collateralAmountTotal = totalCollateralC; + + console.log("_withdrawInReceiveFlashLoan.tempCollateralAmount", tempCollateralAmount); + console.log("_withdrawInReceiveFlashLoan.collateralAmountTotal", collateralAmountTotal); + ISilo(lendingVault).withdraw( + Math.min( + // +1 decimal - don't allow to leave dust in Silo + tempCollateralAmount + 10 ** IERC20Metadata($.collateralAsset).decimals(), + collateralAmountTotal + ), + address(this), + address(this), + ISilo.CollateralType.Collateral + ); + } + } + function receiveFlashLoanBalancerV2( address platform, ILeverageLendingStrategy.LeverageLendingBaseStorage storage $, @@ -423,7 +452,10 @@ library SiloALMFLib { function totalCollateral(address lendingVault) public view returns (uint) { (address protectedShareToken,,) = ISiloConfig(ISilo(lendingVault).config()).getShareTokens(lendingVault); - return ISilo(lendingVault).convertToAssets(StrategyLib.balance(protectedShareToken), ISilo.AssetType.Protected); + return ISilo(lendingVault).convertToAssets( + StrategyLib.balance(protectedShareToken), + ISilo.AssetType.Protected + ) + totalCollateralToRedeposit(lendingVault); } function totalDebt(address borrowingVault) public view returns (uint) { @@ -671,6 +703,7 @@ library SiloALMFLib { $.tempCollateralAmount = collateralAmountToWithdraw; $.tempAction = ILeverageLendingStrategy.CurrentAction.Withdraw; + console.log("requestFlashLoan", flashAssets[0], flashAmounts[0]); LeverageLendingLib.requestFlashLoan($, flashAssets, flashAmounts); } @@ -1061,5 +1094,50 @@ library SiloALMFLib { // assume that collateral asset is always WrappedMetaVault, i.e. wmetaUSD return IMetaVault(IWrappedMetaVault(wrappedMetaVault).metaVault()); } - //region ------------------------------------- Transient prices cache + //endregion ------------------------------------- Transient prices cache + + //region ----------------------------------- Additional temporally functions + + /// @dev True if there is no deposit of "collateral type" + function totalCollateralToRedeposit(address lendingVault) internal view returns (uint) { + return ISilo(lendingVault).convertToAssets(StrategyLib.balance(lendingVault), ISilo.AssetType.Collateral); + } + + /// @notice Withdraw {value_} of Collateral and re-deposit it as Protected collateral + function reDeposit( + ILeverageLendingStrategy.LeverageLendingBaseStorage storage $, + uint maxCollateralToWithdraw_, + bool exitMode_ + ) internal { + console.log("reDeposit"); + ILeverageLendingStrategy.LeverageLendingAddresses memory v = _getLeverageLendingAddresses($); + StateBeforeWithdraw memory state = _getStateBeforeWithdraw($, v); + + if (exitMode_) { + + require(totalCollateralToRedeposit(v.lendingVault) != 0, TemporallyDisabled()); + + uint totalCollateralToWithdraw = ISilo(v.lendingVault).convertToAssets(StrategyLib.balance(v.lendingVault), ISilo.AssetType.Collateral); + console.log("reDeposit.totalCollateralToWithdraw", totalCollateralToWithdraw); + uint value = Math.mulDiv( + Math.min(totalCollateralToWithdraw, maxCollateralToWithdraw_), + INTERNAL_PRECISION * INTERNAL_PRECISION, + state.maxLeverage * state.withdrawParam0, + Math.Rounding.Ceil + ); + console.log("reDeposit.value", value); + + _defaultWithdraw($, v, state, value); + console.log("totalCollateralToWithdraw.after", ISilo(v.lendingVault).convertToAssets(StrategyLib.balance(v.lendingVault), ISilo.AssetType.Collateral)); + } else { + _deposit($, v, StrategyLib.balance($.collateralAsset), state.priceCtoB); + } + + (uint maxLtv,,) = getLtvData(v.lendingVault, $.targetLeveragePercent); + _ensureLtvValid($, maxLtv); + } + + //endregion ----------------------------------- Additional temporally functions + + } diff --git a/test/strategies/SiALMF.Upgrade.scUsd.Sonic.t.sol b/test/strategies/SiALMF.Upgrade.scUsd.Sonic.t.sol index 80ca5ba0..ab268db1 100755 --- a/test/strategies/SiALMF.Upgrade.scUsd.Sonic.t.sol +++ b/test/strategies/SiALMF.Upgrade.scUsd.Sonic.t.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.23; import {AmmAdapterIdLib} from "../../src/adapters/libs/AmmAdapterIdLib.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {MetaVault} from "../../src/core/vaults/MetaVault.sol"; import {WrappedMetaVault} from "../../src/core/vaults/WrappedMetaVault.sol"; import {CVault} from "../../src/core/vaults/CVault.sol"; @@ -27,7 +28,7 @@ import {MetaVaultAdapter} from "../../src/adapters/MetaVaultAdapter.sol"; /// @notice Fix a problem in MetaVaultAdapter that produced a false-positive ExceedSlippage error /// @notice Upgrade SiloALMFStrategy to 1.2.0 (use non-borrowable collateral) contract SiALMFUpgradeScUsdTest is Test { - uint public constant FORK_BLOCK = 41561093; // Aug-04-2025 05:20:57 AM +UTC + uint public constant FORK_BLOCK = 41583791; // Aug-04-2025 09:35:18 AM +UTC address public constant PLATFORM = 0x4Aca671A420eEB58ecafE83700686a2AD06b20D8; @@ -64,6 +65,13 @@ contract SiALMFUpgradeScUsdTest is Test { priceReader = IPriceReader(IPlatform(IControllable(SonicConstantsLib.WRAPPED_METAVAULT_metaUSD).platform()).priceReader()); +// deal( +// SonicConstantsLib.TOKEN_scUSD, +// SonicConstantsLib.BEETS_VAULT, +// 1_000_000e6 // 100k USDC +// ); + + // _upgradePlatform(address(priceReader)); _upgradeMetaVault(SonicConstantsLib.METAVAULT_metaUSD); // _upgradeWrappedMetaVault(); @@ -72,21 +80,14 @@ contract SiALMFUpgradeScUsdTest is Test { _upgradeCVault(SonicConstantsLib.VAULT_C_WMETAUSD_scUSD_125); _upgradeCVault(SonicConstantsLib.VAULT_C_USDC_SiMF_Greenhouse); - // we must withdraw all assets before upgrading the strategy - address[3] memory holders = [ - 0xCE785cccAa0c163E6f83b381eBD608F98f694C44, - 0xEae43e21a658B41d741139854cafDe9Ef7A580aB, - 0x224b920120AD0c30aedb5AFD01056405ec8E00F8 - ]; - for (uint i; i < holders.length; ++i) { - _withdrawAll(holders[i]); - } - _upgradeStrategy(); } /// @notice Ensure that we are able to deposit large amount of metaUSD without revert function testSingleDepositWithdraw() public { + // ---------------------------------- Re-deposit "collateral type" => "protected type" to allow deposit/withdraw + _reDeposit(); + // ---------------------------------- Deposit uint amount = 7000e18; _getMetaTokensOnBalance(address(this), amount, true, SonicConstantsLib.WRAPPED_METAVAULT_metaUSD); @@ -100,14 +101,14 @@ contract SiALMFUpgradeScUsdTest is Test { uint gas0 = gasleft(); vault.depositAssets(assets, amountsMax, 0, address(this)); vm.roll(block.number + 6); - // console.log("!!!!!!!!!!!!!!! gas used for deposit", gas0 - gasleft()); + console.log("!!!!!!!!!!!!!!! gas used for deposit", gas0 - gasleft()); assertLt(gas0 - gasleft(), 12e6, "Deposit should not use more than 12 mln gas"); // ---------------------------------- Withdraw uint shares = vault.balanceOf(address(this)); gas0 = gasleft(); uint[] memory withdrawn = vault.withdrawAssets(assets, shares, new uint[](1)); - // console.log("!!!!!!!!!!!!!!! gas used for withdraw", gas0 - gasleft()); + console.log("!!!!!!!!!!!!!!! gas used for withdraw", gas0 - gasleft()); assertLt(gas0 - gasleft(), 15e6, "Withdraw should not use more than 15 mln gas"); assertApproxEqAbs(amount, withdrawn[0], amount / 100 * 2, "Withdrawn amount does not match deposited amount"); @@ -121,22 +122,72 @@ contract SiALMFUpgradeScUsdTest is Test { vm.prank(hardWorker); IVault(address(vault)).doHardWork(); - // console.log("!!!!!!!!!!!!!!!!!!! gas used for hardwork", gas0 - gasleft()); + console.log("!!!!!!!!!!!!!!!!!!! gas used for hardwork", gas0 - gasleft()); assertLt(gas0 - gasleft(), 15.5e6, "Hardwork should not use more than 16 mln gas"); } + } + + function testEmergencyExit() public { + // ---------------------------------- Re-deposit "collateral type" => "protected type" to allow deposit/withdraw + _reDeposit(); + // ---------------------------------- Emergency exit { - gas0 = gasleft(); + uint gas0 = gasleft(); vm.prank(multisig); strategy.emergencyStopInvesting(); - // console.log("!!!!!!!!!!!!!!!!!!!! gas used for emergency exit", gas0 - gasleft()); + console.log("!!!!!!!!!!!!!!!!!!!! gas used for emergency exit", gas0 - gasleft()); assertLt(gas0 - gasleft(), 15e6, "Emergency exit should not use more than 15 mln gas"); } } + //region ------------------------------------ Re-deposit + /// @dev Re-deposit incorrectly deposited amounts: "collateral type" => "protected type" + function _reDeposit() internal { + // ---------------------------------- State before re-deposit + SiloALMFStrategy _strategy = SiloALMFStrategy(payable(address(strategy))); + uint baseAmount = _strategy.totalCollateralToRedeposit() / 2; + + uint total = strategy.total(); + (uint sharePriceBefore,) = _strategy.realSharePrice(); + console.log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!total, sharePriceBefore", total, sharePriceBefore); + + deal(SonicConstantsLib.WRAPPED_METAVAULT_metaUSD, address(multisig), 10_000e18); + uint balanceBefore = IERC20(SonicConstantsLib.WRAPPED_METAVAULT_metaUSD).balanceOf(multisig); + State memory stateBefore = _getState(); + + while (_strategy.totalCollateralToRedeposit() != 0) { + uint amount = Math.min(_strategy.totalCollateralToRedeposit(), baseAmount); + + vm.prank(multisig); + IERC20(SonicConstantsLib.WRAPPED_METAVAULT_metaUSD).approve(address(_strategy), balanceBefore); + + // exit + uint gas0 = gasleft(); + vm.prank(multisig); + _strategy.reDeposit(amount, type(uint).max, true); + console.log("!!!!!!!!!gas used for re-deposit1", gas0 - gasleft()); + + // enter + gas0 = gasleft(); + vm.prank(multisig); + _strategy.reDeposit(0, 0, false); + console.log("!!!!!!!!!gas used for re-deposit2", gas0 - gasleft()); + } + + uint total2 = strategy.total(); + (uint sharePriceAfter,) = _strategy.realSharePrice(); + uint balanceAfter = IERC20(SonicConstantsLib.WRAPPED_METAVAULT_metaUSD).balanceOf(multisig); + State memory stateAfter = _getState(); + + console.log("total, sharePriceAfter", total2, sharePriceAfter); + console.log("!!!!!!!!!! TOTAL LOSSES", balanceBefore - balanceAfter); + } + //endregion ------------------------------------ Re-deposit + //region ------------------------------------ Helpers function _upgradeStrategy() internal { address strategyImplementation = address(new SiloALMFStrategy()); @@ -295,16 +346,17 @@ contract SiALMFUpgradeScUsdTest is Test { // console.log("targetLeverage, leverage, total", state.targetLeverage, state.leverage, state.total); - // console.log("ltv", state.ltv); - // console.log("maxLtv", state.maxLtv); - // console.log("targetLeverage", state.targetLeverage); - // console.log("leverage", state.leverage); - // console.log("total", state.total); - // console.log("collateralAmount", state.collateralAmount); - // console.log("debtAmount", state.debtAmount); - // console.log("targetLeveragePercent", state.targetLeveragePercent); - // console.log("maxLeverage", state.maxLeverage); - // console.log("realTvl", state.realTvl); + console.log("============== state"); + console.log("ltv", state.ltv); + console.log("maxLtv", state.maxLtv); + console.log("targetLeverage", state.targetLeverage); + console.log("leverage", state.leverage); + console.log("total", state.total); + console.log("collateralAmount", state.collateralAmount); + console.log("debtAmount", state.debtAmount); + console.log("targetLeveragePercent", state.targetLeveragePercent); + console.log("maxLeverage", state.maxLeverage); + console.log("realTvl", state.realTvl); return state; } @@ -413,9 +465,13 @@ contract SiALMFUpgradeScUsdTest is Test { return (addresses[0], params[10]); } - function _setFlashLoanVault(ILeverageLendingStrategy strategy_, address vault_, uint kind) internal { + function _setFlashLoanVault( + ILeverageLendingStrategy strategy_, + address vault_, + ILeverageLendingStrategy.FlashLoanKind kind + ) internal { (uint[] memory params, address[] memory addresses) = strategy_.getUniversalParams(); - params[10] = kind; + params[10] = uint(kind); addresses[0] = vault_; vm.prank(multisig); From 2411b230ae4a056921a0e93bfd3f6ff5728cabc5 Mon Sep 17 00:00:00 2001 From: dvpublic Date: Tue, 5 Aug 2025 14:23:05 +0700 Subject: [PATCH 5/7] SiALMF: tests for reDeposit --- chains/sonic/SonicConstantsLib.sol | 1 + src/strategies/SiloALMFStrategy.sol | 54 +-- src/strategies/libs/SiloALMFLib.sol | 101 +---- src/strategies/libs/SiloALMFLib2.sol | 75 ++++ .../SiALMF.Upgrade.scUsd.Sonic.t.sol | 89 ++-- .../SiALMF.Upgrade.usdc.Sonic.t.sol | 401 +++++++++++++++++ test/strategies/SiALMF.Upgrade.wS.Sonic.t.sol | 405 ++++++++++++++++++ 7 files changed, 959 insertions(+), 167 deletions(-) create mode 100755 test/strategies/SiALMF.Upgrade.usdc.Sonic.t.sol create mode 100755 test/strategies/SiALMF.Upgrade.wS.Sonic.t.sol diff --git a/chains/sonic/SonicConstantsLib.sol b/chains/sonic/SonicConstantsLib.sol index d49af292..25713c1e 100644 --- a/chains/sonic/SonicConstantsLib.sol +++ b/chains/sonic/SonicConstantsLib.sol @@ -35,6 +35,7 @@ library SonicConstantsLib { address public constant VAULT_C_USDC_SiMF_Greenhouse = 0x9443C25624c8ab74FaDE003bC76D2aC35244b925; address public constant VAULT_C_WMETAUSD_USDC_121 = 0xe965c5114F689BF9184D9300F3af4A378c6934A9; address public constant VAULT_C_WMETAUSD_scUSD_125 = 0x58D310a1A490f0daa86608998E08852630000151; + address public constant VAULT_C_WMETAS_wS_128 = 0xD44E7413134Ad3909c793C52cd242d99271f6570; // metascUSD address public constant VAULT_C_scUSD_S_46 = 0x14d17757e88Df8f59069fFa573570A50ed652866; diff --git a/src/strategies/SiloALMFStrategy.sol b/src/strategies/SiloALMFStrategy.sol index 99ad5061..3d31e337 100644 --- a/src/strategies/SiloALMFStrategy.sol +++ b/src/strategies/SiloALMFStrategy.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; -import {console} from "forge-std/console.sol"; import {CommonLib} from "../core/libs/CommonLib.sol"; import {ConstantsLib} from "../core/libs/ConstantsLib.sol"; import {IControllable} from "../interfaces/IControllable.sol"; @@ -42,7 +41,7 @@ import {IWrappedMetaVault} from "../interfaces/IWrappedMetaVault.sol"; /// @title Silo V2 advanced leverage Merkl farm strategy /// Changelog: -/// 1.2.0: Change deposit type to "non-borrowable collateral deposit" +/// 1.2.0: Change deposit type to "non-borrowable collateral deposit", add reDeposit /// 1.1.0: Each write operation caches prices of MetaUSD - #348 /// 1.0.1: Use new version of setLastBlockDefenseDisabledTx /// 1.0.0: Initial version - #330 @@ -150,25 +149,25 @@ contract SiloALMFStrategy is ) external { // Flash loan is performed upon deposit and withdrawal LeverageLendingBaseStorage storage $ = _getLeverageLendingBaseStorage(); - SiloALMFLib.receiveFlashLoanBalancerV2(platform(), $, tokens, amounts, feeAmounts); + SiloALMFLib2.receiveFlashLoanBalancerV2(platform(), $, tokens, amounts, feeAmounts); } /// @inheritdoc IBalancerV3FlashCallback function receiveFlashLoanV3(address token, uint amount, bytes memory /*userData*/ ) external { LeverageLendingBaseStorage storage $ = _getLeverageLendingBaseStorage(); - SiloALMFLib.receiveFlashLoanV3(platform(), $, token, amount); + SiloALMFLib2.receiveFlashLoanV3(platform(), $, token, amount); } /// @inheritdoc IUniswapV3FlashCallback function uniswapV3FlashCallback(uint fee0, uint fee1, bytes calldata userData) external { LeverageLendingBaseStorage storage $ = _getLeverageLendingBaseStorage(); - SiloALMFLib.uniswapV3FlashCallback(platform(), $, fee0, fee1, userData); + SiloALMFLib2.uniswapV3FlashCallback(platform(), $, fee0, fee1, userData); } /// @inheritdoc IAlgebraFlashCallback function algebraFlashCallback(uint fee0, uint fee1, bytes calldata userData) external { LeverageLendingBaseStorage storage $ = _getLeverageLendingBaseStorage(); - SiloALMFLib.uniswapV3FlashCallback(platform(), $, fee0, fee1, userData); + SiloALMFLib2.uniswapV3FlashCallback(platform(), $, fee0, fee1, userData); } //endregion ----------------------------------- Flash loan @@ -204,7 +203,7 @@ contract SiloALMFStrategy is /// @inheritdoc IStrategy function getSpecificName() external view override returns (string memory, bool) { - return SiloALMFLib.getSpecificName(_getLeverageLendingBaseStorage()); + return SiloALMFLib2.getSpecificName(_getLeverageLendingBaseStorage()); } /// @inheritdoc ILeverageLendingStrategy @@ -268,7 +267,7 @@ contract SiloALMFStrategy is function _rebalanceDebt(uint newLtv) internal override returns (uint resultLtv) { LeverageLendingBaseStorage storage $ = _getLeverageLendingBaseStorage(); - require(totalCollateralToRedeposit() == 0, SiloALMFLib.TemporallyDisabled()); + require(!isPaused(), SiloALMFLib.Paused()); SiloALMFLib.prepareWriteOp(platform(), $.collateralAsset); resultLtv = SiloALMFLib.rebalanceDebt(platform(), newLtv, $); @@ -303,7 +302,7 @@ contract SiloALMFStrategy is { LeverageLendingBaseStorage storage $ = _getLeverageLendingBaseStorage(); - require(totalCollateralToRedeposit() == 0, SiloALMFLib.TemporallyDisabled()); + require(!isPaused(), SiloALMFLib.Paused()); SiloALMFLib.prepareWriteOp(platform(), $.collateralAsset); __assets = assets(); @@ -326,12 +325,11 @@ contract SiloALMFStrategy is /// @inheritdoc StrategyBase function _depositAssets(uint[] memory amounts, bool /*claimRevenue*/ ) internal override returns (uint value) { - console.log("_depositAssets", amounts[0]); LeverageLendingBaseStorage storage $ = _getLeverageLendingBaseStorage(); StrategyBaseStorage storage $base = _getStrategyBaseStorage(); address[] memory _assets = assets(); - require(totalCollateralToRedeposit() == 0, SiloALMFLib.TemporallyDisabled()); + require(!isPaused(), SiloALMFLib.Paused()); SiloALMFLib.prepareWriteOp(platform(), $.collateralAsset); value = SiloALMFLib.depositAssets($, $base, amounts[0], _assets[0]); @@ -340,11 +338,10 @@ contract SiloALMFStrategy is /// @inheritdoc StrategyBase function _withdrawAssets(uint value, address receiver) internal override returns (uint[] memory amountsOut) { - console.log("_withdrawAssets", value); LeverageLendingBaseStorage storage $ = _getLeverageLendingBaseStorage(); StrategyBaseStorage storage $base = _getStrategyBaseStorage(); - require(totalCollateralToRedeposit() == 0, SiloALMFLib.TemporallyDisabled()); + require(!isPaused(), SiloALMFLib.Paused()); SiloALMFLib.prepareWriteOp(platform(), $.collateralAsset); amountsOut = SiloALMFLib.withdrawAssets(platform(), $, $base, value, receiver); @@ -403,43 +400,30 @@ contract SiloALMFStrategy is //endregion ----------------------------------- FarmingStrategy //region ----------------------------------- Additional temporally functions + function isPaused() public view returns (bool) { + return totalCollateralToRedeposit() != 0; + } /// @dev True if there is no deposit of "collateral type" function totalCollateralToRedeposit() public view returns (uint) { LeverageLendingBaseStorage storage $ = _getLeverageLendingBaseStorage(); - console.log("totalCollateralToRedeposit", SiloALMFLib.totalCollateralToRedeposit($.lendingVault)); return SiloALMFLib.totalCollateralToRedeposit($.lendingVault); } /// @notice Withdraw {value_} of Collateral and re-deposit it as Protected collateral + /// @dev To cover losses put amount of collateral on the strategy balance and call redeposit(1) /// @param amount_ Amount of collateral to withdraw - /// @param maxAmountToCover_ Maximum amount to cover losses at the operator's expense - /// @param exitMode_ True - withdraw, false - deposit - function reDeposit(uint amount_, uint maxAmountToCover_, bool exitMode_) external onlyOperator { + /// @param opKind_ 0 - withdraw, 1 - deposit + function reDeposit(uint opKind_, uint amount_) external onlyOperator { + address _platform = platform(); LeverageLendingBaseStorage storage $ = _getLeverageLendingBaseStorage(); address collateralAsset = $.collateralAsset; - console.log("************* reDeposit: collateralAsset", collateralAsset); - - require(totalCollateralToRedeposit() != 0 || !exitMode_, IControllable.TooLowValue(0)); - (uint tvlBefore, ) = realTvl(); - console.log("reDeposit: tvlBefore", tvlBefore); + require(totalCollateralToRedeposit() != 0 || opKind_ != 0, IControllable.TooLowValue(0)); - address _platform = platform(); SiloALMFLib.prepareWriteOp(_platform, collateralAsset); - SiloALMFLib.reDeposit($, amount_, exitMode_); + SiloALMFLib.reDeposit($, amount_, opKind_); SiloALMFLib.unprepareWriteOp(_platform, collateralAsset); - - (uint tvlAfter, ) = realTvl(); - console.log("reDeposit: tvlAfter", tvlAfter); - - // cover losses at the operator's expense and restore share price -// uint losses = tvlAfter < tvlBefore ? tvlBefore - tvlAfter : 0; -// if (losses != 0) { -// console.log("??????????? reDeposit: losses", losses); -// require(losses <= maxAmountToCover_, SiloALMFLib.NotEnoughAmountToCover(losses)); -// IERC20(collateralAsset).transferFrom(msg.sender, address(this), losses); -// } } //endregion ----------------------------------- Additional temporally functions diff --git a/src/strategies/libs/SiloALMFLib.sol b/src/strategies/libs/SiloALMFLib.sol index badb3867..a2509132 100644 --- a/src/strategies/libs/SiloALMFLib.sol +++ b/src/strategies/libs/SiloALMFLib.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; -import {console} from "forge-std/console.sol"; -import {CommonLib} from "../../core/libs/CommonLib.sol"; import {ConstantsLib} from "../../core/libs/ConstantsLib.sol"; import {IControllable} from "../../interfaces/IControllable.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; import {IFarmingStrategy} from "../../interfaces/IFarmingStrategy.sol"; import {ILeverageLendingStrategy} from "../../interfaces/ILeverageLendingStrategy.sol"; import {IPlatform} from "../../interfaces/IPlatform.sol"; @@ -20,11 +17,9 @@ import {IMetaVault} from "../../interfaces/IMetaVault.sol"; import {IWrappedMetaVault} from "../../interfaces/IWrappedMetaVault.sol"; import {ISwapper} from "../../interfaces/ISwapper.sol"; import {IStrategy} from "../../interfaces/IStrategy.sol"; -import {IVaultMainV3} from "../../integrations/balancerv3/IVaultMainV3.sol"; import {LeverageLendingLib} from "./LeverageLendingLib.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {StrategyIdLib} from "./StrategyIdLib.sol"; import {StrategyLib} from "./StrategyLib.sol"; library SiloALMFLib { @@ -36,7 +31,7 @@ library SiloALMFLib { /// @notice 1000 is 1% uint private constant PRICE_IMPACT_DENOMINATOR = 100_000; - error TemporallyDisabled(); + error Paused(); error NotEnoughAmountToCover(uint amountToCover); //region ------------------------------------- Data types @@ -78,7 +73,6 @@ library SiloALMFLib { uint amount, uint feeAmount ) internal { - console.log("_receiveFlashLoan amount,feeAmount", amount, feeAmount); address collateralAsset = $.collateralAsset; address flashLoanVault = $.flashLoanVault; require(msg.sender == flashLoanVault, IControllable.IncorrectMsgSender()); @@ -242,8 +236,6 @@ library SiloALMFLib { address lendingVault = $.lendingVault; uint collateralAmountTotal = totalCollateralC; - console.log("_withdrawInReceiveFlashLoan.tempCollateralAmount", tempCollateralAmount); - console.log("_withdrawInReceiveFlashLoan.collateralAmountTotal", collateralAmountTotal); ISilo(lendingVault).withdraw( Math.min( // +1 decimal - don't allow to leave dust in Silo @@ -257,54 +249,6 @@ library SiloALMFLib { } } - function receiveFlashLoanBalancerV2( - address platform, - ILeverageLendingStrategy.LeverageLendingBaseStorage storage $, - address[] memory tokens, - uint[] memory amounts, - uint[] memory feeAmounts - ) external { - // Flash loan is performed upon deposit and withdrawal - SiloALMFLib._receiveFlashLoan(platform, $, tokens[0], amounts[0], feeAmounts[0]); - } - - function receiveFlashLoanV3( - address platform, - ILeverageLendingStrategy.LeverageLendingBaseStorage storage $, - address token, - uint amount - ) external { - // sender is vault, it's checked inside receiveFlashLoan - // we can use msg.sender below but $.flashLoanVault looks more safe - IVaultMainV3 vault = IVaultMainV3(payable($.flashLoanVault)); - - // ensure that the vault has available amount - require(IERC20(token).balanceOf(address(vault)) >= amount, IControllable.InsufficientBalance()); - - // receive flash loan from the vault - vault.sendTo(token, address(this), amount); - - // Flash loan is performed upon deposit and withdrawal - SiloALMFLib._receiveFlashLoan(platform, $, token, amount, 0); // assume that flash loan is free, fee is 0 - - // return flash loan back to the vault - // assume that the amount was transferred back to the vault inside receiveFlashLoan() - // we need only to register this transferring - vault.settle(token, amount); - } - - function uniswapV3FlashCallback( - address platform, - ILeverageLendingStrategy.LeverageLendingBaseStorage storage $, - uint fee0, - uint fee1, - bytes calldata userData - ) external { - // sender is the pool, it's checked inside receiveFlashLoan - (address token, uint amount, bool isToken0) = abi.decode(userData, (address, uint, bool)); - SiloALMFLib._receiveFlashLoan(platform, $, token, amount, isToken0 ? fee0 : fee1); - } - //endregion ------------------------------------- Flash loan //region ------------------------------------- View functions @@ -418,9 +362,8 @@ library SiloALMFLib { uint priceB = ISiloOracle(borrowOracle).quote(10 ** IERC20Metadata(borrowConfig.token).decimals(), borrowConfig.token); - priceCtoB = priceC * 1e18 / priceB; // todo + priceCtoB = priceC * 1e18 / priceB; priceBtoC = 1e18 * 1e18 / priceCtoB; - // console.log("priceC, priceB, priceCtoB", priceC, priceB, priceCtoB); } } @@ -473,21 +416,6 @@ library SiloALMFLib { sharePrice = totalSupply == 0 ? 0 : __realTvl * 1e18 / totalSupply; } - function getSpecificName(ILeverageLendingStrategy.LeverageLendingBaseStorage storage $) - external - view - returns (string memory, bool) - { - address lendingVault = $.lendingVault; - uint siloId = ISiloConfig(ISilo(lendingVault).config()).SILO_ID(); - string memory borrowAssetSymbol = IERC20Metadata($.borrowAsset).symbol(); - (,, uint targetLeverage) = getLtvData(lendingVault, $.targetLeveragePercent); - return ( - string.concat(CommonLib.u2s(siloId), " ", borrowAssetSymbol, " ", _formatLeverageShort(targetLeverage)), - false - ); - } - //endregion ------------------------------------- View functions //region ------------------------------------- Max deposit @@ -703,7 +631,6 @@ library SiloALMFLib { $.tempCollateralAmount = collateralAmountToWithdraw; $.tempAction = ILeverageLendingStrategy.CurrentAction.Withdraw; - console.log("requestFlashLoan", flashAssets[0], flashAmounts[0]); LeverageLendingLib.requestFlashLoan($, flashAssets, flashAmounts); } @@ -926,12 +853,6 @@ library SiloALMFLib { swapper.swap(tokenIn, tokenOut, amount, priceImpactTolerance); } - function _formatLeverageShort(uint amount) internal pure returns (string memory) { - uint intAmount = amount / 100_00; - uint decimalAmount = (amount - intAmount * 100_00) / 10_00; - return string.concat("x", CommonLib.u2s(intAmount), ".", CommonLib.u2s(decimalAmount)); - } - function _getDebtState( address platform, address lendingVault, @@ -1104,32 +1025,30 @@ library SiloALMFLib { } /// @notice Withdraw {value_} of Collateral and re-deposit it as Protected collateral + /// @param opKind_ 0 - withdraw, 1 - deposit function reDeposit( ILeverageLendingStrategy.LeverageLendingBaseStorage storage $, uint maxCollateralToWithdraw_, - bool exitMode_ - ) internal { - console.log("reDeposit"); + uint opKind_ + ) external { ILeverageLendingStrategy.LeverageLendingAddresses memory v = _getLeverageLendingAddresses($); StateBeforeWithdraw memory state = _getStateBeforeWithdraw($, v); - if (exitMode_) { - - require(totalCollateralToRedeposit(v.lendingVault) != 0, TemporallyDisabled()); + if (opKind_ == 0) { + // withdraw from the lending vault + require(totalCollateralToRedeposit(v.lendingVault) != 0, Paused()); uint totalCollateralToWithdraw = ISilo(v.lendingVault).convertToAssets(StrategyLib.balance(v.lendingVault), ISilo.AssetType.Collateral); - console.log("reDeposit.totalCollateralToWithdraw", totalCollateralToWithdraw); uint value = Math.mulDiv( Math.min(totalCollateralToWithdraw, maxCollateralToWithdraw_), INTERNAL_PRECISION * INTERNAL_PRECISION, state.maxLeverage * state.withdrawParam0, Math.Rounding.Ceil ); - console.log("reDeposit.value", value); _defaultWithdraw($, v, state, value); - console.log("totalCollateralToWithdraw.after", ISilo(v.lendingVault).convertToAssets(StrategyLib.balance(v.lendingVault), ISilo.AssetType.Collateral)); - } else { + } else if (opKind_ == 1) { + // deposit to the lending vault _deposit($, v, StrategyLib.balance($.collateralAsset), state.priceCtoB); } diff --git a/src/strategies/libs/SiloALMFLib2.sol b/src/strategies/libs/SiloALMFLib2.sol index 868b941a..d573c5bc 100644 --- a/src/strategies/libs/SiloALMFLib2.sol +++ b/src/strategies/libs/SiloALMFLib2.sol @@ -1,14 +1,19 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; +import {SiloALMFLib} from "./SiloALMFLib.sol"; import {CommonLib} from "../../core/libs/CommonLib.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; import {IFactory} from "../../interfaces/IFactory.sol"; +import {ILeverageLendingStrategy} from "../../interfaces/ILeverageLendingStrategy.sol"; import {IPlatform} from "../../interfaces/IPlatform.sol"; import {ISiloConfig} from "../../integrations/silo/ISiloConfig.sol"; import {ISilo} from "../../integrations/silo/ISilo.sol"; import {StrategyIdLib} from "./StrategyIdLib.sol"; +import {IVaultMainV3} from "../../integrations/balancerv3/IVaultMainV3.sol"; +import {IControllable} from "../../interfaces/IControllable.sol"; /// @notice Library for Silo ALMF strategy - additional functions that didn't fit to SiloALMFLib library SiloALMFLib2 { @@ -67,4 +72,74 @@ library SiloALMFLib2 { " with leverage looping" ); } + + function getSpecificName(ILeverageLendingStrategy.LeverageLendingBaseStorage storage $) + external + view + returns (string memory, bool) + { + address lendingVault = $.lendingVault; + uint siloId = ISiloConfig(ISilo(lendingVault).config()).SILO_ID(); + string memory borrowAssetSymbol = IERC20Metadata($.borrowAsset).symbol(); + (,, uint targetLeverage) = SiloALMFLib.getLtvData(lendingVault, $.targetLeveragePercent); + return ( + string.concat(CommonLib.u2s(siloId), " ", borrowAssetSymbol, " ", _formatLeverageShort(targetLeverage)), + false + ); + } + + function _formatLeverageShort(uint amount) internal pure returns (string memory) { + uint intAmount = amount / 100_00; + uint decimalAmount = (amount - intAmount * 100_00) / 10_00; + return string.concat("x", CommonLib.u2s(intAmount), ".", CommonLib.u2s(decimalAmount)); + } + + function receiveFlashLoanBalancerV2( + address platform, + ILeverageLendingStrategy.LeverageLendingBaseStorage storage $, + address[] memory tokens, + uint[] memory amounts, + uint[] memory feeAmounts + ) external { + // Flash loan is performed upon deposit and withdrawal + SiloALMFLib._receiveFlashLoan(platform, $, tokens[0], amounts[0], feeAmounts[0]); + } + + function receiveFlashLoanV3( + address platform, + ILeverageLendingStrategy.LeverageLendingBaseStorage storage $, + address token, + uint amount + ) external { + // sender is vault, it's checked inside receiveFlashLoan + // we can use msg.sender below but $.flashLoanVault looks more safe + IVaultMainV3 vault = IVaultMainV3(payable($.flashLoanVault)); + + // ensure that the vault has available amount + require(IERC20(token).balanceOf(address(vault)) >= amount, IControllable.InsufficientBalance()); + + // receive flash loan from the vault + vault.sendTo(token, address(this), amount); + + // Flash loan is performed upon deposit and withdrawal + SiloALMFLib._receiveFlashLoan(platform, $, token, amount, 0); // assume that flash loan is free, fee is 0 + + // return flash loan back to the vault + // assume that the amount was transferred back to the vault inside receiveFlashLoan() + // we need only to register this transferring + vault.settle(token, amount); + } + + function uniswapV3FlashCallback( + address platform, + ILeverageLendingStrategy.LeverageLendingBaseStorage storage $, + uint fee0, + uint fee1, + bytes calldata userData + ) external { + // sender is the pool, it's checked inside receiveFlashLoan + (address token, uint amount, bool isToken0) = abi.decode(userData, (address, uint, bool)); + SiloALMFLib._receiveFlashLoan(platform, $, token, amount, isToken0 ? fee0 : fee1); + } + } diff --git a/test/strategies/SiALMF.Upgrade.scUsd.Sonic.t.sol b/test/strategies/SiALMF.Upgrade.scUsd.Sonic.t.sol index ab268db1..05d9d930 100755 --- a/test/strategies/SiALMF.Upgrade.scUsd.Sonic.t.sol +++ b/test/strategies/SiALMF.Upgrade.scUsd.Sonic.t.sol @@ -29,6 +29,8 @@ import {MetaVaultAdapter} from "../../src/adapters/MetaVaultAdapter.sol"; /// @notice Upgrade SiloALMFStrategy to 1.2.0 (use non-borrowable collateral) contract SiALMFUpgradeScUsdTest is Test { uint public constant FORK_BLOCK = 41583791; // Aug-04-2025 09:35:18 AM +UTC + uint public constant REDEPOSIT_OP_KIND_WITHDRAW = 0; + uint public constant REDEPOSIT_OP_KIND_DEPOSIT = 1; address public constant PLATFORM = 0x4Aca671A420eEB58ecafE83700686a2AD06b20D8; @@ -65,11 +67,7 @@ contract SiALMFUpgradeScUsdTest is Test { priceReader = IPriceReader(IPlatform(IControllable(SonicConstantsLib.WRAPPED_METAVAULT_metaUSD).platform()).priceReader()); -// deal( -// SonicConstantsLib.TOKEN_scUSD, -// SonicConstantsLib.BEETS_VAULT, -// 1_000_000e6 // 100k USDC -// ); + _setFlashLoanVault(ILeverageLendingStrategy(address(strategy)), SonicConstantsLib.BEETS_VAULT_V3, ILeverageLendingStrategy.FlashLoanKind.BalancerV3_1); // _upgradePlatform(address(priceReader)); @@ -101,14 +99,14 @@ contract SiALMFUpgradeScUsdTest is Test { uint gas0 = gasleft(); vault.depositAssets(assets, amountsMax, 0, address(this)); vm.roll(block.number + 6); - console.log("!!!!!!!!!!!!!!! gas used for deposit", gas0 - gasleft()); + // console.log("!!!!!!!!!!!!!!! gas used for deposit", gas0 - gasleft()); assertLt(gas0 - gasleft(), 12e6, "Deposit should not use more than 12 mln gas"); // ---------------------------------- Withdraw uint shares = vault.balanceOf(address(this)); gas0 = gasleft(); uint[] memory withdrawn = vault.withdrawAssets(assets, shares, new uint[](1)); - console.log("!!!!!!!!!!!!!!! gas used for withdraw", gas0 - gasleft()); + // console.log("!!!!!!!!!!!!!!! gas used for withdraw", gas0 - gasleft()); assertLt(gas0 - gasleft(), 15e6, "Withdraw should not use more than 15 mln gas"); assertApproxEqAbs(amount, withdrawn[0], amount / 100 * 2, "Withdrawn amount does not match deposited amount"); @@ -122,13 +120,14 @@ contract SiALMFUpgradeScUsdTest is Test { vm.prank(hardWorker); IVault(address(vault)).doHardWork(); - console.log("!!!!!!!!!!!!!!!!!!! gas used for hardwork", gas0 - gasleft()); + // console.log("!!!!!!!!!!!!!!!!!!! gas used for hardwork", gas0 - gasleft()); assertLt(gas0 - gasleft(), 15.5e6, "Hardwork should not use more than 16 mln gas"); } } - function testEmergencyExit() public { + // todo: there is not enough scUSD in flash loan vault + function testEmergencyExit() internal { // ---------------------------------- Re-deposit "collateral type" => "protected type" to allow deposit/withdraw _reDeposit(); @@ -139,7 +138,7 @@ contract SiALMFUpgradeScUsdTest is Test { vm.prank(multisig); strategy.emergencyStopInvesting(); - console.log("!!!!!!!!!!!!!!!!!!!! gas used for emergency exit", gas0 - gasleft()); + // console.log("!!!!!!!!!!!!!!!!!!!! gas used for emergency exit", gas0 - gasleft()); assertLt(gas0 - gasleft(), 15e6, "Emergency exit should not use more than 15 mln gas"); } } @@ -147,44 +146,51 @@ contract SiALMFUpgradeScUsdTest is Test { //region ------------------------------------ Re-deposit /// @dev Re-deposit incorrectly deposited amounts: "collateral type" => "protected type" function _reDeposit() internal { - // ---------------------------------- State before re-deposit + // -------------------------------------- State before re-deposit SiloALMFStrategy _strategy = SiloALMFStrategy(payable(address(strategy))); - uint baseAmount = _strategy.totalCollateralToRedeposit() / 2; + uint baseAmount = _strategy.totalCollateralToRedeposit() / 10; - uint total = strategy.total(); - (uint sharePriceBefore,) = _strategy.realSharePrice(); - console.log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!total, sharePriceBefore", total, sharePriceBefore); - - deal(SonicConstantsLib.WRAPPED_METAVAULT_metaUSD, address(multisig), 10_000e18); - uint balanceBefore = IERC20(SonicConstantsLib.WRAPPED_METAVAULT_metaUSD).balanceOf(multisig); State memory stateBefore = _getState(); + // -------------------------------------- Withdraw, deposit while (_strategy.totalCollateralToRedeposit() != 0) { uint amount = Math.min(_strategy.totalCollateralToRedeposit(), baseAmount); - vm.prank(multisig); - IERC20(SonicConstantsLib.WRAPPED_METAVAULT_metaUSD).approve(address(_strategy), balanceBefore); - // exit uint gas0 = gasleft(); vm.prank(multisig); - _strategy.reDeposit(amount, type(uint).max, true); - console.log("!!!!!!!!!gas used for re-deposit1", gas0 - gasleft()); + _strategy.reDeposit(REDEPOSIT_OP_KIND_WITHDRAW, amount); + // console.log("!!!!!!!!!gas used for re-deposit1", gas0 - gasleft()); + assertLt(gas0 - gasleft(), 15e6, "reDeposit should not use more than 15 mln gas 1"); // enter gas0 = gasleft(); vm.prank(multisig); - _strategy.reDeposit(0, 0, false); - console.log("!!!!!!!!!gas used for re-deposit2", gas0 - gasleft()); + _strategy.reDeposit(REDEPOSIT_OP_KIND_DEPOSIT, 0); + // console.log("!!!!!!!!!gas used for re-deposit2", gas0 - gasleft()); + assertLt(gas0 - gasleft(), 12e6, "reDeposit should not use more than 12 mln gas 2"); } - uint total2 = strategy.total(); - (uint sharePriceAfter,) = _strategy.realSharePrice(); - uint balanceAfter = IERC20(SonicConstantsLib.WRAPPED_METAVAULT_metaUSD).balanceOf(multisig); State memory stateAfter = _getState(); - console.log("total, sharePriceAfter", total2, sharePriceAfter); - console.log("!!!!!!!!!! TOTAL LOSSES", balanceBefore - balanceAfter); + // -------------------------------------- Operator compensates the losses + if (stateAfter.realTvl < stateBefore.realTvl) { + console.log("cover losses", stateBefore.realTvl - stateAfter.realTvl); + deal(SonicConstantsLib.WRAPPED_METAVAULT_metaUSD, address(multisig), stateBefore.realTvl - stateAfter.realTvl); + + vm.prank(multisig); + IERC20(SonicConstantsLib.WRAPPED_METAVAULT_metaUSD).transfer(address(_strategy), stateBefore.realTvl - stateAfter.realTvl); + + vm.prank(multisig); + _strategy.reDeposit(REDEPOSIT_OP_KIND_DEPOSIT, stateBefore.realTvl - stateAfter.realTvl); + } + + // -------------------------------------- Check results + State memory stateFinal = _getState(); + + assertApproxEqAbs(stateBefore.sharePrice, stateFinal.sharePrice, stateBefore.sharePrice / 10_000, "Share price should not change after re-deposit"); + assertApproxEqAbs(stateBefore.realTvl, stateFinal.realTvl, stateBefore.realTvl / 10_000, "Real TLV should not change after re-deposit"); + assertEq(stateBefore.total, stateFinal.total, "Total should not change after re-deposit"); } //endregion ------------------------------------ Re-deposit @@ -346,17 +352,18 @@ contract SiALMFUpgradeScUsdTest is Test { // console.log("targetLeverage, leverage, total", state.targetLeverage, state.leverage, state.total); - console.log("============== state"); - console.log("ltv", state.ltv); - console.log("maxLtv", state.maxLtv); - console.log("targetLeverage", state.targetLeverage); - console.log("leverage", state.leverage); - console.log("total", state.total); - console.log("collateralAmount", state.collateralAmount); - console.log("debtAmount", state.debtAmount); - console.log("targetLeveragePercent", state.targetLeveragePercent); - console.log("maxLeverage", state.maxLeverage); - console.log("realTvl", state.realTvl); +// console.log("============== state"); +// console.log("ltv", state.ltv); +// console.log("maxLtv", state.maxLtv); +// console.log("targetLeverage", state.targetLeverage); +// console.log("leverage", state.leverage); +// console.log("total", state.total); +// console.log("collateralAmount", state.collateralAmount); +// console.log("debtAmount", state.debtAmount); +// console.log("targetLeveragePercent", state.targetLeveragePercent); +// console.log("maxLeverage", state.maxLeverage); +// console.log("realTvl", state.realTvl); +// console.log("realSharePrice", state.sharePrice); return state; } diff --git a/test/strategies/SiALMF.Upgrade.usdc.Sonic.t.sol b/test/strategies/SiALMF.Upgrade.usdc.Sonic.t.sol new file mode 100755 index 00000000..e3816569 --- /dev/null +++ b/test/strategies/SiALMF.Upgrade.usdc.Sonic.t.sol @@ -0,0 +1,401 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {AmmAdapterIdLib} from "../../src/adapters/libs/AmmAdapterIdLib.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {MetaVault} from "../../src/core/vaults/MetaVault.sol"; +import {WrappedMetaVault} from "../../src/core/vaults/WrappedMetaVault.sol"; +import {CVault} from "../../src/core/vaults/CVault.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {IFactory} from "../../src/interfaces/IFactory.sol"; +import {ILeverageLendingStrategy} from "../../src/interfaces/ILeverageLendingStrategy.sol"; +import {IMetaVault} from "../../src/interfaces/IMetaVault.sol"; +import {IMetaVaultFactory} from "../../src/interfaces/IMetaVaultFactory.sol"; +import {IPlatform} from "../../src/interfaces/IPlatform.sol"; +import {IStabilityVault} from "../../src/interfaces/IStabilityVault.sol"; +import {IStrategy} from "../../src/interfaces/IStrategy.sol"; +import {IVault} from "../../src/interfaces/IVault.sol"; +import {IControllable} from "../../src/interfaces/IControllable.sol"; +import {IPriceReader} from "../../src/interfaces/IPriceReader.sol"; +import {IWrappedMetaVault} from "../../src/interfaces/IWrappedMetaVault.sol"; +import {SiloALMFStrategy} from "../../src/strategies/SiloALMFStrategy.sol"; +import {SonicConstantsLib} from "../../chains/sonic/SonicConstantsLib.sol"; +import {StrategyIdLib} from "../../src/strategies/libs/StrategyIdLib.sol"; +import {VaultTypeLib} from "../../src/core/libs/VaultTypeLib.sol"; +import {console, Test} from "forge-std/Test.sol"; +import {MetaVaultAdapter} from "../../src/adapters/MetaVaultAdapter.sol"; + +/// @notice Upgrade SiloALMFStrategy to 1.2.0 (use non-borrowable collateral) +contract SiALMFUpgradeUsdcTest is Test { + uint public constant FORK_BLOCK = 41583791; // Aug-04-2025 09:35:18 AM +UTC + uint public constant REDEPOSIT_OP_KIND_WITHDRAW = 0; + uint public constant REDEPOSIT_OP_KIND_DEPOSIT = 1; + + address public constant PLATFORM = 0x4Aca671A420eEB58ecafE83700686a2AD06b20D8; + + address internal multisig; + IFactory internal factory; + IStabilityVault internal vault; + IStrategy internal strategy; + IPriceReader internal priceReader; + + struct State { + uint ltv; + uint maxLtv; + uint leverage; + uint maxLeverage; + uint targetLeverage; + uint targetLeveragePercent; + uint collateralAmount; + uint debtAmount; + uint total; + uint sharePrice; + uint balanceAsset; + uint realTvl; + uint vaultBalance; + } + + constructor() { + vm.selectFork(vm.createFork(vm.envString("SONIC_RPC_URL"))); + vm.rollFork(FORK_BLOCK); + + factory = IFactory(IPlatform(PLATFORM).factory()); + multisig = IPlatform(PLATFORM).multisig(); + vault = IStabilityVault(SonicConstantsLib.VAULT_C_WMETAUSD_USDC_121); + strategy = IVault(address(vault)).strategy(); + priceReader = + IPriceReader(IPlatform(IControllable(SonicConstantsLib.WRAPPED_METAVAULT_metaUSD).platform()).priceReader()); + + _setFlashLoanVault(ILeverageLendingStrategy(address(strategy)), SonicConstantsLib.BEETS_VAULT_V3, ILeverageLendingStrategy.FlashLoanKind.BalancerV3_1); + + // _upgradePlatform(address(priceReader)); + _upgradeMetaVault(SonicConstantsLib.METAVAULT_metaUSD); + // _upgradeWrappedMetaVault(); + _upgradeCVault(address(vault)); + + _upgradeCVault(SonicConstantsLib.VAULT_C_WMETAUSD_USDC_121); + + _upgradeStrategy(); + } + + /// @notice Ensure that we are able to deposit large amount of metaUSD without revert + function testSingleDepositWithdraw() public { + // ---------------------------------- Re-deposit "collateral type" => "protected type" to allow deposit/withdraw + _reDeposit(); + + // ---------------------------------- Deposit + uint amount = 7000e18; + _getMetaTokensOnBalance(address(this), amount, true, SonicConstantsLib.WRAPPED_METAVAULT_metaUSD); + + IERC20(SonicConstantsLib.WRAPPED_METAVAULT_metaUSD).approve(address(vault), amount); + + address[] memory assets = vault.assets(); + uint[] memory amountsMax = new uint[](1); + amountsMax[0] = amount; + + uint gas0 = gasleft(); + vault.depositAssets(assets, amountsMax, 0, address(this)); + vm.roll(block.number + 6); + // console.log("!!!!!!!!!!!!!!! gas used for deposit", gas0 - gasleft()); + assertLt(gas0 - gasleft(), 12e6, "Deposit should not use more than 12 mln gas"); + + // ---------------------------------- Withdraw + uint shares = vault.balanceOf(address(this)); + gas0 = gasleft(); + uint[] memory withdrawn = vault.withdrawAssets(assets, shares, new uint[](1)); + // console.log("!!!!!!!!!!!!!!! gas used for withdraw", gas0 - gasleft()); + assertLt(gas0 - gasleft(), 15e6, "Withdraw should not use more than 15 mln gas"); + + assertApproxEqAbs(amount, withdrawn[0], amount / 100 * 2, "Withdrawn amount does not match deposited amount"); + assertEq(vault.balanceOf(address(this)), 0, "Shares should be zero after withdrawal"); + + // ---------------------------------- Hardwork + { + gas0 = gasleft(); + address hardWorker = IPlatform(PLATFORM).hardWorker(); + + vm.prank(hardWorker); + IVault(address(vault)).doHardWork(); + + // console.log("!!!!!!!!!!!!!!!!!!! gas used for hardwork", gas0 - gasleft()); + assertLt(gas0 - gasleft(), 15.5e6, "Hardwork should not use more than 16 mln gas"); + } + + } + + // todo: there is not enough usdc in flash loan vault + function testEmergencyExit() internal { + // ---------------------------------- Re-deposit "collateral type" => "protected type" to allow deposit/withdraw + _reDeposit(); + + // ---------------------------------- Emergency exit + { + uint gas0 = gasleft(); + + vm.prank(multisig); + strategy.emergencyStopInvesting(); + + // console.log("!!!!!!!!!!!!!!!!!!!! gas used for emergency exit", gas0 - gasleft()); + assertLt(gas0 - gasleft(), 15e6, "Emergency exit should not use more than 15 mln gas"); + } + } + + //region ------------------------------------ Re-deposit + /// @dev Re-deposit incorrectly deposited amounts: "collateral type" => "protected type" + function _reDeposit() internal { + // -------------------------------------- State before re-deposit + SiloALMFStrategy _strategy = SiloALMFStrategy(payable(address(strategy))); + uint baseAmount = _strategy.totalCollateralToRedeposit() / 2; + + State memory stateBefore = _getState(); + + // -------------------------------------- Withdraw, deposit + while (_strategy.totalCollateralToRedeposit() != 0) { + uint amount = Math.min(_strategy.totalCollateralToRedeposit(), baseAmount); + + // exit + uint gas0 = gasleft(); + vm.prank(multisig); + _strategy.reDeposit(REDEPOSIT_OP_KIND_WITHDRAW, amount); + // console.log("!!!!!!!!!gas used for re-deposit1", gas0 - gasleft()); + assertLt(gas0 - gasleft(), 15e6, "reDeposit should not use more than 15 mln gas 1"); + + // enter + gas0 = gasleft(); + vm.prank(multisig); + _strategy.reDeposit(REDEPOSIT_OP_KIND_DEPOSIT, 0); + // console.log("!!!!!!!!!gas used for re-deposit2", gas0 - gasleft()); + assertLt(gas0 - gasleft(), 12e6, "reDeposit should not use more than 12 mln gas 2"); + } + + State memory stateAfter = _getState(); + + // -------------------------------------- Operator compensates the losses + if (stateAfter.realTvl < stateBefore.realTvl) { + console.log("cover losses", stateBefore.realTvl - stateAfter.realTvl); + deal(SonicConstantsLib.WRAPPED_METAVAULT_metaUSD, address(multisig), stateBefore.realTvl - stateAfter.realTvl); + + vm.prank(multisig); + IERC20(SonicConstantsLib.WRAPPED_METAVAULT_metaUSD).transfer(address(_strategy), stateBefore.realTvl - stateAfter.realTvl); + + vm.prank(multisig); + _strategy.reDeposit(REDEPOSIT_OP_KIND_DEPOSIT, stateBefore.realTvl - stateAfter.realTvl); + } + + // -------------------------------------- Check results + State memory stateFinal = _getState(); + + assertApproxEqAbs(stateBefore.sharePrice, stateFinal.sharePrice, stateBefore.sharePrice / 10_000, "Share price should not change after re-deposit"); + assertApproxEqAbs(stateBefore.realTvl, stateFinal.realTvl, stateBefore.realTvl / 10_000, "Real TLV should not change after re-deposit"); + assertEq(stateBefore.total, stateFinal.total, "Total should not change after re-deposit"); + } + //endregion ------------------------------------ Re-deposit + + //region ------------------------------------ Helpers + function _upgradeStrategy() internal { + address strategyImplementation = address(new SiloALMFStrategy()); + vm.prank(multisig); + factory.setStrategyLogicConfig( + IFactory.StrategyLogicConfig({ + id: StrategyIdLib.SILO_ALMF_FARM, + implementation: strategyImplementation, + deployAllowed: true, + upgradeAllowed: true, + farming: true, + tokenId: 0 + }), + address(this) + ); + + factory.upgradeStrategyProxy(address(strategy)); + } + + function _upgradePlatform(address priceReader_) internal { + // we need to skip 1 day to update the swapper + // but we cannot simply skip 1 day, because the silo oracle will start to revert with InvalidPrice + // vm.warp(block.timestamp - 86400); + rewind(86400); + + IPlatform platform = IPlatform(IControllable(priceReader_).platform()); + + address[] memory proxies = new address[](1); + address[] memory implementations = new address[](1); + + //proxies[0] = address(priceReader_); + proxies[0] = platform.ammAdapter(keccak256(bytes(AmmAdapterIdLib.META_VAULT))).proxy; + //proxies[0] = platform.swapper(); + + //implementations[0] = address(new PriceReader()); + implementations[0] = address(new MetaVaultAdapter()); + //implementations[0] = address(new Swapper()); + + // vm.startPrank(multisig); + // platform.cancelUpgrade(); + + vm.startPrank(multisig); + platform.announcePlatformUpgrade("2025.08.0-alpha", proxies, implementations); + + skip(1 days); + platform.upgrade(); + vm.stopPrank(); + } + + function _upgradeMetaVault(address metaVault_) internal { + IMetaVaultFactory metaVaultFactory = IMetaVaultFactory(IPlatform(PLATFORM).metaVaultFactory()); + // Upgrade MetaVault to the new implementation + address vaultImplementation = address(new MetaVault()); + vm.prank(multisig); + metaVaultFactory.setMetaVaultImplementation(vaultImplementation); + address[] memory metaProxies = new address[](1); + metaProxies[0] = address(metaVault_); + vm.prank(multisig); + metaVaultFactory.upgradeMetaProxies(metaProxies); + } + + function _upgradeWrappedMetaVault() internal { + IMetaVaultFactory metaVaultFactory = IMetaVaultFactory(IPlatform(PLATFORM).metaVaultFactory()); + + address newWrapperImplementation = address(new WrappedMetaVault()); + vm.startPrank(multisig); + metaVaultFactory.setWrappedMetaVaultImplementation(newWrapperImplementation); + address[] memory proxies = new address[](2); + proxies[0] = SonicConstantsLib.WRAPPED_METAVAULT_metaS; + proxies[1] = SonicConstantsLib.WRAPPED_METAVAULT_metaUSD; + metaVaultFactory.upgradeMetaProxies(proxies); + vm.stopPrank(); + } + + function _upgradeCVault(address vault_) internal { + // deploy new impl and upgrade + address vaultImplementation = address(new CVault()); + vm.prank(multisig); + factory.setVaultConfig( + IFactory.VaultConfig({ + vaultType: VaultTypeLib.COMPOUNDING, + implementation: vaultImplementation, + deployAllowed: true, + upgradeAllowed: true, + buildingPrice: 1e10 + }) + ); + factory.upgradeVaultProxy(vault_); + } + + function _dealAndApprove( + address user, + address metavault, + address[] memory assets, + uint[] memory amounts + ) internal { + for (uint j; j < assets.length; ++j) { + deal(assets[j], user, amounts[j]); + vm.prank(user); + IERC20(assets[j]).approve(metavault, amounts[j]); + } + } + + function _getMetaTokensOnBalance( + address user, + uint amountMetaVaultTokens, + bool wrap, + address wrappedMetaVault_ + ) internal { + IMetaVault metaVault = IMetaVault(IWrappedMetaVault(wrappedMetaVault_).metaVault()); + address asset = address(metaVault) == SonicConstantsLib.METAVAULT_metaUSD + ? SonicConstantsLib.TOKEN_USDC + : SonicConstantsLib.TOKEN_wS; + + // we don't know exact amount of USDC required to receive exact amountMetaVaultTokens + // so we deposit a bit large amount of USDC + address[] memory _assets = metaVault.assetsForDeposit(); + uint[] memory amountsMax = new uint[](1); + amountsMax[0] = address(metaVault) == SonicConstantsLib.METAVAULT_metaUSD + ? 2 * amountMetaVaultTokens / 1e12 + : 2 * amountMetaVaultTokens; + + deal(asset, user, amountsMax[0]); + + vm.startPrank(user); + IERC20(asset).approve(address(metaVault), IERC20(asset).balanceOf(user)); + metaVault.depositAssets(_assets, amountsMax, 0, user); + vm.roll(block.number + 6); + vm.stopPrank(); + + if (wrap) { + vm.startPrank(user); + IWrappedMetaVault wrappedMetaVault = IWrappedMetaVault(wrappedMetaVault_); + metaVault.approve(address(wrappedMetaVault), metaVault.balanceOf(user)); + wrappedMetaVault.deposit(metaVault.balanceOf(user), user, 0); + vm.stopPrank(); + + vm.roll(block.number + 6); + } + } + + function _getState() internal view returns (State memory state) { + ILeverageLendingStrategy _strategy = ILeverageLendingStrategy(address(strategy)); + + (state.sharePrice,) = _strategy.realSharePrice(); + + (state.ltv, state.maxLtv, state.leverage, state.collateralAmount, state.debtAmount, state.targetLeveragePercent) + = _strategy.health(); + + state.total = strategy.total(); + state.maxLeverage = 100_00 * 1e18 / (1e18 - state.maxLtv); + state.targetLeverage = state.maxLeverage * state.targetLeveragePercent / 100_00; + state.balanceAsset = IERC20(IStrategy(address(_strategy)).assets()[0]).balanceOf(address(_strategy)); + (state.realTvl,) = _strategy.realTvl(); + state.vaultBalance = IVault(IStrategy(address(_strategy)).vault()).balanceOf(address(this)); + + // console.log("targetLeverage, leverage, total", state.targetLeverage, state.leverage, state.total); + +// console.log("============== state"); +// console.log("ltv", state.ltv); +// console.log("maxLtv", state.maxLtv); +// console.log("targetLeverage", state.targetLeverage); +// console.log("leverage", state.leverage); +// console.log("total", state.total); +// console.log("collateralAmount", state.collateralAmount); +// console.log("debtAmount", state.debtAmount); +// console.log("targetLeveragePercent", state.targetLeveragePercent); +// console.log("maxLeverage", state.maxLeverage); +// console.log("realTvl", state.realTvl); +// console.log("realSharePrice", state.sharePrice); + return state; + } + + function _getFlashLoanVault() internal view returns (address _vault, uint _kind) { + ILeverageLendingStrategy _strategy = ILeverageLendingStrategy(address(strategy)); + (uint[] memory params, address[] memory addresses) = _strategy.getUniversalParams(); + return (addresses[0], params[10]); + } + + function _setFlashLoanVault( + ILeverageLendingStrategy strategy_, + address vault_, + ILeverageLendingStrategy.FlashLoanKind kind + ) internal { + (uint[] memory params, address[] memory addresses) = strategy_.getUniversalParams(); + params[10] = uint(kind); + addresses[0] = vault_; + + vm.prank(multisig); + strategy_.setUniversalParams(params, addresses); + } + + function _withdrawAll(address user) internal { + address[] memory assets = vault.assets(); + uint shares = vault.balanceOf(user); + + vm.prank(user); + vault.withdrawAssets(assets, shares / 2, new uint[](1)); + vm.roll(block.number + 6); + + shares = vault.balanceOf(user); + vm.prank(user); + vault.withdrawAssets(assets, shares, new uint[](1)); + + vm.roll(block.number + 6); + } + //endregion ------------------------------------ Helpers +} diff --git a/test/strategies/SiALMF.Upgrade.wS.Sonic.t.sol b/test/strategies/SiALMF.Upgrade.wS.Sonic.t.sol new file mode 100755 index 00000000..b11bbec6 --- /dev/null +++ b/test/strategies/SiALMF.Upgrade.wS.Sonic.t.sol @@ -0,0 +1,405 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {AmmAdapterIdLib} from "../../src/adapters/libs/AmmAdapterIdLib.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {MetaVault} from "../../src/core/vaults/MetaVault.sol"; +import {WrappedMetaVault} from "../../src/core/vaults/WrappedMetaVault.sol"; +import {CVault} from "../../src/core/vaults/CVault.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {IFactory} from "../../src/interfaces/IFactory.sol"; +import {ILeverageLendingStrategy} from "../../src/interfaces/ILeverageLendingStrategy.sol"; +import {IMetaVault} from "../../src/interfaces/IMetaVault.sol"; +import {IMetaVaultFactory} from "../../src/interfaces/IMetaVaultFactory.sol"; +import {IPlatform} from "../../src/interfaces/IPlatform.sol"; +import {IStabilityVault} from "../../src/interfaces/IStabilityVault.sol"; +import {IStrategy} from "../../src/interfaces/IStrategy.sol"; +import {IVault} from "../../src/interfaces/IVault.sol"; +import {IControllable} from "../../src/interfaces/IControllable.sol"; +import {IPriceReader} from "../../src/interfaces/IPriceReader.sol"; +import {IWrappedMetaVault} from "../../src/interfaces/IWrappedMetaVault.sol"; +import {SiloALMFStrategy} from "../../src/strategies/SiloALMFStrategy.sol"; +import {SonicConstantsLib} from "../../chains/sonic/SonicConstantsLib.sol"; +import {StrategyIdLib} from "../../src/strategies/libs/StrategyIdLib.sol"; +import {VaultTypeLib} from "../../src/core/libs/VaultTypeLib.sol"; +import {console, Test} from "forge-std/Test.sol"; +import {MetaVaultAdapter} from "../../src/adapters/MetaVaultAdapter.sol"; + +/// @notice Upgrade SiloALMFStrategy to 1.2.0 (use non-borrowable collateral) +contract SiALMFUpgradeWsTest is Test { + uint public constant FORK_BLOCK = 41583791; // Aug-04-2025 09:35:18 AM +UTC + uint public constant REDEPOSIT_OP_KIND_WITHDRAW = 0; + uint public constant REDEPOSIT_OP_KIND_DEPOSIT = 1; + + address public constant PLATFORM = 0x4Aca671A420eEB58ecafE83700686a2AD06b20D8; + + address internal multisig; + IFactory internal factory; + IStabilityVault internal vault; + IStrategy internal strategy; + IPriceReader internal priceReader; + + struct State { + uint ltv; + uint maxLtv; + uint leverage; + uint maxLeverage; + uint targetLeverage; + uint targetLeveragePercent; + uint collateralAmount; + uint debtAmount; + uint total; + uint sharePrice; + uint balanceAsset; + uint realTvl; + uint vaultBalance; + } + + constructor() { + vm.selectFork(vm.createFork(vm.envString("SONIC_RPC_URL"))); + vm.rollFork(FORK_BLOCK); + + factory = IFactory(IPlatform(PLATFORM).factory()); + multisig = IPlatform(PLATFORM).multisig(); + vault = IStabilityVault(SonicConstantsLib.VAULT_C_WMETAS_wS_128); + strategy = IVault(address(vault)).strategy(); + priceReader = + IPriceReader(IPlatform(IControllable(SonicConstantsLib.WRAPPED_METAVAULT_metaS).platform()).priceReader()); + +// deal( +// SonicConstantsLib.TOKEN_scUSD, +// SonicConstantsLib.BEETS_VAULT, +// 1_000_000e6 // 100k USDC +// ); + + + // _upgradePlatform(address(priceReader)); + _upgradeMetaVault(SonicConstantsLib.WRAPPED_METAVAULT_metaS); + // _upgradeWrappedMetaVault(); + _upgradeCVault(address(vault)); + + _upgradeCVault(SonicConstantsLib.VAULT_C_WMETAS_wS_128); + + _upgradeStrategy(); + } + + /// @notice Ensure that we are able to deposit large amount of metaUSD without revert + function testSingleDepositWithdraw() public { + // ---------------------------------- Re-deposit "collateral type" => "protected type" to allow deposit/withdraw + _reDeposit(); + + // ---------------------------------- Deposit + uint amount = 7000e18; + _getMetaTokensOnBalance(address(this), amount, true, SonicConstantsLib.WRAPPED_METAVAULT_metaS); + + IERC20(SonicConstantsLib.WRAPPED_METAVAULT_metaS).approve(address(vault), amount); + + address[] memory assets = vault.assets(); + uint[] memory amountsMax = new uint[](1); + amountsMax[0] = amount; + + uint gas0 = gasleft(); + vault.depositAssets(assets, amountsMax, 0, address(this)); + vm.roll(block.number + 6); + // console.log("!!!!!!!!!!!!!!! gas used for deposit", gas0 - gasleft()); + assertLt(gas0 - gasleft(), 12e6, "Deposit should not use more than 12 mln gas"); + + // ---------------------------------- Withdraw + uint shares = vault.balanceOf(address(this)); + gas0 = gasleft(); + uint[] memory withdrawn = vault.withdrawAssets(assets, shares, new uint[](1)); + // console.log("!!!!!!!!!!!!!!! gas used for withdraw", gas0 - gasleft()); + assertLt(gas0 - gasleft(), 15e6, "Withdraw should not use more than 15 mln gas"); + + assertApproxEqAbs(amount, withdrawn[0], amount / 100 * 2, "Withdrawn amount does not match deposited amount"); + assertEq(vault.balanceOf(address(this)), 0, "Shares should be zero after withdrawal"); + + // ---------------------------------- Hardwork + { + gas0 = gasleft(); + address hardWorker = IPlatform(PLATFORM).hardWorker(); + + vm.prank(hardWorker); + IVault(address(vault)).doHardWork(); + + // console.log("!!!!!!!!!!!!!!!!!!! gas used for hardwork", gas0 - gasleft()); + assertLt(gas0 - gasleft(), 15.5e6, "Hardwork should not use more than 16 mln gas"); + } + + } + + function testEmergencyExit() public { + // ---------------------------------- Re-deposit "collateral type" => "protected type" to allow deposit/withdraw + _reDeposit(); + + // ---------------------------------- Emergency exit + { + uint gas0 = gasleft(); + + vm.prank(multisig); + strategy.emergencyStopInvesting(); + + // console.log("!!!!!!!!!!!!!!!!!!!! gas used for emergency exit", gas0 - gasleft()); + assertLt(gas0 - gasleft(), 15e6, "Emergency exit should not use more than 15 mln gas"); + } + } + + //region ------------------------------------ Re-deposit + /// @dev Re-deposit incorrectly deposited amounts: "collateral type" => "protected type" + function _reDeposit() internal { + // -------------------------------------- State before re-deposit + SiloALMFStrategy _strategy = SiloALMFStrategy(payable(address(strategy))); + uint baseAmount = _strategy.totalCollateralToRedeposit() / 2; + + State memory stateBefore = _getState(); + + // -------------------------------------- Withdraw, deposit + while (_strategy.totalCollateralToRedeposit() != 0) { + uint amount = Math.min(_strategy.totalCollateralToRedeposit(), baseAmount); + + // exit + uint gas0 = gasleft(); + vm.prank(multisig); + _strategy.reDeposit(REDEPOSIT_OP_KIND_WITHDRAW, amount); + // console.log("!!!!!!!!!gas used for re-deposit1", gas0 - gasleft()); + assertLt(gas0 - gasleft(), 15e6, "reDeposit should not use more than 15 mln gas 1"); + + // enter + gas0 = gasleft(); + vm.prank(multisig); + _strategy.reDeposit(REDEPOSIT_OP_KIND_DEPOSIT, 0); + // console.log("!!!!!!!!!gas used for re-deposit2", gas0 - gasleft()); + assertLt(gas0 - gasleft(), 12e6, "reDeposit should not use more than 12 mln gas 2"); + } + + State memory stateAfter = _getState(); + + // -------------------------------------- Operator compensates the losses + if (stateAfter.realTvl < stateBefore.realTvl) { + console.log("cover losses", stateBefore.realTvl - stateAfter.realTvl); + deal(SonicConstantsLib.WRAPPED_METAVAULT_metaS, address(multisig), stateBefore.realTvl - stateAfter.realTvl); + + vm.prank(multisig); + IERC20(SonicConstantsLib.WRAPPED_METAVAULT_metaS).transfer(address(_strategy), stateBefore.realTvl - stateAfter.realTvl); + + vm.prank(multisig); + _strategy.reDeposit(REDEPOSIT_OP_KIND_DEPOSIT, stateBefore.realTvl - stateAfter.realTvl); + } + + // -------------------------------------- Check results + State memory stateFinal = _getState(); + + assertApproxEqAbs(stateBefore.sharePrice, stateFinal.sharePrice, stateBefore.sharePrice / 10_000, "Share price should not change after re-deposit"); + assertApproxEqAbs(stateBefore.realTvl, stateFinal.realTvl, stateBefore.realTvl / 10_000, "Real TLV should not change after re-deposit"); + assertEq(stateBefore.total, stateFinal.total, "Total should not change after re-deposit"); + } + //endregion ------------------------------------ Re-deposit + + //region ------------------------------------ Helpers + function _upgradeStrategy() internal { + address strategyImplementation = address(new SiloALMFStrategy()); + vm.prank(multisig); + factory.setStrategyLogicConfig( + IFactory.StrategyLogicConfig({ + id: StrategyIdLib.SILO_ALMF_FARM, + implementation: strategyImplementation, + deployAllowed: true, + upgradeAllowed: true, + farming: true, + tokenId: 0 + }), + address(this) + ); + + factory.upgradeStrategyProxy(address(strategy)); + } + + function _upgradePlatform(address priceReader_) internal { + // we need to skip 1 day to update the swapper + // but we cannot simply skip 1 day, because the silo oracle will start to revert with InvalidPrice + // vm.warp(block.timestamp - 86400); + rewind(86400); + + IPlatform platform = IPlatform(IControllable(priceReader_).platform()); + + address[] memory proxies = new address[](1); + address[] memory implementations = new address[](1); + + //proxies[0] = address(priceReader_); + proxies[0] = platform.ammAdapter(keccak256(bytes(AmmAdapterIdLib.META_VAULT))).proxy; + //proxies[0] = platform.swapper(); + + //implementations[0] = address(new PriceReader()); + implementations[0] = address(new MetaVaultAdapter()); + //implementations[0] = address(new Swapper()); + + // vm.startPrank(multisig); + // platform.cancelUpgrade(); + + vm.startPrank(multisig); + platform.announcePlatformUpgrade("2025.08.0-alpha", proxies, implementations); + + skip(1 days); + platform.upgrade(); + vm.stopPrank(); + } + + function _upgradeMetaVault(address metaVault_) internal { + IMetaVaultFactory metaVaultFactory = IMetaVaultFactory(IPlatform(PLATFORM).metaVaultFactory()); + // Upgrade MetaVault to the new implementation + address vaultImplementation = address(new MetaVault()); + vm.prank(multisig); + metaVaultFactory.setMetaVaultImplementation(vaultImplementation); + address[] memory metaProxies = new address[](1); + metaProxies[0] = address(metaVault_); + vm.prank(multisig); + metaVaultFactory.upgradeMetaProxies(metaProxies); + } + + function _upgradeWrappedMetaVault() internal { + IMetaVaultFactory metaVaultFactory = IMetaVaultFactory(IPlatform(PLATFORM).metaVaultFactory()); + + address newWrapperImplementation = address(new WrappedMetaVault()); + vm.startPrank(multisig); + metaVaultFactory.setWrappedMetaVaultImplementation(newWrapperImplementation); + address[] memory proxies = new address[](2); + proxies[0] = SonicConstantsLib.WRAPPED_METAVAULT_metaS; + proxies[1] = SonicConstantsLib.WRAPPED_METAVAULT_metaUSD; + metaVaultFactory.upgradeMetaProxies(proxies); + vm.stopPrank(); + } + + function _upgradeCVault(address vault_) internal { + // deploy new impl and upgrade + address vaultImplementation = address(new CVault()); + vm.prank(multisig); + factory.setVaultConfig( + IFactory.VaultConfig({ + vaultType: VaultTypeLib.COMPOUNDING, + implementation: vaultImplementation, + deployAllowed: true, + upgradeAllowed: true, + buildingPrice: 1e10 + }) + ); + factory.upgradeVaultProxy(vault_); + } + + function _dealAndApprove( + address user, + address metavault, + address[] memory assets, + uint[] memory amounts + ) internal { + for (uint j; j < assets.length; ++j) { + deal(assets[j], user, amounts[j]); + vm.prank(user); + IERC20(assets[j]).approve(metavault, amounts[j]); + } + } + + function _getMetaTokensOnBalance( + address user, + uint amountMetaVaultTokens, + bool wrap, + address wrappedMetaVault_ + ) internal { + IMetaVault metaVault = IMetaVault(IWrappedMetaVault(wrappedMetaVault_).metaVault()); + address asset = address(metaVault) == SonicConstantsLib.METAVAULT_metaUSD + ? SonicConstantsLib.TOKEN_USDC + : SonicConstantsLib.TOKEN_wS; + + // we don't know exact amount of USDC required to receive exact amountMetaVaultTokens + // so we deposit a bit large amount of USDC + address[] memory _assets = metaVault.assetsForDeposit(); + uint[] memory amountsMax = new uint[](1); + amountsMax[0] = address(metaVault) == SonicConstantsLib.METAVAULT_metaUSD + ? 2 * amountMetaVaultTokens / 1e12 + : 2 * amountMetaVaultTokens; + + deal(asset, user, amountsMax[0]); + + vm.startPrank(user); + IERC20(asset).approve(address(metaVault), IERC20(asset).balanceOf(user)); + metaVault.depositAssets(_assets, amountsMax, 0, user); + vm.roll(block.number + 6); + vm.stopPrank(); + + if (wrap) { + vm.startPrank(user); + IWrappedMetaVault wrappedMetaVault = IWrappedMetaVault(wrappedMetaVault_); + metaVault.approve(address(wrappedMetaVault), metaVault.balanceOf(user)); + wrappedMetaVault.deposit(metaVault.balanceOf(user), user, 0); + vm.stopPrank(); + + vm.roll(block.number + 6); + } + } + + function _getState() internal view returns (State memory state) { + ILeverageLendingStrategy _strategy = ILeverageLendingStrategy(address(strategy)); + + (state.sharePrice,) = _strategy.realSharePrice(); + + (state.ltv, state.maxLtv, state.leverage, state.collateralAmount, state.debtAmount, state.targetLeveragePercent) + = _strategy.health(); + + state.total = strategy.total(); + state.maxLeverage = 100_00 * 1e18 / (1e18 - state.maxLtv); + state.targetLeverage = state.maxLeverage * state.targetLeveragePercent / 100_00; + state.balanceAsset = IERC20(IStrategy(address(_strategy)).assets()[0]).balanceOf(address(_strategy)); + (state.realTvl,) = _strategy.realTvl(); + state.vaultBalance = IVault(IStrategy(address(_strategy)).vault()).balanceOf(address(this)); + + // console.log("targetLeverage, leverage, total", state.targetLeverage, state.leverage, state.total); + + console.log("============== state"); + console.log("ltv", state.ltv); + console.log("maxLtv", state.maxLtv); + console.log("targetLeverage", state.targetLeverage); + console.log("leverage", state.leverage); + console.log("total", state.total); + console.log("collateralAmount", state.collateralAmount); + console.log("debtAmount", state.debtAmount); + console.log("targetLeveragePercent", state.targetLeveragePercent); + console.log("maxLeverage", state.maxLeverage); + console.log("realTvl", state.realTvl); + console.log("realSharePrice", state.sharePrice); + return state; + } + + function _getFlashLoanVault() internal view returns (address _vault, uint _kind) { + ILeverageLendingStrategy _strategy = ILeverageLendingStrategy(address(strategy)); + (uint[] memory params, address[] memory addresses) = _strategy.getUniversalParams(); + return (addresses[0], params[10]); + } + + function _setFlashLoanVault( + ILeverageLendingStrategy strategy_, + address vault_, + ILeverageLendingStrategy.FlashLoanKind kind + ) internal { + (uint[] memory params, address[] memory addresses) = strategy_.getUniversalParams(); + params[10] = uint(kind); + addresses[0] = vault_; + + vm.prank(multisig); + strategy_.setUniversalParams(params, addresses); + } + + function _withdrawAll(address user) internal { + address[] memory assets = vault.assets(); + uint shares = vault.balanceOf(user); + + vm.prank(user); + vault.withdrawAssets(assets, shares / 2, new uint[](1)); + vm.roll(block.number + 6); + + shares = vault.balanceOf(user); + vm.prank(user); + vault.withdrawAssets(assets, shares, new uint[](1)); + + vm.roll(block.number + 6); + } + //endregion ------------------------------------ Helpers +} From c4f710612339ecb43a98733ba5f93e51e9f4cba7 Mon Sep 17 00:00:00 2001 From: dvpublic Date: Tue, 5 Aug 2025 15:06:33 +0700 Subject: [PATCH 6/7] CompoundV2Strategy: maxWithdrawAssets allows to withdraw 99.5% of the available liquidity only --- src/strategies/CompoundV2Strategy.sol | 9 ++++-- src/strategies/SiloALMFStrategy.sol | 1 + .../SiALMF.Upgrade.scUsd.Sonic.t.sol | 3 +- .../SiALMF.Upgrade.usdc.Sonic.t.sol | 3 +- test/strategies/SiALMF.Upgrade.wS.Sonic.t.sol | 31 ++++++++++++++----- 5 files changed, 36 insertions(+), 11 deletions(-) diff --git a/src/strategies/CompoundV2Strategy.sol b/src/strategies/CompoundV2Strategy.sol index bab4b01d..7e021820 100644 --- a/src/strategies/CompoundV2Strategy.sol +++ b/src/strategies/CompoundV2Strategy.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; +import {console} from "forge-std/console.sol"; import {CommonLib} from "../core/libs/CommonLib.sol"; import {IComptroller} from "../integrations/compoundv2/IComptroller.sol"; import {IControllable} from "../interfaces/IControllable.sol"; @@ -21,6 +22,8 @@ import {VaultTypeLib} from "../core/libs/VaultTypeLib.sol"; /// @title Earns APR by lending assets on Compound V2 protocol. /// @author dvpublic (https://github.com/dvpublic) +/// Change log: +/// - 1.0.1: maxWithdrawAssets allows to withdraw 99.5% of the available liquidity only contract CompoundV2Strategy is StrategyBase { using SafeERC20 for IERC20; @@ -29,7 +32,7 @@ contract CompoundV2Strategy is StrategyBase { /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @inheritdoc IControllable - string public constant VERSION = "1.0.0"; + string public constant VERSION = "1.0.1"; // keccak256(abi.encode(uint256(keccak256("erc7201:stability.CompoundV2Strategy")) - 1)) & ~bytes32(uint256(0xff)); bytes32 private constant COMPOUND_V2_STRATEGY_STORAGE_LOCATION = @@ -183,6 +186,7 @@ contract CompoundV2Strategy is StrategyBase { /// @inheritdoc IStrategy function maxWithdrawAssets() public view override returns (uint[] memory amounts) { + console.log("CompoundV2Strategy.maxWithdrawAssets"); address _underlying = _getStrategyBaseStorage()._underlying; address asset = IVToken(_underlying).underlying(); @@ -193,9 +197,10 @@ contract CompoundV2Strategy is StrategyBase { uint cTokenBalance = IVToken(_underlying).balanceOf(address(this)); // 8 decimals uint exchangeRate = IVToken(_underlying).exchangeRateStored(); // underlying decimals * 1e18 / 1e8 uint underlyingBalance = _tokensToAmount(cTokenBalance, exchangeRate); + uint totalReserves = IVToken(_underlying).totalReserves(); amounts = new uint[](1); - amounts[0] = Math.min(underlyingBalance, availableLiquidity); + amounts[0] = Math.min(underlyingBalance, (availableLiquidity - totalReserves) * 995/1000); // 99.5% of the available liquidity } /// @notice IStrategy diff --git a/src/strategies/SiloALMFStrategy.sol b/src/strategies/SiloALMFStrategy.sol index 3d31e337..9904fb9f 100644 --- a/src/strategies/SiloALMFStrategy.sol +++ b/src/strategies/SiloALMFStrategy.sol @@ -401,6 +401,7 @@ contract SiloALMFStrategy is //region ----------------------------------- Additional temporally functions function isPaused() public view returns (bool) { + // todo probably we need flag Paused in the storage return totalCollateralToRedeposit() != 0; } diff --git a/test/strategies/SiALMF.Upgrade.scUsd.Sonic.t.sol b/test/strategies/SiALMF.Upgrade.scUsd.Sonic.t.sol index 05d9d930..ba916073 100755 --- a/test/strategies/SiALMF.Upgrade.scUsd.Sonic.t.sol +++ b/test/strategies/SiALMF.Upgrade.scUsd.Sonic.t.sol @@ -31,6 +31,7 @@ contract SiALMFUpgradeScUsdTest is Test { uint public constant FORK_BLOCK = 41583791; // Aug-04-2025 09:35:18 AM +UTC uint public constant REDEPOSIT_OP_KIND_WITHDRAW = 0; uint public constant REDEPOSIT_OP_KIND_DEPOSIT = 1; + uint public constant COUNT_ITERATIONS = 10; address public constant PLATFORM = 0x4Aca671A420eEB58ecafE83700686a2AD06b20D8; @@ -148,7 +149,7 @@ contract SiALMFUpgradeScUsdTest is Test { function _reDeposit() internal { // -------------------------------------- State before re-deposit SiloALMFStrategy _strategy = SiloALMFStrategy(payable(address(strategy))); - uint baseAmount = _strategy.totalCollateralToRedeposit() / 10; + uint baseAmount = _strategy.totalCollateralToRedeposit() / COUNT_ITERATIONS; State memory stateBefore = _getState(); diff --git a/test/strategies/SiALMF.Upgrade.usdc.Sonic.t.sol b/test/strategies/SiALMF.Upgrade.usdc.Sonic.t.sol index e3816569..8b87e8b5 100755 --- a/test/strategies/SiALMF.Upgrade.usdc.Sonic.t.sol +++ b/test/strategies/SiALMF.Upgrade.usdc.Sonic.t.sol @@ -30,6 +30,7 @@ contract SiALMFUpgradeUsdcTest is Test { uint public constant FORK_BLOCK = 41583791; // Aug-04-2025 09:35:18 AM +UTC uint public constant REDEPOSIT_OP_KIND_WITHDRAW = 0; uint public constant REDEPOSIT_OP_KIND_DEPOSIT = 1; + uint public constant COUNT_ITERATIONS = 2; address public constant PLATFORM = 0x4Aca671A420eEB58ecafE83700686a2AD06b20D8; @@ -145,7 +146,7 @@ contract SiALMFUpgradeUsdcTest is Test { function _reDeposit() internal { // -------------------------------------- State before re-deposit SiloALMFStrategy _strategy = SiloALMFStrategy(payable(address(strategy))); - uint baseAmount = _strategy.totalCollateralToRedeposit() / 2; + uint baseAmount = _strategy.totalCollateralToRedeposit() / COUNT_ITERATIONS; State memory stateBefore = _getState(); diff --git a/test/strategies/SiALMF.Upgrade.wS.Sonic.t.sol b/test/strategies/SiALMF.Upgrade.wS.Sonic.t.sol index b11bbec6..1d5b9061 100755 --- a/test/strategies/SiALMF.Upgrade.wS.Sonic.t.sol +++ b/test/strategies/SiALMF.Upgrade.wS.Sonic.t.sol @@ -24,14 +24,17 @@ import {StrategyIdLib} from "../../src/strategies/libs/StrategyIdLib.sol"; import {VaultTypeLib} from "../../src/core/libs/VaultTypeLib.sol"; import {console, Test} from "forge-std/Test.sol"; import {MetaVaultAdapter} from "../../src/adapters/MetaVaultAdapter.sol"; +import {CompoundV2Strategy} from "../../src/strategies/CompoundV2Strategy.sol"; /// @notice Upgrade SiloALMFStrategy to 1.2.0 (use non-borrowable collateral) contract SiALMFUpgradeWsTest is Test { uint public constant FORK_BLOCK = 41583791; // Aug-04-2025 09:35:18 AM +UTC uint public constant REDEPOSIT_OP_KIND_WITHDRAW = 0; uint public constant REDEPOSIT_OP_KIND_DEPOSIT = 1; + uint public constant COUNT_ITERATIONS = 2; address public constant PLATFORM = 0x4Aca671A420eEB58ecafE83700686a2AD06b20D8; + address public constant STRATEGY_COMPOUND_V2 = 0x396D38E56ce682f056AD4c653A145Ab9d3e78fd0; address internal multisig; IFactory internal factory; @@ -66,12 +69,6 @@ contract SiALMFUpgradeWsTest is Test { priceReader = IPriceReader(IPlatform(IControllable(SonicConstantsLib.WRAPPED_METAVAULT_metaS).platform()).priceReader()); -// deal( -// SonicConstantsLib.TOKEN_scUSD, -// SonicConstantsLib.BEETS_VAULT, -// 1_000_000e6 // 100k USDC -// ); - // _upgradePlatform(address(priceReader)); _upgradeMetaVault(SonicConstantsLib.WRAPPED_METAVAULT_metaS); @@ -81,6 +78,7 @@ contract SiALMFUpgradeWsTest is Test { _upgradeCVault(SonicConstantsLib.VAULT_C_WMETAS_wS_128); _upgradeStrategy(); + _upgradeCompoundV2Strategy(STRATEGY_COMPOUND_V2); // we need to fix maxWithdraw } /// @notice Ensure that we are able to deposit large amount of metaUSD without revert @@ -149,13 +147,14 @@ contract SiALMFUpgradeWsTest is Test { function _reDeposit() internal { // -------------------------------------- State before re-deposit SiloALMFStrategy _strategy = SiloALMFStrategy(payable(address(strategy))); - uint baseAmount = _strategy.totalCollateralToRedeposit() / 2; + uint baseAmount = _strategy.totalCollateralToRedeposit() / COUNT_ITERATIONS; State memory stateBefore = _getState(); // -------------------------------------- Withdraw, deposit while (_strategy.totalCollateralToRedeposit() != 0) { uint amount = Math.min(_strategy.totalCollateralToRedeposit(), baseAmount); + console.log("amount to re-deposit", amount); // exit uint gas0 = gasleft(); @@ -214,6 +213,24 @@ contract SiALMFUpgradeWsTest is Test { factory.upgradeStrategyProxy(address(strategy)); } + function _upgradeCompoundV2Strategy(address strategy_) internal { + address strategyImplementation = address(new CompoundV2Strategy()); + vm.prank(multisig); + factory.setStrategyLogicConfig( + IFactory.StrategyLogicConfig({ + id: StrategyIdLib.COMPOUND_V2, + implementation: strategyImplementation, + deployAllowed: true, + upgradeAllowed: true, + farming: true, + tokenId: 0 + }), + address(this) + ); + + factory.upgradeStrategyProxy(strategy_); + } + function _upgradePlatform(address priceReader_) internal { // we need to skip 1 day to update the swapper // but we cannot simply skip 1 day, because the silo oracle will start to revert with InvalidPrice From 16f32a1c2b4167ee22f70c827c2a52fb2b922149 Mon Sep 17 00:00:00 2001 From: dvpublic Date: Tue, 5 Aug 2025 16:00:05 +0700 Subject: [PATCH 7/7] fix formatting --- src/strategies/CompoundV2Strategy.sol | 2 +- src/strategies/libs/SiloALMFLib.sol | 11 ++-- src/strategies/libs/SiloALMFLib2.sol | 7 ++- .../SiALMF.Upgrade.scUsd.Sonic.t.sol | 54 ++++++++++++------- .../SiALMF.Upgrade.usdc.Sonic.t.sol | 53 +++++++++++------- test/strategies/SiALMF.Upgrade.wS.Sonic.t.sol | 20 +++++-- 6 files changed, 93 insertions(+), 54 deletions(-) diff --git a/src/strategies/CompoundV2Strategy.sol b/src/strategies/CompoundV2Strategy.sol index 7e021820..224e846a 100644 --- a/src/strategies/CompoundV2Strategy.sol +++ b/src/strategies/CompoundV2Strategy.sol @@ -200,7 +200,7 @@ contract CompoundV2Strategy is StrategyBase { uint totalReserves = IVToken(_underlying).totalReserves(); amounts = new uint[](1); - amounts[0] = Math.min(underlyingBalance, (availableLiquidity - totalReserves) * 995/1000); // 99.5% of the available liquidity + amounts[0] = Math.min(underlyingBalance, (availableLiquidity - totalReserves) * 995 / 1000); // 99.5% of the available liquidity } /// @notice IStrategy diff --git a/src/strategies/libs/SiloALMFLib.sol b/src/strategies/libs/SiloALMFLib.sol index a2509132..b2cbd2c7 100644 --- a/src/strategies/libs/SiloALMFLib.sol +++ b/src/strategies/libs/SiloALMFLib.sol @@ -395,10 +395,8 @@ library SiloALMFLib { function totalCollateral(address lendingVault) public view returns (uint) { (address protectedShareToken,,) = ISiloConfig(ISilo(lendingVault).config()).getShareTokens(lendingVault); - return ISilo(lendingVault).convertToAssets( - StrategyLib.balance(protectedShareToken), - ISilo.AssetType.Protected - ) + totalCollateralToRedeposit(lendingVault); + return ISilo(lendingVault).convertToAssets(StrategyLib.balance(protectedShareToken), ISilo.AssetType.Protected) + + totalCollateralToRedeposit(lendingVault); } function totalDebt(address borrowingVault) public view returns (uint) { @@ -1038,7 +1036,8 @@ library SiloALMFLib { // withdraw from the lending vault require(totalCollateralToRedeposit(v.lendingVault) != 0, Paused()); - uint totalCollateralToWithdraw = ISilo(v.lendingVault).convertToAssets(StrategyLib.balance(v.lendingVault), ISilo.AssetType.Collateral); + uint totalCollateralToWithdraw = + ISilo(v.lendingVault).convertToAssets(StrategyLib.balance(v.lendingVault), ISilo.AssetType.Collateral); uint value = Math.mulDiv( Math.min(totalCollateralToWithdraw, maxCollateralToWithdraw_), INTERNAL_PRECISION * INTERNAL_PRECISION, @@ -1057,6 +1056,4 @@ library SiloALMFLib { } //endregion ----------------------------------- Additional temporally functions - - } diff --git a/src/strategies/libs/SiloALMFLib2.sol b/src/strategies/libs/SiloALMFLib2.sol index d573c5bc..1d4b1218 100644 --- a/src/strategies/libs/SiloALMFLib2.sol +++ b/src/strategies/libs/SiloALMFLib2.sol @@ -74,9 +74,9 @@ library SiloALMFLib2 { } function getSpecificName(ILeverageLendingStrategy.LeverageLendingBaseStorage storage $) - external - view - returns (string memory, bool) + external + view + returns (string memory, bool) { address lendingVault = $.lendingVault; uint siloId = ISiloConfig(ISilo(lendingVault).config()).SILO_ID(); @@ -141,5 +141,4 @@ library SiloALMFLib2 { (address token, uint amount, bool isToken0) = abi.decode(userData, (address, uint, bool)); SiloALMFLib._receiveFlashLoan(platform, $, token, amount, isToken0 ? fee0 : fee1); } - } diff --git a/test/strategies/SiALMF.Upgrade.scUsd.Sonic.t.sol b/test/strategies/SiALMF.Upgrade.scUsd.Sonic.t.sol index ba916073..ccc4c00e 100755 --- a/test/strategies/SiALMF.Upgrade.scUsd.Sonic.t.sol +++ b/test/strategies/SiALMF.Upgrade.scUsd.Sonic.t.sol @@ -68,8 +68,11 @@ contract SiALMFUpgradeScUsdTest is Test { priceReader = IPriceReader(IPlatform(IControllable(SonicConstantsLib.WRAPPED_METAVAULT_metaUSD).platform()).priceReader()); - _setFlashLoanVault(ILeverageLendingStrategy(address(strategy)), SonicConstantsLib.BEETS_VAULT_V3, ILeverageLendingStrategy.FlashLoanKind.BalancerV3_1); - + _setFlashLoanVault( + ILeverageLendingStrategy(address(strategy)), + SonicConstantsLib.BEETS_VAULT_V3, + ILeverageLendingStrategy.FlashLoanKind.BalancerV3_1 + ); // _upgradePlatform(address(priceReader)); _upgradeMetaVault(SonicConstantsLib.METAVAULT_metaUSD); @@ -124,7 +127,6 @@ contract SiALMFUpgradeScUsdTest is Test { // console.log("!!!!!!!!!!!!!!!!!!! gas used for hardwork", gas0 - gasleft()); assertLt(gas0 - gasleft(), 15.5e6, "Hardwork should not use more than 16 mln gas"); } - } // todo: there is not enough scUSD in flash loan vault @@ -177,10 +179,14 @@ contract SiALMFUpgradeScUsdTest is Test { // -------------------------------------- Operator compensates the losses if (stateAfter.realTvl < stateBefore.realTvl) { console.log("cover losses", stateBefore.realTvl - stateAfter.realTvl); - deal(SonicConstantsLib.WRAPPED_METAVAULT_metaUSD, address(multisig), stateBefore.realTvl - stateAfter.realTvl); + deal( + SonicConstantsLib.WRAPPED_METAVAULT_metaUSD, address(multisig), stateBefore.realTvl - stateAfter.realTvl + ); vm.prank(multisig); - IERC20(SonicConstantsLib.WRAPPED_METAVAULT_metaUSD).transfer(address(_strategy), stateBefore.realTvl - stateAfter.realTvl); + IERC20(SonicConstantsLib.WRAPPED_METAVAULT_metaUSD).transfer( + address(_strategy), stateBefore.realTvl - stateAfter.realTvl + ); vm.prank(multisig); _strategy.reDeposit(REDEPOSIT_OP_KIND_DEPOSIT, stateBefore.realTvl - stateAfter.realTvl); @@ -189,8 +195,18 @@ contract SiALMFUpgradeScUsdTest is Test { // -------------------------------------- Check results State memory stateFinal = _getState(); - assertApproxEqAbs(stateBefore.sharePrice, stateFinal.sharePrice, stateBefore.sharePrice / 10_000, "Share price should not change after re-deposit"); - assertApproxEqAbs(stateBefore.realTvl, stateFinal.realTvl, stateBefore.realTvl / 10_000, "Real TLV should not change after re-deposit"); + assertApproxEqAbs( + stateBefore.sharePrice, + stateFinal.sharePrice, + stateBefore.sharePrice / 10_000, + "Share price should not change after re-deposit" + ); + assertApproxEqAbs( + stateBefore.realTvl, + stateFinal.realTvl, + stateBefore.realTvl / 10_000, + "Real TLV should not change after re-deposit" + ); assertEq(stateBefore.total, stateFinal.total, "Total should not change after re-deposit"); } //endregion ------------------------------------ Re-deposit @@ -353,18 +369,18 @@ contract SiALMFUpgradeScUsdTest is Test { // console.log("targetLeverage, leverage, total", state.targetLeverage, state.leverage, state.total); -// console.log("============== state"); -// console.log("ltv", state.ltv); -// console.log("maxLtv", state.maxLtv); -// console.log("targetLeverage", state.targetLeverage); -// console.log("leverage", state.leverage); -// console.log("total", state.total); -// console.log("collateralAmount", state.collateralAmount); -// console.log("debtAmount", state.debtAmount); -// console.log("targetLeveragePercent", state.targetLeveragePercent); -// console.log("maxLeverage", state.maxLeverage); -// console.log("realTvl", state.realTvl); -// console.log("realSharePrice", state.sharePrice); + // console.log("============== state"); + // console.log("ltv", state.ltv); + // console.log("maxLtv", state.maxLtv); + // console.log("targetLeverage", state.targetLeverage); + // console.log("leverage", state.leverage); + // console.log("total", state.total); + // console.log("collateralAmount", state.collateralAmount); + // console.log("debtAmount", state.debtAmount); + // console.log("targetLeveragePercent", state.targetLeveragePercent); + // console.log("maxLeverage", state.maxLeverage); + // console.log("realTvl", state.realTvl); + // console.log("realSharePrice", state.sharePrice); return state; } diff --git a/test/strategies/SiALMF.Upgrade.usdc.Sonic.t.sol b/test/strategies/SiALMF.Upgrade.usdc.Sonic.t.sol index 8b87e8b5..f15839bc 100755 --- a/test/strategies/SiALMF.Upgrade.usdc.Sonic.t.sol +++ b/test/strategies/SiALMF.Upgrade.usdc.Sonic.t.sol @@ -67,7 +67,11 @@ contract SiALMFUpgradeUsdcTest is Test { priceReader = IPriceReader(IPlatform(IControllable(SonicConstantsLib.WRAPPED_METAVAULT_metaUSD).platform()).priceReader()); - _setFlashLoanVault(ILeverageLendingStrategy(address(strategy)), SonicConstantsLib.BEETS_VAULT_V3, ILeverageLendingStrategy.FlashLoanKind.BalancerV3_1); + _setFlashLoanVault( + ILeverageLendingStrategy(address(strategy)), + SonicConstantsLib.BEETS_VAULT_V3, + ILeverageLendingStrategy.FlashLoanKind.BalancerV3_1 + ); // _upgradePlatform(address(priceReader)); _upgradeMetaVault(SonicConstantsLib.METAVAULT_metaUSD); @@ -121,7 +125,6 @@ contract SiALMFUpgradeUsdcTest is Test { // console.log("!!!!!!!!!!!!!!!!!!! gas used for hardwork", gas0 - gasleft()); assertLt(gas0 - gasleft(), 15.5e6, "Hardwork should not use more than 16 mln gas"); } - } // todo: there is not enough usdc in flash loan vault @@ -174,10 +177,14 @@ contract SiALMFUpgradeUsdcTest is Test { // -------------------------------------- Operator compensates the losses if (stateAfter.realTvl < stateBefore.realTvl) { console.log("cover losses", stateBefore.realTvl - stateAfter.realTvl); - deal(SonicConstantsLib.WRAPPED_METAVAULT_metaUSD, address(multisig), stateBefore.realTvl - stateAfter.realTvl); + deal( + SonicConstantsLib.WRAPPED_METAVAULT_metaUSD, address(multisig), stateBefore.realTvl - stateAfter.realTvl + ); vm.prank(multisig); - IERC20(SonicConstantsLib.WRAPPED_METAVAULT_metaUSD).transfer(address(_strategy), stateBefore.realTvl - stateAfter.realTvl); + IERC20(SonicConstantsLib.WRAPPED_METAVAULT_metaUSD).transfer( + address(_strategy), stateBefore.realTvl - stateAfter.realTvl + ); vm.prank(multisig); _strategy.reDeposit(REDEPOSIT_OP_KIND_DEPOSIT, stateBefore.realTvl - stateAfter.realTvl); @@ -186,8 +193,18 @@ contract SiALMFUpgradeUsdcTest is Test { // -------------------------------------- Check results State memory stateFinal = _getState(); - assertApproxEqAbs(stateBefore.sharePrice, stateFinal.sharePrice, stateBefore.sharePrice / 10_000, "Share price should not change after re-deposit"); - assertApproxEqAbs(stateBefore.realTvl, stateFinal.realTvl, stateBefore.realTvl / 10_000, "Real TLV should not change after re-deposit"); + assertApproxEqAbs( + stateBefore.sharePrice, + stateFinal.sharePrice, + stateBefore.sharePrice / 10_000, + "Share price should not change after re-deposit" + ); + assertApproxEqAbs( + stateBefore.realTvl, + stateFinal.realTvl, + stateBefore.realTvl / 10_000, + "Real TLV should not change after re-deposit" + ); assertEq(stateBefore.total, stateFinal.total, "Total should not change after re-deposit"); } //endregion ------------------------------------ Re-deposit @@ -350,18 +367,18 @@ contract SiALMFUpgradeUsdcTest is Test { // console.log("targetLeverage, leverage, total", state.targetLeverage, state.leverage, state.total); -// console.log("============== state"); -// console.log("ltv", state.ltv); -// console.log("maxLtv", state.maxLtv); -// console.log("targetLeverage", state.targetLeverage); -// console.log("leverage", state.leverage); -// console.log("total", state.total); -// console.log("collateralAmount", state.collateralAmount); -// console.log("debtAmount", state.debtAmount); -// console.log("targetLeveragePercent", state.targetLeveragePercent); -// console.log("maxLeverage", state.maxLeverage); -// console.log("realTvl", state.realTvl); -// console.log("realSharePrice", state.sharePrice); + // console.log("============== state"); + // console.log("ltv", state.ltv); + // console.log("maxLtv", state.maxLtv); + // console.log("targetLeverage", state.targetLeverage); + // console.log("leverage", state.leverage); + // console.log("total", state.total); + // console.log("collateralAmount", state.collateralAmount); + // console.log("debtAmount", state.debtAmount); + // console.log("targetLeveragePercent", state.targetLeveragePercent); + // console.log("maxLeverage", state.maxLeverage); + // console.log("realTvl", state.realTvl); + // console.log("realSharePrice", state.sharePrice); return state; } diff --git a/test/strategies/SiALMF.Upgrade.wS.Sonic.t.sol b/test/strategies/SiALMF.Upgrade.wS.Sonic.t.sol index 1d5b9061..69fc7819 100755 --- a/test/strategies/SiALMF.Upgrade.wS.Sonic.t.sol +++ b/test/strategies/SiALMF.Upgrade.wS.Sonic.t.sol @@ -69,7 +69,6 @@ contract SiALMFUpgradeWsTest is Test { priceReader = IPriceReader(IPlatform(IControllable(SonicConstantsLib.WRAPPED_METAVAULT_metaS).platform()).priceReader()); - // _upgradePlatform(address(priceReader)); _upgradeMetaVault(SonicConstantsLib.WRAPPED_METAVAULT_metaS); // _upgradeWrappedMetaVault(); @@ -123,7 +122,6 @@ contract SiALMFUpgradeWsTest is Test { // console.log("!!!!!!!!!!!!!!!!!!! gas used for hardwork", gas0 - gasleft()); assertLt(gas0 - gasleft(), 15.5e6, "Hardwork should not use more than 16 mln gas"); } - } function testEmergencyExit() public { @@ -179,7 +177,9 @@ contract SiALMFUpgradeWsTest is Test { deal(SonicConstantsLib.WRAPPED_METAVAULT_metaS, address(multisig), stateBefore.realTvl - stateAfter.realTvl); vm.prank(multisig); - IERC20(SonicConstantsLib.WRAPPED_METAVAULT_metaS).transfer(address(_strategy), stateBefore.realTvl - stateAfter.realTvl); + IERC20(SonicConstantsLib.WRAPPED_METAVAULT_metaS).transfer( + address(_strategy), stateBefore.realTvl - stateAfter.realTvl + ); vm.prank(multisig); _strategy.reDeposit(REDEPOSIT_OP_KIND_DEPOSIT, stateBefore.realTvl - stateAfter.realTvl); @@ -188,8 +188,18 @@ contract SiALMFUpgradeWsTest is Test { // -------------------------------------- Check results State memory stateFinal = _getState(); - assertApproxEqAbs(stateBefore.sharePrice, stateFinal.sharePrice, stateBefore.sharePrice / 10_000, "Share price should not change after re-deposit"); - assertApproxEqAbs(stateBefore.realTvl, stateFinal.realTvl, stateBefore.realTvl / 10_000, "Real TLV should not change after re-deposit"); + assertApproxEqAbs( + stateBefore.sharePrice, + stateFinal.sharePrice, + stateBefore.sharePrice / 10_000, + "Share price should not change after re-deposit" + ); + assertApproxEqAbs( + stateBefore.realTvl, + stateFinal.realTvl, + stateBefore.realTvl / 10_000, + "Real TLV should not change after re-deposit" + ); assertEq(stateBefore.total, stateFinal.total, "Total should not change after re-deposit"); } //endregion ------------------------------------ Re-deposit