Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(flash-swap): support liquidating vaults with underlying as collateral #64

Merged
merged 16 commits into from
Nov 5, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
f222278
feat(flash-swap): implement liquidating underlying as collateral
scorpion9979 Nov 2, 2021
ef7a6d3
feat(flash-swap): add tests for underlying as collateral
scorpion9979 Nov 3, 2021
254bafb
refactor(flash-swap): expect no profit + better error naming
scorpion9979 Nov 3, 2021
4e3f8cb
feat(tasks): add deploy task for underlying as collaeral flash-swap
scorpion9979 Nov 3, 2021
59db094
feat(flash-swap): repay USDC liquidation 0.3% Uniswap fee from bot wa…
scorpion9979 Nov 3, 2021
c238f40
feat(flash-swap): add test to increase coverage
scorpion9979 Nov 3, 2021
ffda179
refactor: move common logic in "HifiFlashUniswapV2.sol"
PaulRBerg Nov 4, 2021
b02aac3
refactor(flash-swap): "HifiFlashUniswapV2" into "CollateralFlashUnisw…
PaulRBerg Nov 4, 2021
5a2e0dc
refactor(flash-swap): use only "underlying" in var names in "Underlyi…
PaulRBerg Nov 4, 2021
e1ed460
test(flash-swap): refactor variable names in "UnderlyingFlashUniswapV…
PaulRBerg Nov 4, 2021
601f296
ci: fix task names in flash swap deployers
PaulRBerg Nov 4, 2021
4da7d20
chore(flash-swap): order variables alphabetically
PaulRBerg Nov 4, 2021
613df07
fix(flash-swap): properly compare "repayUnderlyingAmount" to "seizedU…
PaulRBerg Nov 4, 2021
d86fc14
fix(flash-swap): transfer profit instead of subsidized amount
PaulRBerg Nov 5, 2021
190e0ec
test(flash-swap): repay underlying equal to seized underlying
scorpion9979 Nov 5, 2021
5ced780
test(flash-swap): flash borrow other token when token order is changed
PaulRBerg Nov 5, 2021
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
12 changes: 6 additions & 6 deletions packages/amm/test/integration/hifiPool/effects/buyHToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ async function testBuyHToken(
const buyer: SignerWithAddress = this.signers.alice;

// Call the buyHToken function and calculate the delta in the hToken balance.
const preHTokenBalance: BigNumber = await this.contracts.hToken.balanceOf(buyer.address);
const preUnderlyingBalance: BigNumber = await this.contracts.underlying.balanceOf(buyer.address);
const oldHTokenBalance: BigNumber = await this.contracts.hToken.balanceOf(buyer.address);
const oldUnderlyingBalance: BigNumber = await this.contracts.underlying.balanceOf(buyer.address);
await this.contracts.hifiPool.connect(buyer).buyHToken(buyer.address, hTokenOut);
const postHTokenBalance: BigNumber = await this.contracts.hToken.balanceOf(buyer.address);
const postUnderlyingBalance: BigNumber = await this.contracts.underlying.balanceOf(buyer.address);
const newHTokenBalance: BigNumber = await this.contracts.hToken.balanceOf(buyer.address);
const newUnderlyingBalance: BigNumber = await this.contracts.underlying.balanceOf(buyer.address);

const actualHTokenOut: BigNumber = postHTokenBalance.sub(preHTokenBalance);
const actualUnderlyingIn: BigNumber = preUnderlyingBalance.sub(postUnderlyingBalance);
const actualHTokenOut: BigNumber = newHTokenBalance.sub(oldHTokenBalance);
const actualUnderlyingIn: BigNumber = oldUnderlyingBalance.sub(newUnderlyingBalance);

// Calculate the expected value of the delta using the local mirror implementation.
const timeToMaturity: BigNumber = H_TOKEN_MATURITY_ONE_YEAR.sub(await getLatestBlockTimestamp()).mul(SCALE);
Expand Down
12 changes: 6 additions & 6 deletions packages/amm/test/integration/hifiPool/effects/buyUnderlying.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ async function testBuyUnderlying(
const buyer: SignerWithAddress = this.signers.alice;

// Call the buyUnderlying function and calculate the delta in the token balances.
const preHTokenBalance: BigNumber = await this.contracts.hToken.balanceOf(buyer.address);
const preUnderlyingBalance: BigNumber = await this.contracts.underlying.balanceOf(buyer.address);
const oldHTokenBalance: BigNumber = await this.contracts.hToken.balanceOf(buyer.address);
const oldUnderlyingBalance: BigNumber = await this.contracts.underlying.balanceOf(buyer.address);
await this.contracts.hifiPool.connect(buyer).buyUnderlying(buyer.address, underlyingOut);
const postHTokenBalance: BigNumber = await this.contracts.hToken.balanceOf(buyer.address);
const postUnderlyingBalance: BigNumber = await this.contracts.underlying.balanceOf(buyer.address);
const newHTokenBalance: BigNumber = await this.contracts.hToken.balanceOf(buyer.address);
const newUnderlyingBalance: BigNumber = await this.contracts.underlying.balanceOf(buyer.address);

const actualHTokenIn: BigNumber = preHTokenBalance.sub(postHTokenBalance);
const actualUnderlyingOut: BigNumber = postUnderlyingBalance.sub(preUnderlyingBalance);
const actualHTokenIn: BigNumber = oldHTokenBalance.sub(newHTokenBalance);
const actualUnderlyingOut: BigNumber = newUnderlyingBalance.sub(oldUnderlyingBalance);

// Calculate the expected value of the delta using the local mirror implementation.
const timeToMaturity: BigNumber = H_TOKEN_MATURITY_ONE_YEAR.sub(await getLatestBlockTimestamp()).mul(SCALE);
Expand Down
12 changes: 6 additions & 6 deletions packages/amm/test/integration/hifiPool/effects/sellHToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ async function testSellHToken(
const seller: SignerWithAddress = this.signers.alice;

// Call the sellHToken function and calculate the delta in the token balances.
const preHTokenBalance: BigNumber = await this.contracts.hToken.balanceOf(seller.address);
const preUnderlyingBalance: BigNumber = await this.contracts.underlying.balanceOf(seller.address);
const oldHTokenBalance: BigNumber = await this.contracts.hToken.balanceOf(seller.address);
const oldUnderlyingBalance: BigNumber = await this.contracts.underlying.balanceOf(seller.address);
await this.contracts.hifiPool.connect(seller).sellHToken(seller.address, hTokenIn);
const postUnderlyingBalance: BigNumber = await this.contracts.underlying.balanceOf(seller.address);
const postHTokenBalance: BigNumber = await this.contracts.hToken.balanceOf(seller.address);
const newUnderlyingBalance: BigNumber = await this.contracts.underlying.balanceOf(seller.address);
const newHTokenBalance: BigNumber = await this.contracts.hToken.balanceOf(seller.address);

const actualUnderlyingOut: BigNumber = postUnderlyingBalance.sub(preUnderlyingBalance);
const actualHTokenIn: BigNumber = preHTokenBalance.sub(postHTokenBalance);
const actualUnderlyingOut: BigNumber = newUnderlyingBalance.sub(oldUnderlyingBalance);
const actualHTokenIn: BigNumber = oldHTokenBalance.sub(newHTokenBalance);

// Calculate the expected value of the delta using the local mirror implementation.
const timeToMaturity: BigNumber = H_TOKEN_MATURITY_ONE_YEAR.sub(await getLatestBlockTimestamp()).mul(SCALE);
Expand Down
12 changes: 6 additions & 6 deletions packages/amm/test/integration/hifiPool/effects/sellUnderlying.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ async function testSellUnderlying(
const seller: SignerWithAddress = this.signers.alice;

// Call the sellUnderlying function and calculate the delta in the hToken balance.
const preHTokenBalance: BigNumber = await this.contracts.hToken.balanceOf(seller.address);
const preUnderlyingBalance: BigNumber = await this.contracts.underlying.balanceOf(seller.address);
const oldHTokenBalance: BigNumber = await this.contracts.hToken.balanceOf(seller.address);
const oldUnderlyingBalance: BigNumber = await this.contracts.underlying.balanceOf(seller.address);
await this.contracts.hifiPool.connect(seller).sellUnderlying(seller.address, underlyingIn);
const postHTokenBalance: BigNumber = await this.contracts.hToken.balanceOf(seller.address);
const postUnderlyingBalance: BigNumber = await this.contracts.underlying.balanceOf(seller.address);
const newHTokenBalance: BigNumber = await this.contracts.hToken.balanceOf(seller.address);
const newUnderlyingBalance: BigNumber = await this.contracts.underlying.balanceOf(seller.address);

const actualHTokenOut: BigNumber = postHTokenBalance.sub(preHTokenBalance);
const actualUnderlyingIn: BigNumber = preUnderlyingBalance.sub(postUnderlyingBalance);
const actualHTokenOut: BigNumber = newHTokenBalance.sub(oldHTokenBalance);
const actualUnderlyingIn: BigNumber = oldUnderlyingBalance.sub(newUnderlyingBalance);

// Calculate the expected value of the delta using the local mirror implementation.
const timeToMaturity: BigNumber = H_TOKEN_MATURITY_ONE_YEAR.sub(await getLatestBlockTimestamp()).mul(SCALE);
Expand Down
2 changes: 1 addition & 1 deletion packages/errors/src/flashSwap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ export enum CollateralFlashUniswapV2Errors {

export enum UnderlyingFlashUniswapV2Errors {
CallNotAuthorized = "UnderlyingFlashUniswapV2__CallNotAuthorized",
FlashBorrowWrongToken = "UnderlyingFlashUniswapV2__FlashBorrowWrongToken",
FlashBorrowOtherToken = "UnderlyingFlashUniswapV2__FlashBorrowOtherToken",
UnderlyingNotInPool = "UnderlyingFlashUniswapV2__UnderlyingNotInPool",
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import "./IUniswapV2Pair.sol";
error CollateralFlashUniswapV2__CallNotAuthorized(address caller);

/// @notice Emitted when the flash borrowed asset is the collateral instead of the underlying.
error CollateralFlashUniswapV2__FlashBorrowCollateral(uint256 collateralAmount);
error CollateralFlashUniswapV2__FlashBorrowCollateral();

/// @notice Emitted when the liquidation does not yield a sufficient profit.
error CollateralFlashUniswapV2__InsufficientProfit(
Expand Down Expand Up @@ -72,13 +72,13 @@ contract CollateralFlashUniswapV2 is ICollateralFlashUniswapV2 {
address token1 = pair.token1();
if (token0 == address(underlying)) {
if (amount1 > 0) {
revert CollateralFlashUniswapV2__FlashBorrowCollateral(amount1);
revert CollateralFlashUniswapV2__FlashBorrowCollateral();
}
collateral = IErc20(token1);
underlyingAmount = amount0;
} else if (token1 == address(underlying)) {
if (amount0 > 0) {
revert CollateralFlashUniswapV2__FlashBorrowCollateral(amount0);
revert CollateralFlashUniswapV2__FlashBorrowCollateral();
}
collateral = IErc20(token0);
underlyingAmount = amount1;
Expand Down Expand Up @@ -188,7 +188,7 @@ contract CollateralFlashUniswapV2 is ICollateralFlashUniswapV2 {
vars.collateral.safeTransfer(sender, vars.profitCollateralAmount);

// Emit an event.
emit FlashLiquidateBorrow(
emit FlashSwapCollateralAndLiquidateBorrow(
sender,
vars.borrower,
address(vars.bond),
Expand Down
7 changes: 4 additions & 3 deletions packages/flash-swap/contracts/uniswap-v2/FlashUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity >=0.8.4;

import "@paulrberg/contracts/token/erc20/IErc20.sol";
import "@hifi/protocol/contracts/core/balanceSheet/IBalanceSheetV1.sol";
import "@hifi/protocol/contracts/core/hToken/IHToken.sol";

/// @title FlashUtils
/// @author Hifi
Expand Down Expand Up @@ -73,11 +74,11 @@ library FlashUtils {
}

// Mint hTokens.
uint256 preHTokenBalance = bond.balanceOf(address(this));
uint256 oldHTokenBalance = bond.balanceOf(address(this));
bond.supplyUnderlying(underlyingAmount);
uint256 postHTokenBalance = bond.balanceOf(address(this));
uint256 newHTokenBalance = bond.balanceOf(address(this));
unchecked {
mintedHTokenAmount = postHTokenBalance - preHTokenBalance;
mintedHTokenAmount = newHTokenBalance - oldHTokenBalance;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import "./IUniswapV2Pair.sol";
interface ICollateralFlashUniswapV2 is IUniswapV2Callee {
/// EVENTS ///

event FlashLiquidateBorrow(
event FlashSwapCollateralAndLiquidateBorrow(
address indexed liquidator,
address indexed borrower,
address indexed bond,
Expand Down Expand Up @@ -48,23 +48,23 @@ interface ICollateralFlashUniswapV2 is IUniswapV2Callee {
IErc20 underlying
) external view returns (IErc20 collateral, uint256 underlyingAmount);

/// @notice Calculates the amount that must be repaid to Uniswap. The formula applied is:
/// @notice Calculates the amount of collateral that must be repaid to Uniswap. The formula applied is:
///
/// (collateralReserves * underlyingAmount) * 1000
/// collateralRepayAmount = --------------------------------------------
/// repayCollateralAmount = --------------------------------------------
/// (usdcReserves - underlyingAmount) * 997
///
/// @dev See "getAmountIn" and "getAmountOut" in UniswapV2Library.sol. Flash swaps that are repaid via the
/// corresponding pair token is akin to a normal swap, so the 0.3% LP fee applies.
/// @param pair The Uniswap V2 pair contract.
/// @param underlying The address of the underlying contract.
/// @param underlyingAmount The amount of underlying flash borrowed.
/// @return collateralRepayAmount The minimum amount of collateral that must be repaid.
/// @return repayCollateralAmount The minimum amount of collateral that must be repaid.
function getRepayCollateralAmount(
IUniswapV2Pair pair,
IErc20 underlying,
uint256 underlyingAmount
) external view returns (uint256 collateralRepayAmount);
) external view returns (uint256 repayCollateralAmount);

/// @notice The address of the UniswapV2Factory contract.
function uniV2Factory() external view returns (address);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@ import "./IUniswapV2Pair.sol";
interface IUnderlyingFlashUniswapV2 is IUniswapV2Callee {
/// EVENTS ///

event FlashLiquidateBorrow(
event FlashSwapUnderlyingAndLiquidateBorrow(
address indexed liquidator,
address indexed borrower,
address indexed bond,
uint256 underlyingAmount,
uint256 seizedCollateralAmount,
uint256 repayCollateralAmount
uint256 seizedUnderlyingAmount,
uint256 repayUnderlyingAmount
);

/// CONSTANT FUNCTIONS ///

/// @notice The unique BalanceSheet contract associated with this contract.
function balanceSheet() external view returns (IBalanceSheetV1);

/// @notice Compares the token addresses to find the collateral address and the underlying amount.
/// @notice Compares the token addresses to find the other token and the underlying amount.
/// @dev See this StackExchange post: https://ethereum.stackexchange.com/q/102670/24693.
///
/// Requirements:
Expand All @@ -39,26 +39,26 @@ interface IUnderlyingFlashUniswapV2 is IUniswapV2Callee {
/// @param amount0 The amount of token0.
/// @param amount1 The amount of token1.
/// @param underlying The address of the underlying contract.
/// @return collateral The collateral contract.
/// @return otherToken The address of the other token contract.
/// @return underlyingAmount The amount of underlying flash borrowed.
function getCollateralAndUnderlyingAmount(
function getOtherTokenAndUnderlyingAmount(
IUniswapV2Pair pair,
uint256 amount0,
uint256 amount1,
IErc20 underlying
) external view returns (IErc20 collateral, uint256 underlyingAmount);
) external view returns (IErc20 otherToken, uint256 underlyingAmount);

/// @notice Calculates the amount that must be repaid to Uniswap. The formula applied is:
/// @notice Calculates the amount of underlying that must be repaid to Uniswap. The formula applied is:
///
/// underlyingAmount * 1000
/// collateralRepayAmount = ---------------------
/// repayUnderlyingAmount = ---------------------
/// 997
///
/// @dev See "getAmountIn" and "getAmountOut" in UniswapV2Library.sol. Flash swaps are repaid via the
/// same borrowed pair token and the 0.3% LP fee applies.
/// @param underlyingAmount The amount of underlying flash borrowed.
/// @return collateralRepayAmount The minimum amount of collateral that must be repaid.
function getRepayCollateralAmount(uint256 underlyingAmount) external view returns (uint256 collateralRepayAmount);
/// @return repayUnderlyingAmount The minimum amount of underlying that must be repaid.
function getRepayUnderlyingAmount(uint256 underlyingAmount) external view returns (uint256 repayUnderlyingAmount);

/// @notice The address of the UniswapV2Factory contract.
function uniV2Factory() external view returns (address);
Expand Down
Loading