From f222278342b2f78ac125b6cbd3c58d6d97152efa Mon Sep 17 00:00:00 2001 From: scorpion9979 Date: Wed, 3 Nov 2021 01:38:20 +0300 Subject: [PATCH 01/16] feat(flash-swap): implement liquidating underlying as collateral --- .../HifiFlashUniswapV2Underlying.sol | 261 ++++++++++++++++++ .../IHifiFlashUniswapV2Underlying.sol | 68 +++++ 2 files changed, 329 insertions(+) create mode 100644 packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2Underlying.sol create mode 100644 packages/flash-swap/contracts/uniswap-v2/IHifiFlashUniswapV2Underlying.sol diff --git a/packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2Underlying.sol b/packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2Underlying.sol new file mode 100644 index 00000000..654dceda --- /dev/null +++ b/packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2Underlying.sol @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +pragma solidity >=0.8.4; + +import "@paulrberg/contracts/token/erc20/IErc20.sol"; +import "@paulrberg/contracts/token/erc20/SafeErc20.sol"; +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 "./IHifiFlashUniswapV2Underlying.sol"; +import "./IUniswapV2Pair.sol"; + +/// @notice Emitted when the caller is not the Uniswap V2 pair contract. +error HifiFlashUniswapV2Underlying__CallNotAuthorized(address caller); + +/// @notice Emitted when the flash borrowed asset is the collateral instead of the underlying. +error HifiFlashUniswapV2Underlying__FlashBorrowCollateral(uint256 collateralAmount); + +/// @notice Emitted when the liquidation does not yield a sufficient profit. +error HifiFlashUniswapV2Underlying__InsufficientProfit( + uint256 seizedCollateralAmount, + uint256 repayCollateralAmount, + uint256 minProfit +); + +/// @notice Emitted when neither the token0 nor the token1 is the underlying. +error HifiFlashUniswapV2Underlying__UnderlyingNotInPool( + IUniswapV2Pair pair, + address token0, + address token1, + IErc20 underlying +); + +/// @title HifiFlashUniswapV2Underlying +/// @author Hifi +contract HifiFlashUniswapV2Underlying is IHifiFlashUniswapV2Underlying { + using SafeErc20 for IErc20; + + /// PUBLIC STORAGE /// + + /// @inheritdoc IHifiFlashUniswapV2Underlying + IBalanceSheetV1 public override balanceSheet; + + /// @inheritdoc IHifiFlashUniswapV2Underlying + address public override uniV2Factory; + + /// @inheritdoc IHifiFlashUniswapV2Underlying + bytes32 public override uniV2PairInitCodeHash; + + /// CONSTRUCTOR /// + constructor( + IBalanceSheetV1 balanceSheet_, + address uniV2Factory_, + bytes32 uniV2PairInitCodeHash_ + ) { + balanceSheet = IBalanceSheetV1(balanceSheet_); + uniV2Factory = uniV2Factory_; + uniV2PairInitCodeHash = uniV2PairInitCodeHash_; + } + + /// PUBLIC CONSTANT FUNCTIONS //// + + /// @inheritdoc IHifiFlashUniswapV2Underlying + function getCollateralAndUnderlyingAmount( + IUniswapV2Pair pair, + uint256 amount0, + uint256 amount1, + IErc20 underlying + ) public view override returns (IErc20 collateral, uint256 underlyingAmount) { + address token0 = pair.token0(); + address token1 = pair.token1(); + if (token0 == address(underlying)) { + if (amount1 > 0) { + revert HifiFlashUniswapV2Underlying__FlashBorrowCollateral(amount1); + } + collateral = IErc20(token0); + underlyingAmount = amount0; + } else if (token1 == address(underlying)) { + if (amount0 > 0) { + revert HifiFlashUniswapV2Underlying__FlashBorrowCollateral(amount0); + } + collateral = IErc20(token1); + underlyingAmount = amount1; + } else { + revert HifiFlashUniswapV2Underlying__UnderlyingNotInPool(pair, token0, token1, underlying); + } + } + + /// @inheritdoc IHifiFlashUniswapV2Underlying + function getRepayCollateralAmount(uint256 underlyingAmount) + public + pure + override + returns (uint256 repayCollateralAmount) + { + // Note that we can safely use unchecked arithmetic here because the UniswapV2Pair.sol contract performs + // sanity checks on the amounts before calling the current contract. + unchecked { + uint256 numerator = underlyingAmount * 1000; + uint256 denominator = 997; + repayCollateralAmount = numerator / denominator + 1; + } + } + + /// PUBLIC NON-CONSTANT FUNCTIONS /// + + struct UniswapV2CallLocalVars { + IHToken bond; + address borrower; + IErc20 collateral; + uint256 minProfit; + uint256 mintedHTokenAmount; + uint256 profitCollateralAmount; + uint256 repayCollateralAmount; + uint256 seizedCollateralAmount; + address swapToken; + IErc20 underlying; + uint256 underlyingAmount; + } + + /// @inheritdoc IUniswapV2Callee + function uniswapV2Call( + address sender, + uint256 amount0, + uint256 amount1, + bytes calldata data + ) external override { + UniswapV2CallLocalVars memory vars; + + // Unpack the ABI encoded data passed by the UniswapV2Pair contract. + (vars.borrower, vars.bond, vars.minProfit) = abi.decode(data, (address, IHToken, uint256)); + + // Figure out which token is the collateral and which token is the underlying. + vars.underlying = vars.bond.underlying(); + (vars.collateral, vars.underlyingAmount) = getCollateralAndUnderlyingAmount( + IUniswapV2Pair(msg.sender), + amount0, + amount1, + vars.underlying + ); + + vars.swapToken = address(vars.underlying) == IUniswapV2Pair(msg.sender).token0() + ? IUniswapV2Pair(msg.sender).token1() + : IUniswapV2Pair(msg.sender).token0(); + + // Check that the caller is a genuine UniswapV2Pair contract. + if (msg.sender != pairFor(address(vars.underlying), vars.swapToken)) { + revert HifiFlashUniswapV2Underlying__CallNotAuthorized(msg.sender); + } + + // Mint hTokens and liquidate the borrower. + vars.mintedHTokenAmount = mintHTokensInternal(vars.bond, vars.underlyingAmount); + vars.seizedCollateralAmount = liquidateBorrowInternal( + vars.borrower, + vars.bond, + vars.collateral, + vars.mintedHTokenAmount + ); + + // Calculate the amount of collateral required to repay. + vars.repayCollateralAmount = getRepayCollateralAmount(vars.underlyingAmount); + if (vars.seizedCollateralAmount > vars.repayCollateralAmount) { + vars.profitCollateralAmount = vars.seizedCollateralAmount - vars.repayCollateralAmount; + } + if (vars.profitCollateralAmount < vars.minProfit) { + revert HifiFlashUniswapV2Underlying__InsufficientProfit( + vars.seizedCollateralAmount, + vars.repayCollateralAmount, + vars.minProfit + ); + } + + // Pay back the loan. + vars.collateral.safeTransfer(msg.sender, vars.repayCollateralAmount); + + // Reap the profit, if any. + vars.collateral.safeTransfer(sender, vars.profitCollateralAmount); + + // Emit an event. + emit FlashLiquidateBorrow( + sender, + vars.borrower, + address(vars.bond), + vars.underlyingAmount, + vars.seizedCollateralAmount, + vars.profitCollateralAmount + ); + } + + /// INTERNAL CONSTANT FUNCTIONS /// + + /// @dev Calculates the CREATE2 address for a pair without making any external calls. + function pairFor(address tokenA, address tokenB) internal view returns (address pair) { + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); + pair = address( + uint160( + uint256( + keccak256( + abi.encodePacked( + hex"ff", + uniV2Factory, + keccak256(abi.encodePacked(token0, token1)), + uniV2PairInitCodeHash + ) + ) + ) + ) + ); + } + + /// INTERNAL NON-CONSTANT FUNCTIONS /// + + /// @dev Liquidates the borrower by transferring the underlying to the BalanceSheet. By doing this, the + /// liquidator receives collateral at a discount. + function liquidateBorrowInternal( + address borrower, + IHToken bond, + IErc20 collateral, + uint256 mintedHTokenAmount + ) internal returns (uint256 seizedCollateralAmount) { + uint256 collateralAmount = balanceSheet.getCollateralAmount(borrower, collateral); + uint256 hypotheticalRepayAmount = balanceSheet.getRepayAmount(collateral, collateralAmount, bond); + + // If the hypothetical repay amount is bigger than the debt amount, this could be a single-collateral multi-bond + // vault. Otherwise, it could be a multi-collateral single-bond vault. However, it is difficult to generalize + // for the multi-collateral and multi-bond situation. The repay amount could be either bigger, smaller, or even + // equal to the debt amount depending on the collateral and debt amount distribution. + uint256 debtAmount = balanceSheet.getDebtAmount(borrower, bond); + uint256 repayAmount = hypotheticalRepayAmount > debtAmount ? debtAmount : hypotheticalRepayAmount; + + // Truncate the repay amount such that we keep the dust in this contract rather than the BalanceSheet. + uint256 truncatedRepayAmount = mintedHTokenAmount > repayAmount ? repayAmount : mintedHTokenAmount; + + // Liquidate borrow. + uint256 oldCollateralBalance = collateral.balanceOf(address(this)); + balanceSheet.liquidateBorrow(borrower, bond, truncatedRepayAmount, collateral); + uint256 newCollateralBalance = collateral.balanceOf(address(this)); + unchecked { + seizedCollateralAmount = newCollateralBalance - oldCollateralBalance; + } + } + + /// @dev Supplies the underlying to the HToken contract to mint hTokens without taking on debt. + function mintHTokensInternal(IHToken bond, uint256 underlyingAmount) internal returns (uint256 mintedHTokenAmount) { + IErc20 underlying = bond.underlying(); + + // Allow the HToken contract to spend underlying if allowance not enough. + uint256 allowance = underlying.allowance(address(this), address(bond)); + if (allowance < underlyingAmount) { + underlying.approve(address(bond), type(uint256).max); + } + + // Mint hTokens. + uint256 preHTokenBalance = bond.balanceOf(address(this)); + bond.supplyUnderlying(underlyingAmount); + uint256 postHTokenBalance = bond.balanceOf(address(this)); + unchecked { + mintedHTokenAmount = postHTokenBalance - preHTokenBalance; + } + } +} diff --git a/packages/flash-swap/contracts/uniswap-v2/IHifiFlashUniswapV2Underlying.sol b/packages/flash-swap/contracts/uniswap-v2/IHifiFlashUniswapV2Underlying.sol new file mode 100644 index 00000000..0cc3acfb --- /dev/null +++ b/packages/flash-swap/contracts/uniswap-v2/IHifiFlashUniswapV2Underlying.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.4; + +import "@hifi/protocol/contracts/core/balanceSheet/IBalanceSheetV1.sol"; +import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Callee.sol"; + +import "./IUniswapV2Pair.sol"; + +/// @title IHifiFlashUniswapV2Underlying +/// @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 { + /// EVENTS /// + + event FlashLiquidateBorrow( + address indexed liquidator, + address indexed borrower, + address indexed bond, + uint256 underlyingAmount, + uint256 seizedCollateralAmount, + uint256 profitCollateralAmount + ); + + /// 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. + /// @dev See this StackExchange post: https://ethereum.stackexchange.com/q/102670/24693. + /// + /// Requirements: + /// + /// - The amount of non-underlying flash borrowed must be zero. + /// - The underlying must be one of the pair's tokens. + /// + /// @param pair The Uniswap V2 pair contract. + /// @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 underlyingAmount The amount of underlying flash borrowed. + function getCollateralAndUnderlyingAmount( + IUniswapV2Pair pair, + uint256 amount0, + uint256 amount1, + IErc20 underlying + ) external view returns (IErc20 collateral, uint256 underlyingAmount); + + /// @notice Calculates the amount that must be repaid to Uniswap. The formula applied is: + /// + /// underlyingAmount * 1000 + /// collateralRepayAmount = --------------------- + /// 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 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); + + /// @notice The address of the UniswapV2Factory contract. + function uniV2Factory() external view returns (address); + + /// @notice The init code hash of the UniswapV2Pair contract. + function uniV2PairInitCodeHash() external view returns (bytes32); +} From ef7a6d35fa5a6e90aba463ec180579d2dfce044d Mon Sep 17 00:00:00 2001 From: scorpion9979 Date: Wed, 3 Nov 2021 16:12:15 +0300 Subject: [PATCH 02/16] feat(flash-swap): add tests for underlying as collateral --- packages/errors/src/flashSwap.ts | 7 + packages/errors/src/index.ts | 2 +- .../HifiFlashUniswapV2Underlying.behavior.ts | 7 + .../HifiFlashUniswapV2Underlying.ts | 35 ++ .../effects/uniswapV2Call.ts | 412 ++++++++++++++++++ packages/flash-swap/test/integration/index.ts | 2 + packages/flash-swap/test/shared/fixtures.ts | 15 +- packages/flash-swap/test/shared/types.ts | 2 + 8 files changed, 480 insertions(+), 2 deletions(-) create mode 100644 packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/HifiFlashUniswapV2Underlying.behavior.ts create mode 100644 packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/HifiFlashUniswapV2Underlying.ts create mode 100644 packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/effects/uniswapV2Call.ts diff --git a/packages/errors/src/flashSwap.ts b/packages/errors/src/flashSwap.ts index ace6ea7b..f754c293 100644 --- a/packages/errors/src/flashSwap.ts +++ b/packages/errors/src/flashSwap.ts @@ -4,3 +4,10 @@ export enum HifiFlashUniswapV2Errors { InsufficientProfit = "HifiFlashUniswapV2__InsufficientProfit", UnderlyingNotInPool = "HifiFlashUniswapV2__UnderlyingNotInPool", } + +export enum HifiFlashUniswapV2UnderlyingErrors { + CallNotAuthorized = "HifiFlashUniswapV2Underlying__CallNotAuthorized", + FlashBorrowCollateral = "HifiFlashUniswapV2Underlying__FlashBorrowCollateral", + InsufficientProfit = "HifiFlashUniswapV2Underlying__InsufficientProfit", + UnderlyingNotInPool = "HifiFlashUniswapV2Underlying__UnderlyingNotInPool", +} diff --git a/packages/errors/src/index.ts b/packages/errors/src/index.ts index e5867d70..f51af2a8 100644 --- a/packages/errors/src/index.ts +++ b/packages/errors/src/index.ts @@ -5,7 +5,7 @@ export { HifiPoolErrors, HifiPoolRegistryErrors, YieldSpaceErrors } from "./amm" export { OwnableErrors } from "./external"; // flashSwap.ts -export { HifiFlashUniswapV2Errors } from "./flashSwap"; +export { HifiFlashUniswapV2Errors, HifiFlashUniswapV2UnderlyingErrors } from "./flashSwap"; // protocol.ts export { diff --git a/packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/HifiFlashUniswapV2Underlying.behavior.ts b/packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/HifiFlashUniswapV2Underlying.behavior.ts new file mode 100644 index 00000000..857eb694 --- /dev/null +++ b/packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/HifiFlashUniswapV2Underlying.behavior.ts @@ -0,0 +1,7 @@ +import { shouldBehaveLikeUniswapV2Call } from "./effects/uniswapV2Call"; + +export function shouldBehaveLikeHifiFlashUniswapV2Underlying(): void { + describe("uniswapV2Call", function () { + shouldBehaveLikeUniswapV2Call(); + }); +} diff --git a/packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/HifiFlashUniswapV2Underlying.ts b/packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/HifiFlashUniswapV2Underlying.ts new file mode 100644 index 00000000..2efe85a4 --- /dev/null +++ b/packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/HifiFlashUniswapV2Underlying.ts @@ -0,0 +1,35 @@ +import { integrationFixture } from "../../shared/fixtures"; +import { shouldBehaveLikeHifiFlashUniswapV2Underlying } from "./HifiFlashUniswapV2Underlying.behavior"; + +export function integrationTestHifiFlashUniswapV2Underlying(): void { + describe("HifiFlashUniswapV2Underlying", function () { + beforeEach(async function () { + const { + balanceSheet, + fintroller, + hToken, + hifiFlashUniswapV2Underlying, + maliciousPair, + oracle, + usdc, + usdcPriceFeed, + uniswapV2Pair, + wbtc, + wbtcPriceFeed, + } = await this.loadFixture(integrationFixture); + this.contracts.balanceSheet = balanceSheet; + this.contracts.fintroller = fintroller; + this.contracts.hToken = hToken; + this.contracts.hifiFlashUniswapV2Underlying = hifiFlashUniswapV2Underlying; + this.contracts.maliciousPair = maliciousPair; + this.contracts.oracle = oracle; + this.contracts.usdc = usdc; + this.contracts.usdcPriceFeed = usdcPriceFeed; + this.contracts.uniswapV2Pair = uniswapV2Pair; + this.contracts.wbtc = wbtc; + this.contracts.wbtcPriceFeed = wbtcPriceFeed; + }); + + shouldBehaveLikeHifiFlashUniswapV2Underlying(); + }); +} diff --git a/packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/effects/uniswapV2Call.ts b/packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/effects/uniswapV2Call.ts new file mode 100644 index 00000000..ee760ace --- /dev/null +++ b/packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/effects/uniswapV2Call.ts @@ -0,0 +1,412 @@ +import { defaultAbiCoder } from "@ethersproject/abi"; +import { BigNumber } from "@ethersproject/bignumber"; +import { Zero } from "@ethersproject/constants"; +import { BalanceSheetErrors, HifiFlashUniswapV2UnderlyingErrors } from "@hifi/errors"; +import { USDC, WBTC, hUSDC, price } from "@hifi/helpers"; +import { expect } from "chai"; +import { toBn } from "evm-bn"; + +import type { GodModeErc20 } from "../../../../src/types/GodModeErc20"; +import { deployGodModeErc20 } from "../../../shared/deployers"; + +async function bumpPoolReserves(this: Mocha.Context, wbtcAmount: BigNumber, usdcAmount: BigNumber): Promise { + // Mint WBTC to the pair contract. + if (!wbtcAmount.isZero()) { + await this.contracts.wbtc.__godMode_mint(this.contracts.uniswapV2Pair.address, wbtcAmount); + } + + // Mint USDC to the pair contract. + if (!usdcAmount.isZero()) { + await this.contracts.usdc.__godMode_mint(this.contracts.uniswapV2Pair.address, usdcAmount); + } + + // Sync the token reserves in the UniswapV2Pair contract. + await this.contracts.uniswapV2Pair.sync(); +} + +function encodeCallData(this: Mocha.Context): string { + const types = ["address", "address", "uint256"]; + const minProfit: string = String(USDC("0")); + const values = [this.signers.borrower.address, this.contracts.hToken.address, minProfit]; + const data: string = defaultAbiCoder.encode(types, values); + return data; +} + +async function getSeizableAndProfitCollateralAmounts( + this: Mocha.Context, + repayHUsdcAmount: BigNumber, + underlyingAmount: BigNumber, +): Promise<{ expectedProfitUsdcAmount: BigNumber; seizableUsdcAmount: BigNumber }> { + const seizableUsdcAmount = await this.contracts.balanceSheet.getSeizableCollateralAmount( + this.contracts.hToken.address, + repayHUsdcAmount, + this.contracts.usdc.address, + ); + const repayUsdcAmount = await this.contracts.hifiFlashUniswapV2Underlying.getRepayCollateralAmount(underlyingAmount); + const expectedProfitUsdcAmount = seizableUsdcAmount.sub(repayUsdcAmount); + return { expectedProfitUsdcAmount, seizableUsdcAmount }; +} + +async function getTokenAmounts( + this: Mocha.Context, + wbtcAmount: BigNumber, + usdcAmount: BigNumber, +): Promise<{ token0Amount: BigNumber; token1Amount: BigNumber }> { + const token0: string = await this.contracts.uniswapV2Pair.token0(); + if (token0 == this.contracts.wbtc.address) { + return { + token0Amount: wbtcAmount, + token1Amount: usdcAmount, + }; + } else { + return { + token0Amount: usdcAmount, + token1Amount: wbtcAmount, + }; + } +} + +async function reducePoolReserves(this: Mocha.Context, wbtcAmount: BigNumber, usdcAmount: BigNumber): Promise { + // Mint WBTC to the pair contract. + if (!wbtcAmount.isZero()) { + await this.contracts.wbtc.__godMode_burn(this.contracts.uniswapV2Pair.address, wbtcAmount); + } + + // Mint USDC to the pair contract. + if (!usdcAmount.isZero()) { + await this.contracts.usdc.__godMode_burn(this.contracts.uniswapV2Pair.address, usdcAmount); + } + + // Sync the token reserves in the UniswapV2Pair contract. + await this.contracts.uniswapV2Pair.sync(); +} + +export function shouldBehaveLikeUniswapV2Call(): void { + context("when the data is malformed", function () { + it("reverts", async function () { + const sender: string = this.signers.raider.address; + const token0Amount: BigNumber = Zero; + const token1Amount: BigNumber = Zero; + const data: string = "0x"; + await expect( + this.contracts.hifiFlashUniswapV2Underlying + .connect(this.signers.raider) + .uniswapV2Call(sender, token0Amount, token1Amount, data), + ).to.be.reverted; + }); + }); + + context("when the data is encoded correctly", function () { + let data: string; + + beforeEach(function () { + data = encodeCallData.call(this); + }); + + context("when the caller is not the UniswapV2Pair contract", function () { + const token0Amount: BigNumber = Zero; + const token1Amount: BigNumber = Zero; + let sender: string; + + beforeEach(async function () { + sender = this.signers.raider.address; + }); + + context("when the caller is an externally owned account", function () { + it("reverts", async function () { + await expect( + this.contracts.hifiFlashUniswapV2Underlying + .connect(this.signers.raider) + .uniswapV2Call(sender, token0Amount, token1Amount, data), + ).to.be.revertedWith("function call to a non-contract account"); + }); + }); + + context("when the caller is a malicious pair", function () { + it("reverts", async function () { + const to: string = this.contracts.hifiFlashUniswapV2Underlying.address; + await expect( + this.contracts.maliciousPair.connect(this.signers.raider).swap(token0Amount, token1Amount, to, data), + ).to.be.revertedWith(HifiFlashUniswapV2UnderlyingErrors.CallNotAuthorized); + }); + }); + }); + + context("when the caller is the pair contract", function () { + beforeEach(async function () { + // Set the oracle price to 1 WBTC = $20k. + await this.contracts.wbtcPriceFeed.setPrice(price("20000")); + + // Set the oracle price to 1 USDC = $1. + await this.contracts.usdcPriceFeed.setPrice(price("1")); + + // Mint 100 WBTC and 2m USDC to the pair contract. This makes the price 1 WBTC ~ 20k USDC. + await bumpPoolReserves.call(this, WBTC("100"), USDC("2e6")); + }); + + context("when the underlying is not in the pair contract", function () { + it("reverts", async function () { + const { token0Amount, token1Amount } = await getTokenAmounts.call(this, Zero, USDC("10000")); + const foo: GodModeErc20 = await deployGodModeErc20(this.signers.admin, "Foo", "FOO", BigNumber.from(18)); + await this.contracts.hToken.__godMode_setUnderlying(foo.address); + const to: string = this.contracts.hifiFlashUniswapV2Underlying.address; + await expect( + this.contracts.uniswapV2Pair.connect(this.signers.raider).swap(token0Amount, token1Amount, to, data), + ).to.be.revertedWith(HifiFlashUniswapV2UnderlyingErrors.UnderlyingNotInPool); + }); + }); + + context("when the underlying is in the pair contract", function () { + context("when collateral is flash borrowed", function () { + it("reverts", async function () { + const { token0Amount, token1Amount } = await getTokenAmounts.call(this, WBTC("1"), Zero); + const to: string = this.contracts.hifiFlashUniswapV2Underlying.address; + await expect( + this.contracts.uniswapV2Pair.connect(this.signers.raider).swap(token0Amount, token1Amount, to, data), + ).to.be.revertedWith(HifiFlashUniswapV2UnderlyingErrors.FlashBorrowCollateral); + }); + }); + + context("when underlying is flash borrowed", function () { + const borrowAmount: BigNumber = hUSDC("10000"); + const collateralAmount: BigNumber = Zero; + const usdcCollateralCeiling: BigNumber = USDC("1000000"); + const wbtcCollateralCeiling: BigNumber = WBTC("50"); + const debtCeiling: BigNumber = hUSDC("1e6"); + const usdcLiquidationIncentive: BigNumber = toBn("1"); + const wbtcLiquidationIncentive: BigNumber = toBn("1.10"); + const underlyingAmount: BigNumber = USDC("10000"); + const usdcDepositAmount: BigNumber = USDC("10000"); + const usdcRepayFeeAmount: BigNumber = USDC("30.090271"); + const wbtcDepositAmount: BigNumber = WBTC("0.5"); + + let token0Amount: BigNumber; + let token1Amount: BigNumber; + + beforeEach(async function () { + const tokenAmounts = await getTokenAmounts.call(this, collateralAmount, underlyingAmount); + token0Amount = tokenAmounts.token0Amount; + token1Amount = tokenAmounts.token1Amount; + + // List the bond in the Fintroller. + await this.contracts.fintroller.connect(this.signers.admin).listBond(this.contracts.hToken.address); + + // List the collaterals in the Fintroller. + await this.contracts.fintroller.connect(this.signers.admin).listCollateral(this.contracts.usdc.address); + await this.contracts.fintroller.connect(this.signers.admin).listCollateral(this.contracts.wbtc.address); + + // Set the liquidation incentives. + await this.contracts.fintroller + .connect(this.signers.admin) + .setLiquidationIncentive(this.contracts.usdc.address, usdcLiquidationIncentive); + await this.contracts.fintroller + .connect(this.signers.admin) + .setLiquidationIncentive(this.contracts.wbtc.address, wbtcLiquidationIncentive); + + // Set the collateral ceilings. + await this.contracts.fintroller + .connect(this.signers.admin) + .setCollateralCeiling(this.contracts.usdc.address, usdcCollateralCeiling); + await this.contracts.fintroller + .connect(this.signers.admin) + .setCollateralCeiling(this.contracts.wbtc.address, wbtcCollateralCeiling); + + // Set the debt ceiling. + await this.contracts.fintroller + .connect(this.signers.admin) + .setDebtCeiling(this.contracts.hToken.address, debtCeiling); + + // Mint USDC and approve the BalanceSheet to spend it. + await this.contracts.usdc.__godMode_mint(this.signers.borrower.address, usdcDepositAmount); + await this.contracts.usdc + .connect(this.signers.borrower) + .approve(this.contracts.balanceSheet.address, usdcDepositAmount); + + // Mint WBTC and approve the BalanceSheet to spend it. + await this.contracts.wbtc.__godMode_mint(this.signers.borrower.address, wbtcDepositAmount); + await this.contracts.wbtc + .connect(this.signers.borrower) + .approve(this.contracts.balanceSheet.address, wbtcDepositAmount); + + // Minst USDC and send it to flash-swap to be used for Uniswap V2 fee repay + await this.contracts.usdc.__godMode_mint( + this.contracts.hifiFlashUniswapV2Underlying.address, + usdcRepayFeeAmount, + ); + + // Deposit the USDC in the BalanceSheet. + await this.contracts.balanceSheet + .connect(this.signers.borrower) + .depositCollateral(this.contracts.usdc.address, usdcDepositAmount); + + // Deposit the WBTC in the BalanceSheet. + await this.contracts.balanceSheet + .connect(this.signers.borrower) + .depositCollateral(this.contracts.wbtc.address, wbtcDepositAmount); + + // Borrow hUSDC. + await this.contracts.balanceSheet + .connect(this.signers.borrower) + .borrow(this.contracts.hToken.address, borrowAmount); + }); + + context("when the borrower does not have a liquidity shortfall", function () { + it("reverts", async function () { + const to: string = this.contracts.hifiFlashUniswapV2Underlying.address; + await expect( + this.contracts.uniswapV2Pair + .connect(this.signers.liquidator) + .swap(token0Amount, token1Amount, to, data), + ).to.be.revertedWith(BalanceSheetErrors.NO_LIQUIDITY_SHORTFALL); + }); + }); + + context("when the price given by the pair contract price is better than the oracle price", function () { + beforeEach(async function () { + // Set the WBTC price to $5k to make borrower's collateral ratio 125%. + await this.contracts.wbtcPriceFeed.setPrice(price("5000")); + + // Burn 1.75m USDC from the pair contract. This makes the pair contract price 1 WBTC ~ 2.5k USDC. + await reducePoolReserves.call(this, Zero, USDC("1.75e6")); + }); + + it("reverts", async function () { + const types = ["address", "address", "uint256"]; + const minProfit: string = String(USDC("1")); + const values = [this.signers.borrower.address, this.contracts.hToken.address, minProfit]; + const data: string = defaultAbiCoder.encode(types, values); + const to: string = this.contracts.hifiFlashUniswapV2Underlying.address; + await expect( + this.contracts.uniswapV2Pair + .connect(this.signers.liquidator) + .swap(token0Amount, token1Amount, to, data), + ).to.be.revertedWith(HifiFlashUniswapV2UnderlyingErrors.InsufficientProfit); + }); + }); + + context("when the borrower has a liquidity shortfall", function () { + context("when the price given by the pair contract is the same as the oracle price", function () { + let seizableUsdcAmount: BigNumber; + + context("when the collateral ratio is lower than 110%", function () { + const repayHUsdcAmount: BigNumber = hUSDC("9090.909090909090909090"); + + beforeEach(async function () { + // Set the WBTC price to $10k to make the borrower's collateral ratio 100%. + await this.contracts.wbtcPriceFeed.setPrice(price("10000")); + + // Calculate the amounts necessary for running the tests. + const calculatesAmounts = await getSeizableAndProfitCollateralAmounts.call( + this, + repayHUsdcAmount, + underlyingAmount, + ); + seizableUsdcAmount = calculatesAmounts.seizableUsdcAmount; + }); + + it("flash swaps USDC making no USDC profit and spending allocated USDC to pay swap fee", async function () { + const to: string = this.contracts.hifiFlashUniswapV2Underlying.address; + const preUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); + const preUsdcBalanceContract = await this.contracts.usdc.balanceOf(to); + await this.contracts.uniswapV2Pair + .connect(this.signers.liquidator) + .swap(token0Amount, token1Amount, to, data); + const newUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); + const newUsdcBalanceContract = await this.contracts.usdc.balanceOf(to); + expect(newUsdcBalanceAccount).to.equal(preUsdcBalanceAccount); + expect(preUsdcBalanceContract.sub(newUsdcBalanceContract)).to.equal(usdcRepayFeeAmount); + }); + }); + + context("when the collateral ratio is lower than 150% but higher than 110%", function () { + const repayHUsdcAmount: BigNumber = hUSDC("10000"); + + beforeEach(async function () { + // Set the WBTC price to $5k to make borrower's collateral ratio 125%. + await this.contracts.wbtcPriceFeed.setPrice(price("5000")); + + // Burn 1.5m USDC from the pair contract, which makes the price 1 WBTC ~ 5k USDC. + await reducePoolReserves.call(this, Zero, USDC("1.5e6")); + + // Calculate the amounts necessary for running the tests. + const calculatesAmounts = await getSeizableAndProfitCollateralAmounts.call( + this, + repayHUsdcAmount, + underlyingAmount, + ); + seizableUsdcAmount = calculatesAmounts.seizableUsdcAmount; + }); + + context("new order of tokens in the pair", function () { + let localToken0Amount: BigNumber; + let localToken1Amount: BigNumber; + + beforeEach(async function () { + const token0: string = await this.contracts.uniswapV2Pair.token0(); + if (token0 == this.contracts.wbtc.address) { + await this.contracts.uniswapV2Pair.__godMode_setToken0(this.contracts.usdc.address); + await this.contracts.uniswapV2Pair.__godMode_setToken1(this.contracts.wbtc.address); + localToken0Amount = underlyingAmount; + localToken1Amount = collateralAmount; + } else { + await this.contracts.uniswapV2Pair.__godMode_setToken0(this.contracts.wbtc.address); + await this.contracts.uniswapV2Pair.__godMode_setToken1(this.contracts.usdc.address); + localToken0Amount = collateralAmount; + localToken1Amount = underlyingAmount; + } + await this.contracts.uniswapV2Pair.sync(); + }); + + it("flash swaps USDC making no USDC profit and spending allocated USDC to pay swap fee", async function () { + const to: string = this.contracts.hifiFlashUniswapV2Underlying.address; + const preUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); + const preUsdcBalanceContract = await this.contracts.usdc.balanceOf(to); + await this.contracts.uniswapV2Pair + .connect(this.signers.liquidator) + .swap(localToken0Amount, localToken1Amount, to, data); + const newUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); + const newUsdcBalanceContract = await this.contracts.usdc.balanceOf(to); + expect(newUsdcBalanceAccount).to.equal(preUsdcBalanceAccount); + expect(preUsdcBalanceContract.sub(newUsdcBalanceContract)).to.equal(usdcRepayFeeAmount); + }); + }); + + context("initial order of tokens in the pair", function () { + it("flash swaps USDC making no USDC profit and spending allocated USDC to pay swap fee", async function () { + const to: string = this.contracts.hifiFlashUniswapV2Underlying.address; + const preUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); + const preUsdcBalanceContract = await this.contracts.usdc.balanceOf(to); + await this.contracts.uniswapV2Pair + .connect(this.signers.liquidator) + .swap(token0Amount, token1Amount, to, data); + const newUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); + const newUsdcBalanceContract = await this.contracts.usdc.balanceOf(to); + expect(newUsdcBalanceAccount).to.equal(preUsdcBalanceAccount); + expect(preUsdcBalanceContract.sub(newUsdcBalanceContract)).to.equal(usdcRepayFeeAmount); + }); + + it("emits a FlashLiquidateBorrow event", async function () { + const to: string = this.contracts.hifiFlashUniswapV2Underlying.address; + const contractCall = this.contracts.uniswapV2Pair + .connect(this.signers.liquidator) + .swap(token0Amount, token1Amount, to, data); + await expect(contractCall) + .to.emit(this.contracts.hifiFlashUniswapV2Underlying, "FlashLiquidateBorrow") + .withArgs( + this.signers.liquidator.address, + this.signers.borrower.address, + this.contracts.hToken.address, + underlyingAmount, + seizableUsdcAmount, + USDC("0"), + ); + }); + }); + }); + }); + }); + }); + }); + }); + }); +} diff --git a/packages/flash-swap/test/integration/index.ts b/packages/flash-swap/test/integration/index.ts index c6f3c29d..d156b08a 100644 --- a/packages/flash-swap/test/integration/index.ts +++ b/packages/flash-swap/test/integration/index.ts @@ -1,6 +1,8 @@ import { baseContext } from "../shared/contexts"; import { integrationTestHifiFlashUniswapV2 } from "./hifiFlashUniswapV2/HifiFlashUniswapV2"; +import { integrationTestHifiFlashUniswapV2Underlying } from "./hifiFlashUniswapV2Underlying/HifiFlashUniswapV2Underlying"; baseContext("Integration Tests", function () { integrationTestHifiFlashUniswapV2(); + integrationTestHifiFlashUniswapV2Underlying(); }); diff --git a/packages/flash-swap/test/shared/fixtures.ts b/packages/flash-swap/test/shared/fixtures.ts index 68f38afb..49cc3697 100644 --- a/packages/flash-swap/test/shared/fixtures.ts +++ b/packages/flash-swap/test/shared/fixtures.ts @@ -14,6 +14,7 @@ import type { GodModeErc20 } from "../../src/types/GodModeErc20"; import type { GodModeHToken } from "../../src/types/GodModeHToken"; import type { GodModeUniswapV2Factory } from "../../src/types/GodModeUniswapV2Factory"; import type { HifiFlashUniswapV2 } from "../../src/types/HifiFlashUniswapV2"; +import type { HifiFlashUniswapV2Underlying } from "../../src/types/HifiFlashUniswapV2Underlying"; import type { MaliciousPair } from "../../src/types/MaliciousPair"; import type { SimplePriceFeed } from "../../src/types/SimplePriceFeed"; import type { GodModeUniswapV2Pair } from "../../src/types/GodModeUniswapV2Pair"; @@ -23,6 +24,7 @@ type IntegrationFixtureReturnType = { balanceSheet: BalanceSheetV1; fintroller: FintrollerV1; hifiFlashUniswapV2: HifiFlashUniswapV2; + hifiFlashUniswapV2Underlying: HifiFlashUniswapV2Underlying; hToken: GodModeHToken; maliciousPair: MaliciousPair; uniswapV2Pair: GodModeUniswapV2Pair; @@ -89,8 +91,9 @@ export async function integrationFixture(signers: Signer[]): Promise( await waffle.deployContract(deployer, hifiFlashUniswapV2Artifact, [ balanceSheet.address, @@ -99,10 +102,20 @@ export async function integrationFixture(signers: Signer[]): Promise( + await waffle.deployContract(deployer, hifiFlashUniswapV2UnderlyingArtifact, [ + balanceSheet.address, + uniswapV2Factory.address, + uniV2PairInitCodeHash, + ]) + ); + return { balanceSheet, fintroller, hifiFlashUniswapV2, + hifiFlashUniswapV2Underlying, hToken, maliciousPair, uniswapV2Pair, diff --git a/packages/flash-swap/test/shared/types.ts b/packages/flash-swap/test/shared/types.ts index fd82e796..3b4efef5 100644 --- a/packages/flash-swap/test/shared/types.ts +++ b/packages/flash-swap/test/shared/types.ts @@ -7,6 +7,7 @@ import type { GodModeErc20 } from "../../src/types/GodModeErc20"; import type { GodModeHToken } from "../../src/types/GodModeHToken"; import type { GodModeUniswapV2Pair } from "../../src/types/GodModeUniswapV2Pair"; import type { HifiFlashUniswapV2 } from "../../src/types/HifiFlashUniswapV2"; +import type { HifiFlashUniswapV2Underlying } from "../../src/types/HifiFlashUniswapV2Underlying"; import type { MaliciousPair } from "../../src/types/MaliciousPair"; import type { SimplePriceFeed } from "../../src/types/SimplePriceFeed"; @@ -22,6 +23,7 @@ export interface Contracts { fintroller: FintrollerV1; hToken: GodModeHToken; hifiFlashUniswapV2: HifiFlashUniswapV2; + hifiFlashUniswapV2Underlying: HifiFlashUniswapV2Underlying; maliciousPair: MaliciousPair; oracle: ChainlinkOperator; usdc: GodModeErc20; From 254bafb572597a1a08fbc8b00a8900320abb62a0 Mon Sep 17 00:00:00 2001 From: scorpion9979 Date: Wed, 3 Nov 2021 17:08:46 +0300 Subject: [PATCH 03/16] refactor(flash-swap): expect no profit + better error naming --- packages/errors/src/flashSwap.ts | 3 +- .../HifiFlashUniswapV2Underlying.sol | 34 +++----------- .../IHifiFlashUniswapV2Underlying.sol | 6 +-- .../effects/uniswapV2Call.ts | 46 +++++++++---------- 4 files changed, 33 insertions(+), 56 deletions(-) diff --git a/packages/errors/src/flashSwap.ts b/packages/errors/src/flashSwap.ts index f754c293..75ca2f63 100644 --- a/packages/errors/src/flashSwap.ts +++ b/packages/errors/src/flashSwap.ts @@ -7,7 +7,6 @@ export enum HifiFlashUniswapV2Errors { export enum HifiFlashUniswapV2UnderlyingErrors { CallNotAuthorized = "HifiFlashUniswapV2Underlying__CallNotAuthorized", - FlashBorrowCollateral = "HifiFlashUniswapV2Underlying__FlashBorrowCollateral", - InsufficientProfit = "HifiFlashUniswapV2Underlying__InsufficientProfit", + FlashBorrowWrongToken = "HifiFlashUniswapV2Underlying__FlashBorrowWrongToken", UnderlyingNotInPool = "HifiFlashUniswapV2Underlying__UnderlyingNotInPool", } diff --git a/packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2Underlying.sol b/packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2Underlying.sol index 654dceda..77784623 100644 --- a/packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2Underlying.sol +++ b/packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2Underlying.sol @@ -13,15 +13,8 @@ import "./IUniswapV2Pair.sol"; /// @notice Emitted when the caller is not the Uniswap V2 pair contract. error HifiFlashUniswapV2Underlying__CallNotAuthorized(address caller); -/// @notice Emitted when the flash borrowed asset is the collateral instead of the underlying. -error HifiFlashUniswapV2Underlying__FlashBorrowCollateral(uint256 collateralAmount); - -/// @notice Emitted when the liquidation does not yield a sufficient profit. -error HifiFlashUniswapV2Underlying__InsufficientProfit( - uint256 seizedCollateralAmount, - uint256 repayCollateralAmount, - uint256 minProfit -); +/// @notice Emitted when the flash borrowed asset is the wrong token in the pair. +error HifiFlashUniswapV2Underlying__FlashBorrowWrongToken(uint256 collateralAmount); /// @notice Emitted when neither the token0 nor the token1 is the underlying. error HifiFlashUniswapV2Underlying__UnderlyingNotInPool( @@ -71,13 +64,13 @@ contract HifiFlashUniswapV2Underlying is IHifiFlashUniswapV2Underlying { address token1 = pair.token1(); if (token0 == address(underlying)) { if (amount1 > 0) { - revert HifiFlashUniswapV2Underlying__FlashBorrowCollateral(amount1); + revert HifiFlashUniswapV2Underlying__FlashBorrowWrongToken(amount1); } collateral = IErc20(token0); underlyingAmount = amount0; } else if (token1 == address(underlying)) { if (amount0 > 0) { - revert HifiFlashUniswapV2Underlying__FlashBorrowCollateral(amount0); + revert HifiFlashUniswapV2Underlying__FlashBorrowWrongToken(amount0); } collateral = IErc20(token1); underlyingAmount = amount1; @@ -108,9 +101,7 @@ contract HifiFlashUniswapV2Underlying is IHifiFlashUniswapV2Underlying { IHToken bond; address borrower; IErc20 collateral; - uint256 minProfit; uint256 mintedHTokenAmount; - uint256 profitCollateralAmount; uint256 repayCollateralAmount; uint256 seizedCollateralAmount; address swapToken; @@ -128,7 +119,7 @@ contract HifiFlashUniswapV2Underlying is IHifiFlashUniswapV2Underlying { UniswapV2CallLocalVars memory vars; // Unpack the ABI encoded data passed by the UniswapV2Pair contract. - (vars.borrower, vars.bond, vars.minProfit) = abi.decode(data, (address, IHToken, uint256)); + (vars.borrower, vars.bond) = abi.decode(data, (address, IHToken)); // Figure out which token is the collateral and which token is the underlying. vars.underlying = vars.bond.underlying(); @@ -159,23 +150,10 @@ contract HifiFlashUniswapV2Underlying is IHifiFlashUniswapV2Underlying { // Calculate the amount of collateral required to repay. vars.repayCollateralAmount = getRepayCollateralAmount(vars.underlyingAmount); - if (vars.seizedCollateralAmount > vars.repayCollateralAmount) { - vars.profitCollateralAmount = vars.seizedCollateralAmount - vars.repayCollateralAmount; - } - if (vars.profitCollateralAmount < vars.minProfit) { - revert HifiFlashUniswapV2Underlying__InsufficientProfit( - vars.seizedCollateralAmount, - vars.repayCollateralAmount, - vars.minProfit - ); - } // Pay back the loan. vars.collateral.safeTransfer(msg.sender, vars.repayCollateralAmount); - // Reap the profit, if any. - vars.collateral.safeTransfer(sender, vars.profitCollateralAmount); - // Emit an event. emit FlashLiquidateBorrow( sender, @@ -183,7 +161,7 @@ contract HifiFlashUniswapV2Underlying is IHifiFlashUniswapV2Underlying { address(vars.bond), vars.underlyingAmount, vars.seizedCollateralAmount, - vars.profitCollateralAmount + vars.repayCollateralAmount ); } diff --git a/packages/flash-swap/contracts/uniswap-v2/IHifiFlashUniswapV2Underlying.sol b/packages/flash-swap/contracts/uniswap-v2/IHifiFlashUniswapV2Underlying.sol index 0cc3acfb..c50670bf 100644 --- a/packages/flash-swap/contracts/uniswap-v2/IHifiFlashUniswapV2Underlying.sol +++ b/packages/flash-swap/contracts/uniswap-v2/IHifiFlashUniswapV2Underlying.sol @@ -19,7 +19,7 @@ interface IHifiFlashUniswapV2Underlying is IUniswapV2Callee { address indexed bond, uint256 underlyingAmount, uint256 seizedCollateralAmount, - uint256 profitCollateralAmount + uint256 repayCollateralAmount ); /// CONSTANT FUNCTIONS /// @@ -54,8 +54,8 @@ interface IHifiFlashUniswapV2Underlying is IUniswapV2Callee { /// collateralRepayAmount = --------------------- /// 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. + /// @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); diff --git a/packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/effects/uniswapV2Call.ts b/packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/effects/uniswapV2Call.ts index ee760ace..a7fecf3e 100644 --- a/packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/effects/uniswapV2Call.ts +++ b/packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/effects/uniswapV2Call.ts @@ -25,26 +25,24 @@ async function bumpPoolReserves(this: Mocha.Context, wbtcAmount: BigNumber, usdc } function encodeCallData(this: Mocha.Context): string { - const types = ["address", "address", "uint256"]; - const minProfit: string = String(USDC("0")); - const values = [this.signers.borrower.address, this.contracts.hToken.address, minProfit]; + const types = ["address", "address"]; + const values = [this.signers.borrower.address, this.contracts.hToken.address]; const data: string = defaultAbiCoder.encode(types, values); return data; } -async function getSeizableAndProfitCollateralAmounts( +async function getSeizableAndRepayCollateralAmounts( this: Mocha.Context, repayHUsdcAmount: BigNumber, underlyingAmount: BigNumber, -): Promise<{ expectedProfitUsdcAmount: BigNumber; seizableUsdcAmount: BigNumber }> { +): Promise<{ expectedRepayUsdcAmount: BigNumber; seizableUsdcAmount: BigNumber }> { const seizableUsdcAmount = await this.contracts.balanceSheet.getSeizableCollateralAmount( this.contracts.hToken.address, repayHUsdcAmount, this.contracts.usdc.address, ); - const repayUsdcAmount = await this.contracts.hifiFlashUniswapV2Underlying.getRepayCollateralAmount(underlyingAmount); - const expectedProfitUsdcAmount = seizableUsdcAmount.sub(repayUsdcAmount); - return { expectedProfitUsdcAmount, seizableUsdcAmount }; + const expectedRepayUsdcAmount = underlyingAmount.mul(1000).div(997).add(1); + return { expectedRepayUsdcAmount, seizableUsdcAmount }; } async function getTokenAmounts( @@ -157,13 +155,13 @@ export function shouldBehaveLikeUniswapV2Call(): void { }); context("when the underlying is in the pair contract", function () { - context("when collateral is flash borrowed", function () { + context("when wrong token is flash borrowed", function () { it("reverts", async function () { const { token0Amount, token1Amount } = await getTokenAmounts.call(this, WBTC("1"), Zero); const to: string = this.contracts.hifiFlashUniswapV2Underlying.address; await expect( this.contracts.uniswapV2Pair.connect(this.signers.raider).swap(token0Amount, token1Amount, to, data), - ).to.be.revertedWith(HifiFlashUniswapV2UnderlyingErrors.FlashBorrowCollateral); + ).to.be.revertedWith(HifiFlashUniswapV2UnderlyingErrors.FlashBorrowWrongToken); }); }); @@ -270,23 +268,24 @@ export function shouldBehaveLikeUniswapV2Call(): void { await reducePoolReserves.call(this, Zero, USDC("1.75e6")); }); - it("reverts", async function () { - const types = ["address", "address", "uint256"]; - const minProfit: string = String(USDC("1")); - const values = [this.signers.borrower.address, this.contracts.hToken.address, minProfit]; - const data: string = defaultAbiCoder.encode(types, values); + it("flash swaps USDC making no USDC profit and spending allocated USDC to pay swap fee", async function () { const to: string = this.contracts.hifiFlashUniswapV2Underlying.address; - await expect( - this.contracts.uniswapV2Pair - .connect(this.signers.liquidator) - .swap(token0Amount, token1Amount, to, data), - ).to.be.revertedWith(HifiFlashUniswapV2UnderlyingErrors.InsufficientProfit); + const preUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); + const preUsdcBalanceContract = await this.contracts.usdc.balanceOf(to); + await this.contracts.uniswapV2Pair + .connect(this.signers.liquidator) + .swap(token0Amount, token1Amount, to, data); + const newUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); + const newUsdcBalanceContract = await this.contracts.usdc.balanceOf(to); + expect(newUsdcBalanceAccount).to.equal(preUsdcBalanceAccount); + expect(preUsdcBalanceContract.sub(newUsdcBalanceContract)).to.equal(usdcRepayFeeAmount); }); }); context("when the borrower has a liquidity shortfall", function () { context("when the price given by the pair contract is the same as the oracle price", function () { let seizableUsdcAmount: BigNumber; + let expectedRepayUsdcAmount: BigNumber; context("when the collateral ratio is lower than 110%", function () { const repayHUsdcAmount: BigNumber = hUSDC("9090.909090909090909090"); @@ -296,7 +295,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { await this.contracts.wbtcPriceFeed.setPrice(price("10000")); // Calculate the amounts necessary for running the tests. - const calculatesAmounts = await getSeizableAndProfitCollateralAmounts.call( + const calculatesAmounts = await getSeizableAndRepayCollateralAmounts.call( this, repayHUsdcAmount, underlyingAmount, @@ -329,12 +328,13 @@ export function shouldBehaveLikeUniswapV2Call(): void { await reducePoolReserves.call(this, Zero, USDC("1.5e6")); // Calculate the amounts necessary for running the tests. - const calculatesAmounts = await getSeizableAndProfitCollateralAmounts.call( + const calculatesAmounts = await getSeizableAndRepayCollateralAmounts.call( this, repayHUsdcAmount, underlyingAmount, ); seizableUsdcAmount = calculatesAmounts.seizableUsdcAmount; + expectedRepayUsdcAmount = calculatesAmounts.expectedRepayUsdcAmount; }); context("new order of tokens in the pair", function () { @@ -398,7 +398,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { this.contracts.hToken.address, underlyingAmount, seizableUsdcAmount, - USDC("0"), + expectedRepayUsdcAmount, ); }); }); From 4e3f8cbb35f6fc7fa89bf6e1a550a9ab1a249f1f Mon Sep 17 00:00:00 2001 From: scorpion9979 Date: Wed, 3 Nov 2021 20:50:56 +0300 Subject: [PATCH 04/16] feat(tasks): add deploy task for underlying as collaeral flash-swap --- packages/flash-swap/hardhat.config.ts | 2 + packages/tasks/helpers/constants.ts | 2 + .../deploy/hifiFlashUniswapV2Underlying.ts | 47 +++++++++++++++++++ packages/tasks/src/deploy/index.ts | 1 + 4 files changed, 52 insertions(+) create mode 100644 packages/tasks/src/deploy/hifiFlashUniswapV2Underlying.ts diff --git a/packages/flash-swap/hardhat.config.ts b/packages/flash-swap/hardhat.config.ts index 18b5aa32..10470aad 100644 --- a/packages/flash-swap/hardhat.config.ts +++ b/packages/flash-swap/hardhat.config.ts @@ -42,8 +42,10 @@ const config: HardhatUserConfig = { contracts: [ "Erc20", "HifiFlashUniswapV2", + "HifiFlashUniswapV2Underlying", "IErc20", "IHifiFlashUniswapV2", + "IHifiFlashUniswapV2Underlying", "IUniswapV2Callee", "IUniswapV2Pair", "UniswapV2Pair", diff --git a/packages/tasks/helpers/constants.ts b/packages/tasks/helpers/constants.ts index 05ec0970..6ce89650 100644 --- a/packages/tasks/helpers/constants.ts +++ b/packages/tasks/helpers/constants.ts @@ -1,6 +1,8 @@ export const SUBTASK_DEPLOY_WAIT_FOR_CONFIRMATIONS: string = "deploy:wait-for-confirmations"; export const TASK_DEPLOY_CONTRACT_CHAINLINK_OPERATOR: string = "deploy:contract:chainlink-operator"; export const TASK_DEPLOY_CONTRACT_HIFI_FLASH_UNISWAP_V2: string = "deploy:contract:hifi-flash-uniswap-v2"; +export const TASK_DEPLOY_CONTRACT_HIFI_FLASH_UNISWAP_V2_UNDERLYING: string = + "deploy:contract:hifi-flash-uniswap-v2-underlying"; export const TASK_DEPLOY_CONTRACT_HIFI_POOL: string = "deploy:contract:hifi-pool"; export const TASK_DEPLOY_CONTRACT_HIFI_POOL_REGISTRY: string = "deploy:contract:hifi-pool-registry"; export const TASK_DEPLOY_CONTRACT_HIFI_PROXY_TARGET: string = "deploy:contract:hifi-proxy-target"; diff --git a/packages/tasks/src/deploy/hifiFlashUniswapV2Underlying.ts b/packages/tasks/src/deploy/hifiFlashUniswapV2Underlying.ts new file mode 100644 index 00000000..a6eb0cd9 --- /dev/null +++ b/packages/tasks/src/deploy/hifiFlashUniswapV2Underlying.ts @@ -0,0 +1,47 @@ +import * as core from "@actions/core"; +import type { HifiFlashUniswapV2Underlying } from "@hifi/flash-swap/dist/types/HifiFlashUniswapV2Underlying"; +import { HifiFlashUniswapV2Underlying__factory } from "@hifi/flash-swap/dist/types/factories/HifiFlashUniswapV2Underlying__factory"; +import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { task, types } from "hardhat/config"; +import type { TaskArguments } from "hardhat/types"; + +import { + SUBTASK_DEPLOY_WAIT_FOR_CONFIRMATIONS, + TASK_DEPLOY_CONTRACT_HIFI_FLASH_UNISWAP_V2_UNDERLYING, +} from "../../helpers/constants"; + +task(TASK_DEPLOY_CONTRACT_HIFI_FLASH_UNISWAP_V2_UNDERLYING) + // Contract arguments + .addParam("balanceSheet", "Address of the BalanceSheet contract") + .addParam("uniV2Factory", "Address of the UniswapV2Factory contract") + .addParam("uniV2PairInitCodeHash", "Init code hash of the UniswapV2Pair contract") + // Developer settings + .addOptionalParam("confirmations", "How many block confirmations to wait for", 0, types.int) + .addOptionalParam("printAddress", "Print the address in the console", true, types.boolean) + .addOptionalParam("setOutput", "Set the contract address as an output in GitHub Actions", false, types.boolean) + .setAction(async function (taskArgs: TaskArguments, { ethers, run }): Promise { + const signers: SignerWithAddress[] = await ethers.getSigners(); + const hifiFlashUniswapV2UnderlyingFactory: HifiFlashUniswapV2Underlying__factory = + new HifiFlashUniswapV2Underlying__factory(signers[0]); + const hifiFlashUniswapV2Underlying: HifiFlashUniswapV2Underlying = ( + await hifiFlashUniswapV2UnderlyingFactory.deploy( + taskArgs.balanceSheet, + taskArgs.uniV2Factory, + taskArgs.uniV2PairInitCodeHash, + ) + ); + + await run(SUBTASK_DEPLOY_WAIT_FOR_CONFIRMATIONS, { + contract: hifiFlashUniswapV2Underlying, + confirmations: taskArgs.confirmations, + }); + + if (taskArgs.setOutput) { + core.setOutput("hifi-flash-uniswap-v2-underlying", hifiFlashUniswapV2Underlying.address); + } + if (taskArgs.printAddress) { + console.table([{ name: "HifiFlashUniswapV2Underlying", address: hifiFlashUniswapV2Underlying.address }]); + } + + return hifiFlashUniswapV2Underlying.address; + }); diff --git a/packages/tasks/src/deploy/index.ts b/packages/tasks/src/deploy/index.ts index 22c79bdf..8f692147 100644 --- a/packages/tasks/src/deploy/index.ts +++ b/packages/tasks/src/deploy/index.ts @@ -1,5 +1,6 @@ import "./chainlinkOperator"; import "./hifiFlashUniswapV2"; +import "./hifiFlashUniswapV2Underlying"; import "./hifiPoolRegistry"; import "./hifiProxyTarget"; import "./hToken"; From 59db0945527f39368fa59d09ba5230563636e0bd Mon Sep 17 00:00:00 2001 From: scorpion9979 Date: Wed, 3 Nov 2021 22:24:23 +0300 Subject: [PATCH 05/16] feat(flash-swap): repay USDC liquidation 0.3% Uniswap fee from bot wallet balance --- .../HifiFlashUniswapV2Underlying.sol | 12 +++++- .../effects/uniswapV2Call.ts | 38 +++++++++---------- packages/flash-swap/test/shared/contexts.ts | 1 + packages/flash-swap/test/shared/types.ts | 1 + 4 files changed, 32 insertions(+), 20 deletions(-) diff --git a/packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2Underlying.sol b/packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2Underlying.sol index 77784623..26d812d5 100644 --- a/packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2Underlying.sol +++ b/packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2Underlying.sol @@ -99,9 +99,11 @@ contract HifiFlashUniswapV2Underlying is IHifiFlashUniswapV2Underlying { struct UniswapV2CallLocalVars { IHToken bond; + address bot; address borrower; IErc20 collateral; uint256 mintedHTokenAmount; + uint256 overshootCollateralAmount; uint256 repayCollateralAmount; uint256 seizedCollateralAmount; address swapToken; @@ -119,7 +121,7 @@ contract HifiFlashUniswapV2Underlying is IHifiFlashUniswapV2Underlying { UniswapV2CallLocalVars memory vars; // Unpack the ABI encoded data passed by the UniswapV2Pair contract. - (vars.borrower, vars.bond) = abi.decode(data, (address, IHToken)); + (vars.borrower, vars.bond, vars.bot) = abi.decode(data, (address, IHToken, address)); // Figure out which token is the collateral and which token is the underlying. vars.underlying = vars.bond.underlying(); @@ -151,6 +153,14 @@ contract HifiFlashUniswapV2Underlying is IHifiFlashUniswapV2Underlying { // Calculate the amount of collateral required to repay. vars.repayCollateralAmount = getRepayCollateralAmount(vars.underlyingAmount); + // The bot wallet compensates for any overshoot of collateral repay amount above seized amount. + if (vars.repayCollateralAmount > vars.seizedCollateralAmount) { + unchecked { + vars.overshootCollateralAmount = vars.repayCollateralAmount - vars.seizedCollateralAmount; + } + vars.collateral.safeTransferFrom(vars.bot, address(this), vars.overshootCollateralAmount); + } + // Pay back the loan. vars.collateral.safeTransfer(msg.sender, vars.repayCollateralAmount); diff --git a/packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/effects/uniswapV2Call.ts b/packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/effects/uniswapV2Call.ts index a7fecf3e..c9c57c24 100644 --- a/packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/effects/uniswapV2Call.ts +++ b/packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/effects/uniswapV2Call.ts @@ -25,8 +25,8 @@ async function bumpPoolReserves(this: Mocha.Context, wbtcAmount: BigNumber, usdc } function encodeCallData(this: Mocha.Context): string { - const types = ["address", "address"]; - const values = [this.signers.borrower.address, this.contracts.hToken.address]; + const types = ["address", "address", "address"]; + const values = [this.signers.borrower.address, this.contracts.hToken.address, this.signers.bot.address]; const data: string = defaultAbiCoder.encode(types, values); return data; } @@ -226,11 +226,11 @@ export function shouldBehaveLikeUniswapV2Call(): void { .connect(this.signers.borrower) .approve(this.contracts.balanceSheet.address, wbtcDepositAmount); - // Minst USDC and send it to flash-swap to be used for Uniswap V2 fee repay - await this.contracts.usdc.__godMode_mint( - this.contracts.hifiFlashUniswapV2Underlying.address, - usdcRepayFeeAmount, - ); + // Minst USDC to the bot wallet and approve the flash swap contract to spend it + await this.contracts.usdc.__godMode_mint(this.signers.bot.address, usdcRepayFeeAmount); + await this.contracts.usdc + .connect(this.signers.bot) + .approve(this.contracts.hifiFlashUniswapV2Underlying.address, usdcRepayFeeAmount); // Deposit the USDC in the BalanceSheet. await this.contracts.balanceSheet @@ -271,14 +271,14 @@ export function shouldBehaveLikeUniswapV2Call(): void { it("flash swaps USDC making no USDC profit and spending allocated USDC to pay swap fee", async function () { const to: string = this.contracts.hifiFlashUniswapV2Underlying.address; const preUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); - const preUsdcBalanceContract = await this.contracts.usdc.balanceOf(to); + const preUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.bot.address); await this.contracts.uniswapV2Pair .connect(this.signers.liquidator) .swap(token0Amount, token1Amount, to, data); const newUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); - const newUsdcBalanceContract = await this.contracts.usdc.balanceOf(to); + const newUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.bot.address); expect(newUsdcBalanceAccount).to.equal(preUsdcBalanceAccount); - expect(preUsdcBalanceContract.sub(newUsdcBalanceContract)).to.equal(usdcRepayFeeAmount); + expect(preUsdcBalanceBot.sub(newUsdcBalanceBot)).to.equal(usdcRepayFeeAmount); }); }); @@ -306,14 +306,14 @@ export function shouldBehaveLikeUniswapV2Call(): void { it("flash swaps USDC making no USDC profit and spending allocated USDC to pay swap fee", async function () { const to: string = this.contracts.hifiFlashUniswapV2Underlying.address; const preUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); - const preUsdcBalanceContract = await this.contracts.usdc.balanceOf(to); + const preUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.bot.address); await this.contracts.uniswapV2Pair .connect(this.signers.liquidator) .swap(token0Amount, token1Amount, to, data); const newUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); - const newUsdcBalanceContract = await this.contracts.usdc.balanceOf(to); + const newUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.bot.address); expect(newUsdcBalanceAccount).to.equal(preUsdcBalanceAccount); - expect(preUsdcBalanceContract.sub(newUsdcBalanceContract)).to.equal(usdcRepayFeeAmount); + expect(preUsdcBalanceBot.sub(newUsdcBalanceBot)).to.equal(usdcRepayFeeAmount); }); }); @@ -360,14 +360,14 @@ export function shouldBehaveLikeUniswapV2Call(): void { it("flash swaps USDC making no USDC profit and spending allocated USDC to pay swap fee", async function () { const to: string = this.contracts.hifiFlashUniswapV2Underlying.address; const preUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); - const preUsdcBalanceContract = await this.contracts.usdc.balanceOf(to); + const preUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.bot.address); await this.contracts.uniswapV2Pair .connect(this.signers.liquidator) .swap(localToken0Amount, localToken1Amount, to, data); const newUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); - const newUsdcBalanceContract = await this.contracts.usdc.balanceOf(to); + const newUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.bot.address); expect(newUsdcBalanceAccount).to.equal(preUsdcBalanceAccount); - expect(preUsdcBalanceContract.sub(newUsdcBalanceContract)).to.equal(usdcRepayFeeAmount); + expect(preUsdcBalanceBot.sub(newUsdcBalanceBot)).to.equal(usdcRepayFeeAmount); }); }); @@ -375,14 +375,14 @@ export function shouldBehaveLikeUniswapV2Call(): void { it("flash swaps USDC making no USDC profit and spending allocated USDC to pay swap fee", async function () { const to: string = this.contracts.hifiFlashUniswapV2Underlying.address; const preUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); - const preUsdcBalanceContract = await this.contracts.usdc.balanceOf(to); + const preUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.bot.address); await this.contracts.uniswapV2Pair .connect(this.signers.liquidator) .swap(token0Amount, token1Amount, to, data); const newUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); - const newUsdcBalanceContract = await this.contracts.usdc.balanceOf(to); + const newUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.bot.address); expect(newUsdcBalanceAccount).to.equal(preUsdcBalanceAccount); - expect(preUsdcBalanceContract.sub(newUsdcBalanceContract)).to.equal(usdcRepayFeeAmount); + expect(preUsdcBalanceBot.sub(newUsdcBalanceBot)).to.equal(usdcRepayFeeAmount); }); it("emits a FlashLiquidateBorrow event", async function () { diff --git a/packages/flash-swap/test/shared/contexts.ts b/packages/flash-swap/test/shared/contexts.ts index dc93fab0..9ffce76f 100644 --- a/packages/flash-swap/test/shared/contexts.ts +++ b/packages/flash-swap/test/shared/contexts.ts @@ -18,6 +18,7 @@ export function baseContext(description: string, hooks: () => void): void { this.signers.borrower = signers[1]; this.signers.liquidator = signers[2]; this.signers.raider = signers[3]; + this.signers.bot = signers[4]; // Get rid of this when https://github.com/nomiclabs/hardhat/issues/849 gets fixed. this.loadFixture = createFixtureLoader(signers as Signer[] as Wallet[]); diff --git a/packages/flash-swap/test/shared/types.ts b/packages/flash-swap/test/shared/types.ts index 3b4efef5..3e96fb81 100644 --- a/packages/flash-swap/test/shared/types.ts +++ b/packages/flash-swap/test/shared/types.ts @@ -38,4 +38,5 @@ export interface Signers { borrower: SignerWithAddress; liquidator: SignerWithAddress; raider: SignerWithAddress; + bot: SignerWithAddress; } From c238f40ca25f8c33b0375fda94da21ed138f999e Mon Sep 17 00:00:00 2001 From: scorpion9979 Date: Wed, 3 Nov 2021 23:07:42 +0300 Subject: [PATCH 06/16] feat(flash-swap): add test to increase coverage --- .../effects/uniswapV2Call.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/effects/uniswapV2Call.ts b/packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/effects/uniswapV2Call.ts index c9c57c24..492b8864 100644 --- a/packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/effects/uniswapV2Call.ts +++ b/packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/effects/uniswapV2Call.ts @@ -357,6 +357,18 @@ export function shouldBehaveLikeUniswapV2Call(): void { await this.contracts.uniswapV2Pair.sync(); }); + context("when wrong token is flash borrowed", function () { + it("reverts", async function () { + const { token0Amount, token1Amount } = await getTokenAmounts.call(this, WBTC("1"), Zero); + const to: string = this.contracts.hifiFlashUniswapV2Underlying.address; + await expect( + this.contracts.uniswapV2Pair + .connect(this.signers.raider) + .swap(token0Amount, token1Amount, to, data), + ).to.be.revertedWith(HifiFlashUniswapV2UnderlyingErrors.FlashBorrowWrongToken); + }); + }); + it("flash swaps USDC making no USDC profit and spending allocated USDC to pay swap fee", async function () { const to: string = this.contracts.hifiFlashUniswapV2Underlying.address; const preUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); From ffda1795cf11185e0a76d325ad6cdc6f48af6fb6 Mon Sep 17 00:00:00 2001 From: Paul Razvan Berg Date: Thu, 4 Nov 2021 11:17:15 +0200 Subject: [PATCH 07/16] refactor: move common logic in "HifiFlashUniswapV2.sol" --- .../uniswap-v2/HifiFlashUniswapV2.sol | 88 +++---------------- .../HifiFlashUniswapV2Underlying.sol | 88 +++---------------- .../uniswap-v2/HifiFlashUniswapV2Utils.sol | 87 ++++++++++++++++++ 3 files changed, 113 insertions(+), 150 deletions(-) create mode 100644 packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2Utils.sol diff --git a/packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2.sol b/packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2.sol index d98a5c23..f2f8dee4 100644 --- a/packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2.sol +++ b/packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2.sol @@ -7,6 +7,7 @@ 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 "./IUniswapV2Pair.sol"; @@ -143,13 +144,22 @@ contract HifiFlashUniswapV2 is IHifiFlashUniswapV2 { ); // Check that the caller is a genuine UniswapV2Pair contract. - if (msg.sender != pairFor(address(vars.underlying), address(vars.collateral))) { + if ( + msg.sender != + HifiFlashUniswapV2Utils.pairFor( + uniV2Factory, + uniV2PairInitCodeHash, + address(vars.underlying), + address(vars.collateral) + ) + ) { revert HifiFlashUniswapV2__CallNotAuthorized(msg.sender); } // Mint hTokens and liquidate the borrower. - vars.mintedHTokenAmount = mintHTokensInternal(vars.bond, vars.underlyingAmount); - vars.seizedCollateralAmount = liquidateBorrowInternal( + vars.mintedHTokenAmount = HifiFlashUniswapV2Utils.mintHTokensInternal(vars.bond, vars.underlyingAmount); + vars.seizedCollateralAmount = HifiFlashUniswapV2Utils.liquidateBorrowInternal( + balanceSheet, vars.borrower, vars.bond, vars.collateral, @@ -187,76 +197,4 @@ contract HifiFlashUniswapV2 is IHifiFlashUniswapV2 { vars.profitCollateralAmount ); } - - /// INTERNAL CONSTANT FUNCTIONS /// - - /// @dev Calculates the CREATE2 address for a pair without making any external calls. - function pairFor(address tokenA, address tokenB) internal view returns (address pair) { - (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); - pair = address( - uint160( - uint256( - keccak256( - abi.encodePacked( - hex"ff", - uniV2Factory, - keccak256(abi.encodePacked(token0, token1)), - uniV2PairInitCodeHash - ) - ) - ) - ) - ); - } - - /// INTERNAL NON-CONSTANT FUNCTIONS /// - - /// @dev Liquidates the borrower by transferring the underlying to the BalanceSheet. By doing this, the - /// liquidator receives collateral at a discount. - function liquidateBorrowInternal( - address borrower, - IHToken bond, - IErc20 collateral, - uint256 mintedHTokenAmount - ) internal returns (uint256 seizedCollateralAmount) { - uint256 collateralAmount = balanceSheet.getCollateralAmount(borrower, collateral); - uint256 hypotheticalRepayAmount = balanceSheet.getRepayAmount(collateral, collateralAmount, bond); - - // If the hypothetical repay amount is bigger than the debt amount, this could be a single-collateral multi-bond - // vault. Otherwise, it could be a multi-collateral single-bond vault. However, it is difficult to generalize - // for the multi-collateral and multi-bond situation. The repay amount could be either bigger, smaller, or even - // equal to the debt amount depending on the collateral and debt amount distribution. - uint256 debtAmount = balanceSheet.getDebtAmount(borrower, bond); - uint256 repayAmount = hypotheticalRepayAmount > debtAmount ? debtAmount : hypotheticalRepayAmount; - - // Truncate the repay amount such that we keep the dust in this contract rather than the BalanceSheet. - uint256 truncatedRepayAmount = mintedHTokenAmount > repayAmount ? repayAmount : mintedHTokenAmount; - - // Liquidate borrow. - uint256 oldCollateralBalance = collateral.balanceOf(address(this)); - balanceSheet.liquidateBorrow(borrower, bond, truncatedRepayAmount, collateral); - uint256 newCollateralBalance = collateral.balanceOf(address(this)); - unchecked { - seizedCollateralAmount = newCollateralBalance - oldCollateralBalance; - } - } - - /// @dev Supplies the underlying to the HToken contract to mint hTokens without taking on debt. - function mintHTokensInternal(IHToken bond, uint256 underlyingAmount) internal returns (uint256 mintedHTokenAmount) { - IErc20 underlying = bond.underlying(); - - // Allow the HToken contract to spend underlying if allowance not enough. - uint256 allowance = underlying.allowance(address(this), address(bond)); - if (allowance < underlyingAmount) { - underlying.approve(address(bond), type(uint256).max); - } - - // Mint hTokens. - uint256 preHTokenBalance = bond.balanceOf(address(this)); - bond.supplyUnderlying(underlyingAmount); - uint256 postHTokenBalance = bond.balanceOf(address(this)); - unchecked { - mintedHTokenAmount = postHTokenBalance - preHTokenBalance; - } - } } diff --git a/packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2Underlying.sol b/packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2Underlying.sol index 26d812d5..f697a160 100644 --- a/packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2Underlying.sol +++ b/packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2Underlying.sol @@ -7,6 +7,7 @@ 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 "./IHifiFlashUniswapV2Underlying.sol"; import "./IUniswapV2Pair.sol"; @@ -137,13 +138,22 @@ contract HifiFlashUniswapV2Underlying is IHifiFlashUniswapV2Underlying { : IUniswapV2Pair(msg.sender).token0(); // Check that the caller is a genuine UniswapV2Pair contract. - if (msg.sender != pairFor(address(vars.underlying), vars.swapToken)) { + if ( + msg.sender != + HifiFlashUniswapV2Utils.pairFor( + uniV2Factory, + uniV2PairInitCodeHash, + address(vars.underlying), + vars.swapToken + ) + ) { revert HifiFlashUniswapV2Underlying__CallNotAuthorized(msg.sender); } // Mint hTokens and liquidate the borrower. - vars.mintedHTokenAmount = mintHTokensInternal(vars.bond, vars.underlyingAmount); - vars.seizedCollateralAmount = liquidateBorrowInternal( + vars.mintedHTokenAmount = HifiFlashUniswapV2Utils.mintHTokensInternal(vars.bond, vars.underlyingAmount); + vars.seizedCollateralAmount = HifiFlashUniswapV2Utils.liquidateBorrowInternal( + balanceSheet, vars.borrower, vars.bond, vars.collateral, @@ -174,76 +184,4 @@ contract HifiFlashUniswapV2Underlying is IHifiFlashUniswapV2Underlying { vars.repayCollateralAmount ); } - - /// INTERNAL CONSTANT FUNCTIONS /// - - /// @dev Calculates the CREATE2 address for a pair without making any external calls. - function pairFor(address tokenA, address tokenB) internal view returns (address pair) { - (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); - pair = address( - uint160( - uint256( - keccak256( - abi.encodePacked( - hex"ff", - uniV2Factory, - keccak256(abi.encodePacked(token0, token1)), - uniV2PairInitCodeHash - ) - ) - ) - ) - ); - } - - /// INTERNAL NON-CONSTANT FUNCTIONS /// - - /// @dev Liquidates the borrower by transferring the underlying to the BalanceSheet. By doing this, the - /// liquidator receives collateral at a discount. - function liquidateBorrowInternal( - address borrower, - IHToken bond, - IErc20 collateral, - uint256 mintedHTokenAmount - ) internal returns (uint256 seizedCollateralAmount) { - uint256 collateralAmount = balanceSheet.getCollateralAmount(borrower, collateral); - uint256 hypotheticalRepayAmount = balanceSheet.getRepayAmount(collateral, collateralAmount, bond); - - // If the hypothetical repay amount is bigger than the debt amount, this could be a single-collateral multi-bond - // vault. Otherwise, it could be a multi-collateral single-bond vault. However, it is difficult to generalize - // for the multi-collateral and multi-bond situation. The repay amount could be either bigger, smaller, or even - // equal to the debt amount depending on the collateral and debt amount distribution. - uint256 debtAmount = balanceSheet.getDebtAmount(borrower, bond); - uint256 repayAmount = hypotheticalRepayAmount > debtAmount ? debtAmount : hypotheticalRepayAmount; - - // Truncate the repay amount such that we keep the dust in this contract rather than the BalanceSheet. - uint256 truncatedRepayAmount = mintedHTokenAmount > repayAmount ? repayAmount : mintedHTokenAmount; - - // Liquidate borrow. - uint256 oldCollateralBalance = collateral.balanceOf(address(this)); - balanceSheet.liquidateBorrow(borrower, bond, truncatedRepayAmount, collateral); - uint256 newCollateralBalance = collateral.balanceOf(address(this)); - unchecked { - seizedCollateralAmount = newCollateralBalance - oldCollateralBalance; - } - } - - /// @dev Supplies the underlying to the HToken contract to mint hTokens without taking on debt. - function mintHTokensInternal(IHToken bond, uint256 underlyingAmount) internal returns (uint256 mintedHTokenAmount) { - IErc20 underlying = bond.underlying(); - - // Allow the HToken contract to spend underlying if allowance not enough. - uint256 allowance = underlying.allowance(address(this), address(bond)); - if (allowance < underlyingAmount) { - underlying.approve(address(bond), type(uint256).max); - } - - // Mint hTokens. - uint256 preHTokenBalance = bond.balanceOf(address(this)); - bond.supplyUnderlying(underlyingAmount); - uint256 postHTokenBalance = bond.balanceOf(address(this)); - unchecked { - mintedHTokenAmount = postHTokenBalance - preHTokenBalance; - } - } } diff --git a/packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2Utils.sol b/packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2Utils.sol new file mode 100644 index 00000000..31d8e33b --- /dev/null +++ b/packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2Utils.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +pragma solidity >=0.8.4; + +import "@paulrberg/contracts/token/erc20/IErc20.sol"; +import "@hifi/protocol/contracts/core/balanceSheet/IBalanceSheetV1.sol"; + +/// @title HifiFlashUniswapV2 +/// @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; + + /// @dev Calculates the CREATE2 address for a pair without making any external calls. + function pairFor( + address uniV2Factory, + bytes32 uniV2PairInitCodeHash, + address tokenA, + address tokenB + ) internal pure returns (address pair) { + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); + pair = address( + uint160( + uint256( + keccak256( + abi.encodePacked( + hex"ff", + uniV2Factory, + keccak256(abi.encodePacked(token0, token1)), + uniV2PairInitCodeHash + ) + ) + ) + ) + ); + } + + /// @dev Liquidates the borrower by transferring the underlying to the BalanceSheet. By doing this, the + /// liquidator receives collateral at a discount. + function liquidateBorrowInternal( + IBalanceSheetV1 balanceSheet, + address borrower, + IHToken bond, + IErc20 collateral, + uint256 mintedHTokenAmount + ) internal returns (uint256 seizedCollateralAmount) { + uint256 collateralAmount = balanceSheet.getCollateralAmount(borrower, collateral); + uint256 hypotheticalRepayAmount = balanceSheet.getRepayAmount(collateral, collateralAmount, bond); + + // If the hypothetical repay amount is bigger than the debt amount, this could be a single-collateral multi-bond + // vault. Otherwise, it could be a multi-collateral single-bond vault. However, it is difficult to generalize + // for the multi-collateral and multi-bond situation. The repay amount could be either bigger, smaller, or even + // equal to the debt amount depending on the collateral and debt amount distribution. + uint256 debtAmount = balanceSheet.getDebtAmount(borrower, bond); + uint256 repayAmount = hypotheticalRepayAmount > debtAmount ? debtAmount : hypotheticalRepayAmount; + + // Truncate the repay amount such that we keep the dust in this contract rather than the BalanceSheet. + uint256 truncatedRepayAmount = mintedHTokenAmount > repayAmount ? repayAmount : mintedHTokenAmount; + + // Liquidate borrow. + uint256 oldCollateralBalance = collateral.balanceOf(address(this)); + balanceSheet.liquidateBorrow(borrower, bond, truncatedRepayAmount, collateral); + uint256 newCollateralBalance = collateral.balanceOf(address(this)); + unchecked { + seizedCollateralAmount = newCollateralBalance - oldCollateralBalance; + } + } + + /// @dev Supplies the underlying to the HToken contract to mint hTokens without taking on debt. + function mintHTokensInternal(IHToken bond, uint256 underlyingAmount) internal returns (uint256 mintedHTokenAmount) { + IErc20 underlying = bond.underlying(); + + // Allow the HToken contract to spend underlying if allowance not enough. + uint256 allowance = underlying.allowance(address(this), address(bond)); + if (allowance < underlyingAmount) { + underlying.approve(address(bond), type(uint256).max); + } + + // Mint hTokens. + uint256 preHTokenBalance = bond.balanceOf(address(this)); + bond.supplyUnderlying(underlyingAmount); + uint256 postHTokenBalance = bond.balanceOf(address(this)); + unchecked { + mintedHTokenAmount = postHTokenBalance - preHTokenBalance; + } + } +} From b02aac3ff38c5ad1c687d85ed3e4782e93de92c0 Mon Sep 17 00:00:00 2001 From: Paul Razvan Berg Date: Thu, 4 Nov 2021 12:11:32 +0200 Subject: [PATCH 08/16] refactor(flash-swap): "HifiFlashUniswapV2" into "CollateralFlashUniswapV2" chore: update flash contract deployers to use the latest contract names chore(tasks): update flash contract deployers to use the latest contract names refactor(flash-swap): "HifiFlashUniswapV2Underlying" into "UnderlyingFlashUniswapV2" refactor(flash-swap): "HifiFlashUniswapV2Utils" into "FlashUtils" test(flash-swap): update tests to use the latest contract names --- ...> deploy-collateral-flash-uniswap-v2.yaml} | 6 +- .../deploy-underlying-flash-uniswap-v2.yaml | 76 ++++ packages/errors/src/flashSwap.ts | 18 +- packages/errors/src/index.ts | 2 +- packages/flash-swap/.gitignore | 12 +- packages/flash-swap/README.md | 9 +- ...wapV2.sol => CollateralFlashUniswapV2.sol} | 52 +-- ...FlashUniswapV2Utils.sol => FlashUtils.sol} | 8 +- ...apV2.sol => ICollateralFlashUniswapV2.sol} | 5 +- ...ying.sol => IUnderlyingFlashUniswapV2.sol} | 4 +- ...lying.sol => UnderlyingFlashUniswapV2.sol} | 43 +-- packages/flash-swap/hardhat.config.ts | 8 +- ...pV2.d.ts => CollateralFlashUniswapV2.d.ts} | 6 +- ...V2.d.ts => ICollateralFlashUniswapV2.d.ts} | 6 +- .../src/types/IUnderlyingFlashUniswapV2.d.ts | 333 ++++++++++++++++++ .../src/types/UnderlyingFlashUniswapV2.d.ts | 333 ++++++++++++++++++ .../CollateralFlashUniswapV2.behavior.ts} | 2 +- .../CollateralFlashUniswapV2.ts} | 16 +- .../effects/uniswapV2Call.ts | 36 +- packages/flash-swap/test/integration/index.ts | 8 +- .../UnderlyingFlashUniswapV2.behavior.ts} | 2 +- .../UnderlyingFlashUniswapV2.ts} | 18 +- .../effects/uniswapV2Call.ts | 38 +- packages/flash-swap/test/shared/fixtures.ts | 24 +- packages/flash-swap/test/shared/types.ts | 8 +- packages/tasks/README.md | 10 +- packages/tasks/helpers/constants.ts | 7 +- ...iswapV2.ts => collateralFlashUniswapV2.ts} | 20 +- packages/tasks/src/deploy/index.ts | 4 +- ...erlying.ts => underlyingFlashUniswapV2.ts} | 21 +- 30 files changed, 944 insertions(+), 191 deletions(-) rename .github/workflows/{deploy-hifi-flash-uniswap-v2.yaml => deploy-collateral-flash-uniswap-v2.yaml} (94%) create mode 100644 .github/workflows/deploy-underlying-flash-uniswap-v2.yaml rename packages/flash-swap/contracts/uniswap-v2/{HifiFlashUniswapV2.sol => CollateralFlashUniswapV2.sol} (79%) rename packages/flash-swap/contracts/uniswap-v2/{HifiFlashUniswapV2Utils.sol => FlashUtils.sol} (92%) rename packages/flash-swap/contracts/uniswap-v2/{IHifiFlashUniswapV2.sol => ICollateralFlashUniswapV2.sol} (95%) rename packages/flash-swap/contracts/uniswap-v2/{IHifiFlashUniswapV2Underlying.sol => IUnderlyingFlashUniswapV2.sol} (96%) rename packages/flash-swap/contracts/uniswap-v2/{HifiFlashUniswapV2Underlying.sol => UnderlyingFlashUniswapV2.sol} (78%) rename packages/flash-swap/src/types/{HifiFlashUniswapV2.d.ts => CollateralFlashUniswapV2.d.ts} (98%) rename packages/flash-swap/src/types/{IHifiFlashUniswapV2.d.ts => ICollateralFlashUniswapV2.d.ts} (98%) create mode 100644 packages/flash-swap/src/types/IUnderlyingFlashUniswapV2.d.ts create mode 100644 packages/flash-swap/src/types/UnderlyingFlashUniswapV2.d.ts rename packages/flash-swap/test/integration/{hifiFlashUniswapV2/HifiFlashUniswapV2.behavior.ts => collateralFlashUniswapV2/CollateralFlashUniswapV2.behavior.ts} (70%) rename packages/flash-swap/test/integration/{hifiFlashUniswapV2/HifiFlashUniswapV2.ts => collateralFlashUniswapV2/CollateralFlashUniswapV2.ts} (63%) rename packages/flash-swap/test/integration/{hifiFlashUniswapV2 => collateralFlashUniswapV2}/effects/uniswapV2Call.ts (91%) rename packages/flash-swap/test/integration/{hifiFlashUniswapV2Underlying/HifiFlashUniswapV2Underlying.behavior.ts => underlyingFlashUniswapV2/UnderlyingFlashUniswapV2.behavior.ts} (69%) rename packages/flash-swap/test/integration/{hifiFlashUniswapV2Underlying/HifiFlashUniswapV2Underlying.ts => underlyingFlashUniswapV2/UnderlyingFlashUniswapV2.ts} (63%) rename packages/flash-swap/test/integration/{hifiFlashUniswapV2Underlying => underlyingFlashUniswapV2}/effects/uniswapV2Call.ts (92%) rename packages/tasks/src/deploy/{hifiFlashUniswapV2.ts => collateralFlashUniswapV2.ts} (64%) rename packages/tasks/src/deploy/{hifiFlashUniswapV2Underlying.ts => underlyingFlashUniswapV2.ts} (63%) diff --git a/.github/workflows/deploy-hifi-flash-uniswap-v2.yaml b/.github/workflows/deploy-collateral-flash-uniswap-v2.yaml similarity index 94% rename from .github/workflows/deploy-hifi-flash-uniswap-v2.yaml rename to .github/workflows/deploy-collateral-flash-uniswap-v2.yaml index c92e881a..6aebae04 100644 --- a/.github/workflows/deploy-hifi-flash-uniswap-v2.yaml +++ b/.github/workflows/deploy-collateral-flash-uniswap-v2.yaml @@ -1,4 +1,4 @@ -name: "Deploy: HifiFlashUniswapV2" +name: "Deploy: CollateralFlashUniswapV2" env: ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }} @@ -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 @@ -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 }}" diff --git a/.github/workflows/deploy-underlying-flash-uniswap-v2.yaml b/.github/workflows/deploy-underlying-flash-uniswap-v2.yaml new file mode 100644 index 00000000..9b7e31f2 --- /dev/null +++ b/.github/workflows/deploy-underlying-flash-uniswap-v2.yaml @@ -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 }}" diff --git a/packages/errors/src/flashSwap.ts b/packages/errors/src/flashSwap.ts index 75ca2f63..4b60d308 100644 --- a/packages/errors/src/flashSwap.ts +++ b/packages/errors/src/flashSwap.ts @@ -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", } diff --git a/packages/errors/src/index.ts b/packages/errors/src/index.ts index f51af2a8..6753e89a 100644 --- a/packages/errors/src/index.ts +++ b/packages/errors/src/index.ts @@ -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 { diff --git a/packages/flash-swap/.gitignore b/packages/flash-swap/.gitignore index 6f4c12c4..426a86f5 100644 --- a/packages/flash-swap/.gitignore +++ b/packages/flash-swap/.gitignore @@ -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 diff --git a/packages/flash-swap/README.md b/packages/flash-swap/README.md index 9400296c..022f2f24 100644 --- a/packages/flash-swap/README.md +++ b/packages/flash-swap/README.md @@ -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 @@ -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..."; diff --git a/packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2.sol b/packages/flash-swap/contracts/uniswap-v2/CollateralFlashUniswapV2.sol similarity index 79% rename from packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2.sol rename to packages/flash-swap/contracts/uniswap-v2/CollateralFlashUniswapV2.sol index f2f8dee4..ca868f50 100644 --- a/packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2.sol +++ b/packages/flash-swap/contracts/uniswap-v2/CollateralFlashUniswapV2.sol @@ -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 /// @@ -56,7 +61,7 @@ contract HifiFlashUniswapV2 is IHifiFlashUniswapV2 { /// PUBLIC CONSTANT FUNCTIONS //// - /// @inheritdoc IHifiFlashUniswapV2 + /// @inheritdoc ICollateralFlashUniswapV2 function getCollateralAndUnderlyingAmount( IUniswapV2Pair pair, uint256 amount0, @@ -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, @@ -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, @@ -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 diff --git a/packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2Utils.sol b/packages/flash-swap/contracts/uniswap-v2/FlashUtils.sol similarity index 92% rename from packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2Utils.sol rename to packages/flash-swap/contracts/uniswap-v2/FlashUtils.sol index 31d8e33b..3725bf28 100644 --- a/packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2Utils.sol +++ b/packages/flash-swap/contracts/uniswap-v2/FlashUtils.sol @@ -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, diff --git a/packages/flash-swap/contracts/uniswap-v2/IHifiFlashUniswapV2.sol b/packages/flash-swap/contracts/uniswap-v2/ICollateralFlashUniswapV2.sol similarity index 95% rename from packages/flash-swap/contracts/uniswap-v2/IHifiFlashUniswapV2.sol rename to packages/flash-swap/contracts/uniswap-v2/ICollateralFlashUniswapV2.sol index 64e758fa..ce48f10e 100644 --- a/packages/flash-swap/contracts/uniswap-v2/IHifiFlashUniswapV2.sol +++ b/packages/flash-swap/contracts/uniswap-v2/ICollateralFlashUniswapV2.sol @@ -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( diff --git a/packages/flash-swap/contracts/uniswap-v2/IHifiFlashUniswapV2Underlying.sol b/packages/flash-swap/contracts/uniswap-v2/IUnderlyingFlashUniswapV2.sol similarity index 96% rename from packages/flash-swap/contracts/uniswap-v2/IHifiFlashUniswapV2Underlying.sol rename to packages/flash-swap/contracts/uniswap-v2/IUnderlyingFlashUniswapV2.sol index c50670bf..ba4e4c08 100644 --- a/packages/flash-swap/contracts/uniswap-v2/IHifiFlashUniswapV2Underlying.sol +++ b/packages/flash-swap/contracts/uniswap-v2/IUnderlyingFlashUniswapV2.sol @@ -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( diff --git a/packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2Underlying.sol b/packages/flash-swap/contracts/uniswap-v2/UnderlyingFlashUniswapV2.sol similarity index 78% rename from packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2Underlying.sol rename to packages/flash-swap/contracts/uniswap-v2/UnderlyingFlashUniswapV2.sol index f697a160..c46d71ee 100644 --- a/packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2Underlying.sol +++ b/packages/flash-swap/contracts/uniswap-v2/UnderlyingFlashUniswapV2.sol @@ -7,38 +7,38 @@ 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 "./IHifiFlashUniswapV2Underlying.sol"; +import "./FlashUtils.sol"; +import "./IUnderlyingFlashUniswapV2.sol"; import "./IUniswapV2Pair.sol"; /// @notice Emitted when the caller is not the Uniswap V2 pair contract. -error HifiFlashUniswapV2Underlying__CallNotAuthorized(address caller); +error UnderlyingFlashUniswapV2__CallNotAuthorized(address caller); /// @notice Emitted when the flash borrowed asset is the wrong token in the pair. -error HifiFlashUniswapV2Underlying__FlashBorrowWrongToken(uint256 collateralAmount); +error UnderlyingFlashUniswapV2__FlashBorrowWrongToken(uint256 collateralAmount); /// @notice Emitted when neither the token0 nor the token1 is the underlying. -error HifiFlashUniswapV2Underlying__UnderlyingNotInPool( +error UnderlyingFlashUniswapV2__UnderlyingNotInPool( IUniswapV2Pair pair, address token0, address token1, IErc20 underlying ); -/// @title HifiFlashUniswapV2Underlying +/// @title UnderlyingFlashUniswapV2 /// @author Hifi -contract HifiFlashUniswapV2Underlying is IHifiFlashUniswapV2Underlying { +contract UnderlyingFlashUniswapV2 is IUnderlyingFlashUniswapV2 { using SafeErc20 for IErc20; /// PUBLIC STORAGE /// - /// @inheritdoc IHifiFlashUniswapV2Underlying + /// @inheritdoc IUnderlyingFlashUniswapV2 IBalanceSheetV1 public override balanceSheet; - /// @inheritdoc IHifiFlashUniswapV2Underlying + /// @inheritdoc IUnderlyingFlashUniswapV2 address public override uniV2Factory; - /// @inheritdoc IHifiFlashUniswapV2Underlying + /// @inheritdoc IUnderlyingFlashUniswapV2 bytes32 public override uniV2PairInitCodeHash; /// CONSTRUCTOR /// @@ -54,7 +54,7 @@ contract HifiFlashUniswapV2Underlying is IHifiFlashUniswapV2Underlying { /// PUBLIC CONSTANT FUNCTIONS //// - /// @inheritdoc IHifiFlashUniswapV2Underlying + /// @inheritdoc IUnderlyingFlashUniswapV2 function getCollateralAndUnderlyingAmount( IUniswapV2Pair pair, uint256 amount0, @@ -65,22 +65,22 @@ contract HifiFlashUniswapV2Underlying is IHifiFlashUniswapV2Underlying { address token1 = pair.token1(); if (token0 == address(underlying)) { if (amount1 > 0) { - revert HifiFlashUniswapV2Underlying__FlashBorrowWrongToken(amount1); + revert UnderlyingFlashUniswapV2__FlashBorrowWrongToken(amount1); } collateral = IErc20(token0); underlyingAmount = amount0; } else if (token1 == address(underlying)) { if (amount0 > 0) { - revert HifiFlashUniswapV2Underlying__FlashBorrowWrongToken(amount0); + revert UnderlyingFlashUniswapV2__FlashBorrowWrongToken(amount0); } collateral = IErc20(token1); underlyingAmount = amount1; } else { - revert HifiFlashUniswapV2Underlying__UnderlyingNotInPool(pair, token0, token1, underlying); + revert UnderlyingFlashUniswapV2__UnderlyingNotInPool(pair, token0, token1, underlying); } } - /// @inheritdoc IHifiFlashUniswapV2Underlying + /// @inheritdoc IUnderlyingFlashUniswapV2 function getRepayCollateralAmount(uint256 underlyingAmount) public pure @@ -140,19 +140,14 @@ contract HifiFlashUniswapV2Underlying is IHifiFlashUniswapV2Underlying { // Check that the caller is a genuine UniswapV2Pair contract. if ( msg.sender != - HifiFlashUniswapV2Utils.pairFor( - uniV2Factory, - uniV2PairInitCodeHash, - address(vars.underlying), - vars.swapToken - ) + FlashUtils.pairFor(uniV2Factory, uniV2PairInitCodeHash, address(vars.underlying), vars.swapToken) ) { - revert HifiFlashUniswapV2Underlying__CallNotAuthorized(msg.sender); + revert UnderlyingFlashUniswapV2__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, diff --git a/packages/flash-swap/hardhat.config.ts b/packages/flash-swap/hardhat.config.ts index 10470aad..ccf223e6 100644 --- a/packages/flash-swap/hardhat.config.ts +++ b/packages/flash-swap/hardhat.config.ts @@ -41,13 +41,13 @@ const config: HardhatUserConfig = { packager: { contracts: [ "Erc20", - "HifiFlashUniswapV2", - "HifiFlashUniswapV2Underlying", + "CollateralFlashUniswapV2", + "ICollateralFlashUniswapV2", "IErc20", - "IHifiFlashUniswapV2", - "IHifiFlashUniswapV2Underlying", + "IUnderlyingFlashUniswapV2", "IUniswapV2Callee", "IUniswapV2Pair", + "UnderlyingFlashUniswapV2", "UniswapV2Pair", ], includeFactories: true, diff --git a/packages/flash-swap/src/types/HifiFlashUniswapV2.d.ts b/packages/flash-swap/src/types/CollateralFlashUniswapV2.d.ts similarity index 98% rename from packages/flash-swap/src/types/HifiFlashUniswapV2.d.ts rename to packages/flash-swap/src/types/CollateralFlashUniswapV2.d.ts index e0ac79ce..1b93e987 100644 --- a/packages/flash-swap/src/types/HifiFlashUniswapV2.d.ts +++ b/packages/flash-swap/src/types/CollateralFlashUniswapV2.d.ts @@ -19,7 +19,7 @@ import { Listener, Provider } from "@ethersproject/providers"; import { FunctionFragment, EventFragment, Result } from "@ethersproject/abi"; import type { TypedEventFilter, TypedEvent, TypedListener } from "./common"; -interface HifiFlashUniswapV2Interface extends ethers.utils.Interface { +interface CollateralFlashUniswapV2Interface extends ethers.utils.Interface { functions: { "balanceSheet()": FunctionFragment; "getCollateralAndUnderlyingAmount(address,uint256,uint256,address)": FunctionFragment; @@ -97,7 +97,7 @@ export type FlashLiquidateBorrowEvent = TypedEvent< } >; -export class HifiFlashUniswapV2 extends BaseContract { +export class CollateralFlashUniswapV2 extends BaseContract { connect(signerOrProvider: Signer | Provider | string): this; attach(addressOrName: string): this; deployed(): Promise; @@ -138,7 +138,7 @@ export class HifiFlashUniswapV2 extends BaseContract { toBlock?: string | number | undefined ): Promise>>; - interface: HifiFlashUniswapV2Interface; + interface: CollateralFlashUniswapV2Interface; functions: { balanceSheet(overrides?: CallOverrides): Promise<[string]>; diff --git a/packages/flash-swap/src/types/IHifiFlashUniswapV2.d.ts b/packages/flash-swap/src/types/ICollateralFlashUniswapV2.d.ts similarity index 98% rename from packages/flash-swap/src/types/IHifiFlashUniswapV2.d.ts rename to packages/flash-swap/src/types/ICollateralFlashUniswapV2.d.ts index 6fe3775f..6c970b6f 100644 --- a/packages/flash-swap/src/types/IHifiFlashUniswapV2.d.ts +++ b/packages/flash-swap/src/types/ICollateralFlashUniswapV2.d.ts @@ -19,7 +19,7 @@ import { Listener, Provider } from "@ethersproject/providers"; import { FunctionFragment, EventFragment, Result } from "@ethersproject/abi"; import type { TypedEventFilter, TypedEvent, TypedListener } from "./common"; -interface IHifiFlashUniswapV2Interface extends ethers.utils.Interface { +interface ICollateralFlashUniswapV2Interface extends ethers.utils.Interface { functions: { "balanceSheet()": FunctionFragment; "getCollateralAndUnderlyingAmount(address,uint256,uint256,address)": FunctionFragment; @@ -97,7 +97,7 @@ export type FlashLiquidateBorrowEvent = TypedEvent< } >; -export class IHifiFlashUniswapV2 extends BaseContract { +export class ICollateralFlashUniswapV2 extends BaseContract { connect(signerOrProvider: Signer | Provider | string): this; attach(addressOrName: string): this; deployed(): Promise; @@ -138,7 +138,7 @@ export class IHifiFlashUniswapV2 extends BaseContract { toBlock?: string | number | undefined ): Promise>>; - interface: IHifiFlashUniswapV2Interface; + interface: ICollateralFlashUniswapV2Interface; functions: { balanceSheet(overrides?: CallOverrides): Promise<[string]>; diff --git a/packages/flash-swap/src/types/IUnderlyingFlashUniswapV2.d.ts b/packages/flash-swap/src/types/IUnderlyingFlashUniswapV2.d.ts new file mode 100644 index 00000000..bb12138f --- /dev/null +++ b/packages/flash-swap/src/types/IUnderlyingFlashUniswapV2.d.ts @@ -0,0 +1,333 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ + +import { + ethers, + EventFilter, + Signer, + BigNumber, + BigNumberish, + PopulatedTransaction, + BaseContract, + ContractTransaction, + Overrides, + CallOverrides, +} from "ethers"; +import { BytesLike } from "@ethersproject/bytes"; +import { Listener, Provider } from "@ethersproject/providers"; +import { FunctionFragment, EventFragment, Result } from "@ethersproject/abi"; +import type { TypedEventFilter, TypedEvent, TypedListener } from "./common"; + +interface IUnderlyingFlashUniswapV2Interface extends ethers.utils.Interface { + functions: { + "balanceSheet()": FunctionFragment; + "getCollateralAndUnderlyingAmount(address,uint256,uint256,address)": FunctionFragment; + "getRepayCollateralAmount(uint256)": FunctionFragment; + "uniV2Factory()": FunctionFragment; + "uniV2PairInitCodeHash()": FunctionFragment; + "uniswapV2Call(address,uint256,uint256,bytes)": FunctionFragment; + }; + + encodeFunctionData( + functionFragment: "balanceSheet", + values?: undefined + ): string; + encodeFunctionData( + functionFragment: "getCollateralAndUnderlyingAmount", + values: [string, BigNumberish, BigNumberish, string] + ): string; + encodeFunctionData( + functionFragment: "getRepayCollateralAmount", + values: [BigNumberish] + ): string; + encodeFunctionData( + functionFragment: "uniV2Factory", + values?: undefined + ): string; + encodeFunctionData( + functionFragment: "uniV2PairInitCodeHash", + values?: undefined + ): string; + encodeFunctionData( + functionFragment: "uniswapV2Call", + values: [string, BigNumberish, BigNumberish, BytesLike] + ): string; + + decodeFunctionResult( + functionFragment: "balanceSheet", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "getCollateralAndUnderlyingAmount", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "getRepayCollateralAmount", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "uniV2Factory", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "uniV2PairInitCodeHash", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "uniswapV2Call", + data: BytesLike + ): Result; + + events: { + "FlashLiquidateBorrow(address,address,address,uint256,uint256,uint256)": EventFragment; + }; + + getEvent(nameOrSignatureOrTopic: "FlashLiquidateBorrow"): EventFragment; +} + +export type FlashLiquidateBorrowEvent = TypedEvent< + [string, string, string, BigNumber, BigNumber, BigNumber] & { + liquidator: string; + borrower: string; + bond: string; + underlyingAmount: BigNumber; + seizedCollateralAmount: BigNumber; + repayCollateralAmount: BigNumber; + } +>; + +export class IUnderlyingFlashUniswapV2 extends BaseContract { + connect(signerOrProvider: Signer | Provider | string): this; + attach(addressOrName: string): this; + deployed(): Promise; + + listeners, EventArgsObject>( + eventFilter?: TypedEventFilter + ): Array>; + off, EventArgsObject>( + eventFilter: TypedEventFilter, + listener: TypedListener + ): this; + on, EventArgsObject>( + eventFilter: TypedEventFilter, + listener: TypedListener + ): this; + once, EventArgsObject>( + eventFilter: TypedEventFilter, + listener: TypedListener + ): this; + removeListener, EventArgsObject>( + eventFilter: TypedEventFilter, + listener: TypedListener + ): this; + removeAllListeners, EventArgsObject>( + eventFilter: TypedEventFilter + ): this; + + listeners(eventName?: string): Array; + off(eventName: string, listener: Listener): this; + on(eventName: string, listener: Listener): this; + once(eventName: string, listener: Listener): this; + removeListener(eventName: string, listener: Listener): this; + removeAllListeners(eventName?: string): this; + + queryFilter, EventArgsObject>( + event: TypedEventFilter, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined + ): Promise>>; + + interface: IUnderlyingFlashUniswapV2Interface; + + functions: { + balanceSheet(overrides?: CallOverrides): Promise<[string]>; + + getCollateralAndUnderlyingAmount( + pair: string, + amount0: BigNumberish, + amount1: BigNumberish, + underlying: string, + overrides?: CallOverrides + ): Promise< + [string, BigNumber] & { collateral: string; underlyingAmount: BigNumber } + >; + + getRepayCollateralAmount( + underlyingAmount: BigNumberish, + overrides?: CallOverrides + ): Promise<[BigNumber] & { collateralRepayAmount: BigNumber }>; + + uniV2Factory(overrides?: CallOverrides): Promise<[string]>; + + uniV2PairInitCodeHash(overrides?: CallOverrides): Promise<[string]>; + + uniswapV2Call( + sender: string, + amount0: BigNumberish, + amount1: BigNumberish, + data: BytesLike, + overrides?: Overrides & { from?: string | Promise } + ): Promise; + }; + + balanceSheet(overrides?: CallOverrides): Promise; + + getCollateralAndUnderlyingAmount( + pair: string, + amount0: BigNumberish, + amount1: BigNumberish, + underlying: string, + overrides?: CallOverrides + ): Promise< + [string, BigNumber] & { collateral: string; underlyingAmount: BigNumber } + >; + + getRepayCollateralAmount( + underlyingAmount: BigNumberish, + overrides?: CallOverrides + ): Promise; + + uniV2Factory(overrides?: CallOverrides): Promise; + + uniV2PairInitCodeHash(overrides?: CallOverrides): Promise; + + uniswapV2Call( + sender: string, + amount0: BigNumberish, + amount1: BigNumberish, + data: BytesLike, + overrides?: Overrides & { from?: string | Promise } + ): Promise; + + callStatic: { + balanceSheet(overrides?: CallOverrides): Promise; + + getCollateralAndUnderlyingAmount( + pair: string, + amount0: BigNumberish, + amount1: BigNumberish, + underlying: string, + overrides?: CallOverrides + ): Promise< + [string, BigNumber] & { collateral: string; underlyingAmount: BigNumber } + >; + + getRepayCollateralAmount( + underlyingAmount: BigNumberish, + overrides?: CallOverrides + ): Promise; + + uniV2Factory(overrides?: CallOverrides): Promise; + + uniV2PairInitCodeHash(overrides?: CallOverrides): Promise; + + uniswapV2Call( + sender: string, + amount0: BigNumberish, + amount1: BigNumberish, + data: BytesLike, + overrides?: CallOverrides + ): Promise; + }; + + filters: { + "FlashLiquidateBorrow(address,address,address,uint256,uint256,uint256)"( + liquidator?: string | null, + borrower?: string | null, + bond?: string | null, + underlyingAmount?: null, + seizedCollateralAmount?: null, + repayCollateralAmount?: null + ): TypedEventFilter< + [string, string, string, BigNumber, BigNumber, BigNumber], + { + liquidator: string; + borrower: string; + bond: string; + underlyingAmount: BigNumber; + seizedCollateralAmount: BigNumber; + repayCollateralAmount: BigNumber; + } + >; + + FlashLiquidateBorrow( + liquidator?: string | null, + borrower?: string | null, + bond?: string | null, + underlyingAmount?: null, + seizedCollateralAmount?: null, + repayCollateralAmount?: null + ): TypedEventFilter< + [string, string, string, BigNumber, BigNumber, BigNumber], + { + liquidator: string; + borrower: string; + bond: string; + underlyingAmount: BigNumber; + seizedCollateralAmount: BigNumber; + repayCollateralAmount: BigNumber; + } + >; + }; + + estimateGas: { + balanceSheet(overrides?: CallOverrides): Promise; + + getCollateralAndUnderlyingAmount( + pair: string, + amount0: BigNumberish, + amount1: BigNumberish, + underlying: string, + overrides?: CallOverrides + ): Promise; + + getRepayCollateralAmount( + underlyingAmount: BigNumberish, + overrides?: CallOverrides + ): Promise; + + uniV2Factory(overrides?: CallOverrides): Promise; + + uniV2PairInitCodeHash(overrides?: CallOverrides): Promise; + + uniswapV2Call( + sender: string, + amount0: BigNumberish, + amount1: BigNumberish, + data: BytesLike, + overrides?: Overrides & { from?: string | Promise } + ): Promise; + }; + + populateTransaction: { + balanceSheet(overrides?: CallOverrides): Promise; + + getCollateralAndUnderlyingAmount( + pair: string, + amount0: BigNumberish, + amount1: BigNumberish, + underlying: string, + overrides?: CallOverrides + ): Promise; + + getRepayCollateralAmount( + underlyingAmount: BigNumberish, + overrides?: CallOverrides + ): Promise; + + uniV2Factory(overrides?: CallOverrides): Promise; + + uniV2PairInitCodeHash( + overrides?: CallOverrides + ): Promise; + + uniswapV2Call( + sender: string, + amount0: BigNumberish, + amount1: BigNumberish, + data: BytesLike, + overrides?: Overrides & { from?: string | Promise } + ): Promise; + }; +} diff --git a/packages/flash-swap/src/types/UnderlyingFlashUniswapV2.d.ts b/packages/flash-swap/src/types/UnderlyingFlashUniswapV2.d.ts new file mode 100644 index 00000000..df094a80 --- /dev/null +++ b/packages/flash-swap/src/types/UnderlyingFlashUniswapV2.d.ts @@ -0,0 +1,333 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ + +import { + ethers, + EventFilter, + Signer, + BigNumber, + BigNumberish, + PopulatedTransaction, + BaseContract, + ContractTransaction, + Overrides, + CallOverrides, +} from "ethers"; +import { BytesLike } from "@ethersproject/bytes"; +import { Listener, Provider } from "@ethersproject/providers"; +import { FunctionFragment, EventFragment, Result } from "@ethersproject/abi"; +import type { TypedEventFilter, TypedEvent, TypedListener } from "./common"; + +interface UnderlyingFlashUniswapV2Interface extends ethers.utils.Interface { + functions: { + "balanceSheet()": FunctionFragment; + "getCollateralAndUnderlyingAmount(address,uint256,uint256,address)": FunctionFragment; + "getRepayCollateralAmount(uint256)": FunctionFragment; + "uniV2Factory()": FunctionFragment; + "uniV2PairInitCodeHash()": FunctionFragment; + "uniswapV2Call(address,uint256,uint256,bytes)": FunctionFragment; + }; + + encodeFunctionData( + functionFragment: "balanceSheet", + values?: undefined + ): string; + encodeFunctionData( + functionFragment: "getCollateralAndUnderlyingAmount", + values: [string, BigNumberish, BigNumberish, string] + ): string; + encodeFunctionData( + functionFragment: "getRepayCollateralAmount", + values: [BigNumberish] + ): string; + encodeFunctionData( + functionFragment: "uniV2Factory", + values?: undefined + ): string; + encodeFunctionData( + functionFragment: "uniV2PairInitCodeHash", + values?: undefined + ): string; + encodeFunctionData( + functionFragment: "uniswapV2Call", + values: [string, BigNumberish, BigNumberish, BytesLike] + ): string; + + decodeFunctionResult( + functionFragment: "balanceSheet", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "getCollateralAndUnderlyingAmount", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "getRepayCollateralAmount", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "uniV2Factory", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "uniV2PairInitCodeHash", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "uniswapV2Call", + data: BytesLike + ): Result; + + events: { + "FlashLiquidateBorrow(address,address,address,uint256,uint256,uint256)": EventFragment; + }; + + getEvent(nameOrSignatureOrTopic: "FlashLiquidateBorrow"): EventFragment; +} + +export type FlashLiquidateBorrowEvent = TypedEvent< + [string, string, string, BigNumber, BigNumber, BigNumber] & { + liquidator: string; + borrower: string; + bond: string; + underlyingAmount: BigNumber; + seizedCollateralAmount: BigNumber; + repayCollateralAmount: BigNumber; + } +>; + +export class UnderlyingFlashUniswapV2 extends BaseContract { + connect(signerOrProvider: Signer | Provider | string): this; + attach(addressOrName: string): this; + deployed(): Promise; + + listeners, EventArgsObject>( + eventFilter?: TypedEventFilter + ): Array>; + off, EventArgsObject>( + eventFilter: TypedEventFilter, + listener: TypedListener + ): this; + on, EventArgsObject>( + eventFilter: TypedEventFilter, + listener: TypedListener + ): this; + once, EventArgsObject>( + eventFilter: TypedEventFilter, + listener: TypedListener + ): this; + removeListener, EventArgsObject>( + eventFilter: TypedEventFilter, + listener: TypedListener + ): this; + removeAllListeners, EventArgsObject>( + eventFilter: TypedEventFilter + ): this; + + listeners(eventName?: string): Array; + off(eventName: string, listener: Listener): this; + on(eventName: string, listener: Listener): this; + once(eventName: string, listener: Listener): this; + removeListener(eventName: string, listener: Listener): this; + removeAllListeners(eventName?: string): this; + + queryFilter, EventArgsObject>( + event: TypedEventFilter, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined + ): Promise>>; + + interface: UnderlyingFlashUniswapV2Interface; + + functions: { + balanceSheet(overrides?: CallOverrides): Promise<[string]>; + + getCollateralAndUnderlyingAmount( + pair: string, + amount0: BigNumberish, + amount1: BigNumberish, + underlying: string, + overrides?: CallOverrides + ): Promise< + [string, BigNumber] & { collateral: string; underlyingAmount: BigNumber } + >; + + getRepayCollateralAmount( + underlyingAmount: BigNumberish, + overrides?: CallOverrides + ): Promise<[BigNumber] & { repayCollateralAmount: BigNumber }>; + + uniV2Factory(overrides?: CallOverrides): Promise<[string]>; + + uniV2PairInitCodeHash(overrides?: CallOverrides): Promise<[string]>; + + uniswapV2Call( + sender: string, + amount0: BigNumberish, + amount1: BigNumberish, + data: BytesLike, + overrides?: Overrides & { from?: string | Promise } + ): Promise; + }; + + balanceSheet(overrides?: CallOverrides): Promise; + + getCollateralAndUnderlyingAmount( + pair: string, + amount0: BigNumberish, + amount1: BigNumberish, + underlying: string, + overrides?: CallOverrides + ): Promise< + [string, BigNumber] & { collateral: string; underlyingAmount: BigNumber } + >; + + getRepayCollateralAmount( + underlyingAmount: BigNumberish, + overrides?: CallOverrides + ): Promise; + + uniV2Factory(overrides?: CallOverrides): Promise; + + uniV2PairInitCodeHash(overrides?: CallOverrides): Promise; + + uniswapV2Call( + sender: string, + amount0: BigNumberish, + amount1: BigNumberish, + data: BytesLike, + overrides?: Overrides & { from?: string | Promise } + ): Promise; + + callStatic: { + balanceSheet(overrides?: CallOverrides): Promise; + + getCollateralAndUnderlyingAmount( + pair: string, + amount0: BigNumberish, + amount1: BigNumberish, + underlying: string, + overrides?: CallOverrides + ): Promise< + [string, BigNumber] & { collateral: string; underlyingAmount: BigNumber } + >; + + getRepayCollateralAmount( + underlyingAmount: BigNumberish, + overrides?: CallOverrides + ): Promise; + + uniV2Factory(overrides?: CallOverrides): Promise; + + uniV2PairInitCodeHash(overrides?: CallOverrides): Promise; + + uniswapV2Call( + sender: string, + amount0: BigNumberish, + amount1: BigNumberish, + data: BytesLike, + overrides?: CallOverrides + ): Promise; + }; + + filters: { + "FlashLiquidateBorrow(address,address,address,uint256,uint256,uint256)"( + liquidator?: string | null, + borrower?: string | null, + bond?: string | null, + underlyingAmount?: null, + seizedCollateralAmount?: null, + repayCollateralAmount?: null + ): TypedEventFilter< + [string, string, string, BigNumber, BigNumber, BigNumber], + { + liquidator: string; + borrower: string; + bond: string; + underlyingAmount: BigNumber; + seizedCollateralAmount: BigNumber; + repayCollateralAmount: BigNumber; + } + >; + + FlashLiquidateBorrow( + liquidator?: string | null, + borrower?: string | null, + bond?: string | null, + underlyingAmount?: null, + seizedCollateralAmount?: null, + repayCollateralAmount?: null + ): TypedEventFilter< + [string, string, string, BigNumber, BigNumber, BigNumber], + { + liquidator: string; + borrower: string; + bond: string; + underlyingAmount: BigNumber; + seizedCollateralAmount: BigNumber; + repayCollateralAmount: BigNumber; + } + >; + }; + + estimateGas: { + balanceSheet(overrides?: CallOverrides): Promise; + + getCollateralAndUnderlyingAmount( + pair: string, + amount0: BigNumberish, + amount1: BigNumberish, + underlying: string, + overrides?: CallOverrides + ): Promise; + + getRepayCollateralAmount( + underlyingAmount: BigNumberish, + overrides?: CallOverrides + ): Promise; + + uniV2Factory(overrides?: CallOverrides): Promise; + + uniV2PairInitCodeHash(overrides?: CallOverrides): Promise; + + uniswapV2Call( + sender: string, + amount0: BigNumberish, + amount1: BigNumberish, + data: BytesLike, + overrides?: Overrides & { from?: string | Promise } + ): Promise; + }; + + populateTransaction: { + balanceSheet(overrides?: CallOverrides): Promise; + + getCollateralAndUnderlyingAmount( + pair: string, + amount0: BigNumberish, + amount1: BigNumberish, + underlying: string, + overrides?: CallOverrides + ): Promise; + + getRepayCollateralAmount( + underlyingAmount: BigNumberish, + overrides?: CallOverrides + ): Promise; + + uniV2Factory(overrides?: CallOverrides): Promise; + + uniV2PairInitCodeHash( + overrides?: CallOverrides + ): Promise; + + uniswapV2Call( + sender: string, + amount0: BigNumberish, + amount1: BigNumberish, + data: BytesLike, + overrides?: Overrides & { from?: string | Promise } + ): Promise; + }; +} diff --git a/packages/flash-swap/test/integration/hifiFlashUniswapV2/HifiFlashUniswapV2.behavior.ts b/packages/flash-swap/test/integration/collateralFlashUniswapV2/CollateralFlashUniswapV2.behavior.ts similarity index 70% rename from packages/flash-swap/test/integration/hifiFlashUniswapV2/HifiFlashUniswapV2.behavior.ts rename to packages/flash-swap/test/integration/collateralFlashUniswapV2/CollateralFlashUniswapV2.behavior.ts index f65903bb..dabe9a77 100644 --- a/packages/flash-swap/test/integration/hifiFlashUniswapV2/HifiFlashUniswapV2.behavior.ts +++ b/packages/flash-swap/test/integration/collateralFlashUniswapV2/CollateralFlashUniswapV2.behavior.ts @@ -1,6 +1,6 @@ import { shouldBehaveLikeUniswapV2Call } from "./effects/uniswapV2Call"; -export function shouldBehaveLikeHifiFlashUniswapV2(): void { +export function shouldBehaveLikeCollateralFlashUniswapV2(): void { describe("uniswapV2Call", function () { shouldBehaveLikeUniswapV2Call(); }); diff --git a/packages/flash-swap/test/integration/hifiFlashUniswapV2/HifiFlashUniswapV2.ts b/packages/flash-swap/test/integration/collateralFlashUniswapV2/CollateralFlashUniswapV2.ts similarity index 63% rename from packages/flash-swap/test/integration/hifiFlashUniswapV2/HifiFlashUniswapV2.ts rename to packages/flash-swap/test/integration/collateralFlashUniswapV2/CollateralFlashUniswapV2.ts index d1172ac8..50db6883 100644 --- a/packages/flash-swap/test/integration/hifiFlashUniswapV2/HifiFlashUniswapV2.ts +++ b/packages/flash-swap/test/integration/collateralFlashUniswapV2/CollateralFlashUniswapV2.ts @@ -1,28 +1,30 @@ import { integrationFixture } from "../../shared/fixtures"; -import { shouldBehaveLikeHifiFlashUniswapV2 } from "./HifiFlashUniswapV2.behavior"; +import { shouldBehaveLikeCollateralFlashUniswapV2 } from "./CollateralFlashUniswapV2.behavior"; -export function integrationTestHifiFlashUniswapV2(): void { - describe("HifiFlashUniswapV2", function () { +export function integrationTestCollateralFlashUniswapV2(): void { + describe("CollateralFlashUniswapV2", function () { beforeEach(async function () { const { balanceSheet, fintroller, hToken, - hifiFlashUniswapV2, + collateralFlashUniswapV2, maliciousPair, oracle, + underlyingFlashUniswapV2, + uniswapV2Pair, usdc, usdcPriceFeed, - uniswapV2Pair, wbtc, wbtcPriceFeed, } = await this.loadFixture(integrationFixture); this.contracts.balanceSheet = balanceSheet; this.contracts.fintroller = fintroller; this.contracts.hToken = hToken; - this.contracts.hifiFlashUniswapV2 = hifiFlashUniswapV2; + this.contracts.collateralFlashUniswapV2 = collateralFlashUniswapV2; this.contracts.maliciousPair = maliciousPair; this.contracts.oracle = oracle; + this.contracts.underlyingFlashUniswapV2 = underlyingFlashUniswapV2; this.contracts.usdc = usdc; this.contracts.usdcPriceFeed = usdcPriceFeed; this.contracts.uniswapV2Pair = uniswapV2Pair; @@ -30,6 +32,6 @@ export function integrationTestHifiFlashUniswapV2(): void { this.contracts.wbtcPriceFeed = wbtcPriceFeed; }); - shouldBehaveLikeHifiFlashUniswapV2(); + shouldBehaveLikeCollateralFlashUniswapV2(); }); } diff --git a/packages/flash-swap/test/integration/hifiFlashUniswapV2/effects/uniswapV2Call.ts b/packages/flash-swap/test/integration/collateralFlashUniswapV2/effects/uniswapV2Call.ts similarity index 91% rename from packages/flash-swap/test/integration/hifiFlashUniswapV2/effects/uniswapV2Call.ts rename to packages/flash-swap/test/integration/collateralFlashUniswapV2/effects/uniswapV2Call.ts index e01231c2..f0665541 100644 --- a/packages/flash-swap/test/integration/hifiFlashUniswapV2/effects/uniswapV2Call.ts +++ b/packages/flash-swap/test/integration/collateralFlashUniswapV2/effects/uniswapV2Call.ts @@ -1,7 +1,7 @@ import { defaultAbiCoder } from "@ethersproject/abi"; import { BigNumber } from "@ethersproject/bignumber"; import { Zero } from "@ethersproject/constants"; -import { BalanceSheetErrors, HifiFlashUniswapV2Errors } from "@hifi/errors"; +import { BalanceSheetErrors, CollateralFlashUniswapV2Errors } from "@hifi/errors"; import { USDC, WBTC, hUSDC, price } from "@hifi/helpers"; import { expect } from "chai"; import { toBn } from "evm-bn"; @@ -42,7 +42,7 @@ async function getSeizableAndProfitCollateralAmounts( repayHUsdcAmount, this.contracts.wbtc.address, ); - const repayWbtcAmount = await this.contracts.hifiFlashUniswapV2.getRepayCollateralAmount( + const repayWbtcAmount = await this.contracts.collateralFlashUniswapV2.getRepayCollateralAmount( this.contracts.uniswapV2Pair.address, this.contracts.usdc.address, underlyingAmount, @@ -93,7 +93,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { const token1Amount: BigNumber = Zero; const data: string = "0x"; await expect( - this.contracts.hifiFlashUniswapV2 + this.contracts.collateralFlashUniswapV2 .connect(this.signers.raider) .uniswapV2Call(sender, token0Amount, token1Amount, data), ).to.be.reverted; @@ -119,7 +119,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { context("when the caller is an externally owned account", function () { it("reverts", async function () { await expect( - this.contracts.hifiFlashUniswapV2 + this.contracts.collateralFlashUniswapV2 .connect(this.signers.raider) .uniswapV2Call(sender, token0Amount, token1Amount, data), ).to.be.revertedWith("function call to a non-contract account"); @@ -128,10 +128,10 @@ export function shouldBehaveLikeUniswapV2Call(): void { context("when the caller is a malicious pair", function () { it("reverts", async function () { - const to: string = this.contracts.hifiFlashUniswapV2.address; + const to: string = this.contracts.collateralFlashUniswapV2.address; await expect( this.contracts.maliciousPair.connect(this.signers.raider).swap(token0Amount, token1Amount, to, data), - ).to.be.revertedWith(HifiFlashUniswapV2Errors.CallNotAuthorized); + ).to.be.revertedWith(CollateralFlashUniswapV2Errors.CallNotAuthorized); }); }); }); @@ -153,10 +153,10 @@ export function shouldBehaveLikeUniswapV2Call(): void { const { token0Amount, token1Amount } = await getTokenAmounts.call(this, Zero, USDC("10000")); const foo: GodModeErc20 = await deployGodModeErc20(this.signers.admin, "Foo", "FOO", BigNumber.from(18)); await this.contracts.hToken.__godMode_setUnderlying(foo.address); - const to: string = this.contracts.hifiFlashUniswapV2.address; + const to: string = this.contracts.collateralFlashUniswapV2.address; await expect( this.contracts.uniswapV2Pair.connect(this.signers.raider).swap(token0Amount, token1Amount, to, data), - ).to.be.revertedWith(HifiFlashUniswapV2Errors.UnderlyingNotInPool); + ).to.be.revertedWith(CollateralFlashUniswapV2Errors.UnderlyingNotInPool); }); }); @@ -164,10 +164,10 @@ export function shouldBehaveLikeUniswapV2Call(): void { context("when collateral is flash borrowed", function () { it("reverts", async function () { const { token0Amount, token1Amount } = await getTokenAmounts.call(this, WBTC("1"), Zero); - const to: string = this.contracts.hifiFlashUniswapV2.address; + const to: string = this.contracts.collateralFlashUniswapV2.address; await expect( this.contracts.uniswapV2Pair.connect(this.signers.raider).swap(token0Amount, token1Amount, to, data), - ).to.be.revertedWith(HifiFlashUniswapV2Errors.FlashBorrowCollateral); + ).to.be.revertedWith(CollateralFlashUniswapV2Errors.FlashBorrowCollateral); }); }); @@ -228,7 +228,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { context("when the borrower does not have a liquidity shortfall", function () { it("reverts", async function () { - const to: string = this.contracts.hifiFlashUniswapV2.address; + const to: string = this.contracts.collateralFlashUniswapV2.address; await expect( this.contracts.uniswapV2Pair .connect(this.signers.liquidator) @@ -248,12 +248,12 @@ export function shouldBehaveLikeUniswapV2Call(): void { }); it("reverts", async function () { - const to: string = this.contracts.hifiFlashUniswapV2.address; + const to: string = this.contracts.collateralFlashUniswapV2.address; await expect( this.contracts.uniswapV2Pair .connect(this.signers.liquidator) .swap(token0Amount, token1Amount, to, data), - ).to.be.revertedWith(HifiFlashUniswapV2Errors.InsufficientProfit); + ).to.be.revertedWith(CollateralFlashUniswapV2Errors.InsufficientProfit); }); }); @@ -279,7 +279,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { }); it("flash swaps USDC via and makes a WBTC profit", async function () { - const to: string = this.contracts.hifiFlashUniswapV2.address; + const to: string = this.contracts.collateralFlashUniswapV2.address; const preWbtcBalance = await this.contracts.wbtc.balanceOf(this.signers.liquidator.address); await this.contracts.uniswapV2Pair .connect(this.signers.liquidator) @@ -330,7 +330,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { }); it("flash swaps USDC via and makes a WBTC profit", async function () { - const to: string = this.contracts.hifiFlashUniswapV2.address; + const to: string = this.contracts.collateralFlashUniswapV2.address; const preWbtcBalance = await this.contracts.wbtc.balanceOf(this.signers.liquidator.address); await this.contracts.uniswapV2Pair .connect(this.signers.liquidator) @@ -342,7 +342,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { context("initial order of tokens in the pair", function () { it("flash swaps USDC via and makes a WBTC profit", async function () { - const to: string = this.contracts.hifiFlashUniswapV2.address; + const to: string = this.contracts.collateralFlashUniswapV2.address; const preWbtcBalance = await this.contracts.wbtc.balanceOf(this.signers.liquidator.address); await this.contracts.uniswapV2Pair .connect(this.signers.liquidator) @@ -352,12 +352,12 @@ export function shouldBehaveLikeUniswapV2Call(): void { }); it("emits a FlashLiquidateBorrow event", async function () { - const to: string = this.contracts.hifiFlashUniswapV2.address; + const to: string = this.contracts.collateralFlashUniswapV2.address; const contractCall = this.contracts.uniswapV2Pair .connect(this.signers.liquidator) .swap(token0Amount, token1Amount, to, data); await expect(contractCall) - .to.emit(this.contracts.hifiFlashUniswapV2, "FlashLiquidateBorrow") + .to.emit(this.contracts.collateralFlashUniswapV2, "FlashLiquidateBorrow") .withArgs( this.signers.liquidator.address, this.signers.borrower.address, diff --git a/packages/flash-swap/test/integration/index.ts b/packages/flash-swap/test/integration/index.ts index d156b08a..3e0ab3ae 100644 --- a/packages/flash-swap/test/integration/index.ts +++ b/packages/flash-swap/test/integration/index.ts @@ -1,8 +1,8 @@ import { baseContext } from "../shared/contexts"; -import { integrationTestHifiFlashUniswapV2 } from "./hifiFlashUniswapV2/HifiFlashUniswapV2"; -import { integrationTestHifiFlashUniswapV2Underlying } from "./hifiFlashUniswapV2Underlying/HifiFlashUniswapV2Underlying"; +import { integrationTestCollateralFlashUniswapV2 } from "./collateralFlashUniswapV2/CollateralFlashUniswapV2"; +import { integrationTestUnderlyingFlashUniswapV2 } from "./underlyingFlashUniswapV2/UnderlyingFlashUniswapV2"; baseContext("Integration Tests", function () { - integrationTestHifiFlashUniswapV2(); - integrationTestHifiFlashUniswapV2Underlying(); + integrationTestCollateralFlashUniswapV2(); + integrationTestUnderlyingFlashUniswapV2(); }); diff --git a/packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/HifiFlashUniswapV2Underlying.behavior.ts b/packages/flash-swap/test/integration/underlyingFlashUniswapV2/UnderlyingFlashUniswapV2.behavior.ts similarity index 69% rename from packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/HifiFlashUniswapV2Underlying.behavior.ts rename to packages/flash-swap/test/integration/underlyingFlashUniswapV2/UnderlyingFlashUniswapV2.behavior.ts index 857eb694..f6bb5d96 100644 --- a/packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/HifiFlashUniswapV2Underlying.behavior.ts +++ b/packages/flash-swap/test/integration/underlyingFlashUniswapV2/UnderlyingFlashUniswapV2.behavior.ts @@ -1,6 +1,6 @@ import { shouldBehaveLikeUniswapV2Call } from "./effects/uniswapV2Call"; -export function shouldBehaveLikeHifiFlashUniswapV2Underlying(): void { +export function shouldBehaveLikeUnderlyingFlashUniswapV2(): void { describe("uniswapV2Call", function () { shouldBehaveLikeUniswapV2Call(); }); diff --git a/packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/HifiFlashUniswapV2Underlying.ts b/packages/flash-swap/test/integration/underlyingFlashUniswapV2/UnderlyingFlashUniswapV2.ts similarity index 63% rename from packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/HifiFlashUniswapV2Underlying.ts rename to packages/flash-swap/test/integration/underlyingFlashUniswapV2/UnderlyingFlashUniswapV2.ts index 2efe85a4..54c8c41b 100644 --- a/packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/HifiFlashUniswapV2Underlying.ts +++ b/packages/flash-swap/test/integration/underlyingFlashUniswapV2/UnderlyingFlashUniswapV2.ts @@ -1,35 +1,37 @@ import { integrationFixture } from "../../shared/fixtures"; -import { shouldBehaveLikeHifiFlashUniswapV2Underlying } from "./HifiFlashUniswapV2Underlying.behavior"; +import { shouldBehaveLikeUnderlyingFlashUniswapV2 } from "./UnderlyingFlashUniswapV2.behavior"; -export function integrationTestHifiFlashUniswapV2Underlying(): void { - describe("HifiFlashUniswapV2Underlying", function () { +export function integrationTestUnderlyingFlashUniswapV2(): void { + describe("UnderlyingFlashUniswapV2", function () { beforeEach(async function () { const { balanceSheet, + collateralFlashUniswapV2, fintroller, hToken, - hifiFlashUniswapV2Underlying, maliciousPair, oracle, + underlyingFlashUniswapV2, + uniswapV2Pair, usdc, usdcPriceFeed, - uniswapV2Pair, wbtc, wbtcPriceFeed, } = await this.loadFixture(integrationFixture); this.contracts.balanceSheet = balanceSheet; this.contracts.fintroller = fintroller; this.contracts.hToken = hToken; - this.contracts.hifiFlashUniswapV2Underlying = hifiFlashUniswapV2Underlying; + this.contracts.collateralFlashUniswapV2 = collateralFlashUniswapV2; this.contracts.maliciousPair = maliciousPair; this.contracts.oracle = oracle; + this.contracts.underlyingFlashUniswapV2 = underlyingFlashUniswapV2; + this.contracts.uniswapV2Pair = uniswapV2Pair; this.contracts.usdc = usdc; this.contracts.usdcPriceFeed = usdcPriceFeed; - this.contracts.uniswapV2Pair = uniswapV2Pair; this.contracts.wbtc = wbtc; this.contracts.wbtcPriceFeed = wbtcPriceFeed; }); - shouldBehaveLikeHifiFlashUniswapV2Underlying(); + shouldBehaveLikeUnderlyingFlashUniswapV2(); }); } diff --git a/packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/effects/uniswapV2Call.ts b/packages/flash-swap/test/integration/underlyingFlashUniswapV2/effects/uniswapV2Call.ts similarity index 92% rename from packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/effects/uniswapV2Call.ts rename to packages/flash-swap/test/integration/underlyingFlashUniswapV2/effects/uniswapV2Call.ts index 492b8864..bf07bd7c 100644 --- a/packages/flash-swap/test/integration/hifiFlashUniswapV2Underlying/effects/uniswapV2Call.ts +++ b/packages/flash-swap/test/integration/underlyingFlashUniswapV2/effects/uniswapV2Call.ts @@ -1,7 +1,7 @@ import { defaultAbiCoder } from "@ethersproject/abi"; import { BigNumber } from "@ethersproject/bignumber"; import { Zero } from "@ethersproject/constants"; -import { BalanceSheetErrors, HifiFlashUniswapV2UnderlyingErrors } from "@hifi/errors"; +import { BalanceSheetErrors, UnderlyingFlashUniswapV2Errors } from "@hifi/errors"; import { USDC, WBTC, hUSDC, price } from "@hifi/helpers"; import { expect } from "chai"; import { toBn } from "evm-bn"; @@ -87,7 +87,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { const token1Amount: BigNumber = Zero; const data: string = "0x"; await expect( - this.contracts.hifiFlashUniswapV2Underlying + this.contracts.underlyingFlashUniswapV2 .connect(this.signers.raider) .uniswapV2Call(sender, token0Amount, token1Amount, data), ).to.be.reverted; @@ -113,7 +113,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { context("when the caller is an externally owned account", function () { it("reverts", async function () { await expect( - this.contracts.hifiFlashUniswapV2Underlying + this.contracts.underlyingFlashUniswapV2 .connect(this.signers.raider) .uniswapV2Call(sender, token0Amount, token1Amount, data), ).to.be.revertedWith("function call to a non-contract account"); @@ -122,10 +122,10 @@ export function shouldBehaveLikeUniswapV2Call(): void { context("when the caller is a malicious pair", function () { it("reverts", async function () { - const to: string = this.contracts.hifiFlashUniswapV2Underlying.address; + const to: string = this.contracts.underlyingFlashUniswapV2.address; await expect( this.contracts.maliciousPair.connect(this.signers.raider).swap(token0Amount, token1Amount, to, data), - ).to.be.revertedWith(HifiFlashUniswapV2UnderlyingErrors.CallNotAuthorized); + ).to.be.revertedWith(UnderlyingFlashUniswapV2Errors.CallNotAuthorized); }); }); }); @@ -147,10 +147,10 @@ export function shouldBehaveLikeUniswapV2Call(): void { const { token0Amount, token1Amount } = await getTokenAmounts.call(this, Zero, USDC("10000")); const foo: GodModeErc20 = await deployGodModeErc20(this.signers.admin, "Foo", "FOO", BigNumber.from(18)); await this.contracts.hToken.__godMode_setUnderlying(foo.address); - const to: string = this.contracts.hifiFlashUniswapV2Underlying.address; + const to: string = this.contracts.underlyingFlashUniswapV2.address; await expect( this.contracts.uniswapV2Pair.connect(this.signers.raider).swap(token0Amount, token1Amount, to, data), - ).to.be.revertedWith(HifiFlashUniswapV2UnderlyingErrors.UnderlyingNotInPool); + ).to.be.revertedWith(UnderlyingFlashUniswapV2Errors.UnderlyingNotInPool); }); }); @@ -158,10 +158,10 @@ export function shouldBehaveLikeUniswapV2Call(): void { context("when wrong token is flash borrowed", function () { it("reverts", async function () { const { token0Amount, token1Amount } = await getTokenAmounts.call(this, WBTC("1"), Zero); - const to: string = this.contracts.hifiFlashUniswapV2Underlying.address; + const to: string = this.contracts.underlyingFlashUniswapV2.address; await expect( this.contracts.uniswapV2Pair.connect(this.signers.raider).swap(token0Amount, token1Amount, to, data), - ).to.be.revertedWith(HifiFlashUniswapV2UnderlyingErrors.FlashBorrowWrongToken); + ).to.be.revertedWith(UnderlyingFlashUniswapV2Errors.FlashBorrowWrongToken); }); }); @@ -230,7 +230,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { await this.contracts.usdc.__godMode_mint(this.signers.bot.address, usdcRepayFeeAmount); await this.contracts.usdc .connect(this.signers.bot) - .approve(this.contracts.hifiFlashUniswapV2Underlying.address, usdcRepayFeeAmount); + .approve(this.contracts.underlyingFlashUniswapV2.address, usdcRepayFeeAmount); // Deposit the USDC in the BalanceSheet. await this.contracts.balanceSheet @@ -250,7 +250,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { context("when the borrower does not have a liquidity shortfall", function () { it("reverts", async function () { - const to: string = this.contracts.hifiFlashUniswapV2Underlying.address; + const to: string = this.contracts.underlyingFlashUniswapV2.address; await expect( this.contracts.uniswapV2Pair .connect(this.signers.liquidator) @@ -269,7 +269,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { }); it("flash swaps USDC making no USDC profit and spending allocated USDC to pay swap fee", async function () { - const to: string = this.contracts.hifiFlashUniswapV2Underlying.address; + const to: string = this.contracts.underlyingFlashUniswapV2.address; const preUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); const preUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.bot.address); await this.contracts.uniswapV2Pair @@ -304,7 +304,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { }); it("flash swaps USDC making no USDC profit and spending allocated USDC to pay swap fee", async function () { - const to: string = this.contracts.hifiFlashUniswapV2Underlying.address; + const to: string = this.contracts.underlyingFlashUniswapV2.address; const preUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); const preUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.bot.address); await this.contracts.uniswapV2Pair @@ -360,17 +360,17 @@ export function shouldBehaveLikeUniswapV2Call(): void { context("when wrong token is flash borrowed", function () { it("reverts", async function () { const { token0Amount, token1Amount } = await getTokenAmounts.call(this, WBTC("1"), Zero); - const to: string = this.contracts.hifiFlashUniswapV2Underlying.address; + const to: string = this.contracts.underlyingFlashUniswapV2.address; await expect( this.contracts.uniswapV2Pair .connect(this.signers.raider) .swap(token0Amount, token1Amount, to, data), - ).to.be.revertedWith(HifiFlashUniswapV2UnderlyingErrors.FlashBorrowWrongToken); + ).to.be.revertedWith(UnderlyingFlashUniswapV2Errors.FlashBorrowWrongToken); }); }); it("flash swaps USDC making no USDC profit and spending allocated USDC to pay swap fee", async function () { - const to: string = this.contracts.hifiFlashUniswapV2Underlying.address; + const to: string = this.contracts.underlyingFlashUniswapV2.address; const preUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); const preUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.bot.address); await this.contracts.uniswapV2Pair @@ -385,7 +385,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { context("initial order of tokens in the pair", function () { it("flash swaps USDC making no USDC profit and spending allocated USDC to pay swap fee", async function () { - const to: string = this.contracts.hifiFlashUniswapV2Underlying.address; + const to: string = this.contracts.underlyingFlashUniswapV2.address; const preUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); const preUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.bot.address); await this.contracts.uniswapV2Pair @@ -398,12 +398,12 @@ export function shouldBehaveLikeUniswapV2Call(): void { }); it("emits a FlashLiquidateBorrow event", async function () { - const to: string = this.contracts.hifiFlashUniswapV2Underlying.address; + const to: string = this.contracts.underlyingFlashUniswapV2.address; const contractCall = this.contracts.uniswapV2Pair .connect(this.signers.liquidator) .swap(token0Amount, token1Amount, to, data); await expect(contractCall) - .to.emit(this.contracts.hifiFlashUniswapV2Underlying, "FlashLiquidateBorrow") + .to.emit(this.contracts.underlyingFlashUniswapV2, "FlashLiquidateBorrow") .withArgs( this.signers.liquidator.address, this.signers.borrower.address, diff --git a/packages/flash-swap/test/shared/fixtures.ts b/packages/flash-swap/test/shared/fixtures.ts index 49cc3697..c909a332 100644 --- a/packages/flash-swap/test/shared/fixtures.ts +++ b/packages/flash-swap/test/shared/fixtures.ts @@ -13,8 +13,8 @@ import { GodModeUniswapV2Pair__factory } from "../../src/types/factories/GodMode import type { GodModeErc20 } from "../../src/types/GodModeErc20"; import type { GodModeHToken } from "../../src/types/GodModeHToken"; import type { GodModeUniswapV2Factory } from "../../src/types/GodModeUniswapV2Factory"; -import type { HifiFlashUniswapV2 } from "../../src/types/HifiFlashUniswapV2"; -import type { HifiFlashUniswapV2Underlying } from "../../src/types/HifiFlashUniswapV2Underlying"; +import type { CollateralFlashUniswapV2 } from "../../src/types/CollateralFlashUniswapV2"; +import type { UnderlyingFlashUniswapV2 } from "../../src/types/UnderlyingFlashUniswapV2"; import type { MaliciousPair } from "../../src/types/MaliciousPair"; import type { SimplePriceFeed } from "../../src/types/SimplePriceFeed"; import type { GodModeUniswapV2Pair } from "../../src/types/GodModeUniswapV2Pair"; @@ -23,10 +23,10 @@ import { deployGodModeErc20 } from "./deployers"; type IntegrationFixtureReturnType = { balanceSheet: BalanceSheetV1; fintroller: FintrollerV1; - hifiFlashUniswapV2: HifiFlashUniswapV2; - hifiFlashUniswapV2Underlying: HifiFlashUniswapV2Underlying; + collateralFlashUniswapV2: CollateralFlashUniswapV2; hToken: GodModeHToken; maliciousPair: MaliciousPair; + underlyingFlashUniswapV2: UnderlyingFlashUniswapV2; uniswapV2Pair: GodModeUniswapV2Pair; usdc: GodModeErc20; usdcPriceFeed: SimplePriceFeed; @@ -93,18 +93,18 @@ export async function integrationFixture(signers: Signer[]): Promise( - await waffle.deployContract(deployer, hifiFlashUniswapV2Artifact, [ + const collateralFlashUniswapV2Artifact: Artifact = await artifacts.readArtifact("CollateralFlashUniswapV2"); + const collateralFlashUniswapV2: CollateralFlashUniswapV2 = ( + await waffle.deployContract(deployer, collateralFlashUniswapV2Artifact, [ balanceSheet.address, uniswapV2Factory.address, uniV2PairInitCodeHash, ]) ); - const hifiFlashUniswapV2UnderlyingArtifact: Artifact = await artifacts.readArtifact("HifiFlashUniswapV2Underlying"); - const hifiFlashUniswapV2Underlying: HifiFlashUniswapV2Underlying = ( - await waffle.deployContract(deployer, hifiFlashUniswapV2UnderlyingArtifact, [ + const underlyingFlashUniswapV2Artifact: Artifact = await artifacts.readArtifact("UnderlyingFlashUniswapV2"); + const underlyingFlashUniswapV2: UnderlyingFlashUniswapV2 = ( + await waffle.deployContract(deployer, underlyingFlashUniswapV2Artifact, [ balanceSheet.address, uniswapV2Factory.address, uniV2PairInitCodeHash, @@ -113,11 +113,11 @@ export async function integrationFixture(signers: Signer[]): Promise { const signers: SignerWithAddress[] = await ethers.getSigners(); - const hifiFlashUniswapV2Factory: HifiFlashUniswapV2__factory = new HifiFlashUniswapV2__factory(signers[0]); - const hifiFlashUniswapV2: HifiFlashUniswapV2 = ( - await hifiFlashUniswapV2Factory.deploy( + const collateralFlashUniswapV2Factory: CollateralFlashUniswapV2__factory = new CollateralFlashUniswapV2__factory( + signers[0], + ); + const collateralFlashUniswapV2: CollateralFlashUniswapV2 = ( + await collateralFlashUniswapV2Factory.deploy( taskArgs.balanceSheet, taskArgs.uniV2Factory, taskArgs.uniV2PairInitCodeHash, @@ -31,16 +33,16 @@ task(TASK_DEPLOY_CONTRACT_HIFI_FLASH_UNISWAP_V2) ); await run(SUBTASK_DEPLOY_WAIT_FOR_CONFIRMATIONS, { - contract: hifiFlashUniswapV2, + contract: collateralFlashUniswapV2, confirmations: taskArgs.confirmations, }); if (taskArgs.setOutput) { - core.setOutput("hifi-flash-uniswap-v2", hifiFlashUniswapV2.address); + core.setOutput("collateral-flash-uniswap-v2", collateralFlashUniswapV2.address); } if (taskArgs.printAddress) { - console.table([{ name: "HifiFlashUniswapV2", address: hifiFlashUniswapV2.address }]); + console.table([{ name: "CollateralFlashUniswapV2", address: collateralFlashUniswapV2.address }]); } - return hifiFlashUniswapV2.address; + return collateralFlashUniswapV2.address; }); diff --git a/packages/tasks/src/deploy/index.ts b/packages/tasks/src/deploy/index.ts index 8f692147..6f492289 100644 --- a/packages/tasks/src/deploy/index.ts +++ b/packages/tasks/src/deploy/index.ts @@ -1,10 +1,10 @@ import "./chainlinkOperator"; -import "./hifiFlashUniswapV2"; -import "./hifiFlashUniswapV2Underlying"; +import "./collateralFlashUniswapV2"; import "./hifiPoolRegistry"; import "./hifiProxyTarget"; import "./hToken"; import "./hifiPool"; import "./simplePriceFeed"; import "./stablecoinPriceFeed"; +import "./underlyingFlashUniswapV2"; import "./waitForConfirmations"; diff --git a/packages/tasks/src/deploy/hifiFlashUniswapV2Underlying.ts b/packages/tasks/src/deploy/underlyingFlashUniswapV2.ts similarity index 63% rename from packages/tasks/src/deploy/hifiFlashUniswapV2Underlying.ts rename to packages/tasks/src/deploy/underlyingFlashUniswapV2.ts index a6eb0cd9..02758815 100644 --- a/packages/tasks/src/deploy/hifiFlashUniswapV2Underlying.ts +++ b/packages/tasks/src/deploy/underlyingFlashUniswapV2.ts @@ -1,6 +1,6 @@ import * as core from "@actions/core"; -import type { HifiFlashUniswapV2Underlying } from "@hifi/flash-swap/dist/types/HifiFlashUniswapV2Underlying"; -import { HifiFlashUniswapV2Underlying__factory } from "@hifi/flash-swap/dist/types/factories/HifiFlashUniswapV2Underlying__factory"; +import type { UnderlyingFlashUniswapV2 } from "@hifi/flash-swap/dist/types/UnderlyingFlashUniswapV2"; +import { UnderlyingFlashUniswapV2__factory } from "@hifi/flash-swap/dist/types/factories/UnderlyingFlashUniswapV2__factory"; import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import { task, types } from "hardhat/config"; import type { TaskArguments } from "hardhat/types"; @@ -21,10 +21,11 @@ task(TASK_DEPLOY_CONTRACT_HIFI_FLASH_UNISWAP_V2_UNDERLYING) .addOptionalParam("setOutput", "Set the contract address as an output in GitHub Actions", false, types.boolean) .setAction(async function (taskArgs: TaskArguments, { ethers, run }): Promise { const signers: SignerWithAddress[] = await ethers.getSigners(); - const hifiFlashUniswapV2UnderlyingFactory: HifiFlashUniswapV2Underlying__factory = - new HifiFlashUniswapV2Underlying__factory(signers[0]); - const hifiFlashUniswapV2Underlying: HifiFlashUniswapV2Underlying = ( - await hifiFlashUniswapV2UnderlyingFactory.deploy( + const underlyingFlashUniswapV2Factory: UnderlyingFlashUniswapV2__factory = new UnderlyingFlashUniswapV2__factory( + signers[0], + ); + const underlyingFlashUniswapV2: UnderlyingFlashUniswapV2 = ( + await underlyingFlashUniswapV2Factory.deploy( taskArgs.balanceSheet, taskArgs.uniV2Factory, taskArgs.uniV2PairInitCodeHash, @@ -32,16 +33,16 @@ task(TASK_DEPLOY_CONTRACT_HIFI_FLASH_UNISWAP_V2_UNDERLYING) ); await run(SUBTASK_DEPLOY_WAIT_FOR_CONFIRMATIONS, { - contract: hifiFlashUniswapV2Underlying, + contract: underlyingFlashUniswapV2, confirmations: taskArgs.confirmations, }); if (taskArgs.setOutput) { - core.setOutput("hifi-flash-uniswap-v2-underlying", hifiFlashUniswapV2Underlying.address); + core.setOutput("underlying-flash-uniswap-v2", underlyingFlashUniswapV2.address); } if (taskArgs.printAddress) { - console.table([{ name: "HifiFlashUniswapV2Underlying", address: hifiFlashUniswapV2Underlying.address }]); + console.table([{ name: "UnderlyingFlashUniswapV2", address: underlyingFlashUniswapV2.address }]); } - return hifiFlashUniswapV2Underlying.address; + return underlyingFlashUniswapV2.address; }); From 5a2e0dc08f7dbe29cfe3f38230e9a07107e2e979 Mon Sep 17 00:00:00 2001 From: Paul Razvan Berg Date: Thu, 4 Nov 2021 16:02:55 +0200 Subject: [PATCH 09/16] refactor(flash-swap): use only "underlying" in var names in "UnderlyingFlashUniswapV2" chore(amm,flash-swap,proxy-target): use "old" and "new" prefixes instead of "pre" and "post" chore(flash-swap): better explain the role of subsidizer in the "uniswapV2Call" function refactor(errors): "FlashBorrowWrongToken" to "FlashBorrowOtherToken" refactor(flash-swap): "bot" to "subsidizer" refactor(flash-swap): "collateral" to "otherToken" refactor(flash-swap): "collateralRepayAmount" to "repayCollateralAmount" refactor(flash-swap): delete "collateralAmount" arg in "FlashBorrowCollateral" custom err refactor(flash-swap): delete superfluous "vars.swapToken" var refactor(flash-swap): "FlashLiquidateBorrow" event to "FlashSwapCollateralAndLiquidateBorrow" refactor(flash-swap): "FlashLiquidateBorrow" event to "FlashSwapUnderlyingAndLiquidateBorrow" refactor(flash-swap): "getCollateralAndUnderlyingAmount" to "getOtherTokenAndUnderlyingAmount" refactor(flash-swap): "getRepayCollateralAmount" to "getRepayUnderlyingAmount" refactor(flash-swap): "overshootCollateralAmount" to "shortfallUnderlyingAmount" refactor(flash-swap): import "IHToken" in "FlashUtils.sol" test(flash-swap): order vars alphabetically test(flash-swap): rename "usdcRepayFeeAmount" var to "usdcFeeAmount" --- .../integration/hifiPool/effects/buyHToken.ts | 12 +-- .../hifiPool/effects/buyUnderlying.ts | 12 +-- .../hifiPool/effects/sellHToken.ts | 12 +-- .../hifiPool/effects/sellUnderlying.ts | 12 +-- packages/errors/src/flashSwap.ts | 2 +- .../uniswap-v2/CollateralFlashUniswapV2.sol | 8 +- .../contracts/uniswap-v2/FlashUtils.sol | 7 +- .../uniswap-v2/ICollateralFlashUniswapV2.sol | 10 +-- .../uniswap-v2/IUnderlyingFlashUniswapV2.sol | 22 ++--- .../uniswap-v2/UnderlyingFlashUniswapV2.sol | 68 ++++++++-------- .../src/types/CollateralFlashUniswapV2.d.ts | 12 +-- .../src/types/ICollateralFlashUniswapV2.d.ts | 14 ++-- .../src/types/IUnderlyingFlashUniswapV2.d.ts | 72 +++++++++-------- .../src/types/UnderlyingFlashUniswapV2.d.ts | 72 +++++++++-------- .../effects/uniswapV2Call.ts | 4 +- .../effects/uniswapV2Call.ts | 80 ++++++++----------- packages/flash-swap/test/shared/contexts.ts | 4 +- packages/flash-swap/test/shared/types.ts | 2 +- .../contracts/HifiProxyTarget.sol | 24 +++--- 19 files changed, 221 insertions(+), 228 deletions(-) diff --git a/packages/amm/test/integration/hifiPool/effects/buyHToken.ts b/packages/amm/test/integration/hifiPool/effects/buyHToken.ts index 8a06e15b..2e57fe3b 100644 --- a/packages/amm/test/integration/hifiPool/effects/buyHToken.ts +++ b/packages/amm/test/integration/hifiPool/effects/buyHToken.ts @@ -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); diff --git a/packages/amm/test/integration/hifiPool/effects/buyUnderlying.ts b/packages/amm/test/integration/hifiPool/effects/buyUnderlying.ts index 1eecfdac..557a180f 100644 --- a/packages/amm/test/integration/hifiPool/effects/buyUnderlying.ts +++ b/packages/amm/test/integration/hifiPool/effects/buyUnderlying.ts @@ -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); diff --git a/packages/amm/test/integration/hifiPool/effects/sellHToken.ts b/packages/amm/test/integration/hifiPool/effects/sellHToken.ts index fc86bee0..81bf4a19 100644 --- a/packages/amm/test/integration/hifiPool/effects/sellHToken.ts +++ b/packages/amm/test/integration/hifiPool/effects/sellHToken.ts @@ -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); diff --git a/packages/amm/test/integration/hifiPool/effects/sellUnderlying.ts b/packages/amm/test/integration/hifiPool/effects/sellUnderlying.ts index c701b9fb..435187eb 100644 --- a/packages/amm/test/integration/hifiPool/effects/sellUnderlying.ts +++ b/packages/amm/test/integration/hifiPool/effects/sellUnderlying.ts @@ -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); diff --git a/packages/errors/src/flashSwap.ts b/packages/errors/src/flashSwap.ts index 4b60d308..ec1260e4 100644 --- a/packages/errors/src/flashSwap.ts +++ b/packages/errors/src/flashSwap.ts @@ -7,6 +7,6 @@ export enum CollateralFlashUniswapV2Errors { export enum UnderlyingFlashUniswapV2Errors { CallNotAuthorized = "UnderlyingFlashUniswapV2__CallNotAuthorized", - FlashBorrowWrongToken = "UnderlyingFlashUniswapV2__FlashBorrowWrongToken", + FlashBorrowOtherToken = "UnderlyingFlashUniswapV2__FlashBorrowOtherToken", UnderlyingNotInPool = "UnderlyingFlashUniswapV2__UnderlyingNotInPool", } diff --git a/packages/flash-swap/contracts/uniswap-v2/CollateralFlashUniswapV2.sol b/packages/flash-swap/contracts/uniswap-v2/CollateralFlashUniswapV2.sol index ca868f50..7567edaa 100644 --- a/packages/flash-swap/contracts/uniswap-v2/CollateralFlashUniswapV2.sol +++ b/packages/flash-swap/contracts/uniswap-v2/CollateralFlashUniswapV2.sol @@ -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( @@ -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; @@ -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), diff --git a/packages/flash-swap/contracts/uniswap-v2/FlashUtils.sol b/packages/flash-swap/contracts/uniswap-v2/FlashUtils.sol index 3725bf28..5e5c1208 100644 --- a/packages/flash-swap/contracts/uniswap-v2/FlashUtils.sol +++ b/packages/flash-swap/contracts/uniswap-v2/FlashUtils.sol @@ -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 @@ -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; } } } diff --git a/packages/flash-swap/contracts/uniswap-v2/ICollateralFlashUniswapV2.sol b/packages/flash-swap/contracts/uniswap-v2/ICollateralFlashUniswapV2.sol index ce48f10e..2b9897cc 100644 --- a/packages/flash-swap/contracts/uniswap-v2/ICollateralFlashUniswapV2.sol +++ b/packages/flash-swap/contracts/uniswap-v2/ICollateralFlashUniswapV2.sol @@ -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, @@ -48,10 +48,10 @@ 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 @@ -59,12 +59,12 @@ interface ICollateralFlashUniswapV2 is IUniswapV2Callee { /// @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); diff --git a/packages/flash-swap/contracts/uniswap-v2/IUnderlyingFlashUniswapV2.sol b/packages/flash-swap/contracts/uniswap-v2/IUnderlyingFlashUniswapV2.sol index ba4e4c08..4678fa52 100644 --- a/packages/flash-swap/contracts/uniswap-v2/IUnderlyingFlashUniswapV2.sol +++ b/packages/flash-swap/contracts/uniswap-v2/IUnderlyingFlashUniswapV2.sol @@ -13,13 +13,13 @@ 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 /// @@ -27,7 +27,7 @@ interface IUnderlyingFlashUniswapV2 is IUniswapV2Callee { /// @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: @@ -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); diff --git a/packages/flash-swap/contracts/uniswap-v2/UnderlyingFlashUniswapV2.sol b/packages/flash-swap/contracts/uniswap-v2/UnderlyingFlashUniswapV2.sol index c46d71ee..c38530ea 100644 --- a/packages/flash-swap/contracts/uniswap-v2/UnderlyingFlashUniswapV2.sol +++ b/packages/flash-swap/contracts/uniswap-v2/UnderlyingFlashUniswapV2.sol @@ -14,8 +14,8 @@ import "./IUniswapV2Pair.sol"; /// @notice Emitted when the caller is not the Uniswap V2 pair contract. error UnderlyingFlashUniswapV2__CallNotAuthorized(address caller); -/// @notice Emitted when the flash borrowed asset is the wrong token in the pair. -error UnderlyingFlashUniswapV2__FlashBorrowWrongToken(uint256 collateralAmount); +/// @notice Emitted when the flash borrowed asset is the other token in the pair instead of the underlying. +error UnderlyingFlashUniswapV2__FlashBorrowOtherToken(); /// @notice Emitted when neither the token0 nor the token1 is the underlying. error UnderlyingFlashUniswapV2__UnderlyingNotInPool( @@ -55,25 +55,25 @@ contract UnderlyingFlashUniswapV2 is IUnderlyingFlashUniswapV2 { /// PUBLIC CONSTANT FUNCTIONS //// /// @inheritdoc IUnderlyingFlashUniswapV2 - function getCollateralAndUnderlyingAmount( + function getOtherTokenAndUnderlyingAmount( IUniswapV2Pair pair, uint256 amount0, uint256 amount1, IErc20 underlying - ) public view override returns (IErc20 collateral, uint256 underlyingAmount) { + ) public view override returns (IErc20 otherToken, uint256 underlyingAmount) { address token0 = pair.token0(); address token1 = pair.token1(); if (token0 == address(underlying)) { if (amount1 > 0) { - revert UnderlyingFlashUniswapV2__FlashBorrowWrongToken(amount1); + revert UnderlyingFlashUniswapV2__FlashBorrowOtherToken(); } - collateral = IErc20(token0); + otherToken = IErc20(token1); underlyingAmount = amount0; } else if (token1 == address(underlying)) { if (amount0 > 0) { - revert UnderlyingFlashUniswapV2__FlashBorrowWrongToken(amount0); + revert UnderlyingFlashUniswapV2__FlashBorrowOtherToken(); } - collateral = IErc20(token1); + otherToken = IErc20(token0); underlyingAmount = amount1; } else { revert UnderlyingFlashUniswapV2__UnderlyingNotInPool(pair, token0, token1, underlying); @@ -81,18 +81,18 @@ contract UnderlyingFlashUniswapV2 is IUnderlyingFlashUniswapV2 { } /// @inheritdoc IUnderlyingFlashUniswapV2 - function getRepayCollateralAmount(uint256 underlyingAmount) + function getRepayUnderlyingAmount(uint256 underlyingAmount) public pure override - returns (uint256 repayCollateralAmount) + returns (uint256 repayUnderlyingAmount) { // Note that we can safely use unchecked arithmetic here because the UniswapV2Pair.sol contract performs // sanity checks on the amounts before calling the current contract. unchecked { uint256 numerator = underlyingAmount * 1000; uint256 denominator = 997; - repayCollateralAmount = numerator / denominator + 1; + repayUnderlyingAmount = numerator / denominator + 1; } } @@ -100,14 +100,13 @@ contract UnderlyingFlashUniswapV2 is IUnderlyingFlashUniswapV2 { struct UniswapV2CallLocalVars { IHToken bond; - address bot; address borrower; - IErc20 collateral; uint256 mintedHTokenAmount; - uint256 overshootCollateralAmount; - uint256 repayCollateralAmount; - uint256 seizedCollateralAmount; - address swapToken; + IErc20 otherToken; + uint256 repayUnderlyingAmount; + uint256 seizedUnderlyingAmount; + uint256 shortfallUnderlyingAmount; + address subsidizer; IErc20 underlying; uint256 underlyingAmount; } @@ -122,61 +121,58 @@ contract UnderlyingFlashUniswapV2 is IUnderlyingFlashUniswapV2 { UniswapV2CallLocalVars memory vars; // Unpack the ABI encoded data passed by the UniswapV2Pair contract. - (vars.borrower, vars.bond, vars.bot) = abi.decode(data, (address, IHToken, address)); + (vars.borrower, vars.bond, vars.subsidizer) = abi.decode(data, (address, IHToken, address)); // Figure out which token is the collateral and which token is the underlying. vars.underlying = vars.bond.underlying(); - (vars.collateral, vars.underlyingAmount) = getCollateralAndUnderlyingAmount( + (vars.otherToken, vars.underlyingAmount) = getOtherTokenAndUnderlyingAmount( IUniswapV2Pair(msg.sender), amount0, amount1, vars.underlying ); - vars.swapToken = address(vars.underlying) == IUniswapV2Pair(msg.sender).token0() - ? IUniswapV2Pair(msg.sender).token1() - : IUniswapV2Pair(msg.sender).token0(); - // Check that the caller is a genuine UniswapV2Pair contract. if ( msg.sender != - FlashUtils.pairFor(uniV2Factory, uniV2PairInitCodeHash, address(vars.underlying), vars.swapToken) + FlashUtils.pairFor(uniV2Factory, uniV2PairInitCodeHash, address(vars.underlying), address(vars.otherToken)) ) { revert UnderlyingFlashUniswapV2__CallNotAuthorized(msg.sender); } // Mint hTokens and liquidate the borrower. vars.mintedHTokenAmount = FlashUtils.mintHTokensInternal(vars.bond, vars.underlyingAmount); - vars.seizedCollateralAmount = FlashUtils.liquidateBorrowInternal( + vars.seizedUnderlyingAmount = FlashUtils.liquidateBorrowInternal( balanceSheet, vars.borrower, vars.bond, - vars.collateral, + vars.underlying, vars.mintedHTokenAmount ); - // Calculate the amount of collateral required to repay. - vars.repayCollateralAmount = getRepayCollateralAmount(vars.underlyingAmount); + // Calculate the amount of underlying required to repay. + vars.repayUnderlyingAmount = getRepayUnderlyingAmount(vars.underlyingAmount); - // The bot wallet compensates for any overshoot of collateral repay amount above seized amount. - if (vars.repayCollateralAmount > vars.seizedCollateralAmount) { + // There is no incentive to liquidate underlying-backed vaults after the bond maturation. Thus the flash swap + // fee must be subsidized when the repay underlying amount is greater than the seized underlying amount. + if (vars.repayUnderlyingAmount > vars.seizedUnderlyingAmount) { unchecked { - vars.overshootCollateralAmount = vars.repayCollateralAmount - vars.seizedCollateralAmount; + vars.shortfallUnderlyingAmount = vars.repayUnderlyingAmount - vars.seizedUnderlyingAmount; } - vars.collateral.safeTransferFrom(vars.bot, address(this), vars.overshootCollateralAmount); + vars.underlying.safeTransferFrom(vars.subsidizer, address(this), vars.shortfallUnderlyingAmount); } // Pay back the loan. - vars.collateral.safeTransfer(msg.sender, vars.repayCollateralAmount); + vars.underlying.safeTransfer(msg.sender, vars.repayUnderlyingAmount); // Emit an event. - emit FlashLiquidateBorrow( + emit FlashSwapUnderlyingAndLiquidateBorrow( sender, vars.borrower, address(vars.bond), vars.underlyingAmount, - vars.seizedCollateralAmount, - vars.repayCollateralAmount + vars.seizedUnderlyingAmount, + vars.repayUnderlyingAmount ); } } diff --git a/packages/flash-swap/src/types/CollateralFlashUniswapV2.d.ts b/packages/flash-swap/src/types/CollateralFlashUniswapV2.d.ts index 1b93e987..d7e2ba45 100644 --- a/packages/flash-swap/src/types/CollateralFlashUniswapV2.d.ts +++ b/packages/flash-swap/src/types/CollateralFlashUniswapV2.d.ts @@ -80,13 +80,15 @@ interface CollateralFlashUniswapV2Interface extends ethers.utils.Interface { ): Result; events: { - "FlashLiquidateBorrow(address,address,address,uint256,uint256,uint256)": EventFragment; + "FlashSwapCollateralAndLiquidateBorrow(address,address,address,uint256,uint256,uint256)": EventFragment; }; - getEvent(nameOrSignatureOrTopic: "FlashLiquidateBorrow"): EventFragment; + getEvent( + nameOrSignatureOrTopic: "FlashSwapCollateralAndLiquidateBorrow" + ): EventFragment; } -export type FlashLiquidateBorrowEvent = TypedEvent< +export type FlashSwapCollateralAndLiquidateBorrowEvent = TypedEvent< [string, string, string, BigNumber, BigNumber, BigNumber] & { liquidator: string; borrower: string; @@ -238,7 +240,7 @@ export class CollateralFlashUniswapV2 extends BaseContract { }; filters: { - "FlashLiquidateBorrow(address,address,address,uint256,uint256,uint256)"( + "FlashSwapCollateralAndLiquidateBorrow(address,address,address,uint256,uint256,uint256)"( liquidator?: string | null, borrower?: string | null, bond?: string | null, @@ -257,7 +259,7 @@ export class CollateralFlashUniswapV2 extends BaseContract { } >; - FlashLiquidateBorrow( + FlashSwapCollateralAndLiquidateBorrow( liquidator?: string | null, borrower?: string | null, bond?: string | null, diff --git a/packages/flash-swap/src/types/ICollateralFlashUniswapV2.d.ts b/packages/flash-swap/src/types/ICollateralFlashUniswapV2.d.ts index 6c970b6f..b540dfec 100644 --- a/packages/flash-swap/src/types/ICollateralFlashUniswapV2.d.ts +++ b/packages/flash-swap/src/types/ICollateralFlashUniswapV2.d.ts @@ -80,13 +80,15 @@ interface ICollateralFlashUniswapV2Interface extends ethers.utils.Interface { ): Result; events: { - "FlashLiquidateBorrow(address,address,address,uint256,uint256,uint256)": EventFragment; + "FlashSwapCollateralAndLiquidateBorrow(address,address,address,uint256,uint256,uint256)": EventFragment; }; - getEvent(nameOrSignatureOrTopic: "FlashLiquidateBorrow"): EventFragment; + getEvent( + nameOrSignatureOrTopic: "FlashSwapCollateralAndLiquidateBorrow" + ): EventFragment; } -export type FlashLiquidateBorrowEvent = TypedEvent< +export type FlashSwapCollateralAndLiquidateBorrowEvent = TypedEvent< [string, string, string, BigNumber, BigNumber, BigNumber] & { liquidator: string; borrower: string; @@ -158,7 +160,7 @@ export class ICollateralFlashUniswapV2 extends BaseContract { underlying: string, underlyingAmount: BigNumberish, overrides?: CallOverrides - ): Promise<[BigNumber] & { collateralRepayAmount: BigNumber }>; + ): Promise<[BigNumber] & { repayCollateralAmount: BigNumber }>; uniV2Factory(overrides?: CallOverrides): Promise<[string]>; @@ -238,7 +240,7 @@ export class ICollateralFlashUniswapV2 extends BaseContract { }; filters: { - "FlashLiquidateBorrow(address,address,address,uint256,uint256,uint256)"( + "FlashSwapCollateralAndLiquidateBorrow(address,address,address,uint256,uint256,uint256)"( liquidator?: string | null, borrower?: string | null, bond?: string | null, @@ -257,7 +259,7 @@ export class ICollateralFlashUniswapV2 extends BaseContract { } >; - FlashLiquidateBorrow( + FlashSwapCollateralAndLiquidateBorrow( liquidator?: string | null, borrower?: string | null, bond?: string | null, diff --git a/packages/flash-swap/src/types/IUnderlyingFlashUniswapV2.d.ts b/packages/flash-swap/src/types/IUnderlyingFlashUniswapV2.d.ts index bb12138f..041be602 100644 --- a/packages/flash-swap/src/types/IUnderlyingFlashUniswapV2.d.ts +++ b/packages/flash-swap/src/types/IUnderlyingFlashUniswapV2.d.ts @@ -22,8 +22,8 @@ import type { TypedEventFilter, TypedEvent, TypedListener } from "./common"; interface IUnderlyingFlashUniswapV2Interface extends ethers.utils.Interface { functions: { "balanceSheet()": FunctionFragment; - "getCollateralAndUnderlyingAmount(address,uint256,uint256,address)": FunctionFragment; - "getRepayCollateralAmount(uint256)": FunctionFragment; + "getOtherTokenAndUnderlyingAmount(address,uint256,uint256,address)": FunctionFragment; + "getRepayUnderlyingAmount(uint256)": FunctionFragment; "uniV2Factory()": FunctionFragment; "uniV2PairInitCodeHash()": FunctionFragment; "uniswapV2Call(address,uint256,uint256,bytes)": FunctionFragment; @@ -34,11 +34,11 @@ interface IUnderlyingFlashUniswapV2Interface extends ethers.utils.Interface { values?: undefined ): string; encodeFunctionData( - functionFragment: "getCollateralAndUnderlyingAmount", + functionFragment: "getOtherTokenAndUnderlyingAmount", values: [string, BigNumberish, BigNumberish, string] ): string; encodeFunctionData( - functionFragment: "getRepayCollateralAmount", + functionFragment: "getRepayUnderlyingAmount", values: [BigNumberish] ): string; encodeFunctionData( @@ -59,11 +59,11 @@ interface IUnderlyingFlashUniswapV2Interface extends ethers.utils.Interface { data: BytesLike ): Result; decodeFunctionResult( - functionFragment: "getCollateralAndUnderlyingAmount", + functionFragment: "getOtherTokenAndUnderlyingAmount", data: BytesLike ): Result; decodeFunctionResult( - functionFragment: "getRepayCollateralAmount", + functionFragment: "getRepayUnderlyingAmount", data: BytesLike ): Result; decodeFunctionResult( @@ -80,20 +80,22 @@ interface IUnderlyingFlashUniswapV2Interface extends ethers.utils.Interface { ): Result; events: { - "FlashLiquidateBorrow(address,address,address,uint256,uint256,uint256)": EventFragment; + "FlashSwapUnderlyingAndLiquidateBorrow(address,address,address,uint256,uint256,uint256)": EventFragment; }; - getEvent(nameOrSignatureOrTopic: "FlashLiquidateBorrow"): EventFragment; + getEvent( + nameOrSignatureOrTopic: "FlashSwapUnderlyingAndLiquidateBorrow" + ): EventFragment; } -export type FlashLiquidateBorrowEvent = TypedEvent< +export type FlashSwapUnderlyingAndLiquidateBorrowEvent = TypedEvent< [string, string, string, BigNumber, BigNumber, BigNumber] & { liquidator: string; borrower: string; bond: string; underlyingAmount: BigNumber; - seizedCollateralAmount: BigNumber; - repayCollateralAmount: BigNumber; + seizedUnderlyingAmount: BigNumber; + repayUnderlyingAmount: BigNumber; } >; @@ -143,20 +145,20 @@ export class IUnderlyingFlashUniswapV2 extends BaseContract { functions: { balanceSheet(overrides?: CallOverrides): Promise<[string]>; - getCollateralAndUnderlyingAmount( + getOtherTokenAndUnderlyingAmount( pair: string, amount0: BigNumberish, amount1: BigNumberish, underlying: string, overrides?: CallOverrides ): Promise< - [string, BigNumber] & { collateral: string; underlyingAmount: BigNumber } + [string, BigNumber] & { otherToken: string; underlyingAmount: BigNumber } >; - getRepayCollateralAmount( + getRepayUnderlyingAmount( underlyingAmount: BigNumberish, overrides?: CallOverrides - ): Promise<[BigNumber] & { collateralRepayAmount: BigNumber }>; + ): Promise<[BigNumber] & { repayUnderlyingAmount: BigNumber }>; uniV2Factory(overrides?: CallOverrides): Promise<[string]>; @@ -173,17 +175,17 @@ export class IUnderlyingFlashUniswapV2 extends BaseContract { balanceSheet(overrides?: CallOverrides): Promise; - getCollateralAndUnderlyingAmount( + getOtherTokenAndUnderlyingAmount( pair: string, amount0: BigNumberish, amount1: BigNumberish, underlying: string, overrides?: CallOverrides ): Promise< - [string, BigNumber] & { collateral: string; underlyingAmount: BigNumber } + [string, BigNumber] & { otherToken: string; underlyingAmount: BigNumber } >; - getRepayCollateralAmount( + getRepayUnderlyingAmount( underlyingAmount: BigNumberish, overrides?: CallOverrides ): Promise; @@ -203,17 +205,17 @@ export class IUnderlyingFlashUniswapV2 extends BaseContract { callStatic: { balanceSheet(overrides?: CallOverrides): Promise; - getCollateralAndUnderlyingAmount( + getOtherTokenAndUnderlyingAmount( pair: string, amount0: BigNumberish, amount1: BigNumberish, underlying: string, overrides?: CallOverrides ): Promise< - [string, BigNumber] & { collateral: string; underlyingAmount: BigNumber } + [string, BigNumber] & { otherToken: string; underlyingAmount: BigNumber } >; - getRepayCollateralAmount( + getRepayUnderlyingAmount( underlyingAmount: BigNumberish, overrides?: CallOverrides ): Promise; @@ -232,13 +234,13 @@ export class IUnderlyingFlashUniswapV2 extends BaseContract { }; filters: { - "FlashLiquidateBorrow(address,address,address,uint256,uint256,uint256)"( + "FlashSwapUnderlyingAndLiquidateBorrow(address,address,address,uint256,uint256,uint256)"( liquidator?: string | null, borrower?: string | null, bond?: string | null, underlyingAmount?: null, - seizedCollateralAmount?: null, - repayCollateralAmount?: null + seizedUnderlyingAmount?: null, + repayUnderlyingAmount?: null ): TypedEventFilter< [string, string, string, BigNumber, BigNumber, BigNumber], { @@ -246,18 +248,18 @@ export class IUnderlyingFlashUniswapV2 extends BaseContract { borrower: string; bond: string; underlyingAmount: BigNumber; - seizedCollateralAmount: BigNumber; - repayCollateralAmount: BigNumber; + seizedUnderlyingAmount: BigNumber; + repayUnderlyingAmount: BigNumber; } >; - FlashLiquidateBorrow( + FlashSwapUnderlyingAndLiquidateBorrow( liquidator?: string | null, borrower?: string | null, bond?: string | null, underlyingAmount?: null, - seizedCollateralAmount?: null, - repayCollateralAmount?: null + seizedUnderlyingAmount?: null, + repayUnderlyingAmount?: null ): TypedEventFilter< [string, string, string, BigNumber, BigNumber, BigNumber], { @@ -265,8 +267,8 @@ export class IUnderlyingFlashUniswapV2 extends BaseContract { borrower: string; bond: string; underlyingAmount: BigNumber; - seizedCollateralAmount: BigNumber; - repayCollateralAmount: BigNumber; + seizedUnderlyingAmount: BigNumber; + repayUnderlyingAmount: BigNumber; } >; }; @@ -274,7 +276,7 @@ export class IUnderlyingFlashUniswapV2 extends BaseContract { estimateGas: { balanceSheet(overrides?: CallOverrides): Promise; - getCollateralAndUnderlyingAmount( + getOtherTokenAndUnderlyingAmount( pair: string, amount0: BigNumberish, amount1: BigNumberish, @@ -282,7 +284,7 @@ export class IUnderlyingFlashUniswapV2 extends BaseContract { overrides?: CallOverrides ): Promise; - getRepayCollateralAmount( + getRepayUnderlyingAmount( underlyingAmount: BigNumberish, overrides?: CallOverrides ): Promise; @@ -303,7 +305,7 @@ export class IUnderlyingFlashUniswapV2 extends BaseContract { populateTransaction: { balanceSheet(overrides?: CallOverrides): Promise; - getCollateralAndUnderlyingAmount( + getOtherTokenAndUnderlyingAmount( pair: string, amount0: BigNumberish, amount1: BigNumberish, @@ -311,7 +313,7 @@ export class IUnderlyingFlashUniswapV2 extends BaseContract { overrides?: CallOverrides ): Promise; - getRepayCollateralAmount( + getRepayUnderlyingAmount( underlyingAmount: BigNumberish, overrides?: CallOverrides ): Promise; diff --git a/packages/flash-swap/src/types/UnderlyingFlashUniswapV2.d.ts b/packages/flash-swap/src/types/UnderlyingFlashUniswapV2.d.ts index df094a80..371a649e 100644 --- a/packages/flash-swap/src/types/UnderlyingFlashUniswapV2.d.ts +++ b/packages/flash-swap/src/types/UnderlyingFlashUniswapV2.d.ts @@ -22,8 +22,8 @@ import type { TypedEventFilter, TypedEvent, TypedListener } from "./common"; interface UnderlyingFlashUniswapV2Interface extends ethers.utils.Interface { functions: { "balanceSheet()": FunctionFragment; - "getCollateralAndUnderlyingAmount(address,uint256,uint256,address)": FunctionFragment; - "getRepayCollateralAmount(uint256)": FunctionFragment; + "getOtherTokenAndUnderlyingAmount(address,uint256,uint256,address)": FunctionFragment; + "getRepayUnderlyingAmount(uint256)": FunctionFragment; "uniV2Factory()": FunctionFragment; "uniV2PairInitCodeHash()": FunctionFragment; "uniswapV2Call(address,uint256,uint256,bytes)": FunctionFragment; @@ -34,11 +34,11 @@ interface UnderlyingFlashUniswapV2Interface extends ethers.utils.Interface { values?: undefined ): string; encodeFunctionData( - functionFragment: "getCollateralAndUnderlyingAmount", + functionFragment: "getOtherTokenAndUnderlyingAmount", values: [string, BigNumberish, BigNumberish, string] ): string; encodeFunctionData( - functionFragment: "getRepayCollateralAmount", + functionFragment: "getRepayUnderlyingAmount", values: [BigNumberish] ): string; encodeFunctionData( @@ -59,11 +59,11 @@ interface UnderlyingFlashUniswapV2Interface extends ethers.utils.Interface { data: BytesLike ): Result; decodeFunctionResult( - functionFragment: "getCollateralAndUnderlyingAmount", + functionFragment: "getOtherTokenAndUnderlyingAmount", data: BytesLike ): Result; decodeFunctionResult( - functionFragment: "getRepayCollateralAmount", + functionFragment: "getRepayUnderlyingAmount", data: BytesLike ): Result; decodeFunctionResult( @@ -80,20 +80,22 @@ interface UnderlyingFlashUniswapV2Interface extends ethers.utils.Interface { ): Result; events: { - "FlashLiquidateBorrow(address,address,address,uint256,uint256,uint256)": EventFragment; + "FlashSwapUnderlyingAndLiquidateBorrow(address,address,address,uint256,uint256,uint256)": EventFragment; }; - getEvent(nameOrSignatureOrTopic: "FlashLiquidateBorrow"): EventFragment; + getEvent( + nameOrSignatureOrTopic: "FlashSwapUnderlyingAndLiquidateBorrow" + ): EventFragment; } -export type FlashLiquidateBorrowEvent = TypedEvent< +export type FlashSwapUnderlyingAndLiquidateBorrowEvent = TypedEvent< [string, string, string, BigNumber, BigNumber, BigNumber] & { liquidator: string; borrower: string; bond: string; underlyingAmount: BigNumber; - seizedCollateralAmount: BigNumber; - repayCollateralAmount: BigNumber; + seizedUnderlyingAmount: BigNumber; + repayUnderlyingAmount: BigNumber; } >; @@ -143,20 +145,20 @@ export class UnderlyingFlashUniswapV2 extends BaseContract { functions: { balanceSheet(overrides?: CallOverrides): Promise<[string]>; - getCollateralAndUnderlyingAmount( + getOtherTokenAndUnderlyingAmount( pair: string, amount0: BigNumberish, amount1: BigNumberish, underlying: string, overrides?: CallOverrides ): Promise< - [string, BigNumber] & { collateral: string; underlyingAmount: BigNumber } + [string, BigNumber] & { otherToken: string; underlyingAmount: BigNumber } >; - getRepayCollateralAmount( + getRepayUnderlyingAmount( underlyingAmount: BigNumberish, overrides?: CallOverrides - ): Promise<[BigNumber] & { repayCollateralAmount: BigNumber }>; + ): Promise<[BigNumber] & { repayUnderlyingAmount: BigNumber }>; uniV2Factory(overrides?: CallOverrides): Promise<[string]>; @@ -173,17 +175,17 @@ export class UnderlyingFlashUniswapV2 extends BaseContract { balanceSheet(overrides?: CallOverrides): Promise; - getCollateralAndUnderlyingAmount( + getOtherTokenAndUnderlyingAmount( pair: string, amount0: BigNumberish, amount1: BigNumberish, underlying: string, overrides?: CallOverrides ): Promise< - [string, BigNumber] & { collateral: string; underlyingAmount: BigNumber } + [string, BigNumber] & { otherToken: string; underlyingAmount: BigNumber } >; - getRepayCollateralAmount( + getRepayUnderlyingAmount( underlyingAmount: BigNumberish, overrides?: CallOverrides ): Promise; @@ -203,17 +205,17 @@ export class UnderlyingFlashUniswapV2 extends BaseContract { callStatic: { balanceSheet(overrides?: CallOverrides): Promise; - getCollateralAndUnderlyingAmount( + getOtherTokenAndUnderlyingAmount( pair: string, amount0: BigNumberish, amount1: BigNumberish, underlying: string, overrides?: CallOverrides ): Promise< - [string, BigNumber] & { collateral: string; underlyingAmount: BigNumber } + [string, BigNumber] & { otherToken: string; underlyingAmount: BigNumber } >; - getRepayCollateralAmount( + getRepayUnderlyingAmount( underlyingAmount: BigNumberish, overrides?: CallOverrides ): Promise; @@ -232,13 +234,13 @@ export class UnderlyingFlashUniswapV2 extends BaseContract { }; filters: { - "FlashLiquidateBorrow(address,address,address,uint256,uint256,uint256)"( + "FlashSwapUnderlyingAndLiquidateBorrow(address,address,address,uint256,uint256,uint256)"( liquidator?: string | null, borrower?: string | null, bond?: string | null, underlyingAmount?: null, - seizedCollateralAmount?: null, - repayCollateralAmount?: null + seizedUnderlyingAmount?: null, + repayUnderlyingAmount?: null ): TypedEventFilter< [string, string, string, BigNumber, BigNumber, BigNumber], { @@ -246,18 +248,18 @@ export class UnderlyingFlashUniswapV2 extends BaseContract { borrower: string; bond: string; underlyingAmount: BigNumber; - seizedCollateralAmount: BigNumber; - repayCollateralAmount: BigNumber; + seizedUnderlyingAmount: BigNumber; + repayUnderlyingAmount: BigNumber; } >; - FlashLiquidateBorrow( + FlashSwapUnderlyingAndLiquidateBorrow( liquidator?: string | null, borrower?: string | null, bond?: string | null, underlyingAmount?: null, - seizedCollateralAmount?: null, - repayCollateralAmount?: null + seizedUnderlyingAmount?: null, + repayUnderlyingAmount?: null ): TypedEventFilter< [string, string, string, BigNumber, BigNumber, BigNumber], { @@ -265,8 +267,8 @@ export class UnderlyingFlashUniswapV2 extends BaseContract { borrower: string; bond: string; underlyingAmount: BigNumber; - seizedCollateralAmount: BigNumber; - repayCollateralAmount: BigNumber; + seizedUnderlyingAmount: BigNumber; + repayUnderlyingAmount: BigNumber; } >; }; @@ -274,7 +276,7 @@ export class UnderlyingFlashUniswapV2 extends BaseContract { estimateGas: { balanceSheet(overrides?: CallOverrides): Promise; - getCollateralAndUnderlyingAmount( + getOtherTokenAndUnderlyingAmount( pair: string, amount0: BigNumberish, amount1: BigNumberish, @@ -282,7 +284,7 @@ export class UnderlyingFlashUniswapV2 extends BaseContract { overrides?: CallOverrides ): Promise; - getRepayCollateralAmount( + getRepayUnderlyingAmount( underlyingAmount: BigNumberish, overrides?: CallOverrides ): Promise; @@ -303,7 +305,7 @@ export class UnderlyingFlashUniswapV2 extends BaseContract { populateTransaction: { balanceSheet(overrides?: CallOverrides): Promise; - getCollateralAndUnderlyingAmount( + getOtherTokenAndUnderlyingAmount( pair: string, amount0: BigNumberish, amount1: BigNumberish, @@ -311,7 +313,7 @@ export class UnderlyingFlashUniswapV2 extends BaseContract { overrides?: CallOverrides ): Promise; - getRepayCollateralAmount( + getRepayUnderlyingAmount( underlyingAmount: BigNumberish, overrides?: CallOverrides ): Promise; diff --git a/packages/flash-swap/test/integration/collateralFlashUniswapV2/effects/uniswapV2Call.ts b/packages/flash-swap/test/integration/collateralFlashUniswapV2/effects/uniswapV2Call.ts index f0665541..4a88ff9e 100644 --- a/packages/flash-swap/test/integration/collateralFlashUniswapV2/effects/uniswapV2Call.ts +++ b/packages/flash-swap/test/integration/collateralFlashUniswapV2/effects/uniswapV2Call.ts @@ -351,13 +351,13 @@ export function shouldBehaveLikeUniswapV2Call(): void { expect(newWbtcBalance.sub(expectedProfitWbtcAmount)).to.equal(preWbtcBalance); }); - it("emits a FlashLiquidateBorrow event", async function () { + it("emits a FlashSwapCollateralAndLiquidateBorrow event", async function () { const to: string = this.contracts.collateralFlashUniswapV2.address; const contractCall = this.contracts.uniswapV2Pair .connect(this.signers.liquidator) .swap(token0Amount, token1Amount, to, data); await expect(contractCall) - .to.emit(this.contracts.collateralFlashUniswapV2, "FlashLiquidateBorrow") + .to.emit(this.contracts.collateralFlashUniswapV2, "FlashSwapCollateralAndLiquidateBorrow") .withArgs( this.signers.liquidator.address, this.signers.borrower.address, diff --git a/packages/flash-swap/test/integration/underlyingFlashUniswapV2/effects/uniswapV2Call.ts b/packages/flash-swap/test/integration/underlyingFlashUniswapV2/effects/uniswapV2Call.ts index bf07bd7c..57c44c86 100644 --- a/packages/flash-swap/test/integration/underlyingFlashUniswapV2/effects/uniswapV2Call.ts +++ b/packages/flash-swap/test/integration/underlyingFlashUniswapV2/effects/uniswapV2Call.ts @@ -26,7 +26,7 @@ async function bumpPoolReserves(this: Mocha.Context, wbtcAmount: BigNumber, usdc function encodeCallData(this: Mocha.Context): string { const types = ["address", "address", "address"]; - const values = [this.signers.borrower.address, this.contracts.hToken.address, this.signers.bot.address]; + const values = [this.signers.borrower.address, this.contracts.hToken.address, this.signers.subsidizer.address]; const data: string = defaultAbiCoder.encode(types, values); return data; } @@ -155,28 +155,28 @@ export function shouldBehaveLikeUniswapV2Call(): void { }); context("when the underlying is in the pair contract", function () { - context("when wrong token is flash borrowed", function () { + context("when the other token is flash borrowed", function () { it("reverts", async function () { const { token0Amount, token1Amount } = await getTokenAmounts.call(this, WBTC("1"), Zero); const to: string = this.contracts.underlyingFlashUniswapV2.address; await expect( this.contracts.uniswapV2Pair.connect(this.signers.raider).swap(token0Amount, token1Amount, to, data), - ).to.be.revertedWith(UnderlyingFlashUniswapV2Errors.FlashBorrowWrongToken); + ).to.be.revertedWith(UnderlyingFlashUniswapV2Errors.FlashBorrowOtherToken); }); }); context("when underlying is flash borrowed", function () { const borrowAmount: BigNumber = hUSDC("10000"); const collateralAmount: BigNumber = Zero; - const usdcCollateralCeiling: BigNumber = USDC("1000000"); - const wbtcCollateralCeiling: BigNumber = WBTC("50"); const debtCeiling: BigNumber = hUSDC("1e6"); - const usdcLiquidationIncentive: BigNumber = toBn("1"); - const wbtcLiquidationIncentive: BigNumber = toBn("1.10"); const underlyingAmount: BigNumber = USDC("10000"); + const usdcCollateralCeiling: BigNumber = USDC("1000000"); const usdcDepositAmount: BigNumber = USDC("10000"); - const usdcRepayFeeAmount: BigNumber = USDC("30.090271"); + const usdcLiquidationIncentive: BigNumber = toBn("1"); + const usdcFeeAmount: BigNumber = USDC("30.090271"); + const wbtcCollateralCeiling: BigNumber = WBTC("50"); const wbtcDepositAmount: BigNumber = WBTC("0.5"); + const wbtcLiquidationIncentive: BigNumber = toBn("1.10"); let token0Amount: BigNumber; let token1Amount: BigNumber; @@ -226,11 +226,11 @@ export function shouldBehaveLikeUniswapV2Call(): void { .connect(this.signers.borrower) .approve(this.contracts.balanceSheet.address, wbtcDepositAmount); - // Minst USDC to the bot wallet and approve the flash swap contract to spend it - await this.contracts.usdc.__godMode_mint(this.signers.bot.address, usdcRepayFeeAmount); + // Mint USDC to the subsidizer wallet and approve the flash swap contract to spend it. + await this.contracts.usdc.__godMode_mint(this.signers.subsidizer.address, usdcFeeAmount); await this.contracts.usdc - .connect(this.signers.bot) - .approve(this.contracts.underlyingFlashUniswapV2.address, usdcRepayFeeAmount); + .connect(this.signers.subsidizer) + .approve(this.contracts.underlyingFlashUniswapV2.address, usdcFeeAmount); // Deposit the USDC in the BalanceSheet. await this.contracts.balanceSheet @@ -270,15 +270,15 @@ export function shouldBehaveLikeUniswapV2Call(): void { it("flash swaps USDC making no USDC profit and spending allocated USDC to pay swap fee", async function () { const to: string = this.contracts.underlyingFlashUniswapV2.address; - const preUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); - const preUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.bot.address); + const oldUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); + const oldUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.subsidizer.address); await this.contracts.uniswapV2Pair .connect(this.signers.liquidator) .swap(token0Amount, token1Amount, to, data); const newUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); - const newUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.bot.address); - expect(newUsdcBalanceAccount).to.equal(preUsdcBalanceAccount); - expect(preUsdcBalanceBot.sub(newUsdcBalanceBot)).to.equal(usdcRepayFeeAmount); + const newUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.subsidizer.address); + expect(newUsdcBalanceAccount).to.equal(oldUsdcBalanceAccount); + expect(oldUsdcBalanceBot.sub(newUsdcBalanceBot)).to.equal(usdcFeeAmount); }); }); @@ -305,15 +305,15 @@ export function shouldBehaveLikeUniswapV2Call(): void { it("flash swaps USDC making no USDC profit and spending allocated USDC to pay swap fee", async function () { const to: string = this.contracts.underlyingFlashUniswapV2.address; - const preUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); - const preUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.bot.address); + const oldUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); + const oldUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.subsidizer.address); await this.contracts.uniswapV2Pair .connect(this.signers.liquidator) .swap(token0Amount, token1Amount, to, data); const newUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); - const newUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.bot.address); - expect(newUsdcBalanceAccount).to.equal(preUsdcBalanceAccount); - expect(preUsdcBalanceBot.sub(newUsdcBalanceBot)).to.equal(usdcRepayFeeAmount); + const newUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.subsidizer.address); + expect(newUsdcBalanceAccount).to.equal(oldUsdcBalanceAccount); + expect(oldUsdcBalanceBot.sub(newUsdcBalanceBot)).to.equal(usdcFeeAmount); }); }); @@ -357,53 +357,41 @@ export function shouldBehaveLikeUniswapV2Call(): void { await this.contracts.uniswapV2Pair.sync(); }); - context("when wrong token is flash borrowed", function () { - it("reverts", async function () { - const { token0Amount, token1Amount } = await getTokenAmounts.call(this, WBTC("1"), Zero); - const to: string = this.contracts.underlyingFlashUniswapV2.address; - await expect( - this.contracts.uniswapV2Pair - .connect(this.signers.raider) - .swap(token0Amount, token1Amount, to, data), - ).to.be.revertedWith(UnderlyingFlashUniswapV2Errors.FlashBorrowWrongToken); - }); - }); - it("flash swaps USDC making no USDC profit and spending allocated USDC to pay swap fee", async function () { const to: string = this.contracts.underlyingFlashUniswapV2.address; - const preUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); - const preUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.bot.address); + const oldUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); + const oldUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.subsidizer.address); await this.contracts.uniswapV2Pair .connect(this.signers.liquidator) .swap(localToken0Amount, localToken1Amount, to, data); const newUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); - const newUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.bot.address); - expect(newUsdcBalanceAccount).to.equal(preUsdcBalanceAccount); - expect(preUsdcBalanceBot.sub(newUsdcBalanceBot)).to.equal(usdcRepayFeeAmount); + const newUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.subsidizer.address); + expect(newUsdcBalanceAccount).to.equal(oldUsdcBalanceAccount); + expect(oldUsdcBalanceBot.sub(newUsdcBalanceBot)).to.equal(usdcFeeAmount); }); }); context("initial order of tokens in the pair", function () { it("flash swaps USDC making no USDC profit and spending allocated USDC to pay swap fee", async function () { const to: string = this.contracts.underlyingFlashUniswapV2.address; - const preUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); - const preUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.bot.address); + const oldUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); + const oldUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.subsidizer.address); await this.contracts.uniswapV2Pair .connect(this.signers.liquidator) .swap(token0Amount, token1Amount, to, data); const newUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); - const newUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.bot.address); - expect(newUsdcBalanceAccount).to.equal(preUsdcBalanceAccount); - expect(preUsdcBalanceBot.sub(newUsdcBalanceBot)).to.equal(usdcRepayFeeAmount); + const newUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.subsidizer.address); + expect(newUsdcBalanceAccount).to.equal(oldUsdcBalanceAccount); + expect(oldUsdcBalanceBot.sub(newUsdcBalanceBot)).to.equal(usdcFeeAmount); }); - it("emits a FlashLiquidateBorrow event", async function () { + it("emits a FlashSwapUnderlyingAndLiquidateBorrow event", async function () { const to: string = this.contracts.underlyingFlashUniswapV2.address; const contractCall = this.contracts.uniswapV2Pair .connect(this.signers.liquidator) .swap(token0Amount, token1Amount, to, data); await expect(contractCall) - .to.emit(this.contracts.underlyingFlashUniswapV2, "FlashLiquidateBorrow") + .to.emit(this.contracts.underlyingFlashUniswapV2, "FlashSwapUnderlyingAndLiquidateBorrow") .withArgs( this.signers.liquidator.address, this.signers.borrower.address, diff --git a/packages/flash-swap/test/shared/contexts.ts b/packages/flash-swap/test/shared/contexts.ts index 9ffce76f..cfa7b8cc 100644 --- a/packages/flash-swap/test/shared/contexts.ts +++ b/packages/flash-swap/test/shared/contexts.ts @@ -1,5 +1,5 @@ import type { Signer } from "@ethersproject/abstract-signer"; -import { Wallet } from "@ethersproject/wallet"; +import type { Wallet } from "@ethersproject/wallet"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address"; import { ethers, waffle } from "hardhat"; @@ -18,7 +18,7 @@ export function baseContext(description: string, hooks: () => void): void { this.signers.borrower = signers[1]; this.signers.liquidator = signers[2]; this.signers.raider = signers[3]; - this.signers.bot = signers[4]; + this.signers.subsidizer = signers[4]; // Get rid of this when https://github.com/nomiclabs/hardhat/issues/849 gets fixed. this.loadFixture = createFixtureLoader(signers as Signer[] as Wallet[]); diff --git a/packages/flash-swap/test/shared/types.ts b/packages/flash-swap/test/shared/types.ts index e8437ff6..ff489b00 100644 --- a/packages/flash-swap/test/shared/types.ts +++ b/packages/flash-swap/test/shared/types.ts @@ -38,5 +38,5 @@ export interface Signers { borrower: SignerWithAddress; liquidator: SignerWithAddress; raider: SignerWithAddress; - bot: SignerWithAddress; + subsidizer: SignerWithAddress; } diff --git a/packages/proxy-target/contracts/HifiProxyTarget.sol b/packages/proxy-target/contracts/HifiProxyTarget.sol index 6fcc3efc..fa528e07 100644 --- a/packages/proxy-target/contracts/HifiProxyTarget.sol +++ b/packages/proxy-target/contracts/HifiProxyTarget.sol @@ -440,13 +440,13 @@ contract HifiProxyTarget is IHifiProxyTarget { // Redeem the hTokens. IErc20 underlying = hToken.underlying(); - uint256 preUnderlyingBalance = underlying.balanceOf(address(this)); + uint256 oldUnderlyingBalance = underlying.balanceOf(address(this)); hToken.redeem(hTokenAmount); unchecked { // Calculate how much underlying was redeemed. - uint256 postUnderlyingBalance = underlying.balanceOf(address(this)); - uint256 underlyingAmount = postUnderlyingBalance - preUnderlyingBalance; + uint256 newUnderlyingBalance = underlying.balanceOf(address(this)); + uint256 underlyingAmount = newUnderlyingBalance - oldUnderlyingBalance; // The underlying is now in the DSProxy, so we relay it to the end user. underlying.safeTransfer(msg.sender, underlyingAmount); @@ -477,14 +477,14 @@ contract HifiProxyTarget is IHifiProxyTarget { // Redeem the hTokens. IHToken hToken = hifiPool.hToken(); IErc20 underlying = hToken.underlying(); - uint256 preUnderlyingBalance = underlying.balanceOf(address(this)); + uint256 oldUnderlyingBalance = underlying.balanceOf(address(this)); hToken.redeem(hTokenReturned); // Calculate how much underlying was redeemed. uint256 underlyingAmount; unchecked { - uint256 postUnderlyingBalance = underlying.balanceOf(address(this)); - underlyingAmount = postUnderlyingBalance - preUnderlyingBalance; + uint256 newUnderlyingBalance = underlying.balanceOf(address(this)); + underlyingAmount = newUnderlyingBalance - oldUnderlyingBalance; } // Relay all the underlying it to the end user. @@ -661,13 +661,13 @@ contract HifiProxyTarget is IHifiProxyTarget { /// @inheritdoc IHifiProxyTarget function supplyUnderlying(IHToken hToken, uint256 underlyingAmount) external override { - uint256 preHTokenBalance = hToken.balanceOf(address(this)); + uint256 oldHTokenBalance = hToken.balanceOf(address(this)); supplyUnderlyingInternal(hToken, underlyingAmount); unchecked { // Calculate how many hTokens were minted. - uint256 postHTokenBalance = hToken.balanceOf(address(this)); - uint256 hTokenAmount = postHTokenBalance - preHTokenBalance; + uint256 newHTokenBalance = hToken.balanceOf(address(this)); + uint256 hTokenAmount = newHTokenBalance - oldHTokenBalance; // The hTokens are now in the DSProxy, so we relay them to the end user. hToken.transfer(msg.sender, hTokenAmount); @@ -680,13 +680,13 @@ contract HifiProxyTarget is IHifiProxyTarget { IBalanceSheetV1 balanceSheet, uint256 underlyingAmount ) external override { - uint256 preHTokenBalance = hToken.balanceOf(address(this)); + uint256 oldHTokenBalance = hToken.balanceOf(address(this)); supplyUnderlyingInternal(hToken, underlyingAmount); unchecked { // Calculate how many hTokens were minted. - uint256 postHTokenBalance = hToken.balanceOf(address(this)); - uint256 hTokenAmount = postHTokenBalance - preHTokenBalance; + uint256 newHTokenBalance = hToken.balanceOf(address(this)); + uint256 hTokenAmount = newHTokenBalance - oldHTokenBalance; // Use the newly minted hTokens to repay the debt. balanceSheet.repayBorrow(hToken, hTokenAmount); From e1ed460ba02f4e840c61b3795019ac2028eb9687 Mon Sep 17 00:00:00 2001 From: Paul Razvan Berg Date: Thu, 4 Nov 2021 16:34:09 +0200 Subject: [PATCH 10/16] test(flash-swap): refactor variable names in "UnderlyingFlashUniswapV2" tests test(flash-swap): import "LIQUIDATION_INCENTIVES" to set liquidation incentives for USDC/ WBTC --- .../effects/uniswapV2Call.ts | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/packages/flash-swap/test/integration/underlyingFlashUniswapV2/effects/uniswapV2Call.ts b/packages/flash-swap/test/integration/underlyingFlashUniswapV2/effects/uniswapV2Call.ts index 57c44c86..ca61d943 100644 --- a/packages/flash-swap/test/integration/underlyingFlashUniswapV2/effects/uniswapV2Call.ts +++ b/packages/flash-swap/test/integration/underlyingFlashUniswapV2/effects/uniswapV2Call.ts @@ -1,10 +1,10 @@ import { defaultAbiCoder } from "@ethersproject/abi"; import { BigNumber } from "@ethersproject/bignumber"; import { Zero } from "@ethersproject/constants"; +import { LIQUIDATION_INCENTIVES } from "@hifi/constants"; import { BalanceSheetErrors, UnderlyingFlashUniswapV2Errors } from "@hifi/errors"; import { USDC, WBTC, hUSDC, price } from "@hifi/helpers"; import { expect } from "chai"; -import { toBn } from "evm-bn"; import type { GodModeErc20 } from "../../../../src/types/GodModeErc20"; import { deployGodModeErc20 } from "../../../shared/deployers"; @@ -167,22 +167,20 @@ export function shouldBehaveLikeUniswapV2Call(): void { context("when underlying is flash borrowed", function () { const borrowAmount: BigNumber = hUSDC("10000"); - const collateralAmount: BigNumber = Zero; + const collateralCeilingUsdc: BigNumber = USDC("1000000"); + const collateralCeilingWbtc: BigNumber = WBTC("50"); const debtCeiling: BigNumber = hUSDC("1e6"); - const underlyingAmount: BigNumber = USDC("10000"); - const usdcCollateralCeiling: BigNumber = USDC("1000000"); - const usdcDepositAmount: BigNumber = USDC("10000"); - const usdcLiquidationIncentive: BigNumber = toBn("1"); - const usdcFeeAmount: BigNumber = USDC("30.090271"); - const wbtcCollateralCeiling: BigNumber = WBTC("50"); - const wbtcDepositAmount: BigNumber = WBTC("0.5"); - const wbtcLiquidationIncentive: BigNumber = toBn("1.10"); + const depositUsdcAmount: BigNumber = USDC("10000"); + const depositWbtcAmount: BigNumber = WBTC("0.5"); + const feeUsdcAmount: BigNumber = USDC("30.090271"); + const swapUsdcAmount: BigNumber = USDC("10000"); + const swapWbtcAmount: BigNumber = Zero; let token0Amount: BigNumber; let token1Amount: BigNumber; beforeEach(async function () { - const tokenAmounts = await getTokenAmounts.call(this, collateralAmount, underlyingAmount); + const tokenAmounts = await getTokenAmounts.call(this, swapWbtcAmount, swapUsdcAmount); token0Amount = tokenAmounts.token0Amount; token1Amount = tokenAmounts.token1Amount; @@ -193,21 +191,23 @@ export function shouldBehaveLikeUniswapV2Call(): void { await this.contracts.fintroller.connect(this.signers.admin).listCollateral(this.contracts.usdc.address); await this.contracts.fintroller.connect(this.signers.admin).listCollateral(this.contracts.wbtc.address); - // Set the liquidation incentives. + // Set the liquidation incentive for USDC to 100%. await this.contracts.fintroller .connect(this.signers.admin) - .setLiquidationIncentive(this.contracts.usdc.address, usdcLiquidationIncentive); + .setLiquidationIncentive(this.contracts.usdc.address, LIQUIDATION_INCENTIVES.lowerBound); + + // Set the liquidation incentive for WBTC to 110%. await this.contracts.fintroller .connect(this.signers.admin) - .setLiquidationIncentive(this.contracts.wbtc.address, wbtcLiquidationIncentive); + .setLiquidationIncentive(this.contracts.wbtc.address, LIQUIDATION_INCENTIVES.default); // Set the collateral ceilings. await this.contracts.fintroller .connect(this.signers.admin) - .setCollateralCeiling(this.contracts.usdc.address, usdcCollateralCeiling); + .setCollateralCeiling(this.contracts.usdc.address, collateralCeilingUsdc); await this.contracts.fintroller .connect(this.signers.admin) - .setCollateralCeiling(this.contracts.wbtc.address, wbtcCollateralCeiling); + .setCollateralCeiling(this.contracts.wbtc.address, collateralCeilingWbtc); // Set the debt ceiling. await this.contracts.fintroller @@ -215,32 +215,32 @@ export function shouldBehaveLikeUniswapV2Call(): void { .setDebtCeiling(this.contracts.hToken.address, debtCeiling); // Mint USDC and approve the BalanceSheet to spend it. - await this.contracts.usdc.__godMode_mint(this.signers.borrower.address, usdcDepositAmount); + await this.contracts.usdc.__godMode_mint(this.signers.borrower.address, depositUsdcAmount); await this.contracts.usdc .connect(this.signers.borrower) - .approve(this.contracts.balanceSheet.address, usdcDepositAmount); + .approve(this.contracts.balanceSheet.address, depositUsdcAmount); // Mint WBTC and approve the BalanceSheet to spend it. - await this.contracts.wbtc.__godMode_mint(this.signers.borrower.address, wbtcDepositAmount); + await this.contracts.wbtc.__godMode_mint(this.signers.borrower.address, depositWbtcAmount); await this.contracts.wbtc .connect(this.signers.borrower) - .approve(this.contracts.balanceSheet.address, wbtcDepositAmount); + .approve(this.contracts.balanceSheet.address, depositWbtcAmount); // Mint USDC to the subsidizer wallet and approve the flash swap contract to spend it. - await this.contracts.usdc.__godMode_mint(this.signers.subsidizer.address, usdcFeeAmount); + await this.contracts.usdc.__godMode_mint(this.signers.subsidizer.address, feeUsdcAmount); await this.contracts.usdc .connect(this.signers.subsidizer) - .approve(this.contracts.underlyingFlashUniswapV2.address, usdcFeeAmount); + .approve(this.contracts.underlyingFlashUniswapV2.address, feeUsdcAmount); // Deposit the USDC in the BalanceSheet. await this.contracts.balanceSheet .connect(this.signers.borrower) - .depositCollateral(this.contracts.usdc.address, usdcDepositAmount); + .depositCollateral(this.contracts.usdc.address, depositUsdcAmount); // Deposit the WBTC in the BalanceSheet. await this.contracts.balanceSheet .connect(this.signers.borrower) - .depositCollateral(this.contracts.wbtc.address, wbtcDepositAmount); + .depositCollateral(this.contracts.wbtc.address, depositWbtcAmount); // Borrow hUSDC. await this.contracts.balanceSheet @@ -278,7 +278,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { const newUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); const newUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.subsidizer.address); expect(newUsdcBalanceAccount).to.equal(oldUsdcBalanceAccount); - expect(oldUsdcBalanceBot.sub(newUsdcBalanceBot)).to.equal(usdcFeeAmount); + expect(oldUsdcBalanceBot.sub(newUsdcBalanceBot)).to.equal(feeUsdcAmount); }); }); @@ -298,7 +298,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { const calculatesAmounts = await getSeizableAndRepayCollateralAmounts.call( this, repayHUsdcAmount, - underlyingAmount, + swapUsdcAmount, ); seizableUsdcAmount = calculatesAmounts.seizableUsdcAmount; }); @@ -313,7 +313,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { const newUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); const newUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.subsidizer.address); expect(newUsdcBalanceAccount).to.equal(oldUsdcBalanceAccount); - expect(oldUsdcBalanceBot.sub(newUsdcBalanceBot)).to.equal(usdcFeeAmount); + expect(oldUsdcBalanceBot.sub(newUsdcBalanceBot)).to.equal(feeUsdcAmount); }); }); @@ -331,7 +331,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { const calculatesAmounts = await getSeizableAndRepayCollateralAmounts.call( this, repayHUsdcAmount, - underlyingAmount, + swapUsdcAmount, ); seizableUsdcAmount = calculatesAmounts.seizableUsdcAmount; expectedRepayUsdcAmount = calculatesAmounts.expectedRepayUsdcAmount; @@ -346,13 +346,13 @@ export function shouldBehaveLikeUniswapV2Call(): void { if (token0 == this.contracts.wbtc.address) { await this.contracts.uniswapV2Pair.__godMode_setToken0(this.contracts.usdc.address); await this.contracts.uniswapV2Pair.__godMode_setToken1(this.contracts.wbtc.address); - localToken0Amount = underlyingAmount; - localToken1Amount = collateralAmount; + localToken0Amount = swapUsdcAmount; + localToken1Amount = swapWbtcAmount; } else { await this.contracts.uniswapV2Pair.__godMode_setToken0(this.contracts.wbtc.address); await this.contracts.uniswapV2Pair.__godMode_setToken1(this.contracts.usdc.address); - localToken0Amount = collateralAmount; - localToken1Amount = underlyingAmount; + localToken0Amount = swapWbtcAmount; + localToken1Amount = swapUsdcAmount; } await this.contracts.uniswapV2Pair.sync(); }); @@ -367,7 +367,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { const newUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); const newUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.subsidizer.address); expect(newUsdcBalanceAccount).to.equal(oldUsdcBalanceAccount); - expect(oldUsdcBalanceBot.sub(newUsdcBalanceBot)).to.equal(usdcFeeAmount); + expect(oldUsdcBalanceBot.sub(newUsdcBalanceBot)).to.equal(feeUsdcAmount); }); }); @@ -382,7 +382,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { const newUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); const newUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.subsidizer.address); expect(newUsdcBalanceAccount).to.equal(oldUsdcBalanceAccount); - expect(oldUsdcBalanceBot.sub(newUsdcBalanceBot)).to.equal(usdcFeeAmount); + expect(oldUsdcBalanceBot.sub(newUsdcBalanceBot)).to.equal(feeUsdcAmount); }); it("emits a FlashSwapUnderlyingAndLiquidateBorrow event", async function () { @@ -396,7 +396,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { this.signers.liquidator.address, this.signers.borrower.address, this.contracts.hToken.address, - underlyingAmount, + swapUsdcAmount, seizableUsdcAmount, expectedRepayUsdcAmount, ); From 601f296be40bd8419cef439e855060c461f37a4a Mon Sep 17 00:00:00 2001 From: Paul Razvan Berg Date: Thu, 4 Nov 2021 19:39:08 +0200 Subject: [PATCH 11/16] ci: fix task names in flash swap deployers --- .github/workflows/deploy-collateral-flash-uniswap-v2.yaml | 4 ++-- .github/workflows/deploy-underlying-flash-uniswap-v2.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy-collateral-flash-uniswap-v2.yaml b/.github/workflows/deploy-collateral-flash-uniswap-v2.yaml index 6aebae04..0b919252 100644 --- a/.github/workflows/deploy-collateral-flash-uniswap-v2.yaml +++ b/.github/workflows/deploy-collateral-flash-uniswap-v2.yaml @@ -60,7 +60,7 @@ jobs: 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 }}" @@ -70,7 +70,7 @@ jobs: - 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 }}" diff --git a/.github/workflows/deploy-underlying-flash-uniswap-v2.yaml b/.github/workflows/deploy-underlying-flash-uniswap-v2.yaml index 9b7e31f2..8aecaa71 100644 --- a/.github/workflows/deploy-underlying-flash-uniswap-v2.yaml +++ b/.github/workflows/deploy-underlying-flash-uniswap-v2.yaml @@ -60,7 +60,7 @@ jobs: id: deploy run: >- yarn workspace @hifi/tasks hardhat - deploy:contract:hifi-flash-uniswap-v2 --network "${{ github.event.inputs.chain }}" + 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 }}" @@ -70,7 +70,7 @@ jobs: - name: "Verify UnderlyingFlashUniswapV2" run: >- yarn workspace @hifi/flash-swap hardhat - verify "${{ steps.deploy.outputs.hifi-flash-uniswap-v2 }}" --network "${{ github.event.inputs.chain }}" + 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 }}" From 4da7d20179563eede061198196c6220324319791 Mon Sep 17 00:00:00 2001 From: Paul Razvan Berg Date: Thu, 4 Nov 2021 19:46:11 +0200 Subject: [PATCH 12/16] chore(flash-swap): order variables alphabetically --- .../collateralFlashUniswapV2/CollateralFlashUniswapV2.ts | 6 +++--- .../underlyingFlashUniswapV2/UnderlyingFlashUniswapV2.ts | 2 +- packages/flash-swap/test/shared/fixtures.ts | 2 +- packages/flash-swap/test/shared/types.ts | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/flash-swap/test/integration/collateralFlashUniswapV2/CollateralFlashUniswapV2.ts b/packages/flash-swap/test/integration/collateralFlashUniswapV2/CollateralFlashUniswapV2.ts index 50db6883..89bdf10b 100644 --- a/packages/flash-swap/test/integration/collateralFlashUniswapV2/CollateralFlashUniswapV2.ts +++ b/packages/flash-swap/test/integration/collateralFlashUniswapV2/CollateralFlashUniswapV2.ts @@ -6,9 +6,9 @@ export function integrationTestCollateralFlashUniswapV2(): void { beforeEach(async function () { const { balanceSheet, + collateralFlashUniswapV2, fintroller, hToken, - collateralFlashUniswapV2, maliciousPair, oracle, underlyingFlashUniswapV2, @@ -19,15 +19,15 @@ export function integrationTestCollateralFlashUniswapV2(): void { wbtcPriceFeed, } = await this.loadFixture(integrationFixture); this.contracts.balanceSheet = balanceSheet; + this.contracts.collateralFlashUniswapV2 = collateralFlashUniswapV2; this.contracts.fintroller = fintroller; this.contracts.hToken = hToken; - this.contracts.collateralFlashUniswapV2 = collateralFlashUniswapV2; this.contracts.maliciousPair = maliciousPair; this.contracts.oracle = oracle; this.contracts.underlyingFlashUniswapV2 = underlyingFlashUniswapV2; + this.contracts.uniswapV2Pair = uniswapV2Pair; this.contracts.usdc = usdc; this.contracts.usdcPriceFeed = usdcPriceFeed; - this.contracts.uniswapV2Pair = uniswapV2Pair; this.contracts.wbtc = wbtc; this.contracts.wbtcPriceFeed = wbtcPriceFeed; }); diff --git a/packages/flash-swap/test/integration/underlyingFlashUniswapV2/UnderlyingFlashUniswapV2.ts b/packages/flash-swap/test/integration/underlyingFlashUniswapV2/UnderlyingFlashUniswapV2.ts index 54c8c41b..20c3855f 100644 --- a/packages/flash-swap/test/integration/underlyingFlashUniswapV2/UnderlyingFlashUniswapV2.ts +++ b/packages/flash-swap/test/integration/underlyingFlashUniswapV2/UnderlyingFlashUniswapV2.ts @@ -19,9 +19,9 @@ export function integrationTestUnderlyingFlashUniswapV2(): void { wbtcPriceFeed, } = await this.loadFixture(integrationFixture); this.contracts.balanceSheet = balanceSheet; + this.contracts.collateralFlashUniswapV2 = collateralFlashUniswapV2; this.contracts.fintroller = fintroller; this.contracts.hToken = hToken; - this.contracts.collateralFlashUniswapV2 = collateralFlashUniswapV2; this.contracts.maliciousPair = maliciousPair; this.contracts.oracle = oracle; this.contracts.underlyingFlashUniswapV2 = underlyingFlashUniswapV2; diff --git a/packages/flash-swap/test/shared/fixtures.ts b/packages/flash-swap/test/shared/fixtures.ts index c909a332..3f2dbbc3 100644 --- a/packages/flash-swap/test/shared/fixtures.ts +++ b/packages/flash-swap/test/shared/fixtures.ts @@ -22,8 +22,8 @@ import { deployGodModeErc20 } from "./deployers"; type IntegrationFixtureReturnType = { balanceSheet: BalanceSheetV1; - fintroller: FintrollerV1; collateralFlashUniswapV2: CollateralFlashUniswapV2; + fintroller: FintrollerV1; hToken: GodModeHToken; maliciousPair: MaliciousPair; underlyingFlashUniswapV2: UnderlyingFlashUniswapV2; diff --git a/packages/flash-swap/test/shared/types.ts b/packages/flash-swap/test/shared/types.ts index ff489b00..5b9da9e5 100644 --- a/packages/flash-swap/test/shared/types.ts +++ b/packages/flash-swap/test/shared/types.ts @@ -20,9 +20,9 @@ declare module "mocha" { export interface Contracts { balanceSheet: BalanceSheetV1; + collateralFlashUniswapV2: CollateralFlashUniswapV2; fintroller: FintrollerV1; hToken: GodModeHToken; - collateralFlashUniswapV2: CollateralFlashUniswapV2; maliciousPair: MaliciousPair; oracle: ChainlinkOperator; usdc: GodModeErc20; From 613df07e387a194db08519123ef895ad32e1f4c4 Mon Sep 17 00:00:00 2001 From: Paul Razvan Berg Date: Thu, 4 Nov 2021 22:09:02 +0200 Subject: [PATCH 13/16] fix(flash-swap): properly compare "repayUnderlyingAmount" to "seizedUnderlyingAmount" docs(flash-swap): better formulate the NatSpec comments for "getRepayUnderlyingAmount" test(flash-swap): add time-bound tests for "UnderlyingFlashUniswapV2" test(flash-swap): delete price-related tests for "UnderlyingFlashUniswapV2" test(flash-swap): refer to the "pair contract" as "UniswapV2Pair contract" in descriptors test(flash-swap): rename "bumpPoolReserves" to "increasePoolReserves" test(flash-swap): rename "collateralAmount" to "swapCollateralAmount" test(flash-swap): rename "repayHUsdcAmount" to "repayAmount" test(flash-swap): rename "underlyingAmount" to "swapUnderlyingAmount" --- .../contracts/test/GodModeHToken.sol | 4 + .../uniswap-v2/IUnderlyingFlashUniswapV2.sol | 4 +- .../uniswap-v2/UnderlyingFlashUniswapV2.sol | 11 +- .../effects/uniswapV2Call.ts | 104 ++++---- .../effects/uniswapV2Call.ts | 252 ++++++------------ 5 files changed, 152 insertions(+), 223 deletions(-) diff --git a/packages/flash-swap/contracts/test/GodModeHToken.sol b/packages/flash-swap/contracts/test/GodModeHToken.sol index 3719c3bc..9d41b009 100644 --- a/packages/flash-swap/contracts/test/GodModeHToken.sol +++ b/packages/flash-swap/contracts/test/GodModeHToken.sol @@ -21,4 +21,8 @@ contract GodModeHToken is HToken { function __godMode_setUnderlying(IErc20 newUnderlying) external { underlying = newUnderlying; } + + function __godMode_setMaturity(uint256 newMaturity) external { + maturity = newMaturity; + } } diff --git a/packages/flash-swap/contracts/uniswap-v2/IUnderlyingFlashUniswapV2.sol b/packages/flash-swap/contracts/uniswap-v2/IUnderlyingFlashUniswapV2.sol index 4678fa52..44cf73e7 100644 --- a/packages/flash-swap/contracts/uniswap-v2/IUnderlyingFlashUniswapV2.sol +++ b/packages/flash-swap/contracts/uniswap-v2/IUnderlyingFlashUniswapV2.sol @@ -54,8 +54,8 @@ interface IUnderlyingFlashUniswapV2 is IUniswapV2Callee { /// 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. + /// @dev See "getAmountIn" and "getAmountOut" in UniswapV2Library.sol. Flash swaps can be repaid via the + /// same borrowed pair token but the 0.3% LP fee still applies. /// @param underlyingAmount The amount of underlying flash borrowed. /// @return repayUnderlyingAmount The minimum amount of underlying that must be repaid. function getRepayUnderlyingAmount(uint256 underlyingAmount) external view returns (uint256 repayUnderlyingAmount); diff --git a/packages/flash-swap/contracts/uniswap-v2/UnderlyingFlashUniswapV2.sol b/packages/flash-swap/contracts/uniswap-v2/UnderlyingFlashUniswapV2.sol index c38530ea..9e0c81b7 100644 --- a/packages/flash-swap/contracts/uniswap-v2/UnderlyingFlashUniswapV2.sol +++ b/packages/flash-swap/contracts/uniswap-v2/UnderlyingFlashUniswapV2.sol @@ -107,6 +107,7 @@ contract UnderlyingFlashUniswapV2 is IUnderlyingFlashUniswapV2 { uint256 seizedUnderlyingAmount; uint256 shortfallUnderlyingAmount; address subsidizer; + uint256 surplusUnderlyingAmount; IErc20 underlying; uint256 underlyingAmount; } @@ -153,13 +154,19 @@ contract UnderlyingFlashUniswapV2 is IUnderlyingFlashUniswapV2 { // Calculate the amount of underlying required to repay. vars.repayUnderlyingAmount = getRepayUnderlyingAmount(vars.underlyingAmount); - // There is no incentive to liquidate underlying-backed vaults after the bond maturation. Thus the flash swap - // fee must be subsidized when the repay underlying amount is greater than the seized underlying amount. + // When the liquidation incentive is zero, there is no incentive to liquidate underlying-backed vaults post + // bond maturation. The flash swap fee must be subsidized when the repay underlying amount is greater than + // the seized underlying amount. if (vars.repayUnderlyingAmount > vars.seizedUnderlyingAmount) { unchecked { vars.shortfallUnderlyingAmount = vars.repayUnderlyingAmount - vars.seizedUnderlyingAmount; } vars.underlying.safeTransferFrom(vars.subsidizer, address(this), vars.shortfallUnderlyingAmount); + } else if (vars.seizedUnderlyingAmount > vars.repayUnderlyingAmount) { + unchecked { + vars.surplusUnderlyingAmount = vars.seizedUnderlyingAmount - vars.repayUnderlyingAmount; + } + vars.underlying.safeTransfer(sender, vars.shortfallUnderlyingAmount); } // Pay back the loan. diff --git a/packages/flash-swap/test/integration/collateralFlashUniswapV2/effects/uniswapV2Call.ts b/packages/flash-swap/test/integration/collateralFlashUniswapV2/effects/uniswapV2Call.ts index 4a88ff9e..4cc1e5c5 100644 --- a/packages/flash-swap/test/integration/collateralFlashUniswapV2/effects/uniswapV2Call.ts +++ b/packages/flash-swap/test/integration/collateralFlashUniswapV2/effects/uniswapV2Call.ts @@ -1,15 +1,15 @@ import { defaultAbiCoder } from "@ethersproject/abi"; import { BigNumber } from "@ethersproject/bignumber"; import { Zero } from "@ethersproject/constants"; +import { LIQUIDATION_INCENTIVES } from "@hifi/constants"; import { BalanceSheetErrors, CollateralFlashUniswapV2Errors } from "@hifi/errors"; import { USDC, WBTC, hUSDC, price } from "@hifi/helpers"; import { expect } from "chai"; -import { toBn } from "evm-bn"; import type { GodModeErc20 } from "../../../../src/types/GodModeErc20"; import { deployGodModeErc20 } from "../../../shared/deployers"; -async function bumpPoolReserves(this: Mocha.Context, wbtcAmount: BigNumber, usdcAmount: BigNumber): Promise { +async function increasePoolReserves(this: Mocha.Context, wbtcAmount: BigNumber, usdcAmount: BigNumber): Promise { // Mint WBTC to the pair contract. if (!wbtcAmount.isZero()) { await this.contracts.wbtc.__godMode_mint(this.contracts.uniswapV2Pair.address, wbtcAmount); @@ -34,18 +34,18 @@ function encodeCallData(this: Mocha.Context): string { async function getSeizableAndProfitCollateralAmounts( this: Mocha.Context, - repayHUsdcAmount: BigNumber, - underlyingAmount: BigNumber, + repayAmount: BigNumber, + swapUnderlyingAmount: BigNumber, ): Promise<{ expectedProfitWbtcAmount: BigNumber; seizableWbtcAmount: BigNumber }> { const seizableWbtcAmount = await this.contracts.balanceSheet.getSeizableCollateralAmount( this.contracts.hToken.address, - repayHUsdcAmount, + repayAmount, this.contracts.wbtc.address, ); const repayWbtcAmount = await this.contracts.collateralFlashUniswapV2.getRepayCollateralAmount( this.contracts.uniswapV2Pair.address, this.contracts.usdc.address, - underlyingAmount, + swapUnderlyingAmount, ); const expectedProfitWbtcAmount = seizableWbtcAmount.sub(repayWbtcAmount); return { expectedProfitWbtcAmount, seizableWbtcAmount }; @@ -136,7 +136,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { }); }); - context("when the caller is the pair contract", function () { + context("when the caller is the UniswapV2Pair contract", function () { beforeEach(async function () { // Set the oracle price to 1 WBTC = $20k. await this.contracts.wbtcPriceFeed.setPrice(price("20000")); @@ -145,10 +145,10 @@ export function shouldBehaveLikeUniswapV2Call(): void { await this.contracts.usdcPriceFeed.setPrice(price("1")); // Mint 100 WBTC and 2m USDC to the pair contract. This makes the price 1 WBTC ~ 20k USDC. - await bumpPoolReserves.call(this, WBTC("100"), USDC("2e6")); + await increasePoolReserves.call(this, WBTC("100"), USDC("2e6")); }); - context("when the underlying is not in the pair contract", function () { + context("when the underlying is not part of the UniswapV2Pair contract", function () { it("reverts", async function () { const { token0Amount, token1Amount } = await getTokenAmounts.call(this, Zero, USDC("10000")); const foo: GodModeErc20 = await deployGodModeErc20(this.signers.admin, "Foo", "FOO", BigNumber.from(18)); @@ -160,7 +160,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { }); }); - context("when the underlying is in the pair contract", function () { + context("when the underlying is part of the UniswapV2Pair contract", function () { context("when collateral is flash borrowed", function () { it("reverts", async function () { const { token0Amount, token1Amount } = await getTokenAmounts.call(this, WBTC("1"), Zero); @@ -173,18 +173,17 @@ export function shouldBehaveLikeUniswapV2Call(): void { context("when underlying is flash borrowed", function () { const borrowAmount: BigNumber = hUSDC("10000"); - const collateralAmount: BigNumber = Zero; const collateralCeiling: BigNumber = WBTC("100"); const debtCeiling: BigNumber = hUSDC("1e6"); - const liquidationIncentive: BigNumber = toBn("1.10"); - const underlyingAmount: BigNumber = USDC("10000"); + const swapCollateralAmount: BigNumber = Zero; + const swapUnderlyingAmount: BigNumber = USDC("10000"); const wbtcDepositAmount: BigNumber = WBTC("1"); let token0Amount: BigNumber; let token1Amount: BigNumber; beforeEach(async function () { - const tokenAmounts = await getTokenAmounts.call(this, collateralAmount, underlyingAmount); + const tokenAmounts = await getTokenAmounts.call(this, swapCollateralAmount, swapUnderlyingAmount); token0Amount = tokenAmounts.token0Amount; token1Amount = tokenAmounts.token1Amount; @@ -197,7 +196,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { // Set the liquidation incentive. await this.contracts.fintroller .connect(this.signers.admin) - .setLiquidationIncentive(this.contracts.wbtc.address, liquidationIncentive); + .setLiquidationIncentive(this.contracts.wbtc.address, LIQUIDATION_INCENTIVES.default); // Set the collateral ceiling. await this.contracts.fintroller @@ -238,44 +237,47 @@ export function shouldBehaveLikeUniswapV2Call(): void { }); context("when the borrower has a liquidity shortfall", function () { - context("when the price given by the pair contract price is better than the oracle price", function () { - beforeEach(async function () { - // Set the WBTC price to $12.5k to make borrower's collateral ratio 125%. - await this.contracts.wbtcPriceFeed.setPrice(price("12500")); + context( + "when the price given by the UniswapV2Pair contract price is better than the oracle price", + function () { + beforeEach(async function () { + // Set the WBTC price to $12.5k to make borrower's collateral ratio 125%. + await this.contracts.wbtcPriceFeed.setPrice(price("12500")); - // Burn 1m USDC from the pair contract. This makes the pair contract price 1 WBTC ~ 10k USDC. - await reducePoolReserves.call(this, Zero, USDC("1e6")); - }); + // Burn 1m USDC from the pair contract. This makes the pair contract price 1 WBTC ~ 10k USDC. + await reducePoolReserves.call(this, Zero, USDC("1e6")); + }); - it("reverts", async function () { - const to: string = this.contracts.collateralFlashUniswapV2.address; - await expect( - this.contracts.uniswapV2Pair - .connect(this.signers.liquidator) - .swap(token0Amount, token1Amount, to, data), - ).to.be.revertedWith(CollateralFlashUniswapV2Errors.InsufficientProfit); - }); - }); + it("reverts", async function () { + const to: string = this.contracts.collateralFlashUniswapV2.address; + await expect( + this.contracts.uniswapV2Pair + .connect(this.signers.liquidator) + .swap(token0Amount, token1Amount, to, data), + ).to.be.revertedWith(CollateralFlashUniswapV2Errors.InsufficientProfit); + }); + }, + ); - context("when the price given by the pair contract is the same as the oracle price", function () { + context("when the price given by the UniswapV2Pair contract is the same as the oracle price", function () { let expectedProfitWbtcAmount: BigNumber; let seizableWbtcAmount: BigNumber; context("when the collateral ratio is lower than 110%", function () { - const repayHUsdcAmount: BigNumber = hUSDC("9090.909090909090909090"); + const repayAmount: BigNumber = hUSDC("9090.909090909090909090"); beforeEach(async function () { // Set the WBTC price to $10k to make the borrower's collateral ratio 100%. await this.contracts.wbtcPriceFeed.setPrice(price("10000")); // Calculate the amounts necessary for running the tests. - const calculatesAmounts = await getSeizableAndProfitCollateralAmounts.call( + const calculatedAmounts = await getSeizableAndProfitCollateralAmounts.call( this, - repayHUsdcAmount, - underlyingAmount, + repayAmount, + swapUnderlyingAmount, ); - expectedProfitWbtcAmount = calculatesAmounts.expectedProfitWbtcAmount; - seizableWbtcAmount = calculatesAmounts.seizableWbtcAmount; + expectedProfitWbtcAmount = calculatedAmounts.expectedProfitWbtcAmount; + seizableWbtcAmount = calculatedAmounts.seizableWbtcAmount; }); it("flash swaps USDC via and makes a WBTC profit", async function () { @@ -290,7 +292,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { }); context("when the collateral ratio is lower than 150% but higher than 110%", function () { - const repayHUsdcAmount: BigNumber = hUSDC("10000"); + const repayAmount: BigNumber = hUSDC("10000"); beforeEach(async function () { // Set the WBTC price to $12.5k to make borrower's collateral ratio 125%. @@ -300,16 +302,16 @@ export function shouldBehaveLikeUniswapV2Call(): void { await reducePoolReserves.call(this, Zero, USDC("75e4")); // Calculate the amounts necessary for running the tests. - const calculatesAmounts = await getSeizableAndProfitCollateralAmounts.call( + const calculatedAmounts = await getSeizableAndProfitCollateralAmounts.call( this, - repayHUsdcAmount, - underlyingAmount, + repayAmount, + swapUnderlyingAmount, ); - expectedProfitWbtcAmount = calculatesAmounts.expectedProfitWbtcAmount; - seizableWbtcAmount = calculatesAmounts.seizableWbtcAmount; + expectedProfitWbtcAmount = calculatedAmounts.expectedProfitWbtcAmount; + seizableWbtcAmount = calculatedAmounts.seizableWbtcAmount; }); - context("new order of tokens in the pair", function () { + context("new order of tokens in the UniswapV2Pair contract", function () { let localToken0Amount: BigNumber; let localToken1Amount: BigNumber; @@ -318,13 +320,13 @@ export function shouldBehaveLikeUniswapV2Call(): void { if (token0 == this.contracts.wbtc.address) { await this.contracts.uniswapV2Pair.__godMode_setToken0(this.contracts.usdc.address); await this.contracts.uniswapV2Pair.__godMode_setToken1(this.contracts.wbtc.address); - localToken0Amount = underlyingAmount; - localToken1Amount = collateralAmount; + localToken0Amount = swapUnderlyingAmount; + localToken1Amount = swapCollateralAmount; } else { await this.contracts.uniswapV2Pair.__godMode_setToken0(this.contracts.wbtc.address); await this.contracts.uniswapV2Pair.__godMode_setToken1(this.contracts.usdc.address); - localToken0Amount = collateralAmount; - localToken1Amount = underlyingAmount; + localToken0Amount = swapCollateralAmount; + localToken1Amount = swapUnderlyingAmount; } await this.contracts.uniswapV2Pair.sync(); }); @@ -340,7 +342,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { }); }); - context("initial order of tokens in the pair", function () { + context("initial order of tokens in the UniswapV2Pair contract", function () { it("flash swaps USDC via and makes a WBTC profit", async function () { const to: string = this.contracts.collateralFlashUniswapV2.address; const preWbtcBalance = await this.contracts.wbtc.balanceOf(this.signers.liquidator.address); @@ -362,7 +364,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { this.signers.liquidator.address, this.signers.borrower.address, this.contracts.hToken.address, - underlyingAmount, + swapUnderlyingAmount, seizableWbtcAmount, expectedProfitWbtcAmount, ); diff --git a/packages/flash-swap/test/integration/underlyingFlashUniswapV2/effects/uniswapV2Call.ts b/packages/flash-swap/test/integration/underlyingFlashUniswapV2/effects/uniswapV2Call.ts index ca61d943..3cb60796 100644 --- a/packages/flash-swap/test/integration/underlyingFlashUniswapV2/effects/uniswapV2Call.ts +++ b/packages/flash-swap/test/integration/underlyingFlashUniswapV2/effects/uniswapV2Call.ts @@ -1,15 +1,15 @@ import { defaultAbiCoder } from "@ethersproject/abi"; import { BigNumber } from "@ethersproject/bignumber"; import { Zero } from "@ethersproject/constants"; -import { LIQUIDATION_INCENTIVES } from "@hifi/constants"; +import { COLLATERAL_RATIOS, LIQUIDATION_INCENTIVES } from "@hifi/constants"; import { BalanceSheetErrors, UnderlyingFlashUniswapV2Errors } from "@hifi/errors"; -import { USDC, WBTC, hUSDC, price } from "@hifi/helpers"; +import { USDC, WBTC, getNow, hUSDC, price } from "@hifi/helpers"; import { expect } from "chai"; import type { GodModeErc20 } from "../../../../src/types/GodModeErc20"; import { deployGodModeErc20 } from "../../../shared/deployers"; -async function bumpPoolReserves(this: Mocha.Context, wbtcAmount: BigNumber, usdcAmount: BigNumber): Promise { +async function increasePoolReserves(this: Mocha.Context, wbtcAmount: BigNumber, usdcAmount: BigNumber): Promise { // Mint WBTC to the pair contract. if (!wbtcAmount.isZero()) { await this.contracts.wbtc.__godMode_mint(this.contracts.uniswapV2Pair.address, wbtcAmount); @@ -31,14 +31,14 @@ function encodeCallData(this: Mocha.Context): string { return data; } -async function getSeizableAndRepayCollateralAmounts( +async function getSeizableAndRepayUnderlyingAmounts( this: Mocha.Context, - repayHUsdcAmount: BigNumber, + repayAmount: BigNumber, underlyingAmount: BigNumber, ): Promise<{ expectedRepayUsdcAmount: BigNumber; seizableUsdcAmount: BigNumber }> { const seizableUsdcAmount = await this.contracts.balanceSheet.getSeizableCollateralAmount( this.contracts.hToken.address, - repayHUsdcAmount, + repayAmount, this.contracts.usdc.address, ); const expectedRepayUsdcAmount = underlyingAmount.mul(1000).div(997).add(1); @@ -64,21 +64,6 @@ async function getTokenAmounts( } } -async function reducePoolReserves(this: Mocha.Context, wbtcAmount: BigNumber, usdcAmount: BigNumber): Promise { - // Mint WBTC to the pair contract. - if (!wbtcAmount.isZero()) { - await this.contracts.wbtc.__godMode_burn(this.contracts.uniswapV2Pair.address, wbtcAmount); - } - - // Mint USDC to the pair contract. - if (!usdcAmount.isZero()) { - await this.contracts.usdc.__godMode_burn(this.contracts.uniswapV2Pair.address, usdcAmount); - } - - // Sync the token reserves in the UniswapV2Pair contract. - await this.contracts.uniswapV2Pair.sync(); -} - export function shouldBehaveLikeUniswapV2Call(): void { context("when the data is malformed", function () { it("reverts", async function () { @@ -120,7 +105,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { }); }); - context("when the caller is a malicious pair", function () { + context("when the caller is a malicious pair contract", function () { it("reverts", async function () { const to: string = this.contracts.underlyingFlashUniswapV2.address; await expect( @@ -130,7 +115,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { }); }); - context("when the caller is the pair contract", function () { + context("when the caller is the UniswapV2Pair contract", function () { beforeEach(async function () { // Set the oracle price to 1 WBTC = $20k. await this.contracts.wbtcPriceFeed.setPrice(price("20000")); @@ -139,10 +124,10 @@ export function shouldBehaveLikeUniswapV2Call(): void { await this.contracts.usdcPriceFeed.setPrice(price("1")); // Mint 100 WBTC and 2m USDC to the pair contract. This makes the price 1 WBTC ~ 20k USDC. - await bumpPoolReserves.call(this, WBTC("100"), USDC("2e6")); + await increasePoolReserves.call(this, WBTC("100"), USDC("2e6")); }); - context("when the underlying is not in the pair contract", function () { + context("when the underlying is not part of the UniswapV2Pair contract", function () { it("reverts", async function () { const { token0Amount, token1Amount } = await getTokenAmounts.call(this, Zero, USDC("10000")); const foo: GodModeErc20 = await deployGodModeErc20(this.signers.admin, "Foo", "FOO", BigNumber.from(18)); @@ -154,7 +139,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { }); }); - context("when the underlying is in the pair contract", function () { + context("when the underlying is part of the UniswapV2Pair contract", function () { context("when the other token is flash borrowed", function () { it("reverts", async function () { const { token0Amount, token1Amount } = await getTokenAmounts.call(this, WBTC("1"), Zero); @@ -167,11 +152,9 @@ export function shouldBehaveLikeUniswapV2Call(): void { context("when underlying is flash borrowed", function () { const borrowAmount: BigNumber = hUSDC("10000"); - const collateralCeilingUsdc: BigNumber = USDC("1000000"); - const collateralCeilingWbtc: BigNumber = WBTC("50"); + const collateralCeiling: BigNumber = USDC("1e6"); const debtCeiling: BigNumber = hUSDC("1e6"); const depositUsdcAmount: BigNumber = USDC("10000"); - const depositWbtcAmount: BigNumber = WBTC("0.5"); const feeUsdcAmount: BigNumber = USDC("30.090271"); const swapUsdcAmount: BigNumber = USDC("10000"); const swapWbtcAmount: BigNumber = Zero; @@ -187,27 +170,23 @@ export function shouldBehaveLikeUniswapV2Call(): void { // List the bond in the Fintroller. await this.contracts.fintroller.connect(this.signers.admin).listBond(this.contracts.hToken.address); - // List the collaterals in the Fintroller. + // List the collateral in the Fintroller. await this.contracts.fintroller.connect(this.signers.admin).listCollateral(this.contracts.usdc.address); - await this.contracts.fintroller.connect(this.signers.admin).listCollateral(this.contracts.wbtc.address); - // Set the liquidation incentive for USDC to 100%. + // Set the collateral ratio to 100%. await this.contracts.fintroller .connect(this.signers.admin) - .setLiquidationIncentive(this.contracts.usdc.address, LIQUIDATION_INCENTIVES.lowerBound); + .setCollateralRatio(this.contracts.usdc.address, COLLATERAL_RATIOS.lowerBound); - // Set the liquidation incentive for WBTC to 110%. + // Set the liquidation incentive for USDC to 0. await this.contracts.fintroller .connect(this.signers.admin) - .setLiquidationIncentive(this.contracts.wbtc.address, LIQUIDATION_INCENTIVES.default); + .setLiquidationIncentive(this.contracts.usdc.address, LIQUIDATION_INCENTIVES.lowerBound); - // Set the collateral ceilings. - await this.contracts.fintroller - .connect(this.signers.admin) - .setCollateralCeiling(this.contracts.usdc.address, collateralCeilingUsdc); + // Set the collateral ceiling. await this.contracts.fintroller .connect(this.signers.admin) - .setCollateralCeiling(this.contracts.wbtc.address, collateralCeilingWbtc); + .setCollateralCeiling(this.contracts.usdc.address, collateralCeiling); // Set the debt ceiling. await this.contracts.fintroller @@ -220,36 +199,26 @@ export function shouldBehaveLikeUniswapV2Call(): void { .connect(this.signers.borrower) .approve(this.contracts.balanceSheet.address, depositUsdcAmount); - // Mint WBTC and approve the BalanceSheet to spend it. - await this.contracts.wbtc.__godMode_mint(this.signers.borrower.address, depositWbtcAmount); - await this.contracts.wbtc - .connect(this.signers.borrower) - .approve(this.contracts.balanceSheet.address, depositWbtcAmount); - // Mint USDC to the subsidizer wallet and approve the flash swap contract to spend it. await this.contracts.usdc.__godMode_mint(this.signers.subsidizer.address, feeUsdcAmount); await this.contracts.usdc .connect(this.signers.subsidizer) .approve(this.contracts.underlyingFlashUniswapV2.address, feeUsdcAmount); - // Deposit the USDC in the BalanceSheet. + // Deposit USDC in the BalanceSheet. await this.contracts.balanceSheet .connect(this.signers.borrower) .depositCollateral(this.contracts.usdc.address, depositUsdcAmount); - // Deposit the WBTC in the BalanceSheet. - await this.contracts.balanceSheet - .connect(this.signers.borrower) - .depositCollateral(this.contracts.wbtc.address, depositWbtcAmount); - // Borrow hUSDC. await this.contracts.balanceSheet .connect(this.signers.borrower) .borrow(this.contracts.hToken.address, borrowAmount); }); - context("when the borrower does not have a liquidity shortfall", function () { + context("when the bond did not mature", function () { it("reverts", async function () { + const { token0Amount, token1Amount } = await getTokenAmounts.call(this, Zero, USDC("10000")); const to: string = this.contracts.underlyingFlashUniswapV2.address; await expect( this.contracts.uniswapV2Pair @@ -259,148 +228,95 @@ export function shouldBehaveLikeUniswapV2Call(): void { }); }); - context("when the price given by the pair contract price is better than the oracle price", function () { + context("when the bond matured", function () { beforeEach(async function () { - // Set the WBTC price to $5k to make borrower's collateral ratio 125%. - await this.contracts.wbtcPriceFeed.setPrice(price("5000")); - - // Burn 1.75m USDC from the pair contract. This makes the pair contract price 1 WBTC ~ 2.5k USDC. - await reducePoolReserves.call(this, Zero, USDC("1.75e6")); + const oneHourAgo: BigNumber = getNow().sub(3600); + await this.contracts.hToken.connect(this.signers.admin).__godMode_setMaturity(oneHourAgo); }); - it("flash swaps USDC making no USDC profit and spending allocated USDC to pay swap fee", async function () { - const to: string = this.contracts.underlyingFlashUniswapV2.address; - const oldUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); - const oldUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.subsidizer.address); - await this.contracts.uniswapV2Pair - .connect(this.signers.liquidator) - .swap(token0Amount, token1Amount, to, data); - const newUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); - const newUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.subsidizer.address); - expect(newUsdcBalanceAccount).to.equal(oldUsdcBalanceAccount); - expect(oldUsdcBalanceBot.sub(newUsdcBalanceBot)).to.equal(feeUsdcAmount); - }); - }); + // context("when the repay underlying amount is less than the seized underlying amount", function () {}); - context("when the borrower has a liquidity shortfall", function () { - context("when the price given by the pair contract is the same as the oracle price", function () { - let seizableUsdcAmount: BigNumber; - let expectedRepayUsdcAmount: BigNumber; + // context("when the repay underlying amount is equal to the seized underlying amount", function () {}); - context("when the collateral ratio is lower than 110%", function () { - const repayHUsdcAmount: BigNumber = hUSDC("9090.909090909090909090"); + context("when the repay underlying amount is greater than the seized underlying amount", function () { + context("new order of tokens in the UniswapV2Pair contract", function () { + let localToken0Amount: BigNumber; + let localToken1Amount: BigNumber; beforeEach(async function () { - // Set the WBTC price to $10k to make the borrower's collateral ratio 100%. - await this.contracts.wbtcPriceFeed.setPrice(price("10000")); - - // Calculate the amounts necessary for running the tests. - const calculatesAmounts = await getSeizableAndRepayCollateralAmounts.call( - this, - repayHUsdcAmount, - swapUsdcAmount, - ); - seizableUsdcAmount = calculatesAmounts.seizableUsdcAmount; + const token0: string = await this.contracts.uniswapV2Pair.token0(); + if (token0 == this.contracts.wbtc.address) { + await this.contracts.uniswapV2Pair.__godMode_setToken0(this.contracts.usdc.address); + await this.contracts.uniswapV2Pair.__godMode_setToken1(this.contracts.wbtc.address); + localToken0Amount = swapUsdcAmount; + localToken1Amount = swapWbtcAmount; + } else { + await this.contracts.uniswapV2Pair.__godMode_setToken0(this.contracts.wbtc.address); + await this.contracts.uniswapV2Pair.__godMode_setToken1(this.contracts.usdc.address); + localToken0Amount = swapWbtcAmount; + localToken1Amount = swapUsdcAmount; + } + await this.contracts.uniswapV2Pair.sync(); }); - it("flash swaps USDC making no USDC profit and spending allocated USDC to pay swap fee", async function () { + it("flash swaps USDC making no USDC profit and paying the flash swap fee", async function () { const to: string = this.contracts.underlyingFlashUniswapV2.address; - const oldUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); - const oldUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.subsidizer.address); + const oldLiquidatorUsdcBalance = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); + const oldSubsidizerUsdcBalance = await this.contracts.usdc.balanceOf(this.signers.subsidizer.address); await this.contracts.uniswapV2Pair .connect(this.signers.liquidator) - .swap(token0Amount, token1Amount, to, data); - const newUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); - const newUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.subsidizer.address); - expect(newUsdcBalanceAccount).to.equal(oldUsdcBalanceAccount); - expect(oldUsdcBalanceBot.sub(newUsdcBalanceBot)).to.equal(feeUsdcAmount); + .swap(localToken0Amount, localToken1Amount, to, data); + const newLiquidatorUsdcBalance = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); + const newSubsidizerUsdcBalance = await this.contracts.usdc.balanceOf(this.signers.subsidizer.address); + expect(newLiquidatorUsdcBalance).to.equal(oldLiquidatorUsdcBalance); + expect(oldSubsidizerUsdcBalance.sub(newSubsidizerUsdcBalance)).to.equal(feeUsdcAmount); }); }); - context("when the collateral ratio is lower than 150% but higher than 110%", function () { - const repayHUsdcAmount: BigNumber = hUSDC("10000"); + context("initial order of tokens in the UniswapV2Pair contract", function () { + let seizableUsdcAmount: BigNumber; + let expectedRepayUsdcAmount: BigNumber; + const repayAmount: BigNumber = hUSDC("10000"); beforeEach(async function () { - // Set the WBTC price to $5k to make borrower's collateral ratio 125%. - await this.contracts.wbtcPriceFeed.setPrice(price("5000")); - - // Burn 1.5m USDC from the pair contract, which makes the price 1 WBTC ~ 5k USDC. - await reducePoolReserves.call(this, Zero, USDC("1.5e6")); - // Calculate the amounts necessary for running the tests. - const calculatesAmounts = await getSeizableAndRepayCollateralAmounts.call( + const calculatesAmounts = await getSeizableAndRepayUnderlyingAmounts.call( this, - repayHUsdcAmount, + repayAmount, swapUsdcAmount, ); seizableUsdcAmount = calculatesAmounts.seizableUsdcAmount; expectedRepayUsdcAmount = calculatesAmounts.expectedRepayUsdcAmount; }); - context("new order of tokens in the pair", function () { - let localToken0Amount: BigNumber; - let localToken1Amount: BigNumber; - - beforeEach(async function () { - const token0: string = await this.contracts.uniswapV2Pair.token0(); - if (token0 == this.contracts.wbtc.address) { - await this.contracts.uniswapV2Pair.__godMode_setToken0(this.contracts.usdc.address); - await this.contracts.uniswapV2Pair.__godMode_setToken1(this.contracts.wbtc.address); - localToken0Amount = swapUsdcAmount; - localToken1Amount = swapWbtcAmount; - } else { - await this.contracts.uniswapV2Pair.__godMode_setToken0(this.contracts.wbtc.address); - await this.contracts.uniswapV2Pair.__godMode_setToken1(this.contracts.usdc.address); - localToken0Amount = swapWbtcAmount; - localToken1Amount = swapUsdcAmount; - } - await this.contracts.uniswapV2Pair.sync(); - }); - - it("flash swaps USDC making no USDC profit and spending allocated USDC to pay swap fee", async function () { - const to: string = this.contracts.underlyingFlashUniswapV2.address; - const oldUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); - const oldUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.subsidizer.address); - await this.contracts.uniswapV2Pair - .connect(this.signers.liquidator) - .swap(localToken0Amount, localToken1Amount, to, data); - const newUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); - const newUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.subsidizer.address); - expect(newUsdcBalanceAccount).to.equal(oldUsdcBalanceAccount); - expect(oldUsdcBalanceBot.sub(newUsdcBalanceBot)).to.equal(feeUsdcAmount); - }); + it("flash swaps USDC making no USDC profit and paying the flash swap fee", async function () { + const to: string = this.contracts.underlyingFlashUniswapV2.address; + const oldLiquidatorUsdcBalance = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); + const oldSubsidizerUsdcBalance = await this.contracts.usdc.balanceOf(this.signers.subsidizer.address); + await this.contracts.uniswapV2Pair + .connect(this.signers.liquidator) + .swap(token0Amount, token1Amount, to, data); + const newLiquidatorUsdcBalance = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); + const newSubsidizerUsdcBalance = await this.contracts.usdc.balanceOf(this.signers.subsidizer.address); + expect(newLiquidatorUsdcBalance).to.equal(oldLiquidatorUsdcBalance); + expect(oldSubsidizerUsdcBalance.sub(newSubsidizerUsdcBalance)).to.equal(feeUsdcAmount); }); - context("initial order of tokens in the pair", function () { - it("flash swaps USDC making no USDC profit and spending allocated USDC to pay swap fee", async function () { - const to: string = this.contracts.underlyingFlashUniswapV2.address; - const oldUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); - const oldUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.subsidizer.address); - await this.contracts.uniswapV2Pair - .connect(this.signers.liquidator) - .swap(token0Amount, token1Amount, to, data); - const newUsdcBalanceAccount = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); - const newUsdcBalanceBot = await this.contracts.usdc.balanceOf(this.signers.subsidizer.address); - expect(newUsdcBalanceAccount).to.equal(oldUsdcBalanceAccount); - expect(oldUsdcBalanceBot.sub(newUsdcBalanceBot)).to.equal(feeUsdcAmount); - }); - - it("emits a FlashSwapUnderlyingAndLiquidateBorrow event", async function () { - const to: string = this.contracts.underlyingFlashUniswapV2.address; - const contractCall = this.contracts.uniswapV2Pair - .connect(this.signers.liquidator) - .swap(token0Amount, token1Amount, to, data); - await expect(contractCall) - .to.emit(this.contracts.underlyingFlashUniswapV2, "FlashSwapUnderlyingAndLiquidateBorrow") - .withArgs( - this.signers.liquidator.address, - this.signers.borrower.address, - this.contracts.hToken.address, - swapUsdcAmount, - seizableUsdcAmount, - expectedRepayUsdcAmount, - ); - }); + it("emits a FlashSwapUnderlyingAndLiquidateBorrow event", async function () { + const to: string = this.contracts.underlyingFlashUniswapV2.address; + const contractCall = this.contracts.uniswapV2Pair + .connect(this.signers.liquidator) + .swap(token0Amount, token1Amount, to, data); + await expect(contractCall) + .to.emit(this.contracts.underlyingFlashUniswapV2, "FlashSwapUnderlyingAndLiquidateBorrow") + .withArgs( + this.signers.liquidator.address, + this.signers.borrower.address, + this.contracts.hToken.address, + swapUsdcAmount, + seizableUsdcAmount, + expectedRepayUsdcAmount, + ); }); }); }); From d86fc14bd289a96414b1f437a44b9f124063a9bd Mon Sep 17 00:00:00 2001 From: Paul Razvan Berg Date: Fri, 5 Nov 2021 13:54:55 +0200 Subject: [PATCH 14/16] fix(flash-swap): transfer profit instead of subsidized amount docs(flash-swap): rewrite the @notice for "liquidateBorrowInternal" function refactor(flash-swap): emit "profitUnderlyingAmount" and "subsidizedUnderlyingAmont" in event refactor(flash-swap): rename "shortfallUnderlyingAmount" to "subsidizedUnderlyingAmount" refactor(flash-swap): rename "surplusUnderlyingAmount" to "profitUnderlyingAmount" test(flash-swap): erc-20 approve to "MaxUint256" test(flash-swap): refer to USDC as "underlying" in variable names test(flash-swap): rename "repayAmount" to "repayHTokenAmount" test(flash-swap): when the repay underlying amount is less than the seized underlying amount --- .../contracts/uniswap-v2/FlashUtils.sol | 7 +- .../uniswap-v2/IUnderlyingFlashUniswapV2.sol | 4 +- .../uniswap-v2/UnderlyingFlashUniswapV2.sol | 16 +- .../src/types/IUnderlyingFlashUniswapV2.d.ts | 51 ++++++- .../src/types/UnderlyingFlashUniswapV2.d.ts | 51 ++++++- .../effects/uniswapV2Call.ts | 60 ++++---- .../effects/uniswapV2Call.ts | 141 ++++++++++++------ 7 files changed, 227 insertions(+), 103 deletions(-) diff --git a/packages/flash-swap/contracts/uniswap-v2/FlashUtils.sol b/packages/flash-swap/contracts/uniswap-v2/FlashUtils.sol index 5e5c1208..3cbb8c9f 100644 --- a/packages/flash-swap/contracts/uniswap-v2/FlashUtils.sol +++ b/packages/flash-swap/contracts/uniswap-v2/FlashUtils.sol @@ -32,8 +32,7 @@ library FlashUtils { ); } - /// @dev Liquidates the borrower by transferring the underlying to the BalanceSheet. By doing this, the - /// liquidator receives collateral at a discount. + /// @dev Liquidates the borrower, receiving collateral at a discount. function liquidateBorrowInternal( IBalanceSheetV1 balanceSheet, address borrower, @@ -46,8 +45,8 @@ library FlashUtils { // If the hypothetical repay amount is bigger than the debt amount, this could be a single-collateral multi-bond // vault. Otherwise, it could be a multi-collateral single-bond vault. However, it is difficult to generalize - // for the multi-collateral and multi-bond situation. The repay amount could be either bigger, smaller, or even - // equal to the debt amount depending on the collateral and debt amount distribution. + // for the multi-collateral and multi-bond situation. The repay amount could be greater, smaller, or equal + // to the debt amount depending on the collateral and debt amount distribution. uint256 debtAmount = balanceSheet.getDebtAmount(borrower, bond); uint256 repayAmount = hypotheticalRepayAmount > debtAmount ? debtAmount : hypotheticalRepayAmount; diff --git a/packages/flash-swap/contracts/uniswap-v2/IUnderlyingFlashUniswapV2.sol b/packages/flash-swap/contracts/uniswap-v2/IUnderlyingFlashUniswapV2.sol index 44cf73e7..ef5f57e6 100644 --- a/packages/flash-swap/contracts/uniswap-v2/IUnderlyingFlashUniswapV2.sol +++ b/packages/flash-swap/contracts/uniswap-v2/IUnderlyingFlashUniswapV2.sol @@ -19,7 +19,9 @@ interface IUnderlyingFlashUniswapV2 is IUniswapV2Callee { address indexed bond, uint256 underlyingAmount, uint256 seizedUnderlyingAmount, - uint256 repayUnderlyingAmount + uint256 repayUnderlyingAmount, + uint256 profitUnderlyingAmount, + uint256 subsidizedUnderlyingAmount ); /// CONSTANT FUNCTIONS /// diff --git a/packages/flash-swap/contracts/uniswap-v2/UnderlyingFlashUniswapV2.sol b/packages/flash-swap/contracts/uniswap-v2/UnderlyingFlashUniswapV2.sol index 9e0c81b7..5334cf1f 100644 --- a/packages/flash-swap/contracts/uniswap-v2/UnderlyingFlashUniswapV2.sol +++ b/packages/flash-swap/contracts/uniswap-v2/UnderlyingFlashUniswapV2.sol @@ -103,11 +103,11 @@ contract UnderlyingFlashUniswapV2 is IUnderlyingFlashUniswapV2 { address borrower; uint256 mintedHTokenAmount; IErc20 otherToken; + uint256 profitUnderlyingAmount; uint256 repayUnderlyingAmount; uint256 seizedUnderlyingAmount; - uint256 shortfallUnderlyingAmount; + uint256 subsidizedUnderlyingAmount; address subsidizer; - uint256 surplusUnderlyingAmount; IErc20 underlying; uint256 underlyingAmount; } @@ -159,14 +159,14 @@ contract UnderlyingFlashUniswapV2 is IUnderlyingFlashUniswapV2 { // the seized underlying amount. if (vars.repayUnderlyingAmount > vars.seizedUnderlyingAmount) { unchecked { - vars.shortfallUnderlyingAmount = vars.repayUnderlyingAmount - vars.seizedUnderlyingAmount; + vars.subsidizedUnderlyingAmount = vars.repayUnderlyingAmount - vars.seizedUnderlyingAmount; } - vars.underlying.safeTransferFrom(vars.subsidizer, address(this), vars.shortfallUnderlyingAmount); + vars.underlying.safeTransferFrom(vars.subsidizer, address(this), vars.subsidizedUnderlyingAmount); } else if (vars.seizedUnderlyingAmount > vars.repayUnderlyingAmount) { unchecked { - vars.surplusUnderlyingAmount = vars.seizedUnderlyingAmount - vars.repayUnderlyingAmount; + vars.profitUnderlyingAmount = vars.seizedUnderlyingAmount - vars.repayUnderlyingAmount; } - vars.underlying.safeTransfer(sender, vars.shortfallUnderlyingAmount); + vars.underlying.safeTransfer(sender, vars.profitUnderlyingAmount); } // Pay back the loan. @@ -179,7 +179,9 @@ contract UnderlyingFlashUniswapV2 is IUnderlyingFlashUniswapV2 { address(vars.bond), vars.underlyingAmount, vars.seizedUnderlyingAmount, - vars.repayUnderlyingAmount + vars.repayUnderlyingAmount, + vars.subsidizedUnderlyingAmount, + vars.profitUnderlyingAmount ); } } diff --git a/packages/flash-swap/src/types/IUnderlyingFlashUniswapV2.d.ts b/packages/flash-swap/src/types/IUnderlyingFlashUniswapV2.d.ts index 041be602..e8e207de 100644 --- a/packages/flash-swap/src/types/IUnderlyingFlashUniswapV2.d.ts +++ b/packages/flash-swap/src/types/IUnderlyingFlashUniswapV2.d.ts @@ -80,7 +80,7 @@ interface IUnderlyingFlashUniswapV2Interface extends ethers.utils.Interface { ): Result; events: { - "FlashSwapUnderlyingAndLiquidateBorrow(address,address,address,uint256,uint256,uint256)": EventFragment; + "FlashSwapUnderlyingAndLiquidateBorrow(address,address,address,uint256,uint256,uint256,uint256,uint256)": EventFragment; }; getEvent( @@ -89,13 +89,24 @@ interface IUnderlyingFlashUniswapV2Interface extends ethers.utils.Interface { } export type FlashSwapUnderlyingAndLiquidateBorrowEvent = TypedEvent< - [string, string, string, BigNumber, BigNumber, BigNumber] & { + [ + string, + string, + string, + BigNumber, + BigNumber, + BigNumber, + BigNumber, + BigNumber + ] & { liquidator: string; borrower: string; bond: string; underlyingAmount: BigNumber; seizedUnderlyingAmount: BigNumber; repayUnderlyingAmount: BigNumber; + profitUnderlyingAmount: BigNumber; + subsidizedUnderlyingAmount: BigNumber; } >; @@ -234,15 +245,26 @@ export class IUnderlyingFlashUniswapV2 extends BaseContract { }; filters: { - "FlashSwapUnderlyingAndLiquidateBorrow(address,address,address,uint256,uint256,uint256)"( + "FlashSwapUnderlyingAndLiquidateBorrow(address,address,address,uint256,uint256,uint256,uint256,uint256)"( liquidator?: string | null, borrower?: string | null, bond?: string | null, underlyingAmount?: null, seizedUnderlyingAmount?: null, - repayUnderlyingAmount?: null + repayUnderlyingAmount?: null, + profitUnderlyingAmount?: null, + subsidizedUnderlyingAmount?: null ): TypedEventFilter< - [string, string, string, BigNumber, BigNumber, BigNumber], + [ + string, + string, + string, + BigNumber, + BigNumber, + BigNumber, + BigNumber, + BigNumber + ], { liquidator: string; borrower: string; @@ -250,6 +272,8 @@ export class IUnderlyingFlashUniswapV2 extends BaseContract { underlyingAmount: BigNumber; seizedUnderlyingAmount: BigNumber; repayUnderlyingAmount: BigNumber; + profitUnderlyingAmount: BigNumber; + subsidizedUnderlyingAmount: BigNumber; } >; @@ -259,9 +283,20 @@ export class IUnderlyingFlashUniswapV2 extends BaseContract { bond?: string | null, underlyingAmount?: null, seizedUnderlyingAmount?: null, - repayUnderlyingAmount?: null + repayUnderlyingAmount?: null, + profitUnderlyingAmount?: null, + subsidizedUnderlyingAmount?: null ): TypedEventFilter< - [string, string, string, BigNumber, BigNumber, BigNumber], + [ + string, + string, + string, + BigNumber, + BigNumber, + BigNumber, + BigNumber, + BigNumber + ], { liquidator: string; borrower: string; @@ -269,6 +304,8 @@ export class IUnderlyingFlashUniswapV2 extends BaseContract { underlyingAmount: BigNumber; seizedUnderlyingAmount: BigNumber; repayUnderlyingAmount: BigNumber; + profitUnderlyingAmount: BigNumber; + subsidizedUnderlyingAmount: BigNumber; } >; }; diff --git a/packages/flash-swap/src/types/UnderlyingFlashUniswapV2.d.ts b/packages/flash-swap/src/types/UnderlyingFlashUniswapV2.d.ts index 371a649e..8a0494aa 100644 --- a/packages/flash-swap/src/types/UnderlyingFlashUniswapV2.d.ts +++ b/packages/flash-swap/src/types/UnderlyingFlashUniswapV2.d.ts @@ -80,7 +80,7 @@ interface UnderlyingFlashUniswapV2Interface extends ethers.utils.Interface { ): Result; events: { - "FlashSwapUnderlyingAndLiquidateBorrow(address,address,address,uint256,uint256,uint256)": EventFragment; + "FlashSwapUnderlyingAndLiquidateBorrow(address,address,address,uint256,uint256,uint256,uint256,uint256)": EventFragment; }; getEvent( @@ -89,13 +89,24 @@ interface UnderlyingFlashUniswapV2Interface extends ethers.utils.Interface { } export type FlashSwapUnderlyingAndLiquidateBorrowEvent = TypedEvent< - [string, string, string, BigNumber, BigNumber, BigNumber] & { + [ + string, + string, + string, + BigNumber, + BigNumber, + BigNumber, + BigNumber, + BigNumber + ] & { liquidator: string; borrower: string; bond: string; underlyingAmount: BigNumber; seizedUnderlyingAmount: BigNumber; repayUnderlyingAmount: BigNumber; + profitUnderlyingAmount: BigNumber; + subsidizedUnderlyingAmount: BigNumber; } >; @@ -234,15 +245,26 @@ export class UnderlyingFlashUniswapV2 extends BaseContract { }; filters: { - "FlashSwapUnderlyingAndLiquidateBorrow(address,address,address,uint256,uint256,uint256)"( + "FlashSwapUnderlyingAndLiquidateBorrow(address,address,address,uint256,uint256,uint256,uint256,uint256)"( liquidator?: string | null, borrower?: string | null, bond?: string | null, underlyingAmount?: null, seizedUnderlyingAmount?: null, - repayUnderlyingAmount?: null + repayUnderlyingAmount?: null, + profitUnderlyingAmount?: null, + subsidizedUnderlyingAmount?: null ): TypedEventFilter< - [string, string, string, BigNumber, BigNumber, BigNumber], + [ + string, + string, + string, + BigNumber, + BigNumber, + BigNumber, + BigNumber, + BigNumber + ], { liquidator: string; borrower: string; @@ -250,6 +272,8 @@ export class UnderlyingFlashUniswapV2 extends BaseContract { underlyingAmount: BigNumber; seizedUnderlyingAmount: BigNumber; repayUnderlyingAmount: BigNumber; + profitUnderlyingAmount: BigNumber; + subsidizedUnderlyingAmount: BigNumber; } >; @@ -259,9 +283,20 @@ export class UnderlyingFlashUniswapV2 extends BaseContract { bond?: string | null, underlyingAmount?: null, seizedUnderlyingAmount?: null, - repayUnderlyingAmount?: null + repayUnderlyingAmount?: null, + profitUnderlyingAmount?: null, + subsidizedUnderlyingAmount?: null ): TypedEventFilter< - [string, string, string, BigNumber, BigNumber, BigNumber], + [ + string, + string, + string, + BigNumber, + BigNumber, + BigNumber, + BigNumber, + BigNumber + ], { liquidator: string; borrower: string; @@ -269,6 +304,8 @@ export class UnderlyingFlashUniswapV2 extends BaseContract { underlyingAmount: BigNumber; seizedUnderlyingAmount: BigNumber; repayUnderlyingAmount: BigNumber; + profitUnderlyingAmount: BigNumber; + subsidizedUnderlyingAmount: BigNumber; } >; }; diff --git a/packages/flash-swap/test/integration/collateralFlashUniswapV2/effects/uniswapV2Call.ts b/packages/flash-swap/test/integration/collateralFlashUniswapV2/effects/uniswapV2Call.ts index 4cc1e5c5..2527e5e1 100644 --- a/packages/flash-swap/test/integration/collateralFlashUniswapV2/effects/uniswapV2Call.ts +++ b/packages/flash-swap/test/integration/collateralFlashUniswapV2/effects/uniswapV2Call.ts @@ -1,6 +1,6 @@ import { defaultAbiCoder } from "@ethersproject/abi"; import { BigNumber } from "@ethersproject/bignumber"; -import { Zero } from "@ethersproject/constants"; +import { MaxUint256, Zero } from "@ethersproject/constants"; import { LIQUIDATION_INCENTIVES } from "@hifi/constants"; import { BalanceSheetErrors, CollateralFlashUniswapV2Errors } from "@hifi/errors"; import { USDC, WBTC, hUSDC, price } from "@hifi/helpers"; @@ -34,12 +34,12 @@ function encodeCallData(this: Mocha.Context): string { async function getSeizableAndProfitCollateralAmounts( this: Mocha.Context, - repayAmount: BigNumber, + repayHTokenAmount: BigNumber, swapUnderlyingAmount: BigNumber, -): Promise<{ expectedProfitWbtcAmount: BigNumber; seizableWbtcAmount: BigNumber }> { - const seizableWbtcAmount = await this.contracts.balanceSheet.getSeizableCollateralAmount( +): Promise<{ expectedProfitCollateralAmount: BigNumber; seizableCollateralAmount: BigNumber }> { + const seizableCollateralAmount = await this.contracts.balanceSheet.getSeizableCollateralAmount( this.contracts.hToken.address, - repayAmount, + repayHTokenAmount, this.contracts.wbtc.address, ); const repayWbtcAmount = await this.contracts.collateralFlashUniswapV2.getRepayCollateralAmount( @@ -47,8 +47,8 @@ async function getSeizableAndProfitCollateralAmounts( this.contracts.usdc.address, swapUnderlyingAmount, ); - const expectedProfitWbtcAmount = seizableWbtcAmount.sub(repayWbtcAmount); - return { expectedProfitWbtcAmount, seizableWbtcAmount }; + const expectedProfitCollateralAmount = seizableCollateralAmount.sub(repayWbtcAmount); + return { expectedProfitCollateralAmount, seizableCollateralAmount }; } async function getTokenAmounts( @@ -175,9 +175,9 @@ export function shouldBehaveLikeUniswapV2Call(): void { const borrowAmount: BigNumber = hUSDC("10000"); const collateralCeiling: BigNumber = WBTC("100"); const debtCeiling: BigNumber = hUSDC("1e6"); + const depositWbtcAmount: BigNumber = WBTC("1"); const swapCollateralAmount: BigNumber = Zero; const swapUnderlyingAmount: BigNumber = USDC("10000"); - const wbtcDepositAmount: BigNumber = WBTC("1"); let token0Amount: BigNumber; let token1Amount: BigNumber; @@ -193,7 +193,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { // List the collateral in the Fintroller. await this.contracts.fintroller.connect(this.signers.admin).listCollateral(this.contracts.wbtc.address); - // Set the liquidation incentive. + // Set the liquidation incentive to 10%. await this.contracts.fintroller .connect(this.signers.admin) .setLiquidationIncentive(this.contracts.wbtc.address, LIQUIDATION_INCENTIVES.default); @@ -209,15 +209,15 @@ export function shouldBehaveLikeUniswapV2Call(): void { .setDebtCeiling(this.contracts.hToken.address, debtCeiling); // Mint WBTC and approve the BalanceSheet to spend it. - await this.contracts.wbtc.__godMode_mint(this.signers.borrower.address, wbtcDepositAmount); + await this.contracts.wbtc.__godMode_mint(this.signers.borrower.address, depositWbtcAmount); await this.contracts.wbtc .connect(this.signers.borrower) - .approve(this.contracts.balanceSheet.address, wbtcDepositAmount); + .approve(this.contracts.balanceSheet.address, MaxUint256); // Deposit the WBTC in the BalanceSheet. await this.contracts.balanceSheet .connect(this.signers.borrower) - .depositCollateral(this.contracts.wbtc.address, wbtcDepositAmount); + .depositCollateral(this.contracts.wbtc.address, depositWbtcAmount); // Borrow hUSDC. await this.contracts.balanceSheet @@ -260,11 +260,11 @@ export function shouldBehaveLikeUniswapV2Call(): void { ); context("when the price given by the UniswapV2Pair contract is the same as the oracle price", function () { - let expectedProfitWbtcAmount: BigNumber; - let seizableWbtcAmount: BigNumber; + let expectedProfitCollateralAmount: BigNumber; + let seizableCollateralAmount: BigNumber; context("when the collateral ratio is lower than 110%", function () { - const repayAmount: BigNumber = hUSDC("9090.909090909090909090"); + const repayHTokenAmount: BigNumber = hUSDC("9090.909090909090909090"); beforeEach(async function () { // Set the WBTC price to $10k to make the borrower's collateral ratio 100%. @@ -273,11 +273,11 @@ export function shouldBehaveLikeUniswapV2Call(): void { // Calculate the amounts necessary for running the tests. const calculatedAmounts = await getSeizableAndProfitCollateralAmounts.call( this, - repayAmount, + repayHTokenAmount, swapUnderlyingAmount, ); - expectedProfitWbtcAmount = calculatedAmounts.expectedProfitWbtcAmount; - seizableWbtcAmount = calculatedAmounts.seizableWbtcAmount; + expectedProfitCollateralAmount = calculatedAmounts.expectedProfitCollateralAmount; + seizableCollateralAmount = calculatedAmounts.seizableCollateralAmount; }); it("flash swaps USDC via and makes a WBTC profit", async function () { @@ -287,12 +287,12 @@ export function shouldBehaveLikeUniswapV2Call(): void { .connect(this.signers.liquidator) .swap(token0Amount, token1Amount, to, data); const newWbtcBalance = await this.contracts.wbtc.balanceOf(this.signers.liquidator.address); - expect(newWbtcBalance.sub(expectedProfitWbtcAmount)).to.equal(preWbtcBalance); + expect(newWbtcBalance.sub(expectedProfitCollateralAmount)).to.equal(preWbtcBalance); }); }); context("when the collateral ratio is lower than 150% but higher than 110%", function () { - const repayAmount: BigNumber = hUSDC("10000"); + const repayHTokenAmount: BigNumber = hUSDC("10000"); beforeEach(async function () { // Set the WBTC price to $12.5k to make borrower's collateral ratio 125%. @@ -304,11 +304,11 @@ export function shouldBehaveLikeUniswapV2Call(): void { // Calculate the amounts necessary for running the tests. const calculatedAmounts = await getSeizableAndProfitCollateralAmounts.call( this, - repayAmount, + repayHTokenAmount, swapUnderlyingAmount, ); - expectedProfitWbtcAmount = calculatedAmounts.expectedProfitWbtcAmount; - seizableWbtcAmount = calculatedAmounts.seizableWbtcAmount; + expectedProfitCollateralAmount = calculatedAmounts.expectedProfitCollateralAmount; + seizableCollateralAmount = calculatedAmounts.seizableCollateralAmount; }); context("new order of tokens in the UniswapV2Pair contract", function () { @@ -317,7 +317,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { beforeEach(async function () { const token0: string = await this.contracts.uniswapV2Pair.token0(); - if (token0 == this.contracts.wbtc.address) { + if (token0 === this.contracts.wbtc.address) { await this.contracts.uniswapV2Pair.__godMode_setToken0(this.contracts.usdc.address); await this.contracts.uniswapV2Pair.__godMode_setToken1(this.contracts.wbtc.address); localToken0Amount = swapUnderlyingAmount; @@ -333,24 +333,24 @@ export function shouldBehaveLikeUniswapV2Call(): void { it("flash swaps USDC via and makes a WBTC profit", async function () { const to: string = this.contracts.collateralFlashUniswapV2.address; - const preWbtcBalance = await this.contracts.wbtc.balanceOf(this.signers.liquidator.address); + const oldWbtcBalance = await this.contracts.wbtc.balanceOf(this.signers.liquidator.address); await this.contracts.uniswapV2Pair .connect(this.signers.liquidator) .swap(localToken0Amount, localToken1Amount, to, data); const newWbtcBalance = await this.contracts.wbtc.balanceOf(this.signers.liquidator.address); - expect(newWbtcBalance.sub(expectedProfitWbtcAmount)).to.equal(preWbtcBalance); + expect(newWbtcBalance.sub(expectedProfitCollateralAmount)).to.equal(oldWbtcBalance); }); }); context("initial order of tokens in the UniswapV2Pair contract", function () { it("flash swaps USDC via and makes a WBTC profit", async function () { const to: string = this.contracts.collateralFlashUniswapV2.address; - const preWbtcBalance = await this.contracts.wbtc.balanceOf(this.signers.liquidator.address); + const oldWbtcBalance = await this.contracts.wbtc.balanceOf(this.signers.liquidator.address); await this.contracts.uniswapV2Pair .connect(this.signers.liquidator) .swap(token0Amount, token1Amount, to, data); const newWbtcBalance = await this.contracts.wbtc.balanceOf(this.signers.liquidator.address); - expect(newWbtcBalance.sub(expectedProfitWbtcAmount)).to.equal(preWbtcBalance); + expect(newWbtcBalance.sub(expectedProfitCollateralAmount)).to.equal(oldWbtcBalance); }); it("emits a FlashSwapCollateralAndLiquidateBorrow event", async function () { @@ -365,8 +365,8 @@ export function shouldBehaveLikeUniswapV2Call(): void { this.signers.borrower.address, this.contracts.hToken.address, swapUnderlyingAmount, - seizableWbtcAmount, - expectedProfitWbtcAmount, + seizableCollateralAmount, + expectedProfitCollateralAmount, ); }); }); diff --git a/packages/flash-swap/test/integration/underlyingFlashUniswapV2/effects/uniswapV2Call.ts b/packages/flash-swap/test/integration/underlyingFlashUniswapV2/effects/uniswapV2Call.ts index 3cb60796..3d1b202d 100644 --- a/packages/flash-swap/test/integration/underlyingFlashUniswapV2/effects/uniswapV2Call.ts +++ b/packages/flash-swap/test/integration/underlyingFlashUniswapV2/effects/uniswapV2Call.ts @@ -1,6 +1,6 @@ import { defaultAbiCoder } from "@ethersproject/abi"; import { BigNumber } from "@ethersproject/bignumber"; -import { Zero } from "@ethersproject/constants"; +import { MaxUint256, Zero } from "@ethersproject/constants"; import { COLLATERAL_RATIOS, LIQUIDATION_INCENTIVES } from "@hifi/constants"; import { BalanceSheetErrors, UnderlyingFlashUniswapV2Errors } from "@hifi/errors"; import { USDC, WBTC, getNow, hUSDC, price } from "@hifi/helpers"; @@ -31,20 +31,6 @@ function encodeCallData(this: Mocha.Context): string { return data; } -async function getSeizableAndRepayUnderlyingAmounts( - this: Mocha.Context, - repayAmount: BigNumber, - underlyingAmount: BigNumber, -): Promise<{ expectedRepayUsdcAmount: BigNumber; seizableUsdcAmount: BigNumber }> { - const seizableUsdcAmount = await this.contracts.balanceSheet.getSeizableCollateralAmount( - this.contracts.hToken.address, - repayAmount, - this.contracts.usdc.address, - ); - const expectedRepayUsdcAmount = underlyingAmount.mul(1000).div(997).add(1); - return { expectedRepayUsdcAmount, seizableUsdcAmount }; -} - async function getTokenAmounts( this: Mocha.Context, wbtcAmount: BigNumber, @@ -117,9 +103,6 @@ export function shouldBehaveLikeUniswapV2Call(): void { context("when the caller is the UniswapV2Pair contract", function () { beforeEach(async function () { - // Set the oracle price to 1 WBTC = $20k. - await this.contracts.wbtcPriceFeed.setPrice(price("20000")); - // Set the oracle price to 1 USDC = $1. await this.contracts.usdcPriceFeed.setPrice(price("1")); @@ -154,16 +137,18 @@ export function shouldBehaveLikeUniswapV2Call(): void { const borrowAmount: BigNumber = hUSDC("10000"); const collateralCeiling: BigNumber = USDC("1e6"); const debtCeiling: BigNumber = hUSDC("1e6"); - const depositUsdcAmount: BigNumber = USDC("10000"); - const feeUsdcAmount: BigNumber = USDC("30.090271"); - const swapUsdcAmount: BigNumber = USDC("10000"); + const depositUnderlyingAmount: BigNumber = USDC("10000"); + const feeUnderlyingAmount: BigNumber = USDC("30.090271"); + const repayHTokenAmount: BigNumber = hUSDC("10000"); + const swapUnderlyingAmount: BigNumber = USDC("10000"); const swapWbtcAmount: BigNumber = Zero; let token0Amount: BigNumber; let token1Amount: BigNumber; beforeEach(async function () { - const tokenAmounts = await getTokenAmounts.call(this, swapWbtcAmount, swapUsdcAmount); + // Calculate the amount necessary for running the tests. + const tokenAmounts = await getTokenAmounts.call(this, swapWbtcAmount, swapUnderlyingAmount); token0Amount = tokenAmounts.token0Amount; token1Amount = tokenAmounts.token1Amount; @@ -178,7 +163,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { .connect(this.signers.admin) .setCollateralRatio(this.contracts.usdc.address, COLLATERAL_RATIOS.lowerBound); - // Set the liquidation incentive for USDC to 0. + // Set the liquidation incentive to 0%. await this.contracts.fintroller .connect(this.signers.admin) .setLiquidationIncentive(this.contracts.usdc.address, LIQUIDATION_INCENTIVES.lowerBound); @@ -194,21 +179,21 @@ export function shouldBehaveLikeUniswapV2Call(): void { .setDebtCeiling(this.contracts.hToken.address, debtCeiling); // Mint USDC and approve the BalanceSheet to spend it. - await this.contracts.usdc.__godMode_mint(this.signers.borrower.address, depositUsdcAmount); + await this.contracts.usdc.__godMode_mint(this.signers.borrower.address, depositUnderlyingAmount); await this.contracts.usdc .connect(this.signers.borrower) - .approve(this.contracts.balanceSheet.address, depositUsdcAmount); + .approve(this.contracts.balanceSheet.address, MaxUint256); // Mint USDC to the subsidizer wallet and approve the flash swap contract to spend it. - await this.contracts.usdc.__godMode_mint(this.signers.subsidizer.address, feeUsdcAmount); + await this.contracts.usdc.__godMode_mint(this.signers.subsidizer.address, feeUnderlyingAmount); await this.contracts.usdc .connect(this.signers.subsidizer) - .approve(this.contracts.underlyingFlashUniswapV2.address, feeUsdcAmount); + .approve(this.contracts.underlyingFlashUniswapV2.address, MaxUint256); // Deposit USDC in the BalanceSheet. await this.contracts.balanceSheet .connect(this.signers.borrower) - .depositCollateral(this.contracts.usdc.address, depositUsdcAmount); + .depositCollateral(this.contracts.usdc.address, depositUnderlyingAmount); // Borrow hUSDC. await this.contracts.balanceSheet @@ -233,10 +218,72 @@ export function shouldBehaveLikeUniswapV2Call(): void { const oneHourAgo: BigNumber = getNow().sub(3600); await this.contracts.hToken.connect(this.signers.admin).__godMode_setMaturity(oneHourAgo); }); + // context("when the repay underlying amount is equal to the seized underlying amount", function () {}); - // context("when the repay underlying amount is less than the seized underlying amount", function () {}); + context("when the repay underlying amount is less than the seized underlying amount", function () { + let expectedProfitUnderlyingAmount: BigNumber; + let expectedRepayUnderlyingAmount: BigNumber; + let seizableUnderlyingAmount: BigNumber; + + beforeEach(async function () { + // Mint 10% more USDC. + const addedUnderlyingAmount: BigNumber = depositUnderlyingAmount.div(10); + await this.contracts.usdc.__godMode_mint(this.signers.borrower.address, addedUnderlyingAmount); + + // Deposit the USDC in the vault. + await this.contracts.balanceSheet + .connect(this.signers.borrower) + .depositCollateral(this.contracts.usdc.address, addedUnderlyingAmount); + + // Set the liquidation incentive to 10%. + await this.contracts.fintroller + .connect(this.signers.admin) + .setLiquidationIncentive(this.contracts.usdc.address, LIQUIDATION_INCENTIVES.default); + + // Calculate the amounts necessary for running the tests. + expectedRepayUnderlyingAmount = swapUnderlyingAmount.mul(1000).div(997).add(1); + seizableUnderlyingAmount = await this.contracts.balanceSheet.getSeizableCollateralAmount( + this.contracts.hToken.address, + repayHTokenAmount, + this.contracts.usdc.address, + ); + expectedProfitUnderlyingAmount = seizableUnderlyingAmount.sub(expectedRepayUnderlyingAmount); + }); - // context("when the repay underlying amount is equal to the seized underlying amount", function () {}); + it("flash swaps USDC via and makes a USDC profit", async function () { + const to: string = this.contracts.underlyingFlashUniswapV2.address; + const oldUsdcBalance = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); + await this.contracts.uniswapV2Pair + .connect(this.signers.liquidator) + .swap(token0Amount, token1Amount, to, data); + const newUsdcBalance = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); + console.log({ + oldUsdcBalance: oldUsdcBalance.toString(), + expectedProfitUnderlyingAmount: expectedProfitUnderlyingAmount.toString(), + newUsdcBalance: newUsdcBalance.toString(), + }); + expect(newUsdcBalance.sub(expectedProfitUnderlyingAmount)).to.equal(oldUsdcBalance); + }); + + it("emits a FlashSwapUnderlyingAndLiquidateBorrow event", async function () { + const to: string = this.contracts.underlyingFlashUniswapV2.address; + const contractCall = this.contracts.uniswapV2Pair + .connect(this.signers.liquidator) + .swap(token0Amount, token1Amount, to, data); + await expect(contractCall) + .to.emit(this.contracts.underlyingFlashUniswapV2, "FlashSwapUnderlyingAndLiquidateBorrow") + .withArgs( + this.signers.liquidator.address, + this.signers.borrower.address, + this.contracts.hToken.address, + swapUnderlyingAmount, + seizableUnderlyingAmount, + expectedRepayUnderlyingAmount, + Zero, + expectedProfitUnderlyingAmount, + ); + }); + }); context("when the repay underlying amount is greater than the seized underlying amount", function () { context("new order of tokens in the UniswapV2Pair contract", function () { @@ -245,16 +292,16 @@ export function shouldBehaveLikeUniswapV2Call(): void { beforeEach(async function () { const token0: string = await this.contracts.uniswapV2Pair.token0(); - if (token0 == this.contracts.wbtc.address) { + if (token0 === this.contracts.wbtc.address) { await this.contracts.uniswapV2Pair.__godMode_setToken0(this.contracts.usdc.address); await this.contracts.uniswapV2Pair.__godMode_setToken1(this.contracts.wbtc.address); - localToken0Amount = swapUsdcAmount; + localToken0Amount = swapUnderlyingAmount; localToken1Amount = swapWbtcAmount; } else { await this.contracts.uniswapV2Pair.__godMode_setToken0(this.contracts.wbtc.address); await this.contracts.uniswapV2Pair.__godMode_setToken1(this.contracts.usdc.address); localToken0Amount = swapWbtcAmount; - localToken1Amount = swapUsdcAmount; + localToken1Amount = swapUnderlyingAmount; } await this.contracts.uniswapV2Pair.sync(); }); @@ -269,24 +316,22 @@ export function shouldBehaveLikeUniswapV2Call(): void { const newLiquidatorUsdcBalance = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); const newSubsidizerUsdcBalance = await this.contracts.usdc.balanceOf(this.signers.subsidizer.address); expect(newLiquidatorUsdcBalance).to.equal(oldLiquidatorUsdcBalance); - expect(oldSubsidizerUsdcBalance.sub(newSubsidizerUsdcBalance)).to.equal(feeUsdcAmount); + expect(oldSubsidizerUsdcBalance.sub(newSubsidizerUsdcBalance)).to.equal(feeUnderlyingAmount); }); }); context("initial order of tokens in the UniswapV2Pair contract", function () { - let seizableUsdcAmount: BigNumber; - let expectedRepayUsdcAmount: BigNumber; - const repayAmount: BigNumber = hUSDC("10000"); + let seizableUnderlyingAmount: BigNumber; + let expectedRepayUnderlyingAmount: BigNumber; beforeEach(async function () { // Calculate the amounts necessary for running the tests. - const calculatesAmounts = await getSeizableAndRepayUnderlyingAmounts.call( - this, - repayAmount, - swapUsdcAmount, + expectedRepayUnderlyingAmount = swapUnderlyingAmount.mul(1000).div(997).add(1); + seizableUnderlyingAmount = await this.contracts.balanceSheet.getSeizableCollateralAmount( + this.contracts.hToken.address, + repayHTokenAmount, + this.contracts.usdc.address, ); - seizableUsdcAmount = calculatesAmounts.seizableUsdcAmount; - expectedRepayUsdcAmount = calculatesAmounts.expectedRepayUsdcAmount; }); it("flash swaps USDC making no USDC profit and paying the flash swap fee", async function () { @@ -299,7 +344,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { const newLiquidatorUsdcBalance = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); const newSubsidizerUsdcBalance = await this.contracts.usdc.balanceOf(this.signers.subsidizer.address); expect(newLiquidatorUsdcBalance).to.equal(oldLiquidatorUsdcBalance); - expect(oldSubsidizerUsdcBalance.sub(newSubsidizerUsdcBalance)).to.equal(feeUsdcAmount); + expect(oldSubsidizerUsdcBalance.sub(newSubsidizerUsdcBalance)).to.equal(feeUnderlyingAmount); }); it("emits a FlashSwapUnderlyingAndLiquidateBorrow event", async function () { @@ -313,9 +358,11 @@ export function shouldBehaveLikeUniswapV2Call(): void { this.signers.liquidator.address, this.signers.borrower.address, this.contracts.hToken.address, - swapUsdcAmount, - seizableUsdcAmount, - expectedRepayUsdcAmount, + swapUnderlyingAmount, + seizableUnderlyingAmount, + expectedRepayUnderlyingAmount, + feeUnderlyingAmount, + Zero, ); }); }); From 190e0ec04ccfb8e5b01b269759f83eef02e42d95 Mon Sep 17 00:00:00 2001 From: scorpion9979 Date: Fri, 5 Nov 2021 16:11:12 +0300 Subject: [PATCH 15/16] test(flash-swap): repay underlying equal to seized underlying test(flash-swap): remove redundant debug console logs --- .../effects/uniswapV2Call.ts | 68 +++++++++++++++++-- 1 file changed, 61 insertions(+), 7 deletions(-) diff --git a/packages/flash-swap/test/integration/underlyingFlashUniswapV2/effects/uniswapV2Call.ts b/packages/flash-swap/test/integration/underlyingFlashUniswapV2/effects/uniswapV2Call.ts index 3d1b202d..8fcbc65b 100644 --- a/packages/flash-swap/test/integration/underlyingFlashUniswapV2/effects/uniswapV2Call.ts +++ b/packages/flash-swap/test/integration/underlyingFlashUniswapV2/effects/uniswapV2Call.ts @@ -5,6 +5,7 @@ import { COLLATERAL_RATIOS, LIQUIDATION_INCENTIVES } from "@hifi/constants"; import { BalanceSheetErrors, UnderlyingFlashUniswapV2Errors } from "@hifi/errors"; import { USDC, WBTC, getNow, hUSDC, price } from "@hifi/helpers"; import { expect } from "chai"; +import { toBn } from "evm-bn"; import type { GodModeErc20 } from "../../../../src/types/GodModeErc20"; import { deployGodModeErc20 } from "../../../shared/deployers"; @@ -218,7 +219,65 @@ export function shouldBehaveLikeUniswapV2Call(): void { const oneHourAgo: BigNumber = getNow().sub(3600); await this.contracts.hToken.connect(this.signers.admin).__godMode_setMaturity(oneHourAgo); }); - // context("when the repay underlying amount is equal to the seized underlying amount", function () {}); + + context("when the repay underlying amount is equal to the seized underlying amount", function () { + const liquidationIncentive = toBn("1.003009027081243731"); + let expectedRepayUnderlyingAmount: BigNumber; + let seizableUnderlyingAmount: BigNumber; + + beforeEach(async function () { + // Mint 0.3% more USDC. + const addedUnderlyingAmount: BigNumber = feeUnderlyingAmount; + await this.contracts.usdc.__godMode_mint(this.signers.borrower.address, addedUnderlyingAmount); + + // Deposit the newly minted USDC in the vault. + await this.contracts.balanceSheet + .connect(this.signers.borrower) + .depositCollateral(this.contracts.usdc.address, addedUnderlyingAmount); + + // Set the liquidation incentive to 0.3%. + await this.contracts.fintroller + .connect(this.signers.admin) + .setLiquidationIncentive(this.contracts.usdc.address, liquidationIncentive); + + // Calculate the amounts necessary for running the tests. + expectedRepayUnderlyingAmount = swapUnderlyingAmount.mul(1000).div(997).add(1); + seizableUnderlyingAmount = await this.contracts.balanceSheet.getSeizableCollateralAmount( + this.contracts.hToken.address, + repayHTokenAmount, + this.contracts.usdc.address, + ); + }); + + it("flash swaps USDC via and makes no USDC profit", async function () { + const to: string = this.contracts.underlyingFlashUniswapV2.address; + const oldUsdcBalance = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); + await this.contracts.uniswapV2Pair + .connect(this.signers.liquidator) + .swap(token0Amount, token1Amount, to, data); + const newUsdcBalance = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); + expect(newUsdcBalance).to.equal(oldUsdcBalance); + }); + + it("emits a FlashSwapUnderlyingAndLiquidateBorrow event", async function () { + const to: string = this.contracts.underlyingFlashUniswapV2.address; + const contractCall = this.contracts.uniswapV2Pair + .connect(this.signers.liquidator) + .swap(token0Amount, token1Amount, to, data); + await expect(contractCall) + .to.emit(this.contracts.underlyingFlashUniswapV2, "FlashSwapUnderlyingAndLiquidateBorrow") + .withArgs( + this.signers.liquidator.address, + this.signers.borrower.address, + this.contracts.hToken.address, + swapUnderlyingAmount, + seizableUnderlyingAmount, + expectedRepayUnderlyingAmount, + Zero, + Zero, + ); + }); + }); context("when the repay underlying amount is less than the seized underlying amount", function () { let expectedProfitUnderlyingAmount: BigNumber; @@ -230,7 +289,7 @@ export function shouldBehaveLikeUniswapV2Call(): void { const addedUnderlyingAmount: BigNumber = depositUnderlyingAmount.div(10); await this.contracts.usdc.__godMode_mint(this.signers.borrower.address, addedUnderlyingAmount); - // Deposit the USDC in the vault. + // Deposit the newly minted USDC in the vault. await this.contracts.balanceSheet .connect(this.signers.borrower) .depositCollateral(this.contracts.usdc.address, addedUnderlyingAmount); @@ -257,11 +316,6 @@ export function shouldBehaveLikeUniswapV2Call(): void { .connect(this.signers.liquidator) .swap(token0Amount, token1Amount, to, data); const newUsdcBalance = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); - console.log({ - oldUsdcBalance: oldUsdcBalance.toString(), - expectedProfitUnderlyingAmount: expectedProfitUnderlyingAmount.toString(), - newUsdcBalance: newUsdcBalance.toString(), - }); expect(newUsdcBalance.sub(expectedProfitUnderlyingAmount)).to.equal(oldUsdcBalance); }); From 5ced780ac9dff48cadb423d999e56370f2c92a0b Mon Sep 17 00:00:00 2001 From: Paul Razvan Berg Date: Fri, 5 Nov 2021 17:45:21 +0200 Subject: [PATCH 16/16] test(flash-swap): flash borrow other token when token order is changed --- .../effects/uniswapV2Call.ts | 44 ++++++++++++++----- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/packages/flash-swap/test/integration/underlyingFlashUniswapV2/effects/uniswapV2Call.ts b/packages/flash-swap/test/integration/underlyingFlashUniswapV2/effects/uniswapV2Call.ts index 8fcbc65b..6fa5af7b 100644 --- a/packages/flash-swap/test/integration/underlyingFlashUniswapV2/effects/uniswapV2Call.ts +++ b/packages/flash-swap/test/integration/underlyingFlashUniswapV2/effects/uniswapV2Call.ts @@ -360,17 +360,39 @@ export function shouldBehaveLikeUniswapV2Call(): void { await this.contracts.uniswapV2Pair.sync(); }); - it("flash swaps USDC making no USDC profit and paying the flash swap fee", async function () { - const to: string = this.contracts.underlyingFlashUniswapV2.address; - const oldLiquidatorUsdcBalance = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); - const oldSubsidizerUsdcBalance = await this.contracts.usdc.balanceOf(this.signers.subsidizer.address); - await this.contracts.uniswapV2Pair - .connect(this.signers.liquidator) - .swap(localToken0Amount, localToken1Amount, to, data); - const newLiquidatorUsdcBalance = await this.contracts.usdc.balanceOf(this.signers.liquidator.address); - const newSubsidizerUsdcBalance = await this.contracts.usdc.balanceOf(this.signers.subsidizer.address); - expect(newLiquidatorUsdcBalance).to.equal(oldLiquidatorUsdcBalance); - expect(oldSubsidizerUsdcBalance.sub(newSubsidizerUsdcBalance)).to.equal(feeUnderlyingAmount); + context("when the other token is flash borrowed", function () { + it("reverts", async function () { + const { token0Amount, token1Amount } = await getTokenAmounts.call(this, WBTC("1"), Zero); + const to: string = this.contracts.underlyingFlashUniswapV2.address; + await expect( + this.contracts.uniswapV2Pair + .connect(this.signers.raider) + .swap(token0Amount, token1Amount, to, data), + ).to.be.revertedWith(UnderlyingFlashUniswapV2Errors.FlashBorrowOtherToken); + }); + }); + + context("when the underlying is flash borrowed", function () { + it("flash swaps USDC making no USDC profit and paying the flash swap fee", async function () { + const to: string = this.contracts.underlyingFlashUniswapV2.address; + const oldLiquidatorUsdcBalance = await this.contracts.usdc.balanceOf( + this.signers.liquidator.address, + ); + const oldSubsidizerUsdcBalance = await this.contracts.usdc.balanceOf( + this.signers.subsidizer.address, + ); + await this.contracts.uniswapV2Pair + .connect(this.signers.liquidator) + .swap(localToken0Amount, localToken1Amount, to, data); + const newLiquidatorUsdcBalance = await this.contracts.usdc.balanceOf( + this.signers.liquidator.address, + ); + const newSubsidizerUsdcBalance = await this.contracts.usdc.balanceOf( + this.signers.subsidizer.address, + ); + expect(newLiquidatorUsdcBalance).to.equal(oldLiquidatorUsdcBalance); + expect(oldSubsidizerUsdcBalance.sub(newSubsidizerUsdcBalance)).to.equal(feeUnderlyingAmount); + }); }); });