mirror of
https://github.com/Instadapp/fluid-contracts-public.git
synced 2024-07-29 21:57:37 +00:00
d7a58e88ff
ARB: deploy protocols
446 lines
21 KiB
Solidity
446 lines
21 KiB
Solidity
//SPDX-License-Identifier: MIT
|
|
pragma solidity 0.8.21;
|
|
|
|
import "forge-std/Test.sol";
|
|
import "forge-std/console2.sol";
|
|
|
|
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
import { FluidVaultLiquidationResolver } from "../../../../contracts/periphery/resolvers/vaultLiquidation/main.sol";
|
|
import { Structs } from "../../../../contracts/periphery/resolvers/vaultLiquidation/structs.sol";
|
|
import { IFluidVaultResolver } from "../../../../contracts/periphery/resolvers/vault/iVaultResolver.sol";
|
|
import { IFluidVaultFactory } from "../../../../contracts/protocols/vault/interfaces/iVaultFactory.sol";
|
|
import { MockOracle } from "../../../../contracts/mocks/mockOracle.sol";
|
|
import { IFluidVaultT1 } from "../../../../contracts/protocols/vault/interfaces/iVaultT1.sol";
|
|
import { FluidVaultT1Admin } from "../../../../contracts/protocols/vault/vaultT1/adminModule/main.sol";
|
|
import { FluidVaultResolver } from "../../../../contracts/periphery/resolvers/vault/main.sol";
|
|
import { FluidLiquidityResolver } from "../../../../contracts/periphery/resolvers/liquidity/main.sol";
|
|
import { IFluidLiquidity } from "../../../../contracts/liquidity/interfaces/iLiquidity.sol";
|
|
|
|
contract FluidVaultLiquidationResolverTest is Test {
|
|
IFluidVaultFactory internal constant VAULT_FACTORY = IFluidVaultFactory(0x324c5Dc1fC42c7a4D43d92df1eBA58a54d13Bf2d);
|
|
|
|
address internal constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
|
|
address internal constant WSTETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0;
|
|
address internal constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
|
|
address internal constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7;
|
|
|
|
address internal constant VAULT_ETH_USDC = 0xeAbBfca72F8a8bf14C4ac59e69ECB2eB69F0811C;
|
|
address internal constant VAULT_WSTETH_ETH = 0xA0F83Fc5885cEBc0420ce7C7b139Adc80c4F4D91;
|
|
address internal constant VAULT_WSTETH_USDC = 0x51197586F6A9e2571868b6ffaef308f3bdfEd3aE;
|
|
address internal constant VAULT_WSTETH_USDT = 0x1c2bB46f36561bc4F05A94BD50916496aa501078;
|
|
|
|
address internal constant GOVERNANCE = 0x2386DC45AdDed673317eF068992F19421B481F4c;
|
|
|
|
IFluidLiquidity internal constant LIQUIDITY = IFluidLiquidity(0x52Aa899454998Be5b000Ad077a46Bbe360F4e497);
|
|
|
|
FluidVaultResolver vaultResolver;
|
|
|
|
address bob = makeAddr("bob");
|
|
|
|
FluidVaultLiquidationResolver resolver;
|
|
|
|
function setUp() public virtual {
|
|
vm.createSelectFork(vm.envString("MAINNET_RPC_URL"));
|
|
vm.rollFork(19377005);
|
|
|
|
// deploy resolver dependencies newest state
|
|
FluidLiquidityResolver liquidityResolver = new FluidLiquidityResolver(LIQUIDITY);
|
|
vaultResolver = new FluidVaultResolver(address(VAULT_FACTORY), address(LIQUIDITY), address(liquidityResolver));
|
|
|
|
// constructor params
|
|
// IFluidVaultResolver vaultResolver_
|
|
resolver = new FluidVaultLiquidationResolver(IFluidVaultResolver(address(vaultResolver)));
|
|
}
|
|
|
|
function test_deployment() public {
|
|
assertEq(address(resolver.VAULT_RESOLVER()), address(vaultResolver));
|
|
}
|
|
|
|
function test_getAllSwapPairs() public {
|
|
Structs.VaultData[] memory vaultDatas = resolver.getAllSwapPairs();
|
|
assertEq(vaultDatas.length, 5);
|
|
assertEq(vaultDatas[0].vault, VAULT_ETH_USDC);
|
|
assertEq(vaultDatas[0].tokenIn, USDC);
|
|
assertEq(vaultDatas[0].tokenOut, ETH);
|
|
|
|
assertEq(vaultDatas[2].vault, VAULT_WSTETH_ETH);
|
|
assertEq(vaultDatas[2].tokenIn, ETH);
|
|
assertEq(vaultDatas[2].tokenOut, WSTETH);
|
|
|
|
assertEq(vaultDatas[4].vault, VAULT_WSTETH_USDT);
|
|
assertEq(vaultDatas[4].tokenIn, USDT);
|
|
assertEq(vaultDatas[4].tokenOut, WSTETH);
|
|
}
|
|
|
|
function test_getVaultForSwap() public {
|
|
assertEq(resolver.getVaultForSwap(USDC, ETH), VAULT_ETH_USDC);
|
|
assertEq(resolver.getVaultForSwap(ETH, WSTETH), VAULT_WSTETH_ETH);
|
|
assertEq(resolver.getVaultForSwap(USDT, WSTETH), VAULT_WSTETH_USDT);
|
|
assertEq(resolver.getVaultForSwap(WSTETH, USDC), address(0));
|
|
}
|
|
|
|
function test_getVaultsForSwap() public {
|
|
address[] memory tokensIn = new address[](3);
|
|
tokensIn[0] = ETH;
|
|
tokensIn[1] = WSTETH;
|
|
tokensIn[2] = USDC;
|
|
address[] memory tokensOut = new address[](3);
|
|
tokensOut[0] = ETH;
|
|
tokensOut[1] = WSTETH;
|
|
tokensOut[2] = USDC;
|
|
Structs.VaultData[] memory vaultDatas = resolver.getVaultsForSwap(tokensIn, tokensOut);
|
|
|
|
// should find routes: USDC -> ETH, USDC -> WSTETH, ETH -> WSTETH
|
|
assertEq(vaultDatas.length, 3);
|
|
assertEq(vaultDatas[0].vault, VAULT_WSTETH_ETH);
|
|
assertEq(vaultDatas[0].tokenIn, ETH);
|
|
assertEq(vaultDatas[0].tokenOut, WSTETH);
|
|
|
|
assertEq(vaultDatas[1].vault, VAULT_ETH_USDC);
|
|
assertEq(vaultDatas[1].tokenIn, USDC);
|
|
assertEq(vaultDatas[1].tokenOut, ETH);
|
|
|
|
assertEq(vaultDatas[2].vault, VAULT_WSTETH_USDC);
|
|
assertEq(vaultDatas[2].tokenIn, USDC);
|
|
assertEq(vaultDatas[2].tokenOut, WSTETH);
|
|
}
|
|
|
|
function test_getSwapAvailable() public {
|
|
// assert initially no swap available
|
|
Structs.SwapData memory swapData = resolver.getSwapAvailable(USDC, ETH);
|
|
assertEq(swapData.inAmt, 0);
|
|
assertEq(swapData.outAmt, 0);
|
|
assertEq(swapData.inAmtWithAbsorb, 0);
|
|
assertEq(swapData.outAmtWithAbsorb, 0);
|
|
assertEq(swapData.vault, VAULT_ETH_USDC);
|
|
|
|
// set a mockOracle as oracle and move positions into liquidation territory
|
|
_reduceVaultOraclePrice(VAULT_ETH_USDC, 15);
|
|
|
|
// check that liquidation is now available and swap data is as expected
|
|
swapData = resolver.getSwapAvailable(USDC, ETH);
|
|
assertEq(swapData.inAmt, 13519);
|
|
assertEq(swapData.outAmt, 4261623418724);
|
|
assertEq(swapData.inAmtWithAbsorb, swapData.inAmt);
|
|
assertEq(swapData.outAmtWithAbsorb, swapData.outAmt);
|
|
assertEq(swapData.vault, VAULT_ETH_USDC);
|
|
|
|
// reduce oracle price more, into absorb territory, run absorb and check again
|
|
_reduceVaultOraclePrice(VAULT_ETH_USDC, 20);
|
|
|
|
vm.prank(bob);
|
|
IFluidVaultT1(VAULT_ETH_USDC).absorb();
|
|
|
|
// check that liquidation is now available and swap data is as expected
|
|
swapData = resolver.getSwapAvailable(USDC, ETH);
|
|
assertEq(swapData.inAmt, 1079615031403); // 1.079.615,031403 USDC
|
|
assertEq(swapData.outAmt, 425403010045446193701); // 425,403010045446193701 ETH
|
|
assertEq(swapData.inAmtWithAbsorb, 1529830467285); // 1.529.830,467285 USDC
|
|
assertEq(swapData.outAmtWithAbsorb, 587977363017178865259); // ~587.977 ETH
|
|
assertEq(swapData.vault, VAULT_ETH_USDC);
|
|
}
|
|
|
|
function test_getSwapsAvailable() public {
|
|
address[] memory tokensIn = new address[](2);
|
|
tokensIn[0] = ETH;
|
|
tokensIn[1] = USDC;
|
|
address[] memory tokensOut = new address[](2);
|
|
tokensOut[0] = ETH;
|
|
tokensOut[1] = WSTETH;
|
|
|
|
// assert initially no swap available
|
|
Structs.SwapData[] memory swapData = resolver.getSwapsAvailable(tokensIn, tokensOut);
|
|
assertEq(swapData[0].inAmt, 0);
|
|
assertEq(swapData[0].outAmt, 0);
|
|
assertEq(swapData[0].inAmtWithAbsorb, 0);
|
|
assertEq(swapData[0].outAmtWithAbsorb, 0);
|
|
assertEq(swapData[0].vault, VAULT_WSTETH_ETH);
|
|
|
|
assertEq(swapData[1].inAmt, 0);
|
|
assertEq(swapData[1].outAmt, 0);
|
|
assertEq(swapData[1].inAmtWithAbsorb, 0);
|
|
assertEq(swapData[1].outAmtWithAbsorb, 0);
|
|
assertEq(swapData[1].vault, VAULT_ETH_USDC);
|
|
|
|
// set a mockOracle as oracle and move positions into liquidation territory
|
|
_reduceVaultOraclePrice(VAULT_ETH_USDC, 32); // same as 15% + then 20% subsequently
|
|
vm.prank(bob);
|
|
IFluidVaultT1(VAULT_ETH_USDC).absorb();
|
|
|
|
_reduceVaultOraclePrice(VAULT_WSTETH_ETH, 9);
|
|
vm.prank(bob);
|
|
IFluidVaultT1(VAULT_WSTETH_ETH).absorb();
|
|
|
|
// check that liquidation is now available and swap data is as expected
|
|
swapData = resolver.getSwapsAvailable(tokensIn, tokensOut);
|
|
assertEq(swapData[0].inAmt, 364494819510876217438); // 364 ETH
|
|
assertEq(swapData[0].outAmt, 345820852379987135441); // for 345 wstETH
|
|
assertEq(swapData[0].inAmtWithAbsorb, 8647892178185540329665); // 8647 ETH
|
|
assertEq(swapData[0].outAmtWithAbsorb, 8236620852379986965534); // for 8236 wstETH
|
|
assertEq(swapData[0].vault, VAULT_WSTETH_ETH);
|
|
|
|
assertEq(swapData[1].inAmt, 1079615031403); // 1.079.615,031403 USDC
|
|
assertEq(swapData[1].outAmt, 425403010045446193701); // 425,403010045446193701 ETH
|
|
assertEq(swapData[1].inAmtWithAbsorb, 1529830467285); // 1.529.830,467285 USDC
|
|
assertEq(swapData[1].outAmtWithAbsorb, 587977363017178865259); // ~587.977 ETH
|
|
assertEq(swapData[1].vault, VAULT_ETH_USDC);
|
|
}
|
|
|
|
function test_getSwapCalldata() public {
|
|
// set a mockOracle as oracle and move positions into liquidation territory
|
|
_reduceVaultOraclePrice(VAULT_ETH_USDC, 32); // same as 15% + then 20% subsequently
|
|
vm.prank(bob);
|
|
IFluidVaultT1(VAULT_ETH_USDC).absorb();
|
|
|
|
Structs.SwapData memory swapData = resolver.getSwapAvailable(USDC, ETH);
|
|
assertEq(swapData.inAmt, 1079615031403);
|
|
assertEq(swapData.outAmt, 425403010045446193701);
|
|
assertEq(swapData.inAmtWithAbsorb, 1529830467285);
|
|
assertEq(swapData.outAmtWithAbsorb, 587977363017178865259);
|
|
assertEq(swapData.vault, VAULT_ETH_USDC);
|
|
|
|
uint256 tokenInAmt = swapData.inAmt / 2;
|
|
uint256 tokenOutAmt = swapData.outAmt / 2;
|
|
bytes memory liquidateCalldata = resolver.getSwapCalldata(
|
|
swapData.vault,
|
|
bob,
|
|
tokenInAmt,
|
|
tokenOutAmt,
|
|
10000, // 1% slippage
|
|
false
|
|
);
|
|
|
|
// executing the received calldata should trigger the liquidation and result in desired swap
|
|
address alice = 0x5B541d54e79052B34188db9A43F7b00ea8E2C4B1;
|
|
vm.prank(alice);
|
|
IERC20(USDC).approve(VAULT_ETH_USDC, type(uint256).max);
|
|
|
|
uint256 aliceUSDCBalanceBefore = IERC20(USDC).balanceOf(alice);
|
|
uint256 bobETHBalanceBefore = payable(bob).balance;
|
|
|
|
vm.prank(alice);
|
|
(bool success, ) = address(VAULT_ETH_USDC).call(liquidateCalldata);
|
|
assertTrue(success);
|
|
|
|
assertApproxEqAbs(aliceUSDCBalanceBefore - IERC20(USDC).balanceOf(alice), tokenInAmt, 1);
|
|
// expected output amount is half of 425403010045446193701 so 212701505022723096850
|
|
// we do receive 212701505022328092456
|
|
assertEq(payable(bob).balance - bobETHBalanceBefore, 212701505022328092456);
|
|
assertApproxEqAbs(payable(bob).balance - bobETHBalanceBefore, tokenOutAmt, 1e9); // output amount might be a bit less
|
|
assertLt(payable(bob).balance - bobETHBalanceBefore, tokenOutAmt); // output should be LESS not more
|
|
|
|
// completeley liquidate with absorb
|
|
swapData = resolver.getSwapAvailable(USDC, ETH);
|
|
assertEq(swapData.inAmt, 539807515703);
|
|
assertEq(swapData.outAmt, 212701505023513105640);
|
|
assertEq(swapData.inAmtWithAbsorb, 990022951585);
|
|
assertEq(swapData.outAmtWithAbsorb, 375275857995245777197);
|
|
assertEq(swapData.vault, VAULT_ETH_USDC);
|
|
|
|
tokenInAmt = swapData.inAmtWithAbsorb;
|
|
tokenOutAmt = swapData.outAmtWithAbsorb;
|
|
liquidateCalldata = resolver.getSwapCalldata(
|
|
swapData.vault,
|
|
bob,
|
|
tokenInAmt,
|
|
tokenOutAmt,
|
|
10000, // 1% slippage
|
|
true
|
|
);
|
|
|
|
// executing the received calldata should trigger the liquidation and result in desired swap
|
|
aliceUSDCBalanceBefore = IERC20(USDC).balanceOf(alice);
|
|
bobETHBalanceBefore = payable(bob).balance;
|
|
|
|
vm.prank(alice);
|
|
(success, ) = address(VAULT_ETH_USDC).call(liquidateCalldata);
|
|
assertTrue(success);
|
|
|
|
assertApproxEqAbs(aliceUSDCBalanceBefore - IERC20(USDC).balanceOf(alice), tokenInAmt, 1);
|
|
assertApproxEqAbs(payable(bob).balance - bobETHBalanceBefore, tokenOutAmt, 1e9); // output amount might be a bit less
|
|
assertLt(payable(bob).balance - bobETHBalanceBefore, tokenOutAmt); // output should be LESS not more
|
|
|
|
// ensure no more liquidation available now
|
|
swapData = resolver.getSwapAvailable(USDC, ETH);
|
|
assertEq(swapData.inAmt, 0);
|
|
assertEq(swapData.outAmt, 0);
|
|
assertEq(swapData.inAmtWithAbsorb, 0);
|
|
assertEq(swapData.outAmtWithAbsorb, 0);
|
|
assertEq(swapData.vault, VAULT_ETH_USDC);
|
|
}
|
|
|
|
function test_getSwapDataForVault() public {
|
|
// assert initially no swap available
|
|
Structs.SwapData memory swapData = resolver.getSwapDataForVault(VAULT_ETH_USDC);
|
|
assertEq(swapData.inAmt, 0);
|
|
assertEq(swapData.outAmt, 0);
|
|
assertEq(swapData.inAmtWithAbsorb, 0);
|
|
assertEq(swapData.outAmtWithAbsorb, 0);
|
|
assertEq(swapData.vault, VAULT_ETH_USDC);
|
|
|
|
// set a mockOracle as oracle and move positions into liquidation territory
|
|
_reduceVaultOraclePrice(VAULT_ETH_USDC, 32);
|
|
vm.prank(bob);
|
|
IFluidVaultT1(VAULT_ETH_USDC).absorb();
|
|
|
|
swapData = resolver.getSwapDataForVault(VAULT_ETH_USDC);
|
|
assertEq(swapData.inAmt, 1079615031403); // 1.079.615,031403 USDC
|
|
assertEq(swapData.outAmt, 425403010045446193701); // 425,403010045446193701 ETH
|
|
assertEq(swapData.inAmtWithAbsorb, 1529830467285); // 1.529.830,467285 USDC
|
|
assertEq(swapData.outAmtWithAbsorb, 587977363017178865259); // ~587.977 ETH
|
|
assertEq(swapData.vault, VAULT_ETH_USDC);
|
|
}
|
|
|
|
function test_exactInput() public {
|
|
// set a mockOracle as oracle and move positions into liquidation territory
|
|
_reduceVaultOraclePrice(VAULT_ETH_USDC, 32);
|
|
vm.prank(bob);
|
|
IFluidVaultT1(VAULT_ETH_USDC).absorb();
|
|
|
|
// check should return available amount if wanted input amount is too big
|
|
(address vault, uint256 actualInAmt, uint256 outAmt, bool withAbsorb) = resolver.exactInput(USDC, ETH, 1e60);
|
|
assertEq(withAbsorb, true); // with absorb is true when amount can not be covered
|
|
assertEq(actualInAmt, 1529830467285); // 1.529.830,467285 USDC
|
|
assertEq(outAmt, 587977363017178865259); // ~587.977 ETH
|
|
assertEq(vault, VAULT_ETH_USDC);
|
|
|
|
// ratio out per in with absorb: 587977363017178865259 / 1529830467285 -> 384341517
|
|
// ratio out per in without absorb: 425403010045446193701 / 1079615031403 -> 394032129
|
|
// ratio without absorb is better (more out per in better)
|
|
|
|
// check should return exact amount if available
|
|
(vault, actualInAmt, outAmt, withAbsorb) = resolver.exactInput(USDC, ETH, 1e11);
|
|
assertEq(withAbsorb, false);
|
|
assertEq(actualInAmt, 1e11);
|
|
assertEq(outAmt, 39403212966812727290); // 1e11 * 425403010045446193701 / 1079615031403
|
|
assertEq(vault, VAULT_ETH_USDC);
|
|
|
|
// reduce oracle price more, causing more debt to go into absorb territory but when not running absorb,
|
|
// liquidate() without absorb reverts so now we get a withAbsorb = true case
|
|
_reduceVaultOraclePrice(VAULT_ETH_USDC, 1);
|
|
Structs.SwapData memory swapData = resolver.getSwapDataForVault(VAULT_ETH_USDC);
|
|
assertEq(swapData.inAmt, 0);
|
|
assertEq(swapData.outAmt, 0);
|
|
assertEq(swapData.inAmtWithAbsorb, 450215435882);
|
|
assertEq(swapData.outAmtWithAbsorb, 162574352971732671557);
|
|
assertEq(swapData.vault, VAULT_ETH_USDC);
|
|
|
|
(vault, actualInAmt, outAmt, withAbsorb) = resolver.exactInput(USDC, ETH, 1e11);
|
|
assertEq(withAbsorb, true);
|
|
assertEq(actualInAmt, 1e11);
|
|
assertEq(outAmt, 36110346295266268965); // 1e11 * 162574352971732671557 / 450215435882
|
|
assertEq(vault, VAULT_ETH_USDC);
|
|
|
|
// check execution is as expected
|
|
bytes memory liquidateCalldata = resolver.getSwapCalldata(
|
|
swapData.vault,
|
|
bob,
|
|
actualInAmt,
|
|
outAmt,
|
|
10000, // 1% slippage
|
|
withAbsorb
|
|
);
|
|
|
|
// executing the received calldata should trigger the liquidation and result in desired swap
|
|
address alice = 0x5B541d54e79052B34188db9A43F7b00ea8E2C4B1;
|
|
vm.prank(alice);
|
|
IERC20(USDC).approve(VAULT_ETH_USDC, type(uint256).max);
|
|
|
|
uint256 aliceUSDCBalanceBefore = IERC20(USDC).balanceOf(alice);
|
|
uint256 bobETHBalanceBefore = payable(bob).balance;
|
|
|
|
vm.prank(alice);
|
|
(bool success, ) = address(VAULT_ETH_USDC).call(liquidateCalldata);
|
|
assertTrue(success);
|
|
|
|
assertApproxEqAbs(aliceUSDCBalanceBefore - IERC20(USDC).balanceOf(alice), actualInAmt, 1);
|
|
assertApproxEqAbs(payable(bob).balance - bobETHBalanceBefore, outAmt, 1e8); // output amount might be a bit less
|
|
assertLt(payable(bob).balance - bobETHBalanceBefore, outAmt); // output should be LESS not more
|
|
}
|
|
|
|
function test_exactOutput() public {
|
|
// set a mockOracle as oracle and move positions into liquidation territory
|
|
_reduceVaultOraclePrice(VAULT_ETH_USDC, 32);
|
|
vm.prank(bob);
|
|
IFluidVaultT1(VAULT_ETH_USDC).absorb();
|
|
|
|
// check should return available amount if wanted output amount is too big
|
|
(address vault, uint256 inAmt, uint256 actualOutAmt, bool withAbsorb) = resolver.exactOutput(USDC, ETH, 1e60);
|
|
assertEq(withAbsorb, true); // with absorb is true when amount can not be covered
|
|
assertEq(inAmt, 1529830467285); // 1.529.830,467285 USDC
|
|
assertEq(actualOutAmt, 587977363017178865259); // ~587.977 ETH
|
|
assertEq(vault, VAULT_ETH_USDC);
|
|
|
|
// ratio in per out with absorb: 1529830467285 * 1e18 / 587977363017178865259 -> 2601852662
|
|
// ratio in per out without absorb: 1079615031403 * 1e18 / 425403010045446193701 -> 2537864109
|
|
// ratio without absorb is better (less in per out needed better)
|
|
|
|
// check should return exact amount if available
|
|
(vault, inAmt, actualOutAmt, withAbsorb) = resolver.exactOutput(USDC, ETH, 1e20);
|
|
assertEq(withAbsorb, false);
|
|
// 1e20 * 1079615031403 * 1e27 / 425403010045446193701 / 1e27 = 253786410981
|
|
// -> 1e20 * 2537864109818272644 / 1e27 = 253786410981
|
|
assertEq(inAmt, 253786410981);
|
|
assertEq(actualOutAmt, 1e20);
|
|
assertEq(vault, VAULT_ETH_USDC);
|
|
|
|
// reduce oracle price more, causing more debt to go into absorb territory but when not running absorb,
|
|
// liquidate() without absorb reverts so now we get a withAbsorb = true case
|
|
_reduceVaultOraclePrice(VAULT_ETH_USDC, 1);
|
|
Structs.SwapData memory swapData = resolver.getSwapDataForVault(VAULT_ETH_USDC);
|
|
assertEq(swapData.inAmt, 0);
|
|
assertEq(swapData.outAmt, 0);
|
|
assertEq(swapData.inAmtWithAbsorb, 450215435882);
|
|
assertEq(swapData.outAmtWithAbsorb, 162574352971732671557);
|
|
assertEq(swapData.vault, VAULT_ETH_USDC);
|
|
|
|
(vault, inAmt, actualOutAmt, withAbsorb) = resolver.exactOutput(USDC, ETH, 1e20);
|
|
assertEq(withAbsorb, true);
|
|
assertEq(inAmt, 276928942143); // 1e20 * 450215435882 / 162574352971732671557
|
|
assertEq(actualOutAmt, 1e20);
|
|
assertEq(vault, VAULT_ETH_USDC);
|
|
|
|
// check execution is as expected
|
|
bytes memory liquidateCalldata = resolver.getSwapCalldata(
|
|
swapData.vault,
|
|
bob,
|
|
inAmt,
|
|
actualOutAmt,
|
|
10000, // 1% slippage
|
|
withAbsorb
|
|
);
|
|
|
|
// executing the received calldata should trigger the liquidation and result in desired swap
|
|
address alice = 0x5B541d54e79052B34188db9A43F7b00ea8E2C4B1;
|
|
vm.prank(alice);
|
|
IERC20(USDC).approve(VAULT_ETH_USDC, type(uint256).max);
|
|
|
|
uint256 aliceUSDCBalanceBefore = IERC20(USDC).balanceOf(alice);
|
|
uint256 bobETHBalanceBefore = payable(bob).balance;
|
|
|
|
vm.prank(alice);
|
|
(bool success, ) = address(VAULT_ETH_USDC).call(liquidateCalldata);
|
|
assertTrue(success);
|
|
|
|
assertApproxEqAbs(aliceUSDCBalanceBefore - IERC20(USDC).balanceOf(alice), inAmt, 1);
|
|
assertApproxEqAbs(payable(bob).balance - bobETHBalanceBefore, actualOutAmt, 1e9); // output amount might be a bit less
|
|
assertLt(payable(bob).balance - bobETHBalanceBefore, actualOutAmt); // output should be LESS not more
|
|
}
|
|
|
|
function _reduceVaultOraclePrice(address vault, uint256 reductionInPercent) internal {
|
|
FluidVaultResolver.VaultEntireData memory vaultData = vaultResolver.getVaultEntireData(vault);
|
|
|
|
uint256 currentOraclePrice = vaultData.configs.oraclePriceOperate;
|
|
|
|
// set a mockOracle as oracle and move positions into liquidation territory
|
|
MockOracle oracle = new MockOracle();
|
|
|
|
vm.prank(GOVERNANCE);
|
|
FluidVaultT1Admin(vault).updateOracle(address(oracle));
|
|
|
|
oracle.setPrice((currentOraclePrice * (100 - reductionInPercent)) / 100); // simulate price drop
|
|
vaultData = vaultResolver.getVaultEntireData(vault);
|
|
assertLt(vaultData.configs.oraclePriceOperate, currentOraclePrice);
|
|
}
|
|
}
|