Skip to content

Commit

Permalink
Merge b0821a9 into c743c4e
Browse files Browse the repository at this point in the history
  • Loading branch information
scorpion9979 committed Sep 14, 2021
2 parents c743c4e + b0821a9 commit df53f23
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 65 deletions.
55 changes: 44 additions & 11 deletions packages/flash-swap/contracts/uniswap-v2/HifiFlashUniswapV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,20 @@ contract HifiFlashUniswapV2 is IHifiFlashUniswapV2 {
IBalanceSheetV1 public override balanceSheet;

/// @inheritdoc IHifiFlashUniswapV2
mapping(address => IUniswapV2Pair) public override pairs;
address public override uniV2Factory;

/// @inheritdoc IHifiFlashUniswapV2
bytes32 public override uniV2InitCodeHash;

/// CONSTRUCTOR ///
constructor(IBalanceSheetV1 balanceSheet_, address[] memory pairs_) {
constructor(
IBalanceSheetV1 balanceSheet_,
address uniV2Factory_,
bytes32 uniV2InitCodeHash_
) {
balanceSheet = IBalanceSheetV1(balanceSheet_);
for (uint256 i = 0; i < pairs_.length; i++) {
pairs[pairs_[i]] = IUniswapV2Pair(pairs_[i]);
}
uniV2Factory = uniV2Factory_;
uniV2InitCodeHash = uniV2InitCodeHash_;
}

/// PUBLIC CONSTANT FUNCTIONS ////
Expand Down Expand Up @@ -109,27 +115,31 @@ contract HifiFlashUniswapV2 is IHifiFlashUniswapV2 {
uint256 amount1,
bytes calldata data
) external override {
if (msg.sender != address(pairs[msg.sender])) {
revert HifiFlashUniswapV2__CallNotAuthorized(msg.sender);
}

// Unpack the ABI encoded data passed by the UniswapV2Pair contract.
(address borrower, IHToken bond, uint256 minProfit) = abi.decode(data, (address, IHToken, uint256));

// Figure out which token is the collateral and which token is the underlying.
IErc20 underlying = bond.underlying();
(IErc20 collateral, uint256 underlyingAmount) = getCollateralAndUnderlyingAmount(
pairs[msg.sender],
IUniswapV2Pair(msg.sender),
amount0,
amount1,
underlying
);

if (msg.sender != address(pairFor(address(underlying), address(collateral)))) {
revert HifiFlashUniswapV2__CallNotAuthorized(msg.sender);
}

// Mint hTokens and liquidate the borrower.
uint256 seizedCollateralAmount = mintAndLiquidateBorrow(borrower, bond, underlyingAmount, collateral);

// Calculate the amount of collateral required to repay.
uint256 repayCollateralAmount = getRepayCollateralAmount(pairs[msg.sender], underlying, underlyingAmount);
uint256 repayCollateralAmount = getRepayCollateralAmount(
IUniswapV2Pair(msg.sender),
underlying,
underlyingAmount
);
if (seizedCollateralAmount <= repayCollateralAmount + minProfit) {
revert HifiFlashUniswapV2__InsufficientProfit(seizedCollateralAmount, repayCollateralAmount, minProfit);
}
Expand All @@ -152,6 +162,29 @@ contract HifiFlashUniswapV2 is IHifiFlashUniswapV2 {
);
}

/// INTERNAL CONSTANT FUNCTIONS ///

// solhint-disable-next-line
/// @dev https://raw.githubusercontent.com/Uniswap/uniswap-v2-periphery/v1.0.0-beta.0/contracts/libraries/UniswapV2Library.sol
/// @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)),
uniV2InitCodeHash
)
)
)
)
);
}

/// INTERNAL NON-CONSTANT FUNCTIONS ///

/// @dev Performs two operations:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ interface IHifiFlashUniswapV2 is IUniswapV2Callee {
uint256 underlyingAmount
) external view returns (uint256 collateralRepayAmount);

/// @notice Mapping between the raw address of the pair contract and the interfaced pair contract.
function pairs(address pair) external view returns (IUniswapV2Pair);
/// @notice The Uniswap V2 factory contract address.
function uniV2Factory() external view returns (address);

/// @notice The Uniswap V2 init code hash.
function uniV2InitCodeHash() external view returns (bytes32);
}
52 changes: 52 additions & 0 deletions packages/flash-swap/contracts/uniswap-v2/UniswapV2Factory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: UNLICENSED
// solhint-disable
pragma solidity =0.5.16;

import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol";
import "./UniswapV2Pair.sol";

