Skip to content

Commit

Permalink
Merge b6bd23c into 1c1278e
Browse files Browse the repository at this point in the history
  • Loading branch information
asselstine committed Jun 8, 2022
2 parents 1c1278e + b6bd23c commit 734edaa
Show file tree
Hide file tree
Showing 10 changed files with 568 additions and 133 deletions.
73 changes: 57 additions & 16 deletions contracts/PrizePoolLiquidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ pragma solidity 0.8.6;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/math/SafeCast.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@pooltogether/owner-manager-contracts/contracts/Manageable.sol";

import "./libraries/ExtendedSafeCastLib.sol";
import "./libraries/LiquidatorLib.sol";
import "./interfaces/IPrizePool.sol";
import "./interfaces/IPrizePoolLiquidatorListener.sol";

contract PrizePoolLiquidator {
contract PrizePoolLiquidator is Manageable {
using SafeMath for uint256;
using SafeCast for uint256;
using ExtendedSafeCastLib for uint256;
Expand All @@ -28,10 +29,36 @@ contract PrizePoolLiquidator {
uint256 reserveB;
}

mapping(IPrizePool => LiquidatorConfig) poolLiquidatorConfigs;
mapping(IPrizePool => LiquidatorState) poolLiquidatorStates;
event Swapped(
IPrizePool indexed prizePool,
IERC20 want,
address target,
address indexed account,
uint256 amountIn,
uint256 amountOut
);

IPrizePoolLiquidatorListener public listener;
event ListenerSet(
IPrizePoolLiquidatorListener indexed listener
);

mapping(IPrizePool => LiquidatorConfig) internal poolLiquidatorConfigs;
mapping(IPrizePool => LiquidatorState) internal poolLiquidatorStates;

IPrizePoolLiquidatorListener internal listener;

constructor(address _owner) Ownable(_owner) {
}

function setListener(IPrizePoolLiquidatorListener _listener) external onlyManagerOrOwner {
listener = _listener;

emit ListenerSet(_listener);
}

function getListener() external view returns (IPrizePoolLiquidatorListener) {
return listener;
}

function setPrizePool(
IPrizePool _pool,
Expand All @@ -52,74 +79,84 @@ contract PrizePoolLiquidator {
reserveA: _reserveA,
reserveB: _reserveB
});

// NOTE: need event here

return true;
}

function availableBalanceOf(IPrizePool _prizePool) external returns (uint256) {
return _availableStreamHaveBalance(_prizePool);
return _availableReserveB(_prizePool);
}

function _availableStreamHaveBalance(IPrizePool _prizePool) internal returns (uint256) {
function _availableReserveB(IPrizePool _prizePool) internal returns (uint256) {
return _prizePool.captureAwardBalance();
}

function currentExchangeRate(IPrizePool _prizePool) external returns (uint256) {
function nextLiquidationState(IPrizePool _prizePool) external returns (LiquidatorState memory state) {
LiquidatorState memory state = poolLiquidatorStates[_prizePool];
(uint256 reserveA, uint256 reserveB) = LiquidatorLib.prepareSwap(
state.reserveA,
state.reserveB,
_availableStreamHaveBalance(_prizePool)
_availableReserveB(_prizePool)
);
return (reserveA*1e18) / reserveB;
return LiquidatorState({
reserveA: reserveA,
reserveB: reserveB
});
}

function computeExactAmountIn(IPrizePool _prizePool, uint256 _amountOut) external returns (uint256) {
LiquidatorConfig memory config = poolLiquidatorConfigs[_prizePool];
LiquidatorState memory state = poolLiquidatorStates[_prizePool];
return LiquidatorLib.computeExactAmountIn(
state.reserveA, state.reserveB, _availableStreamHaveBalance(_prizePool), _amountOut, config.swapMultiplier, config.liquidityFraction
state.reserveA, state.reserveB, _availableReserveB(_prizePool), _amountOut
);
}

function computeExactAmountOut(IPrizePool _prizePool, uint256 _amountIn) external returns (uint256) {
LiquidatorConfig memory config = poolLiquidatorConfigs[_prizePool];
LiquidatorState memory state = poolLiquidatorStates[_prizePool];
return LiquidatorLib.computeExactAmountOut(
state.reserveA, state.reserveB, _availableStreamHaveBalance(_prizePool), _amountIn, config.swapMultiplier, config.liquidityFraction
state.reserveA, state.reserveB, _availableReserveB(_prizePool), _amountIn
);
}

function swapExactAmountIn(IPrizePool _prizePool, uint256 _amountIn, uint256 _amountOutMin) external returns (uint256) {
LiquidatorConfig memory config = poolLiquidatorConfigs[_prizePool];
LiquidatorState memory state = poolLiquidatorStates[_prizePool];
uint256 availableBalance = _availableStreamHaveBalance(_prizePool);
uint256 availableBalance = _availableReserveB(_prizePool);
(uint256 reserveA, uint256 reserveB, uint256 amountOut) = LiquidatorLib.swapExactAmountIn(
state.reserveA, state.reserveB,
availableBalance, _amountIn, config.swapMultiplier, config.liquidityFraction
);
state.reserveA = reserveA;
state.reserveB = reserveB;
require(amountOut <= availableBalance, "Whoops! have exceeds available");
require(amountOut >= _amountOutMin, "trade does not meet min");
poolLiquidatorStates[_prizePool] = state;
_swap(_prizePool, config.want, config.target, msg.sender, amountOut, _amountIn);

emit Swapped(_prizePool, config.want, config.target, msg.sender, _amountIn, amountOut);

return amountOut;
}

function swapExactAmountOut(IPrizePool _prizePool, uint256 _amountOut, uint256 _amountInMax) external returns (uint256) {
LiquidatorConfig memory config = poolLiquidatorConfigs[_prizePool];
LiquidatorState memory state = poolLiquidatorStates[_prizePool];
uint256 availableBalance = _availableStreamHaveBalance(_prizePool);
uint256 availableBalance = _availableReserveB(_prizePool);
(uint256 reserveA, uint256 reserveB, uint256 amountIn) = LiquidatorLib.swapExactAmountOut(
state.reserveA, state.reserveB,
availableBalance, _amountOut, config.swapMultiplier, config.liquidityFraction
);
state.reserveA = reserveA;
state.reserveB = reserveB;
require(amountIn <= _amountInMax, "trade does not meet min");
require(_amountOut <= availableBalance, "Whoops! have exceeds available");
require(amountIn <= _amountInMax, "trade does not meet max");
poolLiquidatorStates[_prizePool] = state;
_swap(_prizePool, config.want, config.target, msg.sender, _amountOut, amountIn);

emit Swapped(_prizePool, config.want, config.target, msg.sender, amountIn, _amountOut);

return amountIn;
}

Expand All @@ -132,6 +169,10 @@ contract PrizePoolLiquidator {
}
}

function getLiquidationConfig(IPrizePool _prizePool) external view returns (LiquidatorConfig memory state) {
return poolLiquidatorConfigs[_prizePool];
}

function getLiquidationState(IPrizePool _prizePool) external view returns (LiquidatorState memory state) {
return poolLiquidatorStates[_prizePool];
}
Expand Down
13 changes: 7 additions & 6 deletions contracts/libraries/CpmmLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,19 @@ library CpmmLib {
using SafeMath for uint256;

// given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset
function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) internal pure returns (uint256 amountOut) {
// require(amountIn > 0, "CpmmLib: INSUFFICIENT_INPUT_AMOUNT");
require(reserveIn > 0 && reserveOut > 0, "CpmmLib: INSUFFICIENT_LIQUIDITY");
function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) internal view returns (uint256 amountOut) {
require(reserveIn > 0 && reserveOut > 0, "CpmmLib/INSUFF_PAIR_LIQ");
uint256 numerator = amountIn.mul(reserveOut);
uint256 denominator = reserveIn.add(amountIn);
return numerator / denominator;
uint256 amountOut = numerator / denominator;
return amountOut;
}

// given an output amount of an asset and pair reserves, returns a required input amount of the other asset
function getAmountIn(uint256 amountOut, uint256 reserveIn, uint256 reserveOut) internal pure returns (uint256 amountIn) {
function getAmountIn(uint256 amountOut, uint256 reserveIn, uint256 reserveOut) internal view returns (uint256 amountIn) {
// require(amountOut > 0, "CpmmLib: INSUFFICIENT_OUTPUT_AMOUNT");
require(reserveIn > 0 && reserveOut > 0, "CpmmLib: INSUFFICIENT_LIQUIDITY");
require(amountOut < reserveOut, "CpmmLib/INSUFF_LIQ");
require(reserveIn > 0 && reserveOut > 0, "CpmmLib/INSUFF_PAIR_LIQ");
uint256 numerator = reserveIn.mul(amountOut);
uint256 denominator = reserveOut.sub(amountOut);
amountIn = (numerator / denominator);
Expand Down
87 changes: 43 additions & 44 deletions contracts/libraries/LiquidatorLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "./ExtendedSafeCastLib.sol";
import "./CpmmLib.sol";

import "hardhat/console.sol";

/**
* @title PoolTogether Liquidator Library
* @author PoolTogether Inc. Team
* @notice
*/
library LiquidatorLib {
using SafeMath for uint256;
using SafeCast for uint256;
Expand All @@ -17,102 +24,94 @@ library LiquidatorLib {
function computeExactAmountIn(
uint256 _reserveA,
uint256 _reserveB,
uint256 _availableBalance,
uint256 _amountOut,
uint32 _swapMultiplier,
uint32 _liquidityFraction
) internal pure returns (uint256) {
require(_amountOut <= _availableBalance, "insuff balance");
(uint256 reserveA, uint256 reserveB) = prepareSwap(_reserveA, _reserveB, _availableBalance);
return CpmmLib.getAmountIn(_amountOut, reserveA, reserveB);
uint256 _availableReserveB,
uint256 _amountOutB
) internal view returns (uint256) {
require(_amountOutB <= _availableReserveB, "insuff balance");
(uint256 reserveA, uint256 reserveB) = prepareSwap(_reserveA, _reserveB, _availableReserveB);
return CpmmLib.getAmountIn(_amountOutB, reserveA, reserveB);
}

function computeExactAmountOut(
uint256 _reserveA,
uint256 _reserveB,
uint256 availableBalance,
uint256 amountIn,
uint32 _swapMultiplier,
uint32 _liquidityFraction
) internal pure returns (uint256) {
(uint256 reserveA, uint256 reserveB) = prepareSwap(_reserveA, _reserveB, availableBalance);
uint256 amountOut = CpmmLib.getAmountOut(amountIn, reserveA, reserveB);
require(amountOut <= availableBalance, "insuff balance");
uint256 _availableReserveB,
uint256 _amountInA
) internal view returns (uint256) {
(uint256 reserveA, uint256 reserveB) = prepareSwap(_reserveA, _reserveB, _availableReserveB);
uint256 amountOut = CpmmLib.getAmountOut(_amountInA, reserveA, reserveB);
require(amountOut <= _availableReserveB, "insuff balance");
return amountOut;
}

function prepareSwap(
uint256 _reserveA,
uint256 _reserveB,
uint256 _availableBalance
) internal pure returns (uint256 reserveA, uint256 reserveB) {

uint256 _availableReserveB
) internal view returns (uint256 reserveA, uint256 reserveB) {
// swap back yield
uint256 wantAmount = CpmmLib.getAmountOut(_availableBalance, _reserveA, _reserveB);
reserveB = _reserveB.sub(wantAmount);
reserveA = _reserveA.add(_availableBalance);
uint256 amountInA = CpmmLib.getAmountOut(_availableReserveB, _reserveB, _reserveA);
reserveA = _reserveA.sub(amountInA);
reserveB = _reserveB.add(_availableReserveB);
}

function _finishSwap(
uint256 _reserveA,
uint256 _reserveB,
uint256 _availableBalance,
uint256 _availableReserveB,
uint256 _reserveBOut,
uint32 _swapMultiplier,
uint32 _liquidityFraction
) internal view returns (uint256 reserveA, uint256 reserveB) {

// apply the additional swap
uint256 extraReserveBOut = (_reserveBOut*_swapMultiplier) / 1e9;
uint256 extraReserveAIn = CpmmLib.getAmountIn(extraReserveBOut, _reserveA, _reserveB);
reserveA = _reserveA.add(extraReserveAIn);
reserveB = _reserveB.sub(extraReserveBOut);

// now, we want to ensure that the accrued yield is always a small fraction of virtual LP position.
uint256 multiplier = _availableBalance / (reserveB*_liquidityFraction);
uint256 reserveFraction = (_availableReserveB * 1e9) / reserveB;
uint256 multiplier = (reserveFraction * 1e9) / uint256(_liquidityFraction);
reserveA = (reserveA*multiplier) / 1e9;
reserveB = (reserveB*multiplier) / 1e9;
}

function swapExactAmountIn(
uint256 _reserveA,
uint256 _reserveB,
uint256 _availableBalance,
uint256 _amountIn,
uint256 _availableReserveB,
uint256 _amountInA,
uint32 _swapMultiplier,
uint32 _liquidityFraction
) internal view returns (uint256 reserveA, uint256 reserveB, uint256 amountOut) {
require(_availableBalance > 0, "Whoops! no funds available");

(reserveA, reserveB) = prepareSwap(_reserveA, _reserveB, _availableBalance);
(reserveA, reserveB) = prepareSwap(_reserveA, _reserveB, _availableReserveB);

// do swap
amountOut = CpmmLib.getAmountOut(_amountIn, reserveB, reserveA);
require(amountOut <= _availableBalance, "Whoops! have exceeds available");
reserveB = reserveB.add(_amountIn);
reserveA = reserveA.sub(amountOut);
amountOut = CpmmLib.getAmountOut(_amountInA, reserveA, reserveB);
require(amountOut <= _availableReserveB, "LiqLib/insuff-liq");
reserveA = reserveA.add(_amountInA);
reserveB = reserveB.sub(amountOut);

(reserveA, reserveB) = _finishSwap(reserveA, reserveB, _availableBalance, amountOut, _swapMultiplier, _liquidityFraction);
(reserveA, reserveB) = _finishSwap(reserveA, reserveB, _availableReserveB, amountOut, _swapMultiplier, _liquidityFraction);
}

function swapExactAmountOut(
uint256 _reserveA,
uint256 _reserveB,
uint256 _availableBalance,
uint256 _amountOut,
uint256 _availableReserveB,
uint256 _amountOutB,
uint32 _swapMultiplier,
uint32 _liquidityFraction
) internal view returns (uint256 reserveA, uint256 reserveB, uint256 amountIn) {
require(_availableBalance > 0, "Whoops! no funds available");
require(_amountOut <= _availableBalance, "Whoops! have exceeds available");
require(_amountOutB <= _availableReserveB, "LiqLib/insuff-liq");

(reserveA, reserveB) = prepareSwap(_reserveA, _reserveB, _availableBalance);
(reserveA, reserveB) = prepareSwap(_reserveA, _reserveB, _availableReserveB);

// do swap
amountIn = CpmmLib.getAmountIn(_amountOut, reserveA, reserveB);
reserveB = reserveB.add(amountIn);
reserveA = reserveA.sub(_amountOut);
amountIn = CpmmLib.getAmountIn(_amountOutB, reserveA, reserveB);
reserveA = reserveA.add(amountIn);
reserveB = reserveB.sub(_amountOutB);

(reserveA, reserveB) = _finishSwap(reserveA, reserveB, _availableBalance, _amountOut, _swapMultiplier, _liquidityFraction);
(reserveA, reserveB) = _finishSwap(reserveA, reserveB, _availableReserveB, _amountOutB, _swapMultiplier, _liquidityFraction);
}
}
3 changes: 3 additions & 0 deletions contracts/test/PrizePoolLiquidatorHarness.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@ pragma solidity 0.8.6;
import "../PrizePoolLiquidator.sol";

contract PrizePoolLiquidatorHarness is PrizePoolLiquidator {

constructor(address _owner) PrizePoolLiquidator(_owner) {}

}
22 changes: 22 additions & 0 deletions contracts/test/PrizePoolLiquidatorListenerStub.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.6;

import "../interfaces/IPrizePoolLiquidatorListener.sol";

contract PrizePoolLiquidatorListenerStub is IPrizePoolLiquidatorListener {

event AfterSwap(
IPrizePool prizePool,
ITicket ticket,
uint256 ticketAmount,
IERC20 token,
uint256 tokenAmount
);

function afterSwap(IPrizePool prizePool, ITicket ticket, uint256 ticketAmount, IERC20 token, uint256 tokenAmount) external override {
emit AfterSwap(
prizePool,ticket,ticketAmount,token,tokenAmount
);
}
}
4 changes: 2 additions & 2 deletions contracts/test/libraries/CpmmLibHarness.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import "../../libraries/CpmmLib.sol";
contract CpmmLibHarness {

// given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset
function getAmountOut(uint amountIn, uint x, uint y) external pure returns (uint amountOut) {
function getAmountOut(uint amountIn, uint x, uint y) external view returns (uint amountOut) {
return CpmmLib.getAmountOut(amountIn, x, y);
}

// given an output amount of an asset and pair reserves, returns a required input amount of the other asset
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn) {
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external view returns (uint amountIn) {
return CpmmLib.getAmountIn(amountOut, reserveIn, reserveOut);
}

Expand Down
Loading

0 comments on commit 734edaa

Please sign in to comment.