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

Add minimum purchase amount on liquidations #114

Merged
merged 3 commits into from
Nov 16, 2020
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
6 changes: 4 additions & 2 deletions contracts/masset/liquidator/ILiquidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ contract ILiquidator {
address _bAsset,
int128 _curvePosition,
address[] calldata _uniswapPath,
uint256 _trancheAmount
uint256 _trancheAmount,
uint256 _minReturn
)
external;

Expand All @@ -18,7 +19,8 @@ contract ILiquidator {
address _bAsset,
int128 _curvePosition,
address[] calldata _uniswapPath,
uint256 _trancheAmount
uint256 _trancheAmount,
uint256 _minReturn
)
external;

Expand Down
49 changes: 40 additions & 9 deletions contracts/masset/liquidator/Liquidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { InitializableModule } from "../../shared/InitializableModule.sol";
import { ILiquidator } from "./ILiquidator.sol";
import { MassetHelpers } from "../../masset/shared/MassetHelpers.sol";

import { IBasicToken } from "../../shared/IBasicToken.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
Expand Down Expand Up @@ -40,6 +41,7 @@ contract Liquidator is
uint256 private interval = 7 days;

mapping(address => Liquidation) public liquidations;
mapping(address => uint256) public minReturn;

struct Liquidation {
address sellToken;
Expand Down Expand Up @@ -85,14 +87,16 @@ contract Liquidator is
* @param _curvePosition Position of the bAsset in Curves MetaPool
* @param _uniswapPath The Uniswap path as an array of addresses e.g. [COMP, WETH, DAI]
* @param _trancheAmount The amount of bAsset units to buy in each weekly tranche
* @param _minReturn Minimum exact amount of bAsset to get for each (whole) sellToken unit
*/
function createLiquidation(
address _integration,
address _sellToken,
address _bAsset,
int128 _curvePosition,
address[] calldata _uniswapPath,
uint256 _trancheAmount
uint256 _trancheAmount,
uint256 _minReturn
)
external
onlyGovernance
Expand All @@ -103,7 +107,8 @@ contract Liquidator is
_integration != address(0) &&
_sellToken != address(0) &&
_bAsset != address(0) &&
_uniswapPath.length >= 2,
_uniswapPath.length >= 2 &&
_minReturn > 0,
"Invalid inputs"
);
require(_validUniswapPath(_sellToken, _bAsset, _uniswapPath), "Invalid uniswap path");
Expand All @@ -116,6 +121,7 @@ contract Liquidator is
lastTriggered: 0,
trancheAmount: _trancheAmount
});
minReturn[_integration] = _minReturn;

emit LiquidationModified(_integration);
}
Expand All @@ -127,13 +133,15 @@ contract Liquidator is
* @param _curvePosition Position of the bAsset in Curves MetaPool
* @param _uniswapPath The Uniswap path as an array of addresses e.g. [COMP, WETH, DAI]
* @param _trancheAmount The amount of bAsset units to buy in each weekly tranche
* @param _minReturn Minimum exact amount of bAsset to get for each (whole) sellToken unit
*/
function updateBasset(
address _integration,
address _bAsset,
int128 _curvePosition,
address[] calldata _uniswapPath,
uint256 _trancheAmount
uint256 _trancheAmount,
uint256 _minReturn
)
external
onlyGovernance
Expand All @@ -142,14 +150,16 @@ contract Liquidator is

address oldBasset = liquidation.bAsset;
require(oldBasset != address(0), "Liquidation does not exist");


require(_minReturn > 0, "Must set some minimum value");
require(_bAsset != address(0), "Invalid bAsset");
require(_validUniswapPath(liquidation.sellToken, _bAsset, _uniswapPath), "Invalid uniswap path");

liquidations[_integration].bAsset = _bAsset;
liquidations[_integration].curvePosition = _curvePosition;
liquidations[_integration].uniswapPath = _uniswapPath;
liquidations[_integration].trancheAmount = _trancheAmount;
minReturn[_integration] = _minReturn;

emit LiquidationModified(_integration);
}
Expand Down Expand Up @@ -180,6 +190,8 @@ contract Liquidator is
require(liquidation.bAsset != address(0), "Liquidation does not exist");

delete liquidations[_integration];
delete minReturn[_integration];

emit LiquidationEnded(_integration);
}

Expand All @@ -197,6 +209,9 @@ contract Liquidator is
function triggerLiquidation(address _integration)
external
{
// solium-disable-next-line security/no-tx-origin
require(tx.origin == msg.sender, "Must be EOA");

Liquidation memory liquidation = liquidations[_integration];

address bAsset = liquidation.bAsset;
Expand Down Expand Up @@ -234,19 +249,24 @@ contract Liquidator is
IERC20(sellToken).safeApprove(address(uniswap), 0);
IERC20(sellToken).safeApprove(address(uniswap), sellAmount);
// 3.2. Make the sale > https://uniswap.org/docs/v2/smart-contracts/router02/#swapexacttokensfortokens

// min amount out = sellAmount * priceFloor / 1e18
// e.g. 1e18 * 100e6 / 1e18 = 100e6
// e.g. 30e8 * 100e6 / 1e8 = 3000e6
// e.g. 30e18 * 100e18 / 1e18 = 3000e18
uint256 sellTokenDec = IBasicToken(sellToken).decimals();
uint256 minOut = sellAmount.mul(minReturn[_integration]).div(10 ** sellTokenDec);
require(minOut > 0, "Must have some price floor");
uniswap.swapExactTokensForTokens(
sellAmount,
0,
minOut,
uniswapPath,
address(this),
block.timestamp.add(1800)
);
uint256 bAssetBal = IERC20(bAsset).balanceOf(address(this));

// 3.3. Trade on Curve
IERC20(bAsset).safeApprove(address(curve), 0);
IERC20(bAsset).safeApprove(address(curve), bAssetBal);
uint256 purchased = curve.exchange_underlying(liquidation.curvePosition, 0, bAssetBal, 0);
uint256 purchased = _sellOnCrv(bAsset, liquidation.curvePosition);

// 4.0. Send to SavingsManager
address savings = _savingsManager();
Expand All @@ -256,4 +276,15 @@ contract Liquidator is

emit Liquidated(sellToken, mUSD, purchased, bAsset);
}

function _sellOnCrv(address _bAsset, int128 _curvePosition) internal returns (uint256 purchased) {
uint256 bAssetBal = IERC20(_bAsset).balanceOf(address(this));

IERC20(_bAsset).safeApprove(address(curve), 0);
IERC20(_bAsset).safeApprove(address(curve), bAssetBal);
uint256 bAssetDec = IBasicToken(_bAsset).decimals();
// e.g. 100e6 * 95e16 / 1e6 = 100e18
uint256 minOutCrv = bAssetBal.mul(95e16).div(10 ** bAssetDec);
purchased = curve.exchange_underlying(_curvePosition, 0, bAssetBal, minOutCrv);
}
}
12 changes: 10 additions & 2 deletions contracts/z_mocks/shared/MockCurveMetaPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,30 @@ contract MockCurveMetaPool is ICurveMetaPool {

address[] public coins;
address mUSD;
// number of out per in (scaled)
uint256 ratio = 98e16;


constructor(address[] memory _coins, address _mUSD) public {
require(_coins[0] == _mUSD, "Coin 0 must be mUSD");
coins = _coins;
mUSD = _mUSD;
}

function setRatio(uint256 _newRatio) external {
ratio = _newRatio;
}

// takes dx i from sender, returns j
function exchange_underlying(int128 i, int128 j, uint256 dx, uint256 /*min_dy*/)
function exchange_underlying(int128 i, int128 j, uint256 dx, uint256 min_dy)
external
returns (uint256)
{
require(j == 0, "Output must be mUSD");
address in_tok = coins[uint256(i)];
uint256 decimals = IBasicToken(in_tok).decimals();
uint256 out_amt = dx * (10 ** (18 - decimals));
uint256 out_amt = dx * (10 ** (18 - decimals)) * ratio / 1e18;
require(out_amt >= min_dy, "CRV: Output amount not enough");
IERC20(in_tok).transferFrom(msg.sender, address(this), dx);
IERC20(mUSD).transfer(msg.sender, out_amt);
return out_amt;
Expand Down
11 changes: 11 additions & 0 deletions contracts/z_mocks/shared/MockTrigger.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
pragma solidity 0.5.16;

import { ILiquidator } from "../../masset/liquidator/ILiquidator.sol";


contract MockTrigger {

function trigger(ILiquidator _liq, address _integration) external {
_liq.triggerLiquidation(_integration);
}
}
14 changes: 11 additions & 3 deletions contracts/z_mocks/shared/MockUniswap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,17 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
// out token has 18 decimals
contract MockUniswap is IUniswapV2Router02 {

// how many tokens to give out for 1 in
uint256 ratio = 106;

function setRatio(uint256 _outRatio) external {
ratio = _outRatio;
}

// takes input from sender, produces output
function swapExactTokensForTokens(
uint amountIn,
uint /*amountOutMin*/,
uint amountOutMin,
address[] calldata path,
address /*to*/,
uint /*deadline*/
Expand All @@ -28,7 +34,9 @@ contract MockUniswap is IUniswapV2Router02 {
amounts[0] = amountIn;
IERC20(path[0]).transferFrom(msg.sender, address(this), amountIn);

uint256 output = amountIn * 106;
uint256 output = amountIn * ratio;
require(output >= amountOutMin, "UNI: Output amount not enough");

amounts[len-1] = output;
IERC20(path[len-1]).transfer(msg.sender, output);
}
Expand All @@ -41,7 +49,7 @@ contract MockUniswap is IUniswapV2Router02 {
view
returns (uint[] memory amounts)
{
uint256 amountIn = amountOut / 106;
uint256 amountIn = amountOut / ratio;
uint256 len = path.length;
amounts = new uint[](len);
amounts[0] = amountIn;
Expand Down
Loading