Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Curve lp tests #293

Merged
merged 3 commits into from
Apr 20, 2022
Merged
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
15 changes: 15 additions & 0 deletions contracts/interfaces/ICurvePoolFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
interface ICurvePoolFactory {
function get_coins(address _pool) external view returns (address[4] memory);

function deploy_plain_pool(
string memory _name,
string memory _symbol,
address[4] memory _coins,
uint256 _A,
uint256 _fee
) external returns (address);

function pool_list(uint256 _arg) external view returns (address);

function pool_count() external view returns (uint256);
}
2 changes: 2 additions & 0 deletions contracts/peripheral/IncurDebt.sol
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ contract IncurDebt is OlympusAccessControlledV2, IIncurDebt {
revert IncurDebt_AmountAboveBorrowerBalance(_liquidity);

lpTokenOwnership[_lpToken][msg.sender] -= _liquidity;

IERC20(_lpToken).safeTransfer(_strategy, _liquidity);

ohmRecieved = IStrategy(_strategy).removeLiquidity(_strategyParams, _liquidity, _lpToken, msg.sender);
Expand Down Expand Up @@ -384,6 +385,7 @@ contract IncurDebt is OlympusAccessControlledV2, IIncurDebt {

// borrower can decide to call repayDebtWithOHM() and clear debt
if (borrowers[msg.sender].debt != 0) repayDebtWithCollateral();

lpTokenOwnership[_lpToken][msg.sender] -= _liquidity;

IERC20(_lpToken).safeTransfer(msg.sender, _liquidity);
Expand Down
17 changes: 7 additions & 10 deletions contracts/peripheral/Strategies/Curve.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity ^0.8.10;
import "../../interfaces/IERC20.sol";
import "../../libraries/SafeERC20.sol";
import "../../interfaces/IStrategy.sol";
import "../../interfaces/ICurvePoolFactory.sol";

interface ICurvePool {
function add_liquidity(uint256[2] memory _deposit_amounts, uint256 _min_mint_amount) external returns (uint256);
Expand All @@ -13,10 +14,6 @@ interface ICurvePool {
returns (uint256[2] memory);
}

interface ICurveFactory {
function get_coins(address _pool) external view returns (address[8] memory);
}

error CurveStrategy_NotIncurDebtAddress();
error CurveStrategy_AmountsDoNotMatch();
error CurveStrategy_LPTokenDoesNotMatch();
Expand All @@ -29,7 +26,7 @@ error CurveStrategy_OhmAddressNotFound();
contract CurveStrategy is IStrategy {
using SafeERC20 for IERC20;

ICurveFactory factory;
ICurvePoolFactory factory;
address public immutable incurDebtAddress;
address public immutable ohmAddress;

Expand All @@ -38,7 +35,7 @@ contract CurveStrategy is IStrategy {
address _ohmAddress,
address _factory
) {
factory = ICurveFactory(_factory);
factory = ICurvePoolFactory(_factory);
incurDebtAddress = _incurDebtAddress;
ohmAddress = _ohmAddress;
}
Expand All @@ -63,7 +60,7 @@ contract CurveStrategy is IStrategy {
(uint256[2] memory amounts, uint256 min_mint_amount, address pairTokenAddress, address poolAddress) = abi
.decode(_data, (uint256[2], uint256, address, address));

address[8] memory poolTokens = factory.get_coins(poolAddress);
address[4] memory poolTokens = factory.get_coins(poolAddress);

if (poolTokens[0] == ohmAddress) {
if (poolTokens[1] != pairTokenAddress) revert CurveStrategy_LPTokenDoesNotMatch();
Expand All @@ -85,9 +82,11 @@ contract CurveStrategy is IStrategy {
revert CurveStrategy_LPTokenDoesNotMatch();
}

IERC20(ohmAddress).approve(poolAddress, _ohmAmount);
liquidity = ICurvePool(poolAddress).add_liquidity(amounts, min_mint_amount); // Ohm unused will be 0 since curve uses up all input tokens for LP.

lpTokenAddress = poolAddress; // For factory pools on curve, the LP token is the pool contract.
IERC20(lpTokenAddress).safeTransfer(incurDebtAddress, liquidity);
}

function removeLiquidity(
Expand All @@ -102,11 +101,9 @@ contract CurveStrategy is IStrategy {

if (_burn_amount != _liquidity) revert CurveStrategy_AmountsDoNotMatch();

// probably dont need to but test if need approve token to pool before remove lp. if all good remove this comment.

uint256[2] memory resultAmounts = ICurvePool(_lpTokenAddress).remove_liquidity(_burn_amount, _min_amounts);

address[8] memory poolTokens = factory.get_coins(_lpTokenAddress);
address[4] memory poolTokens = factory.get_coins(_lpTokenAddress);

if (poolTokens[0] == ohmAddress) {
ohmRecieved = resultAmounts[0];
Expand Down
214 changes: 213 additions & 1 deletion test/debt/IncurDebt.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,12 @@ describe("IncurDebt", async () => {
UniSwapStrategy,
uniSwapStrategy,
uniswapLpContract,
uniOhmDaiLpAddress;
uniOhmDaiLpAddress,
curveStrategyFactory,
curveStrategy,
curvePoolFactory;

let zeroAddress = "0x0000000000000000000000000000000000000000";

beforeEach(async () => {
await fork_network(14565910);
Expand All @@ -47,6 +52,7 @@ describe("IncurDebt", async () => {
uniRouter = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D";
factory = "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f";
uniOhmDaiLpAddress = "0x1b851374b8968393c11e8fb30c2842cfc4e986a5";
curveFactoryAddress = "0xB9fC157394Af804a3578134A6585C0dc9cc990d4";

IncurDebt = await ethers.getContractFactory("IncurDebt");
incurDebt = await IncurDebt.deploy(
Expand All @@ -66,6 +72,15 @@ describe("IncurDebt", async () => {
olympus.ohm
);

curveStrategyFactory = await ethers.getContractFactory("CurveStrategy");
curveStrategy = await curveStrategyFactory.deploy(
incurDebt.address,
olympus.ohm,
curveFactoryAddress
);

curvePoolFactory = await ethers.getContractAt("ICurvePoolFactory", curveFactoryAddress);

daiContract = await ethers.getContractAt(
"contracts/interfaces/IERC20.sol:IERC20",
"0x6B175474E89094C44Da98b954EedeAC495271d0F"
Expand Down Expand Up @@ -778,6 +793,94 @@ describe("IncurDebt", async () => {
incurDebt.connect(daiHolder).createLP(ohmAmount, uniSwapStrategy.address, data)
).to.emit(incurDebt, "LpInteraction");
});

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see you only tested for Should allow borrower create lp for curve can you test for other failed instances like the if statements.

Copy link
Contributor

@c-n-o-t-e c-n-o-t-e Apr 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant testing for these instances

       if (poolTokens[0] == ohmAddress) {
            if (poolTokens[1] != pairTokenAddress) revert CurveStrategy_LPTokenDoesNotMatch();
            if (_ohmAmount != amounts[0]) revert CurveStrategy_AmountsDoNotMatch();
            if (_pairTokenAmount != amounts[1]) revert CurveStrategy_AmountsDoNotMatch();
        } else if (poolTokens[1] == ohmAddress) {
            if (poolTokens[0] != pairTokenAddress) revert CurveStrategy_LPTokenDoesNotMatch();
            if (_ohmAmount != amounts[1]) revert CurveStrategy_AmountsDoNotMatch();
            if (_pairTokenAmount != amounts[0]) revert CurveStrategy_AmountsDoNotMatch();
        } else {
            revert CurveStrategy_LPTokenDoesNotMatch();
        }

it("Should allow borrower create lp for curve", async () => {
await curvePoolFactory.deploy_plain_pool(
"Test Ohm Dai Pool",
"TEST",
[olympus.ohm, olympus.sohm, zeroAddress, zeroAddress],
10,
4000000
);
let poolCount = await curvePoolFactory.pool_count();
let lpPoolAddress = await curvePoolFactory.pool_list(poolCount.sub(1));

const curveParaData = ethers.utils.defaultAbiCoder.encode(
["uint256[2]", "uint256", "address", "address"],
[[ohmAmount, ohmAmount], 0, olympus.sohm, lpPoolAddress]
);

await incurDebt.connect(governor).setGlobalDebtLimit(amount);
await incurDebt.connect(governor).allowBorrower(gOhmHolder.address, true, false);

await incurDebt.connect(governor).setBorrowerDebtLimit(gOhmHolder.address, ohmAmount);

await gohm_token.connect(gOhmHolder).approve(incurDebt.address, amountInGOHM);
await sohm_token.connect(sOhmHolder).transfer(gOhmHolder.address, ohmAmount);

await incurDebt.connect(gOhmHolder).deposit(amountInGOHM);
await treasury.connect(governor).setDebtLimit(incurDebt.address, amount);

await incurDebt.connect(governor).whitelistStrategy(curveStrategy.address);
await sohm_token.connect(gOhmHolder).approve(curveStrategy.address, ohmAmount);
await incurDebt
.connect(gOhmHolder)
.createLP(ohmAmount, curveStrategy.address, curveParaData);

let liquidityAmount = await incurDebt.lpTokenOwnership(
lpPoolAddress,
gOhmHolder.address
);

await expect(liquidityAmount).to.equal("66000000000000000000");
await expect(await incurDebt.totalOutstandingGlobalDebt()).to.equal(ohmAmount);
});

it("curve strategy should revert when wrong lp address or amount", async () => {
await curvePoolFactory.deploy_plain_pool(
"Test Ohm Dai Pool",
"TEST",
[olympus.ohm, olympus.sohm, zeroAddress, zeroAddress],
10,
4000000
);
let poolCount = await curvePoolFactory.pool_count();
let lpPoolAddress = await curvePoolFactory.pool_list(poolCount.sub(1));

let curveParaData = ethers.utils.defaultAbiCoder.encode(
["uint256[2]", "uint256", "address", "address"],
[[ohmAmount, ohmAmount], 0, olympus.sohm, olympus.ohm]
);

await incurDebt.connect(governor).setGlobalDebtLimit(amount);
await incurDebt.connect(governor).allowBorrower(gOhmHolder.address, true, false);

await incurDebt.connect(governor).setBorrowerDebtLimit(gOhmHolder.address, ohmAmount);

await gohm_token.connect(gOhmHolder).approve(incurDebt.address, amountInGOHM);
await sohm_token.connect(sOhmHolder).transfer(gOhmHolder.address, ohmAmount);

await incurDebt.connect(gOhmHolder).deposit(amountInGOHM);
await treasury.connect(governor).setDebtLimit(incurDebt.address, amount);

await incurDebt.connect(governor).whitelistStrategy(curveStrategy.address);
await sohm_token.connect(gOhmHolder).approve(curveStrategy.address, ohmAmount);
expect(
incurDebt
.connect(gOhmHolder)
.createLP(ohmAmount, curveStrategy.address, curveParaData)
).to.revertedWith("CurveStrategy_LPTokenDoesNotMatch()");

curveParaData = ethers.utils.defaultAbiCoder.encode(
["uint256[2]", "uint256", "address", "address"],
[[ohmAmount, "1230124902"], 0, olympus.sohm, lpPoolAddress]
);
expect(
incurDebt
.connect(gOhmHolder)
.createLP(ohmAmount, curveStrategy.address, curveParaData)
).to.revertedWith("CurveStrategy_AmountsDoNotMatch()");
});
});

describe("function removeLP(_liquidity, _strategy, _lpToken, _strategyParams)", () => {
Expand Down Expand Up @@ -875,6 +978,61 @@ describe("IncurDebt", async () => {
const totalOutstandingGlobalDebtAfterTx = await incurDebt.totalOutstandingGlobalDebt();
assert.equal(Number(totalOutstandingGlobalDebtAfterTx), 1);
});

it("Should allow borrower remove lp for curve", async () => {
await curvePoolFactory.deploy_plain_pool(
"Test Ohm Dai Pool",
"TEST",
[olympus.ohm, olympus.sohm, zeroAddress, zeroAddress],
10,
4000000
);
let poolCount = await curvePoolFactory.pool_count();
let lpPoolAddress = await curvePoolFactory.pool_list(poolCount.sub(1));

const curveParaData = ethers.utils.defaultAbiCoder.encode(
["uint256[2]", "uint256", "address", "address"],
[[ohmAmount, ohmAmount], 0, olympus.sohm, lpPoolAddress]
);

await incurDebt.connect(governor).setGlobalDebtLimit(amount);
await incurDebt.connect(governor).allowBorrower(gOhmHolder.address, true, false);

await incurDebt.connect(governor).setBorrowerDebtLimit(gOhmHolder.address, ohmAmount);

await gohm_token.connect(gOhmHolder).approve(incurDebt.address, amountInGOHM);
await sohm_token.connect(sOhmHolder).transfer(gOhmHolder.address, ohmAmount);

await incurDebt.connect(gOhmHolder).deposit(amountInGOHM);
await treasury.connect(governor).setDebtLimit(incurDebt.address, amount);

await incurDebt.connect(governor).whitelistStrategy(curveStrategy.address);
await sohm_token.connect(gOhmHolder).approve(curveStrategy.address, ohmAmount);
await incurDebt
.connect(gOhmHolder)
.createLP(ohmAmount, curveStrategy.address, curveParaData);

let liquidityAmount = await incurDebt.lpTokenOwnership(
lpPoolAddress,
gOhmHolder.address
);

let sOhmAmountBeforeRemove = await sohm_token.balanceOf(gOhmHolder.address);

const curveRemoveData = ethers.utils.defaultAbiCoder.encode(
["uint256", "uint256[2]"],
[liquidityAmount, [0, 0]]
);

await incurDebt
.connect(gOhmHolder)
.removeLP(liquidityAmount, curveStrategy.address, lpPoolAddress, curveRemoveData);

let sOhmAmountAfterRemove = await sohm_token.balanceOf(gOhmHolder.address);

await expect(sOhmAmountAfterRemove.sub(sOhmAmountBeforeRemove)).to.equal(ohmAmount);
await expect(await incurDebt.totalOutstandingGlobalDebt()).to.equal(0);
});
});

describe("withdrawLP(uint256 _liquidity, address _lpToken)", async () => {
Expand Down Expand Up @@ -978,6 +1136,60 @@ describe("IncurDebt", async () => {
const borrowerLpBalanceAfterTx = await uniswapLpContract.balanceOf(daiHolder.address);
assert.equal(Number(borrowerLpBalanceAfterTx), Number(borrowerLpBeforeTx));
});

it("Should allow borrower withdraw lp for curve", async () => {
await curvePoolFactory.deploy_plain_pool(
"Test Ohm Dai Pool",
"TEST",
[olympus.ohm, olympus.sohm, zeroAddress, zeroAddress],
10,
4000000
);
let poolCount = await curvePoolFactory.pool_count();
let lpPoolAddress = await curvePoolFactory.pool_list(poolCount.sub(1));

const curveParaData = ethers.utils.defaultAbiCoder.encode(
["uint256[2]", "uint256", "address", "address"],
[[ohmAmount, ohmAmount], 0, olympus.sohm, lpPoolAddress]
);

await incurDebt.connect(governor).setGlobalDebtLimit(amount);
await incurDebt.connect(governor).allowBorrower(gOhmHolder.address, true, false);

await incurDebt.connect(governor).setBorrowerDebtLimit(gOhmHolder.address, ohmAmount);

await gohm_token.connect(gOhmHolder).approve(incurDebt.address, amountInGOHM);
await sohm_token.connect(sOhmHolder).transfer(gOhmHolder.address, ohmAmount);

await incurDebt.connect(gOhmHolder).deposit(amountInGOHM);
await treasury.connect(governor).setDebtLimit(incurDebt.address, amount);

await incurDebt.connect(governor).whitelistStrategy(curveStrategy.address);
await sohm_token.connect(gOhmHolder).approve(curveStrategy.address, ohmAmount);
await incurDebt
.connect(gOhmHolder)
.createLP(ohmAmount, curveStrategy.address, curveParaData);

let liquidityAmount = await incurDebt.lpTokenOwnership(
lpPoolAddress,
gOhmHolder.address
);

await incurDebt.connect(gOhmHolder).repayDebtWithCollateral();

let lpTokenContract = await ethers.getContractAt(
"contracts/interfaces/IERC20.sol:IERC20",
lpPoolAddress
);

await expect(await lpTokenContract.balanceOf(gOhmHolder.address)).to.equal(0);

await incurDebt.connect(gOhmHolder).withdrawLP(liquidityAmount, lpPoolAddress);

await expect(await lpTokenContract.balanceOf(gOhmHolder.address)).to.equal(
liquidityAmount
);
});
});

async function impersonate(address) {
Expand Down