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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: "Deploy: HifiFlashUniswapV2"
name: "Deploy: CollateralFlashUniswapV2"

env:
ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }}
Expand Down Expand Up @@ -56,7 +56,7 @@ jobs:
- name: "Build the TypeChain bindings"
run: "yarn build:types"

- name: "Deploy HifiFlashUniswapV2"
- name: "Deploy CollateralFlashUniswapV2"
id: deploy
run: >-
yarn workspace @hifi/tasks hardhat
Expand All @@ -67,7 +67,7 @@ jobs:
--confirmations "${{ github.event.inputs.confirmations }}"
--set-output true

- name: "Verify HifiFlashUniswapV2"
- name: "Verify CollateralFlashUniswapV2"
run: >-
yarn workspace @hifi/flash-swap hardhat
verify "${{ steps.deploy.outputs.hifi-flash-uniswap-v2 }}" --network "${{ github.event.inputs.chain }}"
Expand Down
76 changes: 76 additions & 0 deletions .github/workflows/deploy-underlying-flash-uniswap-v2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: "Deploy: UnderlyingFlashUniswapV2"

env:
ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }}
INFURA_API_KEY: ${{ secrets.INFURA_API_KEY }}
MNEMONIC: ${{ secrets.MNEMONIC }}

on:
workflow_dispatch:
inputs:
balance-sheet:
description: "Address of the BalanceSheet contract"
required: true
chain:
description: "Chain name in lowercase"
required: true
confirmations:
default: "2"
description: "Number of block confirmations to wait before attempting verification"
required: false
ref:
default: "main"
description: "Git ref to checkout"
required: false
uni-v2-factory:
description: "Address of the UniswapV2Factory contract"
required: true
uni-v2-pair-init-code-hash:
description: "Init code hash of the UniswapV2Pair contract"
required: true

jobs:
deploy-and-verify:
runs-on: "ubuntu-latest"
steps:
- name: "Check out the repo"
uses: "actions/checkout@v2"
with:
ref: ${{ github.event.inputs.ref }}

- name: "Setup Node.js"
uses: "actions/setup-node@v2"
with:
cache: "yarn"
node-version: "16"

- name: "Install the dependencies"
run: "yarn install --immutable"

- name: "Build the TypeScript packages"
run: "yarn build"

- name: "Compile the contracts and generate TypeChain bindings"
run: "yarn compile:sol"

- name: "Build the TypeChain bindings"
run: "yarn build:types"

- name: "Deploy UnderlyingFlashUniswapV2"
id: deploy
run: >-
yarn workspace @hifi/tasks hardhat
deploy:contract:hifi-flash-uniswap-v2 --network "${{ github.event.inputs.chain }}"
--balance-sheet "${{ github.event.inputs.balance-sheet }}"
--uni-v2-factory "${{ github.event.inputs.uni-v2-factory }}"
--uni-v2-pair-init-code-hash "${{ github.event.inputs.uni-v2-pair-init-code-hash }}"
--confirmations "${{ github.event.inputs.confirmations }}"
--set-output true

- name: "Verify UnderlyingFlashUniswapV2"
run: >-
yarn workspace @hifi/flash-swap hardhat
verify "${{ steps.deploy.outputs.hifi-flash-uniswap-v2 }}" --network "${{ github.event.inputs.chain }}"
"${{ github.event.inputs.balance-sheet }}"
"${{ github.event.inputs.uni-v2-factory }}"
"${{ github.event.inputs.uni-v2-pair-init-code-hash }}"
18 changes: 9 additions & 9 deletions packages/errors/src/flashSwap.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
export enum HifiFlashUniswapV2Errors {
CallNotAuthorized = "HifiFlashUniswapV2__CallNotAuthorized",
FlashBorrowCollateral = "HifiFlashUniswapV2__FlashBorrowCollateral",
InsufficientProfit = "HifiFlashUniswapV2__InsufficientProfit",
UnderlyingNotInPool = "HifiFlashUniswapV2__UnderlyingNotInPool",
export enum CollateralFlashUniswapV2Errors {
CallNotAuthorized = "CollateralFlashUniswapV2__CallNotAuthorized",
FlashBorrowCollateral = "CollateralFlashUniswapV2__FlashBorrowCollateral",
InsufficientProfit = "CollateralFlashUniswapV2__InsufficientProfit",
UnderlyingNotInPool = "CollateralFlashUniswapV2__UnderlyingNotInPool",
}

export enum HifiFlashUniswapV2UnderlyingErrors {
CallNotAuthorized = "HifiFlashUniswapV2Underlying__CallNotAuthorized",
FlashBorrowWrongToken = "HifiFlashUniswapV2Underlying__FlashBorrowWrongToken",
UnderlyingNotInPool = "HifiFlashUniswapV2Underlying__UnderlyingNotInPool",
export enum UnderlyingFlashUniswapV2Errors {
CallNotAuthorized = "UnderlyingFlashUniswapV2__CallNotAuthorized",
FlashBorrowWrongToken = "UnderlyingFlashUniswapV2__FlashBorrowWrongToken",
UnderlyingNotInPool = "UnderlyingFlashUniswapV2__UnderlyingNotInPool",
}
2 changes: 1 addition & 1 deletion packages/errors/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export { HifiPoolErrors, HifiPoolRegistryErrors, YieldSpaceErrors } from "./amm"
export { OwnableErrors } from "./external";

// flashSwap.ts
export { HifiFlashUniswapV2Errors, HifiFlashUniswapV2UnderlyingErrors } from "./flashSwap";
export { CollateralFlashUniswapV2Errors, UnderlyingFlashUniswapV2Errors } from "./flashSwap";

// protocol.ts
export {
Expand Down
12 changes: 8 additions & 4 deletions packages/flash-swap/.gitignore
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
# types
src/types/*
!src/types/Erc20.d.ts
!src/types/HifiFlashUniswapV2.d.ts
!src/types/CollateralFlashUniswapV2.d.ts
!src/types/ICollateralFlashUniswapV2.d.ts
!src/types/IErc20.d.ts
!src/types/IHifiFlashUniswapV2.d.ts
!src/types/IUnderlyingFlashUniswapV2.d.ts
!src/types/IUniswapV2Callee.d.ts
!src/types/IUniswapV2Pair.d.ts
!src/types/UnderlyingFlashUniswapV2.d.ts
!src/types/UniswapV2Pair.d.ts
!src/types/factories/Erc20__factory.ts
!src/types/factories/HifiFlashUniswapV2__factory.ts
!src/types/factories/CollateralFlashUniswapV2__factory.ts
!src/types/factories/IErc20__factory.ts
!src/types/factories/IHifiFlashUniswapV2__factory.ts
!src/types/factories/ICollateralFlashUniswapV2__factory.ts
!src/types/factories/IUnderlyingFlashUniswapV2__factory.ts
!src/types/factories/IUniswapV2Callee__factory.ts
!src/types/factories/IUniswapV2Pair__factory.ts
!src/types/factories/UnderlyingFlashUniswapV2__factory.ts
!src/types/factories/UniswapV2Pair__factory.ts
9 changes: 5 additions & 4 deletions packages/flash-swap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ themselves; the latter, the smart contract ABIs and the TypeChain bindings.

You are not supposed to import the smart contracts. Instead, you should interact with the Uniswap pool
directly. For example, with the [UniswapV2Pair](https://github.com/Uniswap/v2-core/blob/v1.0.1/contracts/UniswapV2Pair.sol)
contract you would call the `swap` function. Then Uniswap will forward the call to `HifiFlashUniswapV2`. You can read more about flash
swaps work in Uniswap on [docs.uniswap.org](https://docs.uniswap.org/protocol/V2/concepts/core-concepts/flash-swaps).
contract you would call the `swap` function. Then, Uniswap will forward the call to the `CollateralFlashUniswapV2`
contract. You can read more about flash swaps work in Uniswap on
[docs.uniswap.org](https://docs.uniswap.org/protocol/V2/concepts/core-concepts/flash-swaps).

### JavaScript

Expand All @@ -41,13 +42,13 @@ import { getDefaultProvider } from "@ethersproject/providers";
import { parseUnits } from "@ethersproject/units";
import type { UniswapV2Pair__factory } from "@hifi/flash-swap/dist/types/factories/UniswapV2Pair__factory";

async function flashSwap() {
async function collateralFlashSwap() {
const defaultProvider = getDefaultProvider();
const pair = UniswapV2Pair__factory("0x...", defaultProvider);

const token0Amount = parseUnits("100", 18);
const token1Amount = parseUnits("0", 18);
const to = "0x..."; // Address of HifiFlashUniswapV2, get it from https://docs.hifi.finance
const to = "0x..."; // Address of CollateralFlashUniswapV2, get it from https://docs.hifi.finance

const borrower = "0x...";
const hToken = "0x...";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,40 +7,45 @@ import "@hifi/protocol/contracts/core/balanceSheet/IBalanceSheetV1.sol";
import "@hifi/protocol/contracts/core/balanceSheet/SBalanceSheetV1.sol";
import "@hifi/protocol/contracts/core/hToken/IHToken.sol";

import "./HifiFlashUniswapV2Utils.sol";
import "./IHifiFlashUniswapV2.sol";
import "./FlashUtils.sol";
import "./ICollateralFlashUniswapV2.sol";
import "./IUniswapV2Pair.sol";

/// @notice Emitted when the caller is not the Uniswap V2 pair contract.
error HifiFlashUniswapV2__CallNotAuthorized(address caller);
error CollateralFlashUniswapV2__CallNotAuthorized(address caller);

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

/// @notice Emitted when the liquidation does not yield a sufficient profit.
error HifiFlashUniswapV2__InsufficientProfit(
error CollateralFlashUniswapV2__InsufficientProfit(
uint256 seizedCollateralAmount,
uint256 repayCollateralAmount,
uint256 minProfit
);

/// @notice Emitted when neither the token0 nor the token1 is the underlying.
error HifiFlashUniswapV2__UnderlyingNotInPool(IUniswapV2Pair pair, address token0, address token1, IErc20 underlying);
error CollateralFlashUniswapV2__UnderlyingNotInPool(
IUniswapV2Pair pair,
address token0,
address token1,
IErc20 underlying
);

/// @title HifiFlashUniswapV2
/// @title CollateralFlashUniswapV2
/// @author Hifi
contract HifiFlashUniswapV2 is IHifiFlashUniswapV2 {
contract CollateralFlashUniswapV2 is ICollateralFlashUniswapV2 {
using SafeErc20 for IErc20;

/// PUBLIC STORAGE ///

/// @inheritdoc IHifiFlashUniswapV2
/// @inheritdoc ICollateralFlashUniswapV2
IBalanceSheetV1 public override balanceSheet;

/// @inheritdoc IHifiFlashUniswapV2
/// @inheritdoc ICollateralFlashUniswapV2
address public override uniV2Factory;

/// @inheritdoc IHifiFlashUniswapV2
/// @inheritdoc ICollateralFlashUniswapV2
bytes32 public override uniV2PairInitCodeHash;

/// CONSTRUCTOR ///
Expand All @@ -56,7 +61,7 @@ contract HifiFlashUniswapV2 is IHifiFlashUniswapV2 {

/// PUBLIC CONSTANT FUNCTIONS ////

/// @inheritdoc IHifiFlashUniswapV2
/// @inheritdoc ICollateralFlashUniswapV2
function getCollateralAndUnderlyingAmount(
IUniswapV2Pair pair,
uint256 amount0,
Expand All @@ -67,22 +72,22 @@ contract HifiFlashUniswapV2 is IHifiFlashUniswapV2 {
address token1 = pair.token1();
if (token0 == address(underlying)) {
if (amount1 > 0) {
revert HifiFlashUniswapV2__FlashBorrowCollateral(amount1);
revert CollateralFlashUniswapV2__FlashBorrowCollateral(amount1);
}
collateral = IErc20(token1);
underlyingAmount = amount0;
} else if (token1 == address(underlying)) {
if (amount0 > 0) {
revert HifiFlashUniswapV2__FlashBorrowCollateral(amount0);
revert CollateralFlashUniswapV2__FlashBorrowCollateral(amount0);
}
collateral = IErc20(token0);
underlyingAmount = amount1;
} else {
revert HifiFlashUniswapV2__UnderlyingNotInPool(pair, token0, token1, underlying);
revert CollateralFlashUniswapV2__UnderlyingNotInPool(pair, token0, token1, underlying);
}
}

/// @inheritdoc IHifiFlashUniswapV2
/// @inheritdoc ICollateralFlashUniswapV2
function getRepayCollateralAmount(
IUniswapV2Pair pair,
IErc20 underlying,
Expand Down Expand Up @@ -146,19 +151,14 @@ contract HifiFlashUniswapV2 is IHifiFlashUniswapV2 {
// Check that the caller is a genuine UniswapV2Pair contract.
if (
msg.sender !=
HifiFlashUniswapV2Utils.pairFor(
uniV2Factory,
uniV2PairInitCodeHash,
address(vars.underlying),
address(vars.collateral)
)
FlashUtils.pairFor(uniV2Factory, uniV2PairInitCodeHash, address(vars.underlying), address(vars.collateral))
) {
revert HifiFlashUniswapV2__CallNotAuthorized(msg.sender);
revert CollateralFlashUniswapV2__CallNotAuthorized(msg.sender);
}

// Mint hTokens and liquidate the borrower.
vars.mintedHTokenAmount = HifiFlashUniswapV2Utils.mintHTokensInternal(vars.bond, vars.underlyingAmount);
vars.seizedCollateralAmount = HifiFlashUniswapV2Utils.liquidateBorrowInternal(
vars.mintedHTokenAmount = FlashUtils.mintHTokensInternal(vars.bond, vars.underlyingAmount);
vars.seizedCollateralAmount = FlashUtils.liquidateBorrowInternal(
balanceSheet,
vars.borrower,
vars.bond,
Expand All @@ -173,7 +173,7 @@ contract HifiFlashUniswapV2 is IHifiFlashUniswapV2 {
vars.underlyingAmount
);
if (vars.seizedCollateralAmount <= vars.repayCollateralAmount + vars.minProfit) {
revert HifiFlashUniswapV2__InsufficientProfit(
revert CollateralFlashUniswapV2__InsufficientProfit(
vars.seizedCollateralAmount,
vars.repayCollateralAmount,
vars.minProfit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,9 @@ pragma solidity >=0.8.4;
import "@paulrberg/contracts/token/erc20/IErc20.sol";
import "@hifi/protocol/contracts/core/balanceSheet/IBalanceSheetV1.sol";

/// @title HifiFlashUniswapV2
/// @title FlashUtils
/// @author Hifi
library HifiFlashUniswapV2Utils {
/// @dev The init code hash of the UniswapV2Pair contract. The same for all Uniswap v2 deployments.
bytes32 public constant UNI_V2_PAIR_INIT_CODE_HASH =
0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f;

library FlashUtils {
/// @dev Calculates the CREATE2 address for a pair without making any external calls.
function pairFor(
address uniV2Factory,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Callee.sol";

import "./IUniswapV2Pair.sol";

/// @title IHifiFlashUniswapV2
/// @title ICollateralFlashUniswapV2
/// @author Hifi
/// @notice Integration of Uniswap V2 flash swaps for liquidating underwater accounts in Hifi.
interface IHifiFlashUniswapV2 is IUniswapV2Callee {
/// that are collateralized with non-underlying tokens.
interface ICollateralFlashUniswapV2 is IUniswapV2Callee {
/// EVENTS ///

event FlashLiquidateBorrow(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Callee.sol";

import "./IUniswapV2Pair.sol";

/// @title IHifiFlashUniswapV2Underlying
/// @title IUnderlyingFlashUniswapV2
/// @author Hifi
/// @notice Integration of Uniswap V2 flash swaps for liquidating underwater accounts in Hifi
/// that are collateralized with underlying tokens.
interface IHifiFlashUniswapV2Underlying is IUniswapV2Callee {
interface IUnderlyingFlashUniswapV2 is IUniswapV2Callee {
/// EVENTS ///

event FlashLiquidateBorrow(
Expand Down