Skip to content

Commit 9f1a111

Browse files
authored
Merge pull request #278 from stabilitydao/276-sial-support-swapx-flashloan
276 sial support swapx flashloan
2 parents 180e7b3 + be22df4 commit 9f1a111

File tree

8 files changed

+186
-163
lines changed

8 files changed

+186
-163
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
pragma solidity ^0.8.23;
3+
4+
/// @title Callback for IAlgebraPoolActions#flash
5+
/// @notice Any contract that calls IAlgebraPoolActions#flash must implement this interface
6+
/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license:
7+
/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces
8+
interface IAlgebraFlashCallback {
9+
/// @notice Called to `msg.sender` after transferring to the recipient from IAlgebraPool#flash.
10+
/// @dev In the implementation you must repay the pool the tokens sent by flash plus the computed fee amounts.
11+
/// The caller of this method _must_ be checked to be a AlgebraPool deployed by the canonical AlgebraFactory.
12+
/// @param fee0 The fee amount in token0 due to the pool by the end of the flash
13+
/// @param fee1 The fee amount in token1 due to the pool by the end of the flash
14+
/// @param data Any data passed through by the caller via the IAlgebraPoolActions#flash call
15+
function algebraFlashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external;
16+
}

src/interfaces/IControllable.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ interface IControllable {
2525
error IncorrectInitParams();
2626
error InsufficientBalance();
2727
error IncorrectLtv(uint ltv);
28+
error TooLowValue(uint value);
2829
//endregion -- Custom Errors -----
2930

3031
event ContractInitialized(address platform, uint ts, uint block);

src/interfaces/ILeverageLendingStrategy.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ interface ILeverageLendingStrategy {
9393
/// @notice Balancer V2
9494
Default_0,
9595
BalancerV3_1,
96-
UniswapV3_2
96+
UniswapV3_2,
97+
AlgebraV4_3
9798
}
9899

99100
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/

src/strategies/SiloAdvancedLeverageStrategy.sol

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {ISilo} from "../integrations/silo/ISilo.sol";
1818
import {IStrategy} from "../interfaces/IStrategy.sol";
1919
import {IVaultMainV3} from "../integrations/balancerv3/IVaultMainV3.sol";
2020
import {IUniswapV3FlashCallback} from "../integrations/uniswapv3/IUniswapV3FlashCallback.sol";
21+
import {IAlgebraFlashCallback} from "../integrations/algebrav4/callback/IAlgebraFlashCallback.sol";
2122
import {IBalancerV3FlashCallback} from "../integrations/balancerv3/IBalancerV3FlashCallback.sol";
2223
import {LeverageLendingBase} from "./base/LeverageLendingBase.sol";
2324
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
@@ -30,6 +31,12 @@ import {XStaking} from "../tokenomics/XStaking.sol";
3031

3132
/// @title Silo V2 advanced leverage strategy
3233
/// Changelog:
34+
/// 2.1.0:
35+
/// + support of algebra-v4 flash loan #276
36+
/// * reduce amount of swap in withdraw
37+
/// * use withdrawParam0 in deposit to fit result leverage
38+
/// * check ltv on exit of deposit
39+
/// * use LeverageLendingBase 1.2.1 #277
3340
/// 2.0.0:
3441
/// * bug: decreasing LTV on exits #254
3542
/// * bug: deposit logic must use oracle price #247
@@ -45,7 +52,8 @@ contract SiloAdvancedLeverageStrategy is
4552
LeverageLendingBase,
4653
IFlashLoanRecipient,
4754
IUniswapV3FlashCallback,
48-
IBalancerV3FlashCallback
55+
IBalancerV3FlashCallback,
56+
IAlgebraFlashCallback
4957
{
5058
using SafeERC20 for IERC20;
5159

@@ -54,7 +62,7 @@ contract SiloAdvancedLeverageStrategy is
5462
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
5563

5664
/// @inheritdoc IControllable
57-
string public constant VERSION = "2.0.0";
65+
string public constant VERSION = "2.1.0";
5866

5967
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
6068
/* INITIALIZATION */
@@ -151,6 +159,14 @@ contract SiloAdvancedLeverageStrategy is
151159
LeverageLendingBaseStorage storage $ = _getLeverageLendingBaseStorage();
152160
SiloAdvancedLib.receiveFlashLoan(platform(), $, token, amount, isToken0 ? fee0 : fee1);
153161
}
162+
163+
function algebraFlashCallback(uint fee0, uint fee1, bytes calldata userData) external {
164+
// sender is the pool, it's checked inside receiveFlashLoan
165+
(address token, uint amount, bool isToken0) = abi.decode(userData, (address, uint, bool));
166+
167+
LeverageLendingBaseStorage storage $ = _getLeverageLendingBaseStorage();
168+
SiloAdvancedLib.receiveFlashLoan(platform(), $, token, amount, isToken0 ? fee0 : fee1);
169+
}
154170
//endregion ----------------------------------- Flash loan
155171

156172
//region ----------------------------------- View
@@ -340,7 +356,7 @@ contract SiloAdvancedLeverageStrategy is
340356
LeverageLendingBaseStorage storage $ = _getLeverageLendingBaseStorage();
341357
StrategyBaseStorage storage $base = _getStrategyBaseStorage();
342358
address[] memory _assets = assets();
343-
value = SiloAdvancedLib.depositAssets($, $base, amounts[0], _assets[0]);
359+
value = SiloAdvancedLib.depositAssets(platform(), $, $base, amounts[0], _assets[0]);
344360
}
345361

