Skip to content

Curve #32

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions constants/addresses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,5 @@ export const DODOV2Proxy = {
Heco: "0xAc7cC7d2374492De2D1ce21e2FEcA26EB0d113e7",
Arbitrum: "0x88CBf433471A0CD8240D2a12354362988b4593E5",
};

export const CurveSwapsAddress = "0x1d8b86e3D88cDb2d34688e87E72F388Cb541B7C8";
21 changes: 20 additions & 1 deletion contracts/Flashloan.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "./uniswap/IUniswapV2Router.sol";
import "./uniswap/v3/ISwapRouter.sol";

import "./curve/ICurveFiSwaps.sol";

import "./dodo/IDODO.sol";

import "./interfaces/IFlashloan.sol";
Expand Down Expand Up @@ -135,8 +137,10 @@ contract Flashloan is IFlashloan, DodoBase, FlashloanValidation, Withdraw {
amountOut = uniswapV3(swap.data, amountIn, path);
} else if (swap.protocol < 8) {
amountOut = uniswapV2(swap.data, amountIn, path);
} else {
} else if (swap.protocol == 8) {
amountOut = dodoV2Swap(swap.data, amountIn, path);
} else {
amountOut = curveFiSwap(swap.data, amountIn, path);
}
}

Expand Down Expand Up @@ -181,6 +185,21 @@ contract Flashloan is IFlashloan, DodoBase, FlashloanValidation, Withdraw {
)[1];
}

function curveFiSwap(
bytes memory data,
uint256 amountIn,
address[] memory path
) internal returns (uint256 amountOut) {
(uint256 i, uint256 j, address router) = abi.decode(
data,
(uint256, uint256, address)
);
uint256 initialBalance = IERC20(path[1]).balanceOf(address(this));
approveToken(path[0], router, amountIn);
ICurveFiSwaps(router).exchange_underlying(i, j, amountIn, 0);
return IERC20(path[1]).balanceOf(address(this)) - initialBalance;
}

