Skip to content

Commit

Permalink
Add full implementation flow
Browse files Browse the repository at this point in the history
  • Loading branch information
alsco77 committed Oct 14, 2020
1 parent d4cfc29 commit d202826
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 200 deletions.
24 changes: 24 additions & 0 deletions contracts/masset/liquidator/ILiquidator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
pragma solidity 0.5.16;



contract ILiquidator {

enum LendingPlatform { Null, Compound, Aave }

function createLiquidation(
address _integration,
LendingPlatform _lendingPlatform,
address _sellToken,
address _bAsset,
address[] calldata _uniswapPath,
uint256 _sellTranche
) external;
function updateBasset(address _bAsset, address[] calldata _uniswapPath) external;
function deleteLiquidation(address _integration) external;
function changeTrancheAmount(uint256 _sellTranche) external;

function triggerLiquidation(address _integration) external;

function collect() external;
}
210 changes: 113 additions & 97 deletions contracts/masset/liquidator/Liquidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import { IPlatformIntegration } from "../../interfaces/IPlatformIntegration.sol"

import { Initializable } from "@openzeppelin/upgrades/contracts/Initializable.sol";
import { InitializableModule } from "../../shared/InitializableModule.sol";
import { ILiquidator } from "./ILiquidator.sol";
import { MassetHelpers } from "../../masset/shared/MassetHelpers.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";


/**
* @title Liquidator
* @author Stability Labs Pty. Ltd.
Expand All @@ -22,6 +24,7 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
* DATE: 2020-10-13
*/
contract Liquidator is
ILiquidator,
Initializable,
InitializableModule
{
Expand All @@ -37,15 +40,18 @@ contract Liquidator is

mapping(address => Liquidation) public liquidations;

enum LendingPlatform { Null, Compound, Aave }

struct Liquidation {
LendingPlatform platform;

address sellToken;

address bAsset;
address pToken;
address[] uniswapPath;

uint256 collectUnits; // Minimum collection amount for the integration, updated after liquidation
uint256 lastTriggered;
uint256 trancheAmount;
uint256 sellTranche; // Tranche amount, with token decimals
}

/** @dev Constructor */
Expand All @@ -72,15 +78,15 @@ contract Liquidator is
* @param _sellToken The integration contract address for the _bAsset
* @param _bAsset The _bAsset address that this liquidation is for
* @param _uniswapPath The Uniswap path as an array of addresses e.g. [COMP, WETH, DAI]
* @param _trancheAmount The amount of tokens to be sold when triggered
* @param _sellTranche The amount of tokens to be sold when triggered (in token decimals)
*/
function createLiquidation(
address _integration,
LendingPlatform _lendingPlatform,
address _sellToken,
address _bAsset,
address[] calldata _uniswapPath,
uint256 _trancheAmount
uint256 _sellTranche
)
external
onlyGovernance
Expand All @@ -89,11 +95,12 @@ contract Liquidator is
require(
_integration != address(0) &&
_lendingPlatform != LendingPlatform.Null &&
_sellToken != address(0),
_bAsset != address(0),
_sellToken != address(0) &&
_bAsset != address(0) &&
_uniswapPath.length >= uint(2),
"Invalid inputs"
);
require(_validUniswapPath(_sellToken, _bAsset, _uniswapPath), "Invalid uniswap path");

address pToken = IPlatformIntegration(_integration).bAssetToPToken(_bAsset);
require(pToken != address(0), "no pToken for this bAsset");
Expand All @@ -102,31 +109,30 @@ contract Liquidator is
platform: _lendingPlatform,
sellToken: _sellToken,
bAsset: _bAsset,
pToken: pToken,
uniswapPath: _uniswapPath,
lastTriggered: uint256(0),
trancheAmount: _trancheAmount
collectUnits: 0,
lastTriggered: 0,
sellTranche: _sellTranche
});

_giveApproval(_integration, pToken);
if (_lendingPlatform == LendingPlatform.Compound) {
MassetHelpers.safeInfiniteApprove(_bAsset, pToken);
}

emit LiquidationModified(_integration);
}

function _giveApproval(address _integration, address _pToken) internal {

// 1. Approve integration to collect pToken (bAsset or pToken change)
// 2. Approve cToken to mint (bAsset or pToken change)

Liquidation memory liquidation = liquidations[_integration];

MassetHelpers.safeInfiniteApprove(_pToken, _integration);

if (liquidation.platform == LendingPlatform.Compound) {
MassetHelpers.safeInfiniteApprove(liquidation.bAsset, _pToken);
}
function _validUniswapPath(address _sellToken, address _bAsset, address[] memory _uniswapPath)
internal
view
returns (bool)
{
uint256 len = _uniswapPath.length;
return _sellToken == _uniswapPath[0] && _bAsset == _uniswapPath[len];
}

function changeBasset(
function updateBasset(
address _bAsset,
address[] calldata _uniswapPath
)
Expand All @@ -145,7 +151,7 @@ contract Liquidator is
}

function changeTrancheAmount(
uint256 _trancheAmount
uint256 _sellTranche
)
external
onlyGovernance
Expand All @@ -161,17 +167,17 @@ contract Liquidator is
external
onlyGovernance
{
Liquidation memory liquidation = liquidations[_integration];
require(liquidation.bAsset != address(0), "No liquidation for this integration");
// Liquidation memory liquidation = liquidations[_integration];
// require(liquidation.bAsset != address(0), "No liquidation for this integration");


// todo
// 1. Deal will old bAsset (if changed)
// > transfer remainer of pToken to integration
// > remove approval for both bAsset and pToken
// // todo
// // 1. Deal will old bAsset (if changed)
// // > transfer remainer of pToken to integration
// // > remove approval for both bAsset and pToken

delete liquidations[_integration];
emit LiquidationEnded(_integration);
// delete liquidations[_integration];
// emit LiquidationEnded(_integration);
}

/***************************************
Expand All @@ -183,110 +189,120 @@ contract Liquidator is
external
{
Liquidation memory liquidation = liquidations[_integration];

address bAsset = liquidation.bAsset;
require(bAsset != address(0), "Liquidation does not exist");
require(block.timestamp > liquidation.lastTriggered.add(interval), "Must wait for interval");

liquidation.lastTriggered = block.timestamp;
require(block.timestamp > liquidation.lastTriggered.add(interval), "Must wait for interval");
liquidations[_integration].lastTriggered = block.timestamp;

// Cache variables
address sellToken = liquidation.sellToken;
address integration = liquidation.integration;
address[] memory uniswapPath = liquidation.uniswapPath;

// Transfer sellTokens from integration contract if there are some
// Assumes infinite approval
uint256 integrationBal = IERC20(sellToken).balanceOf(integration);
// 1. Transfer sellTokens from integration contract if there are some
// Assumes infinite approval
uint256 integrationBal = IERC20(sellToken).balanceOf(_integration);
if (integrationBal > 0) {
IERC20(sellToken).safeTransferFrom(integration, address(this), integrationBal);
IERC20(sellToken).safeTransferFrom(_integration, address(this), integrationBal);
}

// Check contract balance
uint256 bal = IERC20(sellToken).balanceOf(address(this));
require((bal > 0), "No sell tokens to liquidate");

// Get the amount to sell based on the tranche amount we want to buy
(uint256 amountToSell, uint256 expectedAmount) = getAmountToSell(liquidation.uniswapPath, liquidation.trancheAmount);
// 2. Get the amount to sell based on the tranche amount we want to buy
// Check contract balance
uint256 sellTokenBal = IERC20(sellToken).balanceOf(address(this));
require(sellTokenBal > 0, "No sell tokens to liquidate");
// Calc amounts for max tranche
uint[] memory amountsIn = IUniswapV2Router02(uniswapAddress).getAmountsIn(liquidation.sellTranche, uniswapPath);
uint256 sellAmount = amountsIn[0];

// The minimum amount of output tokens that must be received for the transaction not to revert
// Set to 80% of expected
uint256 minAcceptable = expectedAmount.mul(uint(8000)).div(uint(10000));

// Sell amountToSell unless balance is lower in which case sell everything and relax acceptable check
uint256 sellAmount;
if (bal > amountToSell) {
sellAmount = amountToSell;
} else {
sellAmount = bal;
minAcceptable = 0;
if (sellTokenBal < sellAmount) {
sellAmount = sellTokenBal;
}

// Approve Uniswap and make the swap
// https://uniswap.org/docs/v2/smart-contracts/router02/#swapexacttokensfortokens
// 3. Make the swap
// 3.1 Approve Uniswap and make the swap
IERC20(sellToken).safeApprove(uniswapAddress, 0);
IERC20(sellToken).safeApprove(uniswapAddress, amountToSell);
IERC20(sellToken).safeApprove(uniswapAddress, sellAmount);

// 3.2. Make the sale > https://uniswap.org/docs/v2/smart-contracts/router02/#swapexacttokensfortokens
IUniswapV2Router02(uniswapAddress).swapExactTokensForTokens(
sellAmount,
minAcceptable,
liquidation.uniswapPath,
0,
uniswapPath,
address(this),
block.timestamp.add(1800)
);
uint256 bAssetBal = IERC20(bAsset).balanceOf(address(this));

// 4. Deposit to lending platform
// Assumes integration contracts have inifinte approval to collect them
if (liquidation.platform == LendingPlatform.Compound) {
// 4.1. Exec deposit
ICERC20 cToken = ICERC20(liquidation.pToken);
require(cToken.mint(bAssetBal) == 0, "cToken mint failed");

// Deposit to lending platform
// Assumes integration contracts have inifinte approval to collect them
if (liquidation.lendingPlatform == LendingPlatform.Compound) {
depositToCompound(liquidation.pToken, _bAsset);
// 4.2. Set minCollect to 25% of received
uint256 cTokenBal = cToken.balanceOf(address(this));
liquidations[_integration].collectUnits = cTokenBal.mul(2).div(10);
} else {
revert("Lending Platform not supported");
}

emit LiquidationTriggered(_bAsset);
}

/**
* @dev Deposits to Compound
* @param _pToken The _pToken to mint
* @param _bAsset The _bAsset liquidation to be triggered
*/
function depositToCompound(address _pToken, address _bAsset)
internal
{
uint256 bAssetBalance = IERC20(_bAsset).balanceOf(address(this));
require((bAssetBalance > 0), "No tokens to deposit");
require(ICERC20(_pToken).mint(bAssetBalance) == 0, "cToken mint failed");
emit Liquidated(sellToken, bAsset, bAssetBal);
}

/**
* @dev Get the amount of sellToken to be sold for a number of bAsset
* @param _uniswapPath The Uniswap path for this liquidation
* @param _trancheAmount The tranche size that we want to buy each time
* @param _sellTranche The tranche size that we want to buy each time
*/
function getAmountToSell(
address[] memory _uniswapPath,
uint256 _trancheAmount
function _getAmountToSell(
address[] memory _uniswapPath,
uint256 _sellTranche
)
internal view returns (uint256, uint256)
internal
view
returns (uint256, uint256)
{

// The _trancheAmount is the number of bAsset we want to buy each time
// DAI has 18 decimals so 1000 DAI is 10*10^18 or 1000000000000000000000
// This value is set when adding the liquidation
// We randomize this amount by buying betwen 80% and 95% of the amount.
// Uniswap gives us the amount we need to sell with `getAmountsIn`.
uint256 randomBasisPoint = uint256(blockhash(block.number-1)).mod(uint(1500)).add(uint(8000));
uint256 amountWanted = _trancheAmount.mul(randomBasisPoint).div(uint(10000));
// // The _sellTranche is the number of bAsset we want to buy each time
// // DAI has 18 decimals so 1000 DAI is 10*10^18 or 1000000000000000000000
// // This value is set when adding the liquidation
// // We randomize this amount by buying betwen 80% and 95% of the amount.
// // Uniswap gives us the amount we need to sell with `getAmountsIn`.
// uint256 randomBasisPoint = uint256(blockhash(block.number-1)).mod(uint(1500)).add(uint(8000));
// uint256 amountWanted = _sellTranche.mul(randomBasisPoint).div(uint(10000));

// Returns the minimum input asset amount required to buy
// the given output asset amount (accounting for fees) given reserves
// https://uniswap.org/docs/v2/smart-contracts/router02/#getamountsin
uint[] memory amountsIn = IUniswapV2Router02(uniswapAddress).getAmountsIn(amountWanted, _uniswapPath);
// // Returns the minimum input asset amount required to buy
// // the given output asset amount (accounting for fees) given reserves
// // https://uniswap.org/docs/v2/smart-contracts/router02/#getamountsin
// uint[] memory amountsIn = IUniswapV2Router02(uniswapAddress).getAmountsIn(amountWanted, _uniswapPath);

return (amountsIn[0], amountWanted);
// return (amountsIn[0], amountWanted);
}


/***************************************
CLAIM
COLLECT
****************************************/

function collect()
external
{
Liquidation memory liquidation = liquidations[msg.sender];
address pToken = liquidation.pToken;
if(pToken != address(0)){
uint256 bal = IERC20(pToken).balanceOf(address(this));
if (bal > 0) {
// If we are below the threshold transfer the entire balance
// otherwise send between 5 and 35%
if (bal > liquidation.collectUnits) {
bytes32 bHash = blockhash(block.number - 1);
uint256 randomBp = uint256(keccak256(abi.encodePacked(block.timestamp, bHash))).mod(3e4).add(5e3);
bal = bal.mul(randomBp).div(1e5);
}
IERC20(pToken).transfer(msg.sender, bal);
}
}
}
}

0 comments on commit d202826

Please sign in to comment.