Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions chains/plasma/PlasmaConstantsLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ library PlasmaConstantsLib {
address public constant TOKEN_EUL = 0xca632FA58397391C750c13F935DAA61AbBe0BaA6;
address public constant TOKEN_REUL = 0xe2011F2bF6556863c3bacE991Efc8DaC26CD84c2;

// Stability vaults
address public constant STABILITY_VAULT_ALMF_WEETH_USDT0 = 0xab0087D6fbC877246A4Ba33636f80E5dCbd5BE01;

// Oracles
// address public constant ORACLE_CHAINLINK_USDT0_USD = 0xdBbB0b5DD13E7AC9C56624834ef193df87b022c3;
address public constant ORACLE_CHAINLINK_USDT0_USD = 0x70b77FcdbE2293423e41AdD2FB599808396807BC;
Expand Down
5 changes: 3 additions & 2 deletions src/strategies/AaveLeverageMerklFarmStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {VaultTypeLib} from "../core/libs/VaultTypeLib.sol";
/// @title Earns APR by lending assets on AAVE with leverage
/// @dev ALMF strategy
/// Changelog:
/// 1.4.0: depositParam1 and withdrawParam1 are used to set deposit/withdraw fee % (decimals 1e4) - #441
/// 1.3.0: New strategy param revenueBaseAsset. 0 - share price is calculated in collateral asset, 1 - in borrow asset
/// 1.2.0: share price is calculated in collateral asset, not in usd
/// 1.1.0: add support of e-mode
Expand All @@ -47,7 +48,7 @@ contract AaveLeverageMerklFarmStrategy is
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @inheritdoc IControllable
string public constant VERSION = "1.3.0";
string public constant VERSION = "1.4.0";

//region ----------------------------------- Initialization and restricted actions
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
Expand Down Expand Up @@ -438,7 +439,7 @@ contract AaveLeverageMerklFarmStrategy is
address[] memory rewardAssets_,
uint[] memory rewardAmounts_
) internal override(FarmingStrategyBase, StrategyBase, LeverageLendingBase) returns (uint earnedExchangeAsset) {
return ALMFLib.liquidateRewards(
return ALMFLib2.liquidateRewards(
platform(), exchangeAsset, rewardAssets_, rewardAmounts_, customPriceImpactTolerance()
);
}
Expand Down
67 changes: 48 additions & 19 deletions src/strategies/libs/ALMFLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {IStrategy} from "../../interfaces/IStrategy.sol";
import {ISwapper} from "../../interfaces/ISwapper.sol";
import {IVaultMainV3} from "../../integrations/balancerv3/IVaultMainV3.sol";
import {IVault} from "../../interfaces/IVault.sol";
import {IRevenueRouter} from "../../interfaces/IRevenueRouter.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";
Expand Down Expand Up @@ -305,6 +306,9 @@ library ALMFLib {
ALMFCalcLib.StaticData memory data = _getStaticData(platform_, $, farm);
ALMFCalcLib.State memory state = _getState(data);

/// @dev Reduce input amount on deposit fee, send fee amount to revenue router
amount = _takeFee($, amount, true, data.collateralAsset, platform_);

uint valueWas = ALMFCalcLib.collateralToBase(StrategyLib.balance(data.collateralAsset), data) + calcTotal(state);

uint threshold = _getStorage().thresholds[data.collateralAsset];
Expand Down Expand Up @@ -451,19 +455,24 @@ library ALMFLib {
}

// ---------------------- Transfer required amount to the user
uint balance = StrategyLib.balance(data.collateralAsset);
uint valueNow = ALMFCalcLib.collateralToBase(balance, data) + calcTotal(state);
{
uint balance = StrategyLib.balance(data.collateralAsset);
uint valueNow = ALMFCalcLib.collateralToBase(balance, data) + calcTotal(state);

amountsOut = new uint[](1);
if (valueWas > valueNow) {
amountsOut[0] = Math.min(ALMFCalcLib.baseToCollateral(value - (valueWas - valueNow), data), balance);
} else {
amountsOut[0] = Math.min(ALMFCalcLib.baseToCollateral(value + (valueNow - valueWas), data), balance);
amountsOut = new uint[](1);
if (valueWas > valueNow) {
amountsOut[0] = Math.min(ALMFCalcLib.baseToCollateral(value - (valueWas - valueNow), data), balance);
} else {
amountsOut[0] = Math.min(ALMFCalcLib.baseToCollateral(value + (valueNow - valueWas), data), balance);
}
}

// we can have dust amounts of collateral on strategy balance here

if (receiver != address(this)) {
/// @dev Reduce output amount on withdraw fee, send fee amount to revenue router
amountsOut[0] = _takeFee($, amountsOut[0], false, data.collateralAsset, platform);

IERC20(data.collateralAsset).safeTransfer(receiver, amountsOut[0]);
}

Expand Down Expand Up @@ -916,18 +925,6 @@ library ALMFLib {
}
}

function liquidateRewards(
address platform_,
address exchangeAsset,
address[] memory rewardAssets_,
uint[] memory rewardAmounts_,
uint priceImpactTolerance
) external returns (uint earnedExchangeAsset) {
earnedExchangeAsset = StrategyLib.liquidateRewards(
platform_, exchangeAsset, rewardAssets_, rewardAmounts_, priceImpactTolerance
);
}

function compound(
address platform_,
ILeverageLendingStrategy.LeverageLendingBaseStorage storage $,
Expand Down Expand Up @@ -1001,6 +998,38 @@ library ALMFLib {
//endregion ------------------------------------- Additional functionality

//region ------------------------------------- Internal utils
/// @dev Take deposit/withdraw fee from amount, send fee to revenue router and return amount after fee deduction.
/// Fee percent should be set in depositParam1/withdrawParam1 with precision 1e4.
/// @param amount Amount of collateral asset to take fee from
/// @param isDeposit If true, deposit fee is taken; otherwise, withdraw fee is taken.
/// @param collateralAsset_ Collateral asset address, needed for fee transfer
/// @return amountOut Amount after fee deduction. If fee is 0, amountOut is equal to amount.
function _takeFee(
ILeverageLendingStrategy.LeverageLendingBaseStorage storage $,
uint amount,
bool isDeposit,
address collateralAsset_,
address platform_
) internal returns (uint amountOut) {
amountOut = amount;

/// @dev Fee percent with precision 1e4, should be set in depositParam1/withdrawParam1
uint feePercent = isDeposit ? $.depositParam1 : $.withdrawParam1;

/// @dev Fee amount in collateral asset
uint feeAmount = amount * feePercent / ALMFCalcLib.INTERNAL_PRECISION;

// assume below that feePercent < ALMFCalcLib.INTERNAL_PRECISION and so amount > feeAmount always

/// @dev Take a fee in the same way as doHardwork does
if (feeAmount != 0) {
amountOut = amount - feeAmount;
address revenueRouter = IPlatform(platform_).revenueRouter();
IERC20(collateralAsset_).forceApprove(revenueRouter, feeAmount);
IRevenueRouter(revenueRouter).processFeeAsset(collateralAsset_, feeAmount);
}
}

function _getFlashLoanAmounts(
uint borrowAmount,
address borrowAsset
Expand Down
23 changes: 19 additions & 4 deletions src/strategies/libs/ALMFLib2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol
import {SharedLib} from "./SharedLib.sol";
import {StrategyIdLib} from "./StrategyIdLib.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {StrategyLib} from "./StrategyLib.sol";

/// @notice Several standalone functions were moved here to reduce size of ALMFLib
library ALMFLib2 {
Expand Down Expand Up @@ -115,8 +116,9 @@ library ALMFLib2 {
// ------------------------------ Set up all params in use
// // Multiplier of flash amount for borrow on deposit. Default is 100_00 = 100%
// $.depositParam0 = 100_00;
// // Multiplier of borrow amount to take into account max flash loan fee in maxDeposit. Default is 99_80 = 99.8%
// $.depositParam1 = 99_80;

// Deposit fee, percent, 1e4; i.e. 0001 = 0.01%
// $.depositParam1 = 0;

// Multiplier of debt diff
$.increaseLtvParam0 = 100_80;
Expand All @@ -132,13 +134,26 @@ library ALMFLib2 {
// Leverage correction coefficient, INTERNAL_PRECISION. Default is 300 = 0.03
$.withdrawParam0 = 300;

// // Multiplier of amount allowed to be deposited after withdraw. Default is 100_00 == 100% (deposit forbidden)
// $.withdrawParam1 = 100_00;
// Withdraw fee, percent, 1e4; i.e. 0001 = 0.01%
// $.withdrawParam1 = 0;

// // withdrawParam2 allows to disable withdraw through increasing ltv if leverage is near to target
// $.withdrawParam2 = 100_00;

$.flashLoanKind = farm.nums[2];
}

//endregion ------------------------------------- Init vars, desc

function liquidateRewards(
address platform_,
address exchangeAsset,
address[] memory rewardAssets_,
uint[] memory rewardAmounts_,
uint priceImpactTolerance
) external returns (uint earnedExchangeAsset) {
earnedExchangeAsset = StrategyLib.liquidateRewards(
platform_, exchangeAsset, rewardAssets_, rewardAmounts_, priceImpactTolerance
);
}
}
127 changes: 127 additions & 0 deletions test/strategies/ALMF.Plasma.Upgrade.441.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import {Test} from "forge-std/Test.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {AaveLeverageMerklFarmStrategy} from "../../src/strategies/AaveLeverageMerklFarmStrategy.sol";
import {IVault} from "../../src/interfaces/IVault.sol";
import {IPlatform} from "../../src/interfaces/IPlatform.sol";
import {IStabilityVault} from "../../src/interfaces/IStabilityVault.sol";
import {ILeverageLendingStrategy} from "../../src/interfaces/ILeverageLendingStrategy.sol";
import {IFactory} from "../../src/interfaces/IFactory.sol";
import {PlasmaConstantsLib} from "../../chains/plasma/PlasmaConstantsLib.sol";
import {StrategyIdLib} from "../../src/strategies/libs/StrategyIdLib.sol";

/// @notice #441: Use depositParam1 and withdrawParam1 to set deposit/withdraw fee
contract ALMFUpgrade441PlasmaTest is Test {
uint internal constant FORK_BLOCK = 13688033; // Feb-09-2026 06:49:49 AM +UTC

/// @notice Stability weETH Aave Leverage Merkl Farm USDT0
address internal constant VAULT = PlasmaConstantsLib.STABILITY_VAULT_ALMF_WEETH_USDT0;

address internal multisig;

constructor() {
vm.selectFork(vm.createFork(vm.envString("PLASMA_RPC_URL"), FORK_BLOCK));

_upgradeStrategy(address(IVault(VAULT).strategy()));
}

function testUpgradeStrategy() public {
uint[4] memory depositParam1 = [uint(0), 10, 0, 10]; // 0.1% for deposit
uint[4] memory withdrawParam1 = [uint(0), 0, 20, 20]; // 0.2 % for withdraw
uint[4] memory withdrawn;
uint[4] memory receivedByRevenueRouter;

// --------------------------------- zero fee

for (uint i; i < 4; ++i) {
uint snapshot = vm.snapshotState();
{
ILeverageLendingStrategy strategy = ILeverageLendingStrategy(address(IVault(VAULT).strategy()));
(uint[] memory params, address[] memory addresses) = strategy.getUniversalParams();
params[1] = depositParam1[i];
params[3] = withdrawParam1[i];
vm.prank(PlasmaConstantsLib.MULTISIG);
strategy.setUniversalParams(params, addresses);
}

uint before = IERC20(PlasmaConstantsLib.TOKEN_WEETH)
.balanceOf(IPlatform(PlasmaConstantsLib.PLATFORM).revenueRouter());

// deposit 1 weeth
_depositToVault(VAULT, 1e18, address(this));

vm.roll(block.number + 6);

// withdraw all
withdrawn[i] = _withdrawFromVault(VAULT, IVault(VAULT).balanceOf(address(this)), address(this));

receivedByRevenueRouter[i] = IERC20(PlasmaConstantsLib.TOKEN_WEETH)
.balanceOf(IPlatform(PlasmaConstantsLib.PLATFORM).revenueRouter()) - before;
vm.revertToState(snapshot);
}

assertApproxEqAbs(withdrawn[0], withdrawn[1], 0.001e18, "~0.1% for deposit");
assertApproxEqAbs(withdrawn[0], withdrawn[2], 0.002e18, "~0.2% for withdraw");
assertApproxEqAbs(withdrawn[0], withdrawn[3], 0.003e18, "~0.3% for deposit and withdraw");

assertEq(receivedByRevenueRouter[0], 0, "no fees for deposit and withdraw");
assertEq(receivedByRevenueRouter[1], 0.001e18, "0.1% for deposit");
assertApproxEqAbs(receivedByRevenueRouter[2], 0.002e18, 1e14, "0.2% for withdraw");
assertApproxEqAbs(receivedByRevenueRouter[3], 0.003e18, 1e14, "0.3% for deposit and withdraw");
}

function _depositToVault(
address vault,
uint amount,
address user
) internal returns (uint deposited, uint depositedValue) {
address[] memory assets = IVault(vault).assets();
uint[] memory amountsToDeposit = new uint[](1);
amountsToDeposit[0] = amount;

// ----------------------------- Prepare amount on user's balance
_dealAndApprove(user, vault, assets, amountsToDeposit);
// console.log("Deposit to vault", assets[0], amounts_[0]);

uint balanceBefore = IVault(vault).balanceOf(user);

// ----------------------------- Try to deposit assets to the vault
vm.prank(user);
IStabilityVault(vault).depositAssets(assets, amountsToDeposit, 0, user);

return (amountsToDeposit[0], IVault(vault).balanceOf(user) - balanceBefore);
}

function _withdrawFromVault(address vault, uint value, address user) internal returns (uint withdrawn) {
address[] memory _assets = IVault(vault).assets();

uint balanceBefore = IERC20(_assets[0]).balanceOf(user);

vm.prank(user);
IStabilityVault(vault).withdrawAssets(_assets, value, new uint[](1));

return IERC20(_assets[0]).balanceOf(user) - balanceBefore;
}

function _dealAndApprove(address user, address spender, 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(spender, amounts[j]);
}
}

function _upgradeStrategy(address strategyAddress) internal {
address strategyImplementation = address(new AaveLeverageMerklFarmStrategy());

IFactory factory = IFactory(IPlatform(PlasmaConstantsLib.PLATFORM).factory());

vm.prank(PlasmaConstantsLib.MULTISIG);
factory.setStrategyImplementation(StrategyIdLib.AAVE_LEVERAGE_MERKL_FARM, strategyImplementation);

factory.upgradeStrategyProxy(strategyAddress);
}
}
Loading