mirror of
https://github.com/Instadapp/fluid-contracts-public.git
synced 2024-07-29 21:57:37 +00:00
285 lines
16 KiB
Solidity
285 lines
16 KiB
Solidity
// SPDX-License-Identifier: BUSL-1.1
|
|
pragma solidity 0.8.21;
|
|
|
|
import { Variables } from "./variables.sol";
|
|
import { Structs } from "./structs.sol";
|
|
import { Structs as VaultResolverStructs } from "../vault/structs.sol";
|
|
import { IFluidVaultResolver } from "../vault/iVaultResolver.sol";
|
|
import { IFluidVaultT1 } from "../../../protocols/vault/interfaces/iVaultT1.sol";
|
|
|
|
/// @notice Resolver contract that helps in finding available token swaps through Fluid Vault liquidations.
|
|
contract FluidVaultLiquidationResolver is Variables, Structs {
|
|
/// @notice thrown if an input param address is zero
|
|
error FluidVaultLiquidationsResolver__AddressZero();
|
|
/// @notice thrown if an invalid param is given to a method
|
|
error FluidVaultLiquidationsResolver__InvalidParams();
|
|
|
|
/// @notice constructor sets the immutable vault resolver address
|
|
constructor(IFluidVaultResolver vaultResolver_) Variables(vaultResolver_) {
|
|
if (address(vaultResolver_) == address(0)) {
|
|
revert FluidVaultLiquidationsResolver__AddressZero();
|
|
}
|
|
}
|
|
|
|
/// @notice returns all token swap pairs available through Fluid Vault Liquidations
|
|
function getAllSwapPairs() public view returns (VaultData[] memory vaultDatas_) {
|
|
address[] memory vaultAddresses_ = VAULT_RESOLVER.getAllVaultsAddresses();
|
|
vaultDatas_ = new VaultData[](vaultAddresses_.length);
|
|
|
|
IFluidVaultT1.ConstantViews memory constants_;
|
|
for (uint256 i; i < vaultAddresses_.length; ++i) {
|
|
constants_ = IFluidVaultT1(vaultAddresses_[i]).constantsView();
|
|
vaultDatas_[i] = VaultData({
|
|
vault: vaultAddresses_[i],
|
|
tokenIn: constants_.borrowToken,
|
|
tokenOut: constants_.supplyToken
|
|
});
|
|
}
|
|
}
|
|
|
|
/// @notice returns the vault address for a certain `tokenIn_` swapped to a `tokenOut_`.
|
|
/// returns zero address if no vault is available for a given pair.
|
|
/// @dev for native token, send 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE.
|
|
function getVaultForSwap(address tokenIn_, address tokenOut_) public view returns (address vault_) {
|
|
address[] memory vaults_ = VAULT_RESOLVER.getAllVaultsAddresses();
|
|
|
|
IFluidVaultT1.ConstantViews memory constants_;
|
|
for (uint256 i; i < vaults_.length; ++i) {
|
|
constants_ = IFluidVaultT1(vaults_[i]).constantsView();
|
|
|
|
if (constants_.borrowToken == tokenIn_ && constants_.supplyToken == tokenOut_) {
|
|
return vaults_[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
/// @notice returns all available token pair swaps for any `tokensIn_` to any `tokensOut_` with the vault address.
|
|
/// @dev for native token, send 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE.
|
|
function getVaultsForSwap(
|
|
address[] calldata tokensIn_,
|
|
address[] calldata tokensOut_
|
|
) public view returns (VaultData[] memory vaultDatas_) {
|
|
uint256 maxCombinations_ = tokensIn_.length * tokensOut_.length;
|
|
|
|
VaultData[] memory allVaults_ = new VaultData[](maxCombinations_);
|
|
|
|
address[] memory vaultAddresses_ = VAULT_RESOLVER.getAllVaultsAddresses();
|
|
|
|
uint256 matches_;
|
|
uint256 index_;
|
|
|
|
IFluidVaultT1.ConstantViews memory constants_;
|
|
for (uint256 vi; vi < vaultAddresses_.length; ++vi) {
|
|
constants_ = IFluidVaultT1(vaultAddresses_[vi]).constantsView();
|
|
|
|
index_ = 0;
|
|
// for each vault, iterate over all possible input params token combinations
|
|
for (uint256 i; i < tokensIn_.length; ++i) {
|
|
for (uint256 j; j < tokensOut_.length; ++j) {
|
|
if (constants_.borrowToken == tokensIn_[i] && constants_.supplyToken == tokensOut_[j]) {
|
|
allVaults_[index_] = VaultData({
|
|
vault: vaultAddresses_[vi],
|
|
tokenIn: tokensIn_[i],
|
|
tokenOut: tokensOut_[j]
|
|
});
|
|
++matches_;
|
|
}
|
|
++index_;
|
|
}
|
|
}
|
|
}
|
|
|
|
vaultDatas_ = new VaultData[](matches_);
|
|
index_ = 0;
|
|
for (uint256 i; i < maxCombinations_; ++i) {
|
|
if (allVaults_[i].vault != address(0)) {
|
|
vaultDatas_[index_] = allVaults_[i];
|
|
++index_;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// @notice finds the total available swappable amount for a `tokenIn_` to `tokenOut_` swap, considering both a swap
|
|
/// that uses liquidation with absorb and without absorb. Sometimes with absorb can provide better swaps,
|
|
/// sometimes without absorb can provide better swaps. But available liquidity for "withAbsorb" amounts will
|
|
/// always be >= normal amounts.
|
|
/// @dev returned data can be fed into `getSwapCalldata` to prepare the tx that executes the swap.
|
|
/// @dev expected to be called with callStatic, although this method does not do any actual state changes anyway.
|
|
/// @dev for native token, send 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE.
|
|
function getSwapAvailable(address tokenIn_, address tokenOut_) public returns (SwapData memory swapData_) {
|
|
return getSwapDataForVault(getVaultForSwap(tokenIn_, tokenOut_));
|
|
}
|
|
|
|
/// @notice finds the total available swappable amount for any `tokensIn_` to any `tokesnOut_` swap, considering both
|
|
/// a swap that uses liquidation with absorb and without absorb. Sometimes with absorb can provide better swaps,
|
|
/// sometimes without absorb can provide better swaps. But available liquidity for "withAbsorb" amounts will
|
|
/// always be >= normal amounts. Token pairs that are not available will not be listed in returned SwapData array.
|
|
/// e.g. for tokensIn_: USDC & USDT and tokensOut_: ETH & wstETH, this would return any available token pair incl.
|
|
/// the available swappable amounts, so for USDC -> ETH, USDC -> wstETH, USDT -> ETH, USDT -> wstETH.
|
|
/// @dev returned data can be fed into `getSwapCalldata` to prepare the tx that executes the swap.
|
|
/// @dev expected to be called with callStatic, although this method does not do any actual state changes anyway.
|
|
/// @dev for native token, send 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE.
|
|
function getSwapsAvailable(
|
|
address[] calldata tokensIn_,
|
|
address[] calldata tokensOut_
|
|
) public returns (SwapData[] memory swapDatas_) {
|
|
VaultData[] memory vaults_ = getVaultsForSwap(tokensIn_, tokensOut_);
|
|
|
|
swapDatas_ = new SwapData[](vaults_.length);
|
|
|
|
for (uint256 i; i < vaults_.length; ++i) {
|
|
swapDatas_[i] = getSwapDataForVault(vaults_[i].vault);
|
|
}
|
|
}
|
|
|
|
/// @notice returns the calldata to execute a swap as found through this contract by triggering a vault liquidation.
|
|
/// `tokenInAmt_` must come from msg.sender, `tokenOutAmt_` goes to `receiver_`. If the input token is the
|
|
/// native token, msg.value must be sent along when triggering the actual call with the returned calldata.
|
|
/// @param vault_ vault address at which the liquidation is executed
|
|
/// @param receiver_ receiver address that the output token is sent to
|
|
/// @param tokenInAmt_ input token amount (debt token at vault)
|
|
/// @param tokenOutAmt_ expected output token amount (collateral token at vault)
|
|
/// @param slippage_ maximum allowed slippage for the expected output token amount. Reverts iIf received token out
|
|
/// amount is lower than this. in 1e4 percentage, e.g. 1% = 10000, 0.3% = 3000, 0.01% = 100, 0.0001% = 1.
|
|
/// @param withAbsorb_ set to true to trigger liquidation with executing `absorb()` first. Liquidity is >= when this
|
|
/// is set to true. Rate can be better with or without, check before via other methods.
|
|
/// @return calldata_ the calldata that can be used to trigger the liquidation call, resulting in the desired swap.
|
|
function getSwapCalldata(
|
|
address vault_,
|
|
address receiver_,
|
|
uint256 tokenInAmt_,
|
|
uint256 tokenOutAmt_,
|
|
uint256 slippage_,
|
|
bool withAbsorb_
|
|
) public pure returns (bytes memory calldata_) {
|
|
if (vault_ == address(0) || receiver_ == address(0)) {
|
|
revert FluidVaultLiquidationsResolver__AddressZero();
|
|
}
|
|
if (slippage_ >= 1e6 || tokenInAmt_ == 0 || tokenOutAmt_ == 0) {
|
|
revert FluidVaultLiquidationsResolver__InvalidParams();
|
|
}
|
|
|
|
uint256 colPerUnitDebt_ = (tokenOutAmt_ * 1e18) / tokenInAmt_;
|
|
colPerUnitDebt_ = (colPerUnitDebt_ * (1e6 - slippage_)) / 1e6; // e.g. 50 * 99% / 100% = 49.5
|
|
|
|
calldata_ = abi.encodeWithSelector(
|
|
IFluidVaultT1(vault_).liquidate.selector,
|
|
tokenInAmt_,
|
|
colPerUnitDebt_,
|
|
receiver_,
|
|
withAbsorb_
|
|
);
|
|
}
|
|
|
|
/// @notice returns the available swap (liquidation) amounts at a certain `vault_`, considering both
|
|
/// a swap that uses liquidation with absorb and without absorb. Sometimes with absorb can provide better swaps,
|
|
/// sometimes without absorb can provide better swaps. But available liquidity for "withAbsorb" amounts will
|
|
/// always be >= normal amounts.
|
|
/// @dev returned data can be fed into `getSwapCalldata` to prepare the tx that executes the swap.
|
|
/// @dev expected to be called with callStatic, although this method does not do any actual state changes anyway.
|
|
function getSwapDataForVault(address vault_) public returns (SwapData memory swapData_) {
|
|
if (vault_ == address(0)) {
|
|
return swapData_;
|
|
}
|
|
|
|
VaultResolverStructs.LiquidationStruct memory liquidationData_ = VAULT_RESOLVER.getVaultLiquidation(vault_, 0);
|
|
swapData_.vault = vault_;
|
|
swapData_.inAmt = liquidationData_.tokenInAmtOne;
|
|
swapData_.outAmt = liquidationData_.tokenOutAmtOne;
|
|
swapData_.inAmtWithAbsorb = liquidationData_.tokenInAmtTwo;
|
|
swapData_.outAmtWithAbsorb = liquidationData_.tokenOutAmtTwo;
|
|
}
|
|
|
|
/// @notice finds a swap from `tokenIn_` to `tokenOut_` for an exact input amount `inAmt_`. If available amount is
|
|
/// less then the desired input amount, it returns the available amount. Considers the best rate available
|
|
/// for mode with absorb and mode without absorb.
|
|
/// @dev returned data can be fed into `getSwapCalldata` to prepare the tx that executes the swap.
|
|
/// @param tokenIn_ input token (debt token at vault)
|
|
/// @param tokenOut_ output token (collateral token at vault)
|
|
/// @param inAmt_ exact input token amount that should be swapped to output token
|
|
/// @return vault_ vault address at which the swap is available.
|
|
/// @return actualInAmt_ actual input token amount. Equals `inAmt_`, but if less then the desired swap amount is
|
|
/// available, then the available amount is returned instead.
|
|
/// @return outAmt_ received output token amount for `actualInAmt_` of input token
|
|
/// @return withAbsorb_ flag for using mode "withAbsorb". Is set to true if a) liquidity without absorb would not
|
|
/// cover the desired `inAmt_` or if b) the rate of with absorb is better than without absorb.
|
|
function exactInput(
|
|
address tokenIn_,
|
|
address tokenOut_,
|
|
uint256 inAmt_
|
|
) public returns (address vault_, uint256 actualInAmt_, uint256 outAmt_, bool withAbsorb_) {
|
|
SwapData memory swapData_ = getSwapAvailable(tokenIn_, tokenOut_);
|
|
vault_ = swapData_.vault;
|
|
|
|
actualInAmt_ = inAmt_; // assume inAmt_ can be covered by available amount, var is updated otherwise
|
|
|
|
uint256 withAbsorbRatio_ = (swapData_.outAmtWithAbsorb * 1e27) / swapData_.inAmtWithAbsorb;
|
|
if (inAmt_ > swapData_.inAmt && swapData_.inAmtWithAbsorb > swapData_.inAmt) {
|
|
// with absorb has more liquidity
|
|
withAbsorb_ = true;
|
|
if (inAmt_ > swapData_.inAmtWithAbsorb) {
|
|
actualInAmt_ = swapData_.inAmtWithAbsorb; // can not cover full requested inAmt_, so set to available
|
|
outAmt_ = swapData_.outAmtWithAbsorb;
|
|
} else {
|
|
// inAmt_ fully covered by with absorb liquidation, get out amount
|
|
outAmt_ = (inAmt_ * withAbsorbRatio_) / 1e27;
|
|
}
|
|
} else {
|
|
// inAmt_ is covered by available liquidation with or without absorb, check which one has better ratio
|
|
uint256 withoutAbsorbRatio_ = (swapData_.outAmt * 1e27) / swapData_.inAmt;
|
|
if (withAbsorbRatio_ > withoutAbsorbRatio_) {
|
|
withAbsorb_ = true;
|
|
outAmt_ = (inAmt_ * withAbsorbRatio_) / 1e27;
|
|
} else {
|
|
outAmt_ = (inAmt_ * withoutAbsorbRatio_) / 1e27;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// @notice finds a swap from `tokenIn_` to `tokenOut_` for an exact output amount `outAmt_`. If available amount is
|
|
/// less then the desired output amount, it returns the available amount. Considers the best rate available
|
|
/// for mode with absorb and mode without absorb.
|
|
/// @dev returned data can be fed into `getSwapCalldata` to prepare the tx that executes the swap.
|
|
/// @param tokenIn_ input token (debt token at vault)
|
|
/// @param tokenOut_ output token (collateral token at vault)
|
|
/// @param outAmt_ exact output token amount that should be received as a result of the swap
|
|
/// @return vault_ vault address at which the swap is available.
|
|
/// @return inAmt_ required input token amount to receive `actualOutAmt_` of output token
|
|
/// @return actualOutAmt_ actual output token amount. Equals `outAmt_`, but if less then the desired swap amount is
|
|
/// available, then the available amount is returned instead
|
|
/// @return withAbsorb_ flag for using mode "withAbsorb". Is set to true if a) liquidity without absorb would not
|
|
/// cover the desired `outAmt_` or if b) the rate of with absorb is better than without absorb.
|
|
function exactOutput(
|
|
address tokenIn_,
|
|
address tokenOut_,
|
|
uint256 outAmt_
|
|
) public returns (address vault_, uint256 inAmt_, uint256 actualOutAmt_, bool withAbsorb_) {
|
|
SwapData memory swapData_ = getSwapAvailable(tokenIn_, tokenOut_);
|
|
vault_ = swapData_.vault;
|
|
|
|
actualOutAmt_ = outAmt_; // assume outAmt_ can be covered by available amount, var is updated otherwise
|
|
|
|
uint256 withAbsorbRatio_ = (swapData_.inAmtWithAbsorb * 1e27) / swapData_.outAmtWithAbsorb;
|
|
if (outAmt_ > swapData_.outAmt && swapData_.inAmtWithAbsorb > swapData_.inAmt) {
|
|
// with absorb has more liquidity
|
|
withAbsorb_ = true;
|
|
if (outAmt_ > swapData_.outAmtWithAbsorb) {
|
|
actualOutAmt_ = swapData_.outAmtWithAbsorb; // can not cover full requested inAmt_, so set to available
|
|
inAmt_ = swapData_.inAmtWithAbsorb;
|
|
} else {
|
|
// outAmt_ fully covered by with absorb liquidation, get in amount
|
|
inAmt_ = (outAmt_ * withAbsorbRatio_) / 1e27;
|
|
}
|
|
} else {
|
|
// outAmt_ is covered by available liquidation with or without absorb, check which one has better ratio
|
|
uint256 withoutAbsorbRatio_ = (swapData_.inAmt * 1e27) / swapData_.outAmt; // in per out
|
|
if (withAbsorbRatio_ < withoutAbsorbRatio_) {
|
|
withAbsorb_ = true;
|
|
inAmt_ = (outAmt_ * withAbsorbRatio_) / 1e27;
|
|
} else {
|
|
inAmt_ = (outAmt_ * withoutAbsorbRatio_) / 1e27;
|
|
}
|
|
}
|
|
}
|
|
}
|