function dodoV2Swap(
bytes memory data,
uint256 amountIn,
Expand Down
2 changes: 1 addition & 1 deletion contracts/base/FlashloanValidation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity ^0.8.0;
import "../interfaces/IFlashloan.sol";

abstract contract FlashloanValidation {
uint256 constant MAX_PROTOCOL = 8;
uint256 constant MAX_PROTOCOL = 9;

modifier checkTotalRoutePart(IFlashloan.Route[] memory routes) {
uint16 totalPart = 0;
Expand Down
17 changes: 17 additions & 0 deletions contracts/curve/CurveFiSwaps.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract CurveFiSwaps {
function get_dy_underlying(
uint256 i,
uint256 j,
uint256 _dx
) external view returns (uint256) {}

function exchange_underlying(
uint256 i,
uint256 j,
uint256 _dx,
uint256 _min_dy
) external {}
}
35 changes: 35 additions & 0 deletions contracts/curve/CurveSwap.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./ICurveFiSwaps.sol";

import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";

contract CurveSwap {
using SafeMath for uint256;
using SafeERC20 for IERC20;

function curveFiSwap(
bytes memory data,
uint256 amountIn,
address[] memory path
) external returns (uint256 amountOut) {
(uint256 i, uint256 j, address router) = abi.decode(
data,
(uint256, uint256, address)
);
uint256 initialBalance = IERC20(path[1]).balanceOf(address(this));
approveToken(path[0], router, amountIn);
ICurveFiSwaps(router).exchange_underlying(i, j, amountIn, 0);
return IERC20(path[1]).balanceOf(address(this)) - initialBalance;
}

function approveToken(
address token,
address to,
uint256 amountIn
) internal {
require(IERC20(token).approve(to, amountIn), "approve failed.");
}
}
17 changes: 17 additions & 0 deletions contracts/curve/ICurveFiSwaps.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface ICurveFiSwaps {
function get_dy_underlying(
uint256 i,
uint256 j,
uint256 _dx
) external view returns (uint256);

function exchange_underlying(
uint256 i,
uint256 j,
uint256 _dx,
uint256 _min_dy
) external;
}
7 changes: 6 additions & 1 deletion contracts/dodo/IDODOV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

pragma solidity ^0.8;

// pragma solidity 0.6.9;

// import {IERC20} from "../interfaces/IERC20.sol";
Expand All @@ -21,4 +22,8 @@ interface IDODOV2 {
external
view
returns (uint256 receiveBaseAmount, uint256 mtFee);
}

function _BASE_TOKEN_() external view returns (address);

function _QUOTE_TOKEN_() external view returns (address);
}
110 changes: 110 additions & 0 deletions test/curve.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
import { expect } from "chai";
import { ethers } from "hardhat";
import { ERC20Mock, CurveFiSwaps, CurveFiSwaps__factory } from "../typechain";
import { USDC_WHALE, CurveSwapsAddress } from "../constants/addresses";
import { getErc20Balance, impersonateFundErc20 } from "../utils/token";
import { getBigNumber, getERC20ContractFromAddress } from "../utils";
import { ERC20Token } from "../constants/token";

describe("Curve fi", () => {
let Curve: CurveFiSwaps;
let owner: SignerWithAddress;
let addr1: SignerWithAddress;
let addr2: SignerWithAddress;
let addrs: SignerWithAddress[];
let USDC: ERC20Mock;
let DAI: ERC20Mock;
let WBTC: ERC20Mock;

let fixture: any;

before(async () => {
USDC = await getERC20ContractFromAddress(ERC20Token.USDC.address);
DAI = await getERC20ContractFromAddress(ERC20Token.DAI.address);
WBTC = await getERC20ContractFromAddress(ERC20Token.WBTC.address);
fixture = async () => {
[owner, addr1, addr2, ...addrs] = await ethers.getSigners();

const factory = (await ethers.getContractFactory(
"CurveFiSwaps"
)) as CurveFiSwaps__factory;
Curve = factory.attach(CurveSwapsAddress);
};
});

beforeEach(async () => {
await fixture();
});

describe("get price", async () => {
it("DAI - USDC", async () => {
const amountOut = await Curve.get_dy_underlying(0, 1, getBigNumber(1000));
expect(amountOut.gt(getBigNumber(0))).to.be.true;
});

it("USDC - WBTC", async () => {
const amountOut = await Curve.get_dy_underlying(
1,
3,
getBigNumber(1000, 6)
);
expect(amountOut.gt(getBigNumber(0))).to.be.true;
});

it("WBTC - WETH", async () => {
const amountOut = await Curve.get_dy_underlying(3, 4, getBigNumber(1, 8));
expect(amountOut.gt(getBigNumber(0))).to.be.true;
});
});

describe("exchange", async () => {
it("USDC - DAI", async () => {
await impersonateFundErc20(USDC, USDC_WHALE, owner.address, "1000.0", 6);
await USDC.approve(Curve.address, getBigNumber(10, 6));
await Curve.exchange_underlying(
1,
0,
getBigNumber(10, 6),
getBigNumber(9)
);
const balance = await DAI.balanceOf(owner.address);
expect(balance.gt(getBigNumber(9))).to.be.true;
});

it("USDC - DAI - USDC", async () => {
await impersonateFundErc20(USDC, USDC_WHALE, owner.address, "1000.0", 6);
await USDC.approve(Curve.address, getBigNumber(1000, 6));
await Curve.exchange_underlying(
1,
0,
getBigNumber(1000, 6),
getBigNumber(9)
);
const daiBalance = await DAI.balanceOf(owner.address);
await DAI.approve(Curve.address, daiBalance);
await Curve.exchange_underlying(0, 1, daiBalance, getBigNumber(9, 6));
const balance = await USDC.balanceOf(owner.address);
expect(balance.gt(getBigNumber(900, 6))).to.be.true;
});

it("USDC - WBTC", async () => {
await impersonateFundErc20(USDC, USDC_WHALE, owner.address, "1000.0", 6);
await USDC.approve(Curve.address, getBigNumber(1000, 6));
await Curve.exchange_underlying(1, 3, getBigNumber(1000, 6), 0);
const balance = await WBTC.balanceOf(owner.address);
expect(balance.gt(getBigNumber(0))).to.be.true;
});

it("USDC - WBTC - USDC", async () => {
await impersonateFundErc20(USDC, USDC_WHALE, owner.address, "1000.0", 6);
await USDC.approve(Curve.address, getBigNumber(1000, 6));
await Curve.exchange_underlying(1, 3, getBigNumber(1000, 6), 0);
const wbtcBalance = await WBTC.balanceOf(owner.address);
await WBTC.approve(Curve.address, wbtcBalance);
await Curve.exchange_underlying(3, 1, wbtcBalance, 0);
const balance = await USDC.balanceOf(owner.address);
expect(balance.gt(getBigNumber(0))).to.be.true;
});
});
});
Loading