/// @dev https://raw.githubusercontent.com/Uniswap/uniswap-v2-core/v1.0.1/contracts/UniswapV2Factory.sol
contract UniswapV2Factory is IUniswapV2Factory {
address public feeTo;
address public feeToSetter;

mapping(address => mapping(address => address)) public getPair;
address[] public allPairs;

event PairCreated(address indexed token0, address indexed token1, address pair, uint256);

constructor(address _feeToSetter) public {
feeToSetter = _feeToSetter;
}

function allPairsLength() external view returns (uint256) {
return allPairs.length;
}

function createPair(address tokenA, address tokenB) external returns (address pair) {
require(tokenA != tokenB, "UniswapV2: IDENTICAL_ADDRESSES");
(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
require(token0 != address(0), "UniswapV2: ZERO_ADDRESS");
require(getPair[token0][token1] == address(0), "UniswapV2: PAIR_EXISTS"); // single check is sufficient
bytes memory bytecode = type(UniswapV2Pair).creationCode;
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
assembly {
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
IUniswapV2Pair(pair).initialize(token0, token1);
getPair[token0][token1] = pair;
getPair[token1][token0] = pair; // populate mapping in the reverse direction
allPairs.push(pair);
emit PairCreated(token0, token1, pair, allPairs.length);
}

function setFeeTo(address _feeTo) external {
require(msg.sender == feeToSetter, "UniswapV2: FORBIDDEN");
feeTo = _feeTo;
}

function setFeeToSetter(address _feeToSetter) external {
require(msg.sender == feeToSetter, "UniswapV2: FORBIDDEN");
feeToSetter = _feeToSetter;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { defaultAbiCoder } from "@ethersproject/abi";
import { BigNumber } from "@ethersproject/bignumber";
import { Zero } from "@ethersproject/constants";
import { USDC, WBTC, bn, hUSDC, price } from "@hifi/helpers";
import { expect } from "chai";
import fp from "evm-fp";

import { BigNumber } from "@ethersproject/bignumber";
import { GodModeErc20 } from "../../../../typechain";
import { deployGodModeErc20 } from "../../../shared/deployers";
import { HifiFlashUniswapV2Errors } from "../../../shared/errors";
import { Zero } from "@ethersproject/constants";
import { defaultAbiCoder } from "@ethersproject/abi";
import { deployGodModeErc20 } from "../../../shared/deployers";
import { expect } from "chai";
import fp from "evm-fp";

async function bumpPoolReserves(this: Mocha.Context, wbtcAmount: BigNumber, usdcAmount: BigNumber): Promise<void> {
// Mint WBTC to the pool.
Expand Down Expand Up @@ -51,14 +51,14 @@ export default function shouldBehaveLikeUniswapV2Call(): void {
context("when the caller is not the UniswapV2Pair contract", function () {
it("reverts", async function () {
const sender: string = this.signers.raider.address;
const token0Amount: BigNumber = Zero;
const token1Amount: BigNumber = USDC("20000");
const token0Amount: BigNumber = USDC("20000");
const token1Amount: BigNumber = Zero;
const data: string = "0x";
await expect(
this.contracts.hifiFlashUniswapV2
.connect(this.signers.raider)
.uniswapV2Call(sender, token0Amount, token1Amount, data),
).to.be.revertedWith(HifiFlashUniswapV2Errors.CallNotAuthorized);
).to.be.revertedWith("");
});
});

Expand All @@ -75,8 +75,8 @@ export default function shouldBehaveLikeUniswapV2Call(): void {
});

context("when the underlying is not in the pool", function () {
const token1Amount: BigNumber = USDC("10000");
const token0Amount: BigNumber = Zero;
const token1Amount: BigNumber = Zero;
const token0Amount: BigNumber = USDC("10000");

it("reverts", async function () {
const foo: GodModeErc20 = await deployGodModeErc20(this.signers.admin, "Foo", "FOO", bn("18"));
Expand All @@ -91,8 +91,8 @@ export default function shouldBehaveLikeUniswapV2Call(): void {

context("when the underlying is in the pool", function () {
context("when collateral is flash borrowed", function () {
const token0Amount: BigNumber = WBTC("1");
const token1Amount: BigNumber = Zero;
const token0Amount: BigNumber = Zero;
const token1Amount: BigNumber = WBTC("1");

it("reverts", async function () {
const to: string = this.contracts.hifiFlashUniswapV2.address;
Expand All @@ -107,8 +107,8 @@ export default function shouldBehaveLikeUniswapV2Call(): void {
const borrowAmount: BigNumber = hUSDC("10000");
const debtCeiling: BigNumber = hUSDC("1e6");
const liquidationIncentive: BigNumber = fp("1.10");
const token0Amount: BigNumber = Zero;
const token1Amount: BigNumber = USDC("10000");
const token0Amount: BigNumber = USDC("10000");
const token1Amount: BigNumber = Zero;
const wbtcDepositAmount: BigNumber = WBTC("1");

beforeEach(async function () {
Expand Down Expand Up @@ -217,7 +217,7 @@ export default function shouldBehaveLikeUniswapV2Call(): void {
repayWbtcAmount = await this.contracts.hifiFlashUniswapV2.getRepayCollateralAmount(
this.contracts.uniswapV2Pair.address,
this.contracts.usdc.address,
token1Amount,
token0Amount,
);
expectedProfitWbtcAmount = seizableWbtcAmount.sub(repayWbtcAmount);
});
Expand All @@ -227,8 +227,6 @@ export default function shouldBehaveLikeUniswapV2Call(): void {
const localToken1Amount: BigNumber = Zero;

beforeEach(async function () {
await this.contracts.uniswapV2Pair.__godMode_setToken0(this.contracts.usdc.address);
await this.contracts.uniswapV2Pair.__godMode_setToken1(this.contracts.wbtc.address);
await this.contracts.uniswapV2Pair.sync();
});

Expand Down Expand Up @@ -271,7 +269,7 @@ export default function shouldBehaveLikeUniswapV2Call(): void {
this.signers.liquidator.address,
this.signers.borrower.address,
this.contracts.hToken.address,
token1Amount,
token0Amount,
seizableWbtcAmount,
expectedProfitWbtcAmount,
);
Expand Down
24 changes: 17 additions & 7 deletions packages/flash-swap/test/shared/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ import {
import { getHTokenName, getHTokenSymbol } from "@hifi/helpers";
import { GodModeErc20 } from "../../typechain/GodModeErc20";
import { GodModeHToken } from "../../typechain/GodModeHToken";
import { GodModeUniswapV2Pair } from "../../typechain/GodModeUniswapV2Pair";
import { HifiFlashUniswapV2 } from "../../typechain/HifiFlashUniswapV2";
import { SimplePriceFeed } from "../../typechain/SimplePriceFeed";
import { deployGodModeErc20 } from "./deployers";
import { Contract, utils } from "ethers";
import { UniswapV2Factory } from "../../typechain/UniswapV2Factory";
import { UniswapV2Pair } from "../../typechain";

const { deployContract } = waffle;

Expand All @@ -34,7 +36,7 @@ type IntegrationFixtureReturnType = {
hToken: GodModeHToken;
usdc: GodModeErc20;
usdcPriceFeed: SimplePriceFeed;
uniswapV2Pair: GodModeUniswapV2Pair;
uniswapV2Pair: UniswapV2Pair;
wbtc: GodModeErc20;
wbtcPriceFeed: SimplePriceFeed;
};
Expand Down Expand Up @@ -70,15 +72,23 @@ export async function integrationFixture(signers: Signer[]): Promise<Integration
])
);

const godModeUniswapV2PairArtifact: Artifact = await artifacts.readArtifact("GodModeUniswapV2Pair");
const uniswapV2Pair: GodModeUniswapV2Pair = <GodModeUniswapV2Pair>(
await deployContract(deployer, godModeUniswapV2PairArtifact, [])
const uniswapV2FactoryArtifact: Artifact = await artifacts.readArtifact("UniswapV2Factory");
const uniswapV2Factory: UniswapV2Factory = <UniswapV2Factory>(
await deployContract(deployer, uniswapV2FactoryArtifact, [await deployer.getAddress()])
);
await uniswapV2Pair.initialize(wbtc.address, usdc.address);
await uniswapV2Factory.createPair(usdc.address, wbtc.address);
const pairAddress = await uniswapV2Factory.allPairs(0);

const uniswapV2PairArtifact: Artifact = await artifacts.readArtifact("UniswapV2Pair");
const uniswapV2Pair = new Contract(pairAddress, uniswapV2PairArtifact.abi, deployer) as UniswapV2Pair;

const hifiFlashUniswapV2Artifact: Artifact = await artifacts.readArtifact("HifiFlashUniswapV2");
const hifiFlashUniswapV2: HifiFlashUniswapV2 = <HifiFlashUniswapV2>(
await deployContract(deployer, hifiFlashUniswapV2Artifact, [balanceSheet.address, [uniswapV2Pair.address]])
await deployContract(deployer, hifiFlashUniswapV2Artifact, [
balanceSheet.address,
uniswapV2Factory.address,
utils.keccak256(uniswapV2PairArtifact.bytecode),
])
);

return {
Expand Down
7 changes: 3 additions & 4 deletions packages/flash-swap/test/types.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { BalanceSheetV1 } from "@hifi/protocol/typechain/BalanceSheetV1";
import { ChainlinkOperator } from "@hifi/protocol/typechain/ChainlinkOperator";
import { FintrollerV1 } from "@hifi/protocol/typechain/FintrollerV1";
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address";

import { GodModeErc20 } from "../typechain/GodModeErc20";
import { GodModeHToken } from "../typechain/GodModeHToken";
import { GodModeUniswapV2Pair } from "../typechain/GodModeUniswapV2Pair";
import { HifiFlashUniswapV2 } from "../typechain/HifiFlashUniswapV2";
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address";
import { SimplePriceFeed } from "../typechain/SimplePriceFeed";
import { UniswapV2Pair } from "../typechain/UniswapV2Pair";

declare module "mocha" {
export interface Context {
Expand All @@ -24,7 +23,7 @@ export interface Contracts {
oracle: ChainlinkOperator;
usdc: GodModeErc20;
usdcPriceFeed: SimplePriceFeed;
uniswapV2Pair: GodModeUniswapV2Pair;
uniswapV2Pair: UniswapV2Pair;
wbtc: GodModeErc20;
wbtcPriceFeed: SimplePriceFeed;
}
Expand Down

0 comments on commit df53f23

Please sign in to comment.