346362
/// @inheritdoc StrategyBase

src/strategies/base/LeverageLendingBase.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {IControllable} from "../../interfaces/IControllable.sol";
1313

1414
/// @notice Base strategy for leverage lending
1515
/// Changelog:
16+
/// 1.2.1: rebalanceDebt reverts if result share price less #277
1617
/// 1.2.0: feat: return new share price by rebalanceDebt #256; feat: use BeetsV3 OR UniswapV3-like DeX free flash loans #268
1718
/// 1.1.1: StrategyBase 2.1.3
1819
/// 1.1.0: targetLeveragePercent setup in strategy initializer; 8 universal configurable params
@@ -62,8 +63,6 @@ abstract contract LeverageLendingBase is StrategyBase, ILeverageLendingStrategy
6263

6364
/// @inheritdoc ILeverageLendingStrategy
6465
function rebalanceDebt(uint newLtv, uint minSharePrice) external returns (uint resultLtv, uint resultSharePrice) {
65-
minSharePrice; // todo use below
66-
6766
IPlatform _platform = IPlatform(platform());
6867
IHardWorker hardworker = IHardWorker(_platform.hardWorker());
6968
address rebalancer = _platform.rebalancer();
@@ -76,6 +75,7 @@ abstract contract LeverageLendingBase is StrategyBase, ILeverageLendingStrategy
7675

7776
resultLtv = _rebalanceDebt(newLtv);
7877
(resultSharePrice,) = _realSharePrice();
78+
require(resultSharePrice >= minSharePrice, IControllable.TooLowValue(resultSharePrice));
7979
}
8080

8181
/// @inheritdoc ILeverageLendingStrategy

src/strategies/libs/SiloAdvancedLib.sol

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,6 @@ library SiloAdvancedLib {
124124
collateralAmountTotal -= collateralAmountTotal / 1000;
125125
ISilo(lendingVault).withdraw(
126126
Math.min(tempCollateralAmount, collateralAmountTotal),
127-
// todo
128-
// _estimateCollateralAmountToRepay(
129-
// platform, collateralAmountTotal, collateralAsset, token, tempCollateralAmount
130-
// ),
131127
address(this),
132128
address(this),
133129
ISilo.CollateralType.Collateral
@@ -139,10 +135,14 @@ library SiloAdvancedLib {
139135
platform,
140136
collateralAsset,
141137
token,
142-
Math.min(tempCollateralAmount, StrategyLib.balance(collateralAsset)),
138+
_estimateSwapAmount(platform, amount + feeAmount, collateralAsset, token, swapPriceImpactTolerance0),
139+
// Math.min(tempCollateralAmount, StrategyLib.balance(collateralAsset)),
143140
swapPriceImpactTolerance0
144141
);
145142

143+
// explicit error for the case when _estimateSwapAmount gives incorrect amount
144+
require(IERC20(token).balanceOf(address(this)) >= amount + feeAmount, IControllable.InsufficientBalance());
145+
146146
// pay flash loan
147147
IERC20(token).safeTransfer(flashLoanVault, amount + feeAmount);
148148

@@ -492,8 +492,12 @@ library SiloAdvancedLib {
492492
);
493493

494494
IVaultMainV3(payable(vault)).unlock(data);
495-
} else if (flashLoanKind == ILeverageLendingStrategy.FlashLoanKind.UniswapV3_2) {
496-
// ensure that the vault has available amount
495+
} else if (
496+
// assume here that Algebra uses exactly same API as UniswapV3
497+
flashLoanKind == ILeverageLendingStrategy.FlashLoanKind.UniswapV3_2
498+
|| flashLoanKind == ILeverageLendingStrategy.FlashLoanKind.AlgebraV4_3
499+
) {
500+
// ensure that the pool has available amount
497501
require(
498502
IERC20(flashAssets[0]).balanceOf(address(vault)) >= flashAmounts[0], IControllable.InsufficientBalance()
499503
);
@@ -511,26 +515,31 @@ library SiloAdvancedLib {
511515
}
512516
}
513517

514-
function _estimateCollateralAmountToRepay(
518+
/// @notice Estimate amount of collateral to swap to receive {amountToRepay} on balance
519+
/// @param priceImpactTolerance Price impact tolerance. Must include fees at least. Denominator is 100_000.
520+
function _estimateSwapAmount(
515521
address platform,
516522
uint amountToRepay,
517523
address collateralAsset,
518524
address token,
519-
uint tempCollateralAmount
525+
uint priceImpactTolerance
520526
) internal view returns (uint) {
521527
// We have collateral C = C1 + C2 where C1 is amount to withdraw, C2 is amount to swap to B (to repay)
522528
// We don't need to swap whole C, we can swap only C2 with same addon (i.e. 10%) for safety
523529

524530
ISwapper swapper = ISwapper(IPlatform(platform).swapper());
531+
uint requiredAmount = amountToRepay - IERC20(token).balanceOf(address(this));
525532

526-
// 10% for price impact and slippage
527-
uint minCollateralToSwap = swapper.getPrice(token, collateralAsset, amountToRepay) * 110 / 100;
533+
// we use higher (x2) price impact then required for safety
534+
uint minCollateralToSwap =
535+
swapper.getPrice(token, collateralAsset, requiredAmount * (100_000 + 2 * priceImpactTolerance) / 100_000); // priceImpactTolerance has its own denominator
528536

529-
return Math.min(minCollateralToSwap, Math.min(tempCollateralAmount, StrategyLib.balance(collateralAsset)));
537+
return Math.min(minCollateralToSwap, StrategyLib.balance(collateralAsset));
530538
}
531539

532540
//region ------------------------------------- Deposit
533541
function depositAssets(
542+
address platform,
534543
ILeverageLendingStrategy.LeverageLendingBaseStorage storage $,
535544
IStrategy.StrategyBaseStorage storage $base,
536545
uint amount,
@@ -549,6 +558,10 @@ library SiloAdvancedLib {
549558
}
550559

551560
$base.total += value;
561+
562+
// ensure that result LTV doesn't exceed max
563+
(uint maxLtv,,) = getLtvData(v.lendingVault, $.targetLeveragePercent);
564+
_ensureLtvValid($, platform, maxLtv);
552565
}
553566

554567
function _deposit(
@@ -574,7 +587,9 @@ library SiloAdvancedLib {
574587

575588
return amountToDeposit * priceCtoB * (10 ** IERC20Metadata(v.borrowAsset).decimals())
576589
* (targetLeverage - INTERNAL_PRECISION) / INTERNAL_PRECISION / 1e18 // priceCtoB has decimals 1e18
577-
/ (10 ** IERC20Metadata(v.collateralAsset).decimals());
590+
// depositParam0 is used to move result leverage to targetValue.
591+
// Otherwise result leverage is higher the target value because of swap losses
592+
* $.depositParam0 / INTERNAL_PRECISION / (10 ** IERC20Metadata(v.collateralAsset).decimals());
578593
// not sure that its right way, but its working
579594
// flashAmounts[0] = flashAmounts[0] * $.depositParam0 / INTERNAL_PRECISION;
580595
}
@@ -628,6 +643,9 @@ library SiloAdvancedLib {
628643
SiloAdvancedLib._deposit($, v, Math.min(state.withdrawParam1 * value / INTERNAL_PRECISION, balance));
629644
}
630645
}
646+
647+
// ensure that result LTV doesn't exceed max
648+
_ensureLtvValid($, platform, state.maxLtv);
631649
}
632650

633651
function withdrawFromLendingVault(
@@ -681,10 +699,6 @@ library SiloAdvancedLib {
681699
_withdrawReduceLeverage($, v, state, valueToWithdraw);
682700
}
683701
}
684-
685-
// ensure that result LTV doesn't exceed max
686-
(uint ltv,,,,,) = health(platform, $);
687-
require(ltv <= state.maxLtv, IControllable.IncorrectLtv(ltv));
688702
}
689703

690704
function _withdrawReduceLeverage(
@@ -808,6 +822,17 @@ library SiloAdvancedLib {
808822
//endregion ------------------------------------- Withdraw
809823

810824
//region ------------------------------------- Internal
825+
826+
/// @notice ensure that result LTV doesn't exceed max
827+
function _ensureLtvValid(
828+
ILeverageLendingStrategy.LeverageLendingBaseStorage storage $,
829+
address platform,
830+
uint maxLtv
831+
) internal view {
832+
(uint ltv,,,,,) = health(platform, $);
833+
require(ltv <= maxLtv, IControllable.IncorrectLtv(ltv));
834+
}
835+
811836
function _getFlashLoanAmounts(
812837
uint borrowAmount,
813838
address borrowAsset

test/strategies/SiAL.Sonic.sol

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ contract SiloAdvancedLeverageStrategyTest is SonicSetup, UniversalTest {
1313
//vm.rollFork(13119000); // Mar-11-2025 08:29:09 PM +UTC
1414
// vm.rollFork(18553912); // Apr-01-2025 03:26:51 PM +UTC
1515
// vm.rollFork(24454255); // May-05-2025 06:57:40 AM +UTC
16-
vm.rollFork(25224612); // May-08-2025 10:18:00 AM +UTC
16+
// vm.rollFork(25224612); // May-08-2025 10:18:00 AM +UTC
17+
vm.rollFork(26428190); // May-13-2025 06:22:27 AM +UTC
1718
allowZeroApr = true;
1819
duration1 = 0.1 hours;
1920
duration2 = 0.1 hours;
@@ -22,9 +23,9 @@ contract SiloAdvancedLeverageStrategyTest is SonicSetup, UniversalTest {
2223

2324
function testSiALSonic() public universalTest {
2425
// _addStrategy(SonicConstantsLib.SILO_VAULT_22_wOS, SonicConstantsLib.SILO_VAULT_22_wS, 86_90);
25-
// _addStrategy(SonicConstantsLib.SILO_VAULT_23_wstkscUSD, SonicConstantsLib.SILO_VAULT_23_USDC, 88_00);
26-
// _addStrategy(SonicConstantsLib.SILO_VAULT_26_wstkscETH, SonicConstantsLib.SILO_VAULT_26_wETH, 90_00);
27-
// _addStrategy(SonicConstantsLib.SILO_VAULT_25_wanS, SonicConstantsLib.SILO_VAULT_25_wS, 90_00);
26+
// _addStrategy(SonicConstantsLib.SILO_VAULT_23_wstkscUSD, SonicConstantsLib.SILO_VAULT_23_USDC, 88_00);
27+
// _addStrategy(SonicConstantsLib.SILO_VAULT_26_wstkscETH, SonicConstantsLib.SILO_VAULT_26_wETH, 90_00);
28+
// _addStrategy(SonicConstantsLib.SILO_VAULT_25_wanS, SonicConstantsLib.SILO_VAULT_25_wS, 90_00);
2829
_addStrategy(SonicConstantsLib.SILO_VAULT_46_PT_aUSDC_14AUG, SonicConstantsLib.SILO_VAULT_46_scUSD, 60_00);
2930
_addStrategy(SonicConstantsLib.SILO_VAULT_40_PT_stS_29MAY, SonicConstantsLib.SILO_VAULT_40_wS, 65_00);
3031
// _addStrategy(SonicConstantsLib.SILO_VAULT_37_PT_wstkscUSD_29MAY, SonicConstantsLib.SILO_VAULT_37_frxUSD, 65_00);

0 commit comments

Comments
 (0)