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 13 commits
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
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,21 +56,21 @@ jobs:
- name: "Build the TypeChain bindings"
run: "yarn build:types"

- name: "Deploy HifiFlashUniswapV2"
- name: "Deploy CollateralFlashUniswapV2"
id: deploy
run: >-
yarn workspace @hifi/tasks hardhat
deploy:contract:hifi-flash-uniswap-v2 --network "${{ github.event.inputs.chain }}"
deploy:contract:collateral-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 HifiFlashUniswapV2"
- name: "Verify CollateralFlashUniswapV2"
run: >-
yarn workspace @hifi/flash-swap hardhat
verify "${{ steps.deploy.outputs.hifi-flash-uniswap-v2 }}" --network "${{ github.event.inputs.chain }}"
verify "${{ steps.deploy.outputs.collateral-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 }}"
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:underlying-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.underlying-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 }}"
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
16 changes: 11 additions & 5 deletions packages/errors/src/flashSwap.ts
Original file line number Diff line number Diff line change
@@ -1,6 +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 UnderlyingFlashUniswapV2Errors {
CallNotAuthorized = "UnderlyingFlashUniswapV2__CallNotAuthorized",
FlashBorrowOtherToken = "UnderlyingFlashUniswapV2__FlashBorrowOtherToken",
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 } 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
4 changes: 4 additions & 0 deletions packages/flash-swap/contracts/test/GodModeHToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ contract GodModeHToken is HToken {
function __godMode_setUnderlying(IErc20 newUnderlying) external {
underlying = newUnderlying;
}

function __godMode_setMaturity(uint256 newMaturity) external {
maturity = newMaturity;
}
}
Loading