Created
September 21, 2022 23:48
-
-
Save JTraversa/9eff8ada74bfb1736554a6223dcc33d4 to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=soljson-v0.8.14+commit.80d49f37.js&optimize=true&runs=2000&gist=
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: BUSL-1.1 | |
pragma solidity 0.8.14; | |
import "./IERC20.sol"; | |
import "./IERC20Metadata.sol"; | |
import "./MinimalTransferHelper.sol"; | |
import "./CastU256U128.sol"; | |
import "./CastU256U112.sol"; | |
import "./CastU256I256.sol"; | |
import "./CastU128U112.sol"; | |
import "./CastU128I128.sol"; | |
import "./IPool.sol"; | |
import "./IFYToken.sol"; | |
import "./YieldMath.sol"; | |
import "./ERC20Permit.sol"; | |
/// @dev The Pool contract exchanges base for fyToken at a price defined by a specific formula. | |
contract Pool is IPool, ERC20Permit { | |
using CastU256U128 for uint256; | |
using CastU256U112 for uint256; | |
using CastU256I256 for uint256; | |
using CastU128U112 for uint128; | |
using CastU128I128 for uint128; | |
using MinimalTransferHelper for IERC20; | |
event Trade(uint32 maturity, address indexed from, address indexed to, int256 bases, int256 fyTokens); | |
event Liquidity(uint32 maturity, address indexed from, address indexed to, address indexed fyTokenTo, int256 bases, int256 fyTokens, int256 poolTokens); | |
event Sync(uint112 baseCached, uint112 fyTokenCached, uint256 cumulativeBalancesRatio); | |
int128 public immutable override ts; // 1 / Seconds in 10 years, in 64.64 | |
int128 public immutable override g1; // To be used when selling base to the pool | |
int128 public immutable override g2; // To be used when selling fyToken to the pool | |
uint32 public immutable override maturity; | |
uint96 public immutable override scaleFactor; // Scale up to 18 low decimal tokens to get the right precision in YieldMath | |
IERC20 public immutable override base; | |
IFYToken public immutable override fyToken; | |
uint112 private baseCached; // uses single storage slot, accessible via getCache | |
uint112 private fyTokenCached; // uses single storage slot, accessible via getCache | |
uint32 private blockTimestampLast; // uses single storage slot, accessible via getCache | |
uint256 public override cumulativeBalancesRatio; // Fixed point factor with 27 decimals (ray) | |
/// @dev Deploy a Pool. | |
/// Make sure that the fyToken follows ERC20 standards with regards to name, symbol and decimals | |
constructor(IERC20 base_, IFYToken fyToken_, int128 ts_, int128 g1_, int128 g2_) | |
ERC20Permit( | |
string(abi.encodePacked(IERC20Metadata(address(fyToken_)).name(), " LP")), | |
string(abi.encodePacked(IERC20Metadata(address(fyToken_)).symbol(), "LP")), | |
IERC20Metadata(address(fyToken_)).decimals() | |
) | |
{ | |
fyToken = fyToken_; | |
base = base_; | |
uint256 maturity_ = fyToken_.maturity(); | |
require (maturity_ <= type(uint32).max, "Pool: Maturity too far in the future"); | |
maturity = uint32(maturity_); | |
ts = ts_; | |
g1 = g1_; | |
g2 = g2_; | |
scaleFactor = uint96(10 ** (18 - uint96(decimals))); | |
} | |
/// @dev Trading can only be done before maturity | |
modifier beforeMaturity() { | |
require( | |
block.timestamp < maturity, | |
"Pool: Too late" | |
); | |
_; | |
} | |
// ---- Balances management ---- | |
/// @dev Updates the cache to match the actual balances. | |
function sync() external { | |
_update(_getBaseBalance(), _getFYTokenBalance(), baseCached, fyTokenCached); | |
} | |
/// @dev Returns the cached balances & last updated timestamp. | |
/// @return Cached base token balance. | |
/// @return Cached virtual FY token balance. | |
/// @return Timestamp that balances were last cached. | |
function getCache() | |
external view override | |
returns (uint112, uint112, uint32) | |
{ | |
return (baseCached, fyTokenCached, blockTimestampLast); | |
} | |
/// @dev Returns the "virtual" fyToken balance, which is the real balance plus the pool token supply. | |
function getFYTokenBalance() | |
public view override | |
returns(uint112) | |
{ | |
return _getFYTokenBalance(); | |
} | |
/// @dev Returns the base balance | |
function getBaseBalance() | |
public view override | |
returns(uint112) | |
{ | |
return _getBaseBalance(); | |
} | |
/// @dev Returns the "virtual" fyToken balance, which is the real balance plus the pool token supply. | |
function _getFYTokenBalance() | |
internal view | |
returns(uint112) | |
{ | |
return (fyToken.balanceOf(address(this)) + _totalSupply).u112(); | |
} | |
/// @dev Returns the base balance | |
function _getBaseBalance() | |
internal view | |
returns(uint112) | |
{ | |
return base.balanceOf(address(this)).u112(); | |
} | |
/// @dev Retrieve any base tokens not accounted for in the cache | |
function retrieveBase(address to) | |
external override | |
returns(uint128 retrieved) | |
{ | |
retrieved = _getBaseBalance() - baseCached; // Cache can never be above balances | |
base.safeTransfer(to, retrieved); | |
// Now the current balances match the cache, so no need to update the TWAR | |
} | |
/// @dev Retrieve any fyTokens not accounted for in the cache | |
function retrieveFYToken(address to) | |
external override | |
returns(uint128 retrieved) | |
{ | |
retrieved = _getFYTokenBalance() - fyTokenCached; // Cache can never be above balances | |
IERC20(address(fyToken)).safeTransfer(to, retrieved); | |
// Now the balances match the cache, so no need to update the TWAR | |
} | |
/// @dev Update cache and, on the first call per block, ratio accumulators | |
function _update(uint128 baseBalance, uint128 fyBalance, uint112 _baseCached, uint112 _fyTokenCached) private { | |
uint32 blockTimestamp = uint32(block.timestamp); | |
uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired | |
if (timeElapsed > 0 && _baseCached != 0 && _fyTokenCached != 0) { | |
// We multiply by 1e27 here so that r = t * y/x is a fixed point factor with 27 decimals | |
uint256 scaledFYTokenCached = uint256(_fyTokenCached) * 1e27; | |
cumulativeBalancesRatio += scaledFYTokenCached * timeElapsed / _baseCached; | |
} | |
baseCached = baseBalance.u112(); | |
fyTokenCached = fyBalance.u112(); | |
blockTimestampLast = blockTimestamp; | |
emit Sync(baseCached, fyTokenCached, cumulativeBalancesRatio); | |
} | |
// ---- Liquidity ---- | |
/// @dev Mint liquidity tokens in exchange for adding base and fyToken | |
/// The amount of liquidity tokens to mint is calculated from the amount of unaccounted for fyToken in this contract. | |
/// A proportional amount of base tokens need to be present in this contract, also unaccounted for. | |
/// @param to Wallet receiving the minted liquidity tokens. | |
/// @param remainder Wallet receiving any surplus base. | |
/// @param minRatio Minimum ratio of base to fyToken in the pool. | |
/// @param maxRatio Maximum ratio of base to fyToken in the pool. | |
/// @return The amount of liquidity tokens minted. | |
function mint(address to, address remainder, uint256 minRatio, uint256 maxRatio) | |
external override | |
returns (uint256, uint256, uint256) | |
{ | |
return _mintInternal(to, remainder, 0, minRatio, maxRatio); | |
} | |
/// @dev Mint liquidity tokens in exchange for adding only base | |
/// The amount of liquidity tokens is calculated from the amount of fyToken to buy from the pool, | |
/// plus the amount of unaccounted for fyToken in this contract. | |
/// The base tokens need to be present in this contract, unaccounted for. | |
/// @param to Wallet receiving the minted liquidity tokens. | |
/// @param remainder Wallet receiving any surplus base. | |
/// @param fyTokenToBuy Amount of `fyToken` being bought in the Pool, from this we calculate how much base it will be taken in. | |
/// @param minRatio Minimum ratio of base to fyToken in the pool. | |
/// @param maxRatio Maximum ratio of base to fyToken in the pool. | |
/// @return The amount of liquidity tokens minted. | |
function mintWithBase(address to, address remainder, uint256 fyTokenToBuy, uint256 minRatio, uint256 maxRatio) | |
external override | |
returns (uint256, uint256, uint256) | |
{ | |
return _mintInternal(to, remainder, fyTokenToBuy, minRatio, maxRatio); | |
} | |
/// @dev Mint liquidity tokens, with an optional internal trade to buy fyToken beforehand. | |
/// The amount of liquidity tokens is calculated from the amount of fyToken to buy from the pool, | |
/// plus the amount of unaccounted for fyToken in this contract. | |
/// The base tokens need to be present in this contract, unaccounted for. | |
/// @param to Wallet receiving the minted liquidity tokens. | |
/// @param remainder Wallet receiving any surplus base. | |
/// @param fyTokenToBuy Amount of `fyToken` being bought in the Pool, from this we calculate how much base it will be taken in. | |
/// @param minRatio Minimum ratio of base to fyToken in the pool. | |
/// @param maxRatio Minimum ratio of base to fyToken in the pool. | |
function _mintInternal(address to, address remainder, uint256 fyTokenToBuy, uint256 minRatio, uint256 maxRatio) | |
internal | |
returns (uint256 baseIn, uint256 fyTokenIn, uint256 tokensMinted) | |
{ | |
// Gather data | |
uint256 supply = _totalSupply; | |
(uint112 _baseCached, uint112 _fyTokenCached) = | |
(baseCached, fyTokenCached); | |
uint256 _realFYTokenCached = _fyTokenCached - supply; // The fyToken cache includes the virtual fyToken, equal to the supply | |
uint256 baseBalance = base.balanceOf(address(this)); | |
uint256 fyTokenBalance = fyToken.balanceOf(address(this)); | |
uint256 baseAvailable = baseBalance - _baseCached; | |
// Check the burn wasn't sandwiched | |
require ( | |
_realFYTokenCached == 0 || ( | |
uint256(_baseCached) * 1e18 / _realFYTokenCached >= minRatio && | |
uint256(_baseCached) * 1e18 / _realFYTokenCached <= maxRatio | |
), | |
"Pool: Reserves ratio changed" | |
); | |
// Calculate token amounts | |
if (supply == 0) { // Initialize at 1 pool token minted per base token supplied | |
baseIn = baseAvailable; | |
tokensMinted = baseIn; | |
} else if (_realFYTokenCached == 0) { // Edge case, no fyToken in the Pool after initialization | |
baseIn = baseAvailable; | |
tokensMinted = supply * baseIn / _baseCached; | |
} else { | |
// There is an optional virtual trade before the mint | |
uint256 baseToSell; | |
if (fyTokenToBuy > 0) { | |
baseToSell = _buyFYTokenPreview( | |
fyTokenToBuy.u128(), | |
_baseCached, | |
_fyTokenCached | |
); | |
} | |
// We use all the available fyTokens, plus a virtual trade if it happened, surplus is in base tokens | |
fyTokenIn = fyTokenBalance - _realFYTokenCached; | |
tokensMinted = (supply * (fyTokenToBuy + fyTokenIn)) / (_realFYTokenCached - fyTokenToBuy); | |
baseIn = baseToSell + ((_baseCached + baseToSell) * tokensMinted) / supply; | |
require(baseAvailable >= baseIn, "Pool: Not enough base token in"); | |
} | |
// Update TWAR | |
_update( | |
(_baseCached + baseIn).u128(), | |
(_fyTokenCached + fyTokenIn + tokensMinted).u128(), // Account for the "virtual" fyToken from the new minted LP tokens | |
_baseCached, | |
_fyTokenCached | |
); | |
// Execute mint | |
_mint(to, tokensMinted); | |
// Return any unused base | |
if (baseAvailable - baseIn > 0) base.safeTransfer(remainder, baseAvailable - baseIn); | |
emit Liquidity(maturity, msg.sender, to, address(0), -(baseIn.i256()), -(fyTokenIn.i256()), tokensMinted.i256()); | |
} | |
/// @dev Burn liquidity tokens in exchange for base and fyToken. | |
/// The liquidity tokens need to be in this contract. | |
/// @param baseTo Wallet receiving the base. | |
/// @param fyTokenTo Wallet receiving the fyToken. | |
/// @param minRatio Minimum ratio of base to fyToken in the pool. | |
/// @param maxRatio Maximum ratio of base to fyToken in the pool. | |
/// @return The amount of tokens burned and returned (tokensBurned, bases, fyTokens). | |
function burn(address baseTo, address fyTokenTo, uint256 minRatio, uint256 maxRatio) | |
external override | |
returns (uint256, uint256, uint256) | |
{ | |
return _burnInternal(baseTo, fyTokenTo, false, minRatio, maxRatio); | |
} | |
/// @dev Burn liquidity tokens in exchange for base. | |
/// The liquidity provider needs to have called `pool.approve`. | |
/// @param to Wallet receiving the base and fyToken. | |
/// @param minRatio Minimum ratio of base to fyToken in the pool. | |
/// @param maxRatio Minimum ratio of base to fyToken in the pool. | |
/// @return tokensBurned The amount of lp tokens burned. | |
/// @return baseOut The amount of base tokens returned. | |
function burnForBase(address to, uint256 minRatio, uint256 maxRatio) | |
external override | |
returns (uint256 tokensBurned, uint256 baseOut) | |
{ | |
(tokensBurned, baseOut, ) = _burnInternal(to, address(0), true, minRatio, maxRatio); | |
} | |
/// @dev Burn liquidity tokens in exchange for base. | |
/// The liquidity provider needs to have called `pool.approve`. | |
/// @param baseTo Wallet receiving the base. | |
/// @param fyTokenTo Wallet receiving the fyToken. | |
/// @param tradeToBase Whether the resulting fyToken should be traded for base tokens. | |
/// @param minRatio Minimum ratio of base to fyToken in the pool. | |
/// @param maxRatio Minimum ratio of base to fyToken in the pool. | |
/// @return tokensBurned The amount of pool tokens burned. | |
/// @return tokenOut The amount of base tokens returned. | |
/// @return fyTokenOut The amount of fyTokens returned. | |
function _burnInternal(address baseTo, address fyTokenTo, bool tradeToBase, uint256 minRatio, uint256 maxRatio) | |
internal | |
returns (uint256 tokensBurned, uint256 tokenOut, uint256 fyTokenOut) | |
{ | |
// Gather data | |
tokensBurned = _balanceOf[address(this)]; | |
uint256 supply = _totalSupply; | |
(uint112 _baseCached, uint112 _fyTokenCached) = | |
(baseCached, fyTokenCached); | |
uint256 _realFYTokenCached = _fyTokenCached - supply; // The fyToken cache includes the virtual fyToken, equal to the supply | |
// Check the burn wasn't sandwiched | |
require ( | |
_realFYTokenCached == 0 || ( | |
uint256(_baseCached) * 1e18 / _realFYTokenCached >= minRatio && | |
uint256(_baseCached) * 1e18 / _realFYTokenCached <= maxRatio | |
), | |
"Pool: Reserves ratio changed" | |
); | |
// Calculate trade | |
tokenOut = (tokensBurned * _baseCached) / supply; | |
fyTokenOut = (tokensBurned * _realFYTokenCached) / supply; | |
if (tradeToBase) { | |
tokenOut += YieldMath.baseOutForFYTokenIn( // This is a virtual sell | |
(_baseCached - tokenOut.u128()) * scaleFactor, // Cache, minus virtual burn | |
(_fyTokenCached - fyTokenOut.u128()) * scaleFactor, // Cache, minus virtual burn | |
fyTokenOut.u128() * scaleFactor, // Sell the virtual fyToken obtained | |
maturity - uint32(block.timestamp), // This can't be called after maturity | |
ts, | |
g2 | |
) / scaleFactor; | |
fyTokenOut = 0; | |
} | |
// Update TWAR | |
_update( | |
(_baseCached - tokenOut).u128(), | |
(_fyTokenCached - fyTokenOut - tokensBurned).u128(), | |
_baseCached, | |
_fyTokenCached | |
); | |
// Transfer assets | |
_burn(address(this), tokensBurned); | |
base.safeTransfer(baseTo, tokenOut); | |
if (fyTokenOut > 0) IERC20(address(fyToken)).safeTransfer(fyTokenTo, fyTokenOut); | |
emit Liquidity(maturity, msg.sender, baseTo, fyTokenTo, tokenOut.i256(), fyTokenOut.i256(), -(tokensBurned.i256())); | |
} | |
// ---- Trading ---- | |
/// @dev Sell base for fyToken. | |
/// The trader needs to have transferred the amount of base to sell to the pool before in the same transaction. | |
/// @param to Wallet receiving the fyToken being bought | |
/// @param min Minimm accepted amount of fyToken | |
/// @return Amount of fyToken that will be deposited on `to` wallet | |
function sellBase(address to, uint128 min) | |
external override | |
returns(uint128) | |
{ | |
// Calculate trade | |
(uint112 _baseCached, uint112 _fyTokenCached) = | |
(baseCached, fyTokenCached); | |
uint112 _baseBalance = _getBaseBalance(); | |
uint112 _fyTokenBalance = _getFYTokenBalance(); | |
uint128 baseIn = _baseBalance - _baseCached; | |
uint128 fyTokenOut = _sellBasePreview( | |
baseIn, | |
_baseCached, | |
_fyTokenBalance | |
); | |
// Slippage check | |
require( | |
fyTokenOut >= min, | |
"Pool: Not enough fyToken obtained" | |
); | |
// Update TWAR | |
_update( | |
_baseBalance, | |
_fyTokenBalance - fyTokenOut, | |
_baseCached, | |
_fyTokenCached | |
); | |
// Transfer assets | |
IERC20(address(fyToken)).safeTransfer(to, fyTokenOut); | |
emit Trade(maturity, msg.sender, to, -(baseIn.i128()), fyTokenOut.i128()); | |
return fyTokenOut; | |
} | |
/// @dev Returns how much fyToken would be obtained by selling `baseIn` base | |
/// @param baseIn Amount of base hypothetically sold. | |
/// @return Amount of fyToken hypothetically bought. | |
function sellBasePreview(uint128 baseIn) | |
external view override | |
returns(uint128) | |
{ | |
(uint112 _baseCached, uint112 _fyTokenCached) = | |
(baseCached, fyTokenCached); | |
return _sellBasePreview(baseIn, _baseCached, _fyTokenCached); | |
} | |
/// @dev Returns how much fyToken would be obtained by selling `baseIn` base | |
function _sellBasePreview( | |
uint128 baseIn, | |
uint112 baseBalance, | |
uint112 fyTokenBalance | |
) | |
private view | |
beforeMaturity | |
returns(uint128) | |
{ | |
uint128 fyTokenOut = YieldMath.fyTokenOutForBaseIn( | |
baseBalance * scaleFactor, | |
fyTokenBalance * scaleFactor, | |
baseIn * scaleFactor, | |
maturity - uint32(block.timestamp), // This can't be called after maturity | |
ts, | |
g1 | |
) / scaleFactor; | |
require( | |
fyTokenBalance - fyTokenOut >= baseBalance + baseIn, | |
"Pool: fyToken balance too low" | |
); | |
return fyTokenOut; | |
} | |
/// @dev Buy base for fyToken | |
/// The trader needs to have called `fyToken.approve` | |
/// @param to Wallet receiving the base being bought | |
/// @param tokenOut Amount of base being bought that will be deposited in `to` wallet | |
/// @param max Maximum amount of fyToken that will be paid for the trade | |
/// @return Amount of fyToken that will be taken from caller | |
function buyBase(address to, uint128 tokenOut, uint128 max) | |
external override | |
returns(uint128) | |
{ | |
// Calculate trade | |
uint128 fyTokenBalance = _getFYTokenBalance(); | |
(uint112 _baseCached, uint112 _fyTokenCached) = | |
(baseCached, fyTokenCached); | |
uint128 fyTokenIn = _buyBasePreview( | |
tokenOut, | |
_baseCached, | |
_fyTokenCached | |
); | |
require( | |
fyTokenBalance - _fyTokenCached >= fyTokenIn, | |
"Pool: Not enough fyToken in" | |
); | |
// Slippage check | |
require( | |
fyTokenIn <= max, | |
"Pool: Too much fyToken in" | |
); | |
// Update TWAR | |
_update( | |
_baseCached - tokenOut, | |
_fyTokenCached + fyTokenIn, | |
_baseCached, | |
_fyTokenCached | |
); | |
// Transfer assets | |
base.safeTransfer(to, tokenOut); | |
emit Trade(maturity, msg.sender, to, tokenOut.i128(), -(fyTokenIn.i128())); | |
return fyTokenIn; | |
} | |
/// @dev Returns how much fyToken would be required to buy `tokenOut` base. | |
/// @param tokenOut Amount of base hypothetically desired. | |
/// @return Amount of fyToken hypothetically required. | |
function buyBasePreview(uint128 tokenOut) | |
external view override | |
returns(uint128) | |
{ | |
(uint112 _baseCached, uint112 _fyTokenCached) = | |
(baseCached, fyTokenCached); | |
return _buyBasePreview(tokenOut, _baseCached, _fyTokenCached); | |
} | |
/// @dev Returns how much fyToken would be required to buy `tokenOut` base. | |
function _buyBasePreview( | |
uint128 tokenOut, | |
uint112 baseBalance, | |
uint112 fyTokenBalance | |
) | |
private view | |
beforeMaturity | |
returns(uint128) | |
{ | |
return YieldMath.fyTokenInForBaseOut( | |
baseBalance * scaleFactor, | |
fyTokenBalance * scaleFactor, | |
tokenOut * scaleFactor, | |
maturity - uint32(block.timestamp), // This can't be called after maturity | |
ts, | |
g2 | |
) / scaleFactor; | |
} | |
/// @dev Sell fyToken for base | |
/// The trader needs to have transferred the amount of fyToken to sell to the pool before in the same transaction. | |
/// @param to Wallet receiving the base being bought | |
/// @param min Minimm accepted amount of base | |
/// @return Amount of base that will be deposited on `to` wallet | |
function sellFYToken(address to, uint128 min) | |
external override | |
returns(uint128) | |
{ | |
// Calculate trade | |
(uint112 _baseCached, uint112 _fyTokenCached) = | |
(baseCached, fyTokenCached); | |
uint112 _fyTokenBalance = _getFYTokenBalance(); | |
uint112 _baseBalance = _getBaseBalance(); | |
uint128 fyTokenIn = _fyTokenBalance - _fyTokenCached; | |
uint128 baseOut = _sellFYTokenPreview( | |
fyTokenIn, | |
_baseCached, | |
_fyTokenCached | |
); | |
// Slippage check | |
require( | |
baseOut >= min, | |
"Pool: Not enough base obtained" | |
); | |
// Update TWAR | |
_update( | |
_baseBalance - baseOut, | |
_fyTokenBalance, | |
_baseCached, | |
_fyTokenCached | |
); | |
// Transfer assets | |
base.safeTransfer(to, baseOut); | |
emit Trade(maturity, msg.sender, to, baseOut.i128(), -(fyTokenIn.i128())); | |
return baseOut; | |
} | |
/// @dev Returns how much base would be obtained by selling `fyTokenIn` fyToken. | |
/// @param fyTokenIn Amount of fyToken hypothetically sold. | |
/// @return Amount of base hypothetically bought. | |
function sellFYTokenPreview(uint128 fyTokenIn) | |
external view override | |
returns(uint128) | |
{ | |
(uint112 _baseCached, uint112 _fyTokenCached) = | |
(baseCached, fyTokenCached); | |
return _sellFYTokenPreview(fyTokenIn, _baseCached, _fyTokenCached); | |
} | |
/// @dev Returns how much base would be obtained by selling `fyTokenIn` fyToken. | |
function _sellFYTokenPreview( | |
uint128 fyTokenIn, | |
uint112 baseBalance, | |
uint112 fyTokenBalance | |
) | |
private view | |
beforeMaturity | |
returns(uint128) | |
{ | |
return YieldMath.baseOutForFYTokenIn( | |
baseBalance * scaleFactor, | |
fyTokenBalance * scaleFactor, | |
fyTokenIn * scaleFactor, | |
maturity - uint32(block.timestamp), // This can't be called after maturity | |
ts, | |
g2 | |
) / scaleFactor; | |
} | |
/// @dev Buy fyToken for base | |
/// The trader needs to have called `base.approve` | |
/// @param to Wallet receiving the fyToken being bought | |
/// @param fyTokenOut Amount of fyToken being bought that will be deposited in `to` wallet | |
/// @param max Maximum amount of base token that will be paid for the trade | |
/// @return Amount of base that will be taken from caller's wallet | |
function buyFYToken(address to, uint128 fyTokenOut, uint128 max) | |
external override | |
returns(uint128) | |
{ | |
// Calculate trade | |
uint128 baseBalance = _getBaseBalance(); | |
(uint112 _baseCached, uint112 _fyTokenCached) = | |
(baseCached, fyTokenCached); | |
uint128 baseIn = _buyFYTokenPreview( | |
fyTokenOut, | |
_baseCached, | |
_fyTokenCached | |
); | |
require( | |
baseBalance - _baseCached >= baseIn, | |
"Pool: Not enough base token in" | |
); | |
// Slippage check | |
require( | |
baseIn <= max, | |
"Pool: Too much base token in" | |
); | |
// Update TWAR | |
_update( | |
_baseCached + baseIn, | |
_fyTokenCached - fyTokenOut, | |
_baseCached, | |
_fyTokenCached | |
); | |
// Transfer assets | |
IERC20(address(fyToken)).safeTransfer(to, fyTokenOut); | |
emit Trade(maturity, msg.sender, to, -(baseIn.i128()), fyTokenOut.i128()); | |
return baseIn; | |
} | |
/// @dev Returns how much base would be required to buy `fyTokenOut` fyToken. | |
/// @param fyTokenOut Amount of fyToken hypothetically desired. | |
/// @return Amount of base hypothetically required. | |
function buyFYTokenPreview(uint128 fyTokenOut) | |
external view override | |
returns(uint128) | |
{ | |
(uint112 _baseCached, uint112 _fyTokenCached) = | |
(baseCached, fyTokenCached); | |
return _buyFYTokenPreview(fyTokenOut, _baseCached, _fyTokenCached); | |
} | |
/// @dev Returns how much base would be required to buy `fyTokenOut` fyToken. | |
function _buyFYTokenPreview( | |
uint128 fyTokenOut, | |
uint128 baseBalance, | |
uint128 fyTokenBalance | |
) | |
private view | |
beforeMaturity | |
returns(uint128) | |
{ | |
uint128 baseIn = YieldMath.baseInForFYTokenOut( | |
baseBalance * scaleFactor, | |
fyTokenBalance * scaleFactor, | |
fyTokenOut * scaleFactor, | |
maturity - uint32(block.timestamp), // This can't be called after maturity | |
ts, | |
g1 | |
) / scaleFactor; | |
require( | |
fyTokenBalance - fyTokenOut >= baseBalance + baseIn, | |
"Pool: fyToken balance too low" | |
); | |
return baseIn; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment