Contract Source Code:
File 1 of 1 : XPool
// File: contracts/XVersion.sol
pragma solidity 0.5.17;
contract XVersion {
function getVersion() external view returns (bytes32);
}
contract XApollo is XVersion {
function getVersion() external view returns (bytes32) {
return bytes32("APOLLO");
}
}
// File: contracts/XConst.sol
pragma solidity 0.5.17;
contract XConst {
uint256 public constant BONE = 10**18;
uint256 public constant MIN_BOUND_TOKENS = 2;
uint256 public constant MAX_BOUND_TOKENS = 8;
uint256 public constant EXIT_ZERO_FEE = 0;
uint256 public constant MIN_WEIGHT = BONE;
uint256 public constant MAX_WEIGHT = BONE * 50;
uint256 public constant MAX_TOTAL_WEIGHT = BONE * 50;
// min effective value: 0.000001 TOKEN
uint256 public constant MIN_BALANCE = 10**6;
// BONE/(10**10) XPT
uint256 public constant MIN_POOL_AMOUNT = 10**8;
uint256 public constant INIT_POOL_SUPPLY = BONE * 100;
uint256 public constant MAX_IN_RATIO = BONE / 2;
uint256 public constant MAX_OUT_RATIO = (BONE / 3) + 1 wei;
}
// File: contracts/lib/XNum.sol
pragma solidity 0.5.17;
library XNum {
uint256 public constant BONE = 10**18;
uint256 public constant MIN_BPOW_BASE = 1 wei;
uint256 public constant MAX_BPOW_BASE = (2 * BONE) - 1 wei;
uint256 public constant BPOW_PRECISION = BONE / 10**10;
function btoi(uint256 a) internal pure returns (uint256) {
return a / BONE;
}
function bfloor(uint256 a) internal pure returns (uint256) {
return btoi(a) * BONE;
}
function badd(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "ERR_ADD_OVERFLOW");
return c;
}
function bsub(uint256 a, uint256 b) internal pure returns (uint256) {
(uint256 c, bool flag) = bsubSign(a, b);
require(!flag, "ERR_SUB_UNDERFLOW");
return c;
}
function bsubSign(uint256 a, uint256 b)
internal
pure
returns (uint256, bool)
{
if (a >= b) {
return (a - b, false);
} else {
return (b - a, true);
}
}
function bmul(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c0 = a * b;
require(a == 0 || c0 / a == b, "ERR_MUL_OVERFLOW");
uint256 c1 = c0 + (BONE / 2);
require(c1 >= c0, "ERR_MUL_OVERFLOW");
uint256 c2 = c1 / BONE;
return c2;
}
function bdiv(uint256 a, uint256 b) internal pure returns (uint256) {
require(b != 0, "ERR_DIV_ZERO");
uint256 c0 = a * BONE;
require(a == 0 || c0 / a == BONE, "ERR_DIV_INTERNAL"); // bmul overflow
uint256 c1 = c0 + (b / 2);
require(c1 >= c0, "ERR_DIV_INTERNAL"); // badd require
uint256 c2 = c1 / b;
return c2;
}
// DSMath.wpow
function bpowi(uint256 a, uint256 n) internal pure returns (uint256) {
uint256 z = n % 2 != 0 ? a : BONE;
for (n /= 2; n != 0; n /= 2) {
a = bmul(a, a);
if (n % 2 != 0) {
z = bmul(z, a);
}
}
return z;
}
// Compute b^(e.w) by splitting it into (b^e)*(b^0.w).
// Use `bpowi` for `b^e` and `bpowK` for k iterations
// of approximation of b^0.w
function bpow(uint256 base, uint256 exp) internal pure returns (uint256) {
require(base >= MIN_BPOW_BASE, "ERR_BPOW_BASE_TOO_LOW");
require(base <= MAX_BPOW_BASE, "ERR_BPOW_BASE_TOO_HIGH");
uint256 whole = bfloor(exp);
uint256 remain = bsub(exp, whole);
uint256 wholePow = bpowi(base, btoi(whole));
if (remain == 0) {
return wholePow;
}
uint256 partialResult = bpowApprox(base, remain, BPOW_PRECISION);
return bmul(wholePow, partialResult);
}
function bpowApprox(
uint256 base,
uint256 exp,
uint256 precision
) internal pure returns (uint256) {
// term 0:
uint256 a = exp;
(uint256 x, bool xneg) = bsubSign(base, BONE);
uint256 term = BONE;
uint256 sum = term;
bool negative = false;
// term(k) = numer / denom
// = (product(a - i + 1, i=1-->k) * x^k) / (k!)
// each iteration, multiply previous term by (a-(k-1)) * x / k
// continue until term is less than precision
for (uint256 i = 1; term >= precision; i++) {
uint256 bigK = i * BONE;
(uint256 c, bool cneg) = bsubSign(a, bsub(bigK, BONE));
term = bmul(term, bmul(c, x));
term = bdiv(term, bigK);
if (term == 0) break;
if (xneg) negative = !negative;
if (cneg) negative = !negative;
if (negative) {
sum = bsub(sum, term);
} else {
sum = badd(sum, term);
}
}
return sum;
}
}
// File: contracts/interface/IERC20.sol
pragma solidity 0.5.17;
interface IERC20 {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
function totalSupply() external view returns (uint256);
function balanceOf(address _owner) external view returns (uint256 balance);
function transfer(address _to, uint256 _value)
external
returns (bool success);
function transferFrom(
address _from,
address _to,
uint256 _value
) external returns (bool success);
function approve(address _spender, uint256 _value)
external
returns (bool success);
function allowance(address _owner, address _spender)
external
view
returns (uint256 remaining);
}
// File: contracts/XPToken.sol
pragma solidity 0.5.17;
// Highly opinionated token implementation
contract XTokenBase {
using XNum for uint256;
mapping(address => uint256) internal _balance;
mapping(address => mapping(address => uint256)) internal _allowance;
uint256 internal _totalSupply;
event Approval(address indexed src, address indexed dst, uint256 amt);
event Transfer(address indexed src, address indexed dst, uint256 amt);
function _mint(uint256 amt) internal {
_balance[address(this)] = (_balance[address(this)]).badd(amt);
_totalSupply = _totalSupply.badd(amt);
emit Transfer(address(0), address(this), amt);
}
function _burn(uint256 amt) internal {
require(_balance[address(this)] >= amt, "ERR_INSUFFICIENT_BAL");
_balance[address(this)] = (_balance[address(this)]).bsub(amt);
_totalSupply = _totalSupply.bsub(amt);
emit Transfer(address(this), address(0), amt);
}
function _move(
address src,
address dst,
uint256 amt
) internal {
require(_balance[src] >= amt, "ERR_INSUFFICIENT_BAL");
_balance[src] = (_balance[src]).bsub(amt);
_balance[dst] = (_balance[dst]).badd(amt);
emit Transfer(src, dst, amt);
}
}
contract XPToken is XTokenBase, IERC20, XApollo {
using XNum for uint256;
string private constant _name = "XDeFi Pool Token";
string private constant _symbol = "XPT";
uint8 private constant _decimals = 18;
function name() external view returns (string memory) {
return _name;
}
function symbol() external view returns (string memory) {
return _symbol;
}
function decimals() external view returns (uint8) {
return _decimals;
}
function allowance(address src, address dst)
external
view
returns (uint256)
{
return _allowance[src][dst];
}
function balanceOf(address whom) external view returns (uint256) {
return _balance[whom];
}
function totalSupply() public view returns (uint256) {
return _totalSupply;
}
function approve(address dst, uint256 amt) external returns (bool) {
_allowance[msg.sender][dst] = amt;
emit Approval(msg.sender, dst, amt);
return true;
}
function transfer(address dst, uint256 amt) external returns (bool) {
_move(msg.sender, dst, amt);
return true;
}
function transferFrom(
address src,
address dst,
uint256 amt
) external returns (bool) {
require(
msg.sender == src || amt <= _allowance[src][msg.sender],
"ERR_BTOKEN_BAD_CALLER"
);
_move(src, dst, amt);
if (msg.sender != src && _allowance[src][msg.sender] != uint256(-1)) {
_allowance[src][msg.sender] = (_allowance[src][msg.sender]).bsub(
amt
);
emit Approval(msg.sender, dst, _allowance[src][msg.sender]);
}
return true;
}
}
// File: contracts/lib/XMath.sol
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
pragma solidity 0.5.17;
library XMath {
using XNum for uint256;
uint256 public constant BONE = 10**18;
uint256 public constant EXIT_ZERO_FEE = 0;
/**********************************************************************************************
// calcSpotPrice //
// sP = spotPrice //
// bI = tokenBalanceIn ( bI / wI ) 1 //
// bO = tokenBalanceOut sP = ----------- * ---------- //
// wI = tokenWeightIn ( bO / wO ) ( 1 - sF ) //
// wO = tokenWeightOut //
// sF = swapFee //
**********************************************************************************************/
function calcSpotPrice(
uint256 tokenBalanceIn,
uint256 tokenWeightIn,
uint256 tokenBalanceOut,
uint256 tokenWeightOut,
uint256 swapFee
) public pure returns (uint256 spotPrice) {
uint256 numer = tokenBalanceIn.bdiv(tokenWeightIn);
uint256 denom = tokenBalanceOut.bdiv(tokenWeightOut);
uint256 ratio = numer.bdiv(denom);
uint256 scale = BONE.bdiv(BONE.bsub(swapFee));
return (spotPrice = ratio.bmul(scale));
}
/**********************************************************************************************
// calcOutGivenIn //
// aO = tokenAmountOut //
// bO = tokenBalanceOut //
// bI = tokenBalanceIn / / bI \ (wI / wO) \ //
// aI = tokenAmountIn aO = bO * | 1 - | -------------------------- | ^ | //
// wI = tokenWeightIn \ \ ( bI + ( aI * ( 1 - sF )) / / //
// wO = tokenWeightOut //
// sF = swapFee //
**********************************************************************************************/
function calcOutGivenIn(
uint256 tokenBalanceIn,
uint256 tokenWeightIn,
uint256 tokenBalanceOut,
uint256 tokenWeightOut,
uint256 tokenAmountIn,
uint256 swapFee
) public pure returns (uint256 tokenAmountOut) {
uint256 weightRatio;
if (tokenWeightIn == tokenWeightOut) {
weightRatio = 1 * BONE;
} else if (tokenWeightIn >> 1 == tokenWeightOut) {
weightRatio = 2 * BONE;
} else {
weightRatio = tokenWeightIn.bdiv(tokenWeightOut);
}
uint256 adjustedIn = BONE.bsub(swapFee);
adjustedIn = tokenAmountIn.bmul(adjustedIn);
uint256 y = tokenBalanceIn.bdiv(tokenBalanceIn.badd(adjustedIn));
uint256 foo;
if (tokenWeightIn == tokenWeightOut) {
foo = y;
} else if (tokenWeightIn >> 1 == tokenWeightOut) {
foo = y.bmul(y);
} else {
foo = y.bpow(weightRatio);
}
uint256 bar = BONE.bsub(foo);
tokenAmountOut = tokenBalanceOut.bmul(bar);
return tokenAmountOut;
}
/**********************************************************************************************
// calcInGivenOut //
// aI = tokenAmountIn //
// bO = tokenBalanceOut / / bO \ (wO / wI) \ //
// bI = tokenBalanceIn bI * | | ------------ | ^ - 1 | //
// aO = tokenAmountOut aI = \ \ ( bO - aO ) / / //
// wI = tokenWeightIn -------------------------------------------- //
// wO = tokenWeightOut ( 1 - sF ) //
// sF = swapFee //
**********************************************************************************************/
function calcInGivenOut(
uint256 tokenBalanceIn,
uint256 tokenWeightIn,
uint256 tokenBalanceOut,
uint256 tokenWeightOut,
uint256 tokenAmountOut,
uint256 swapFee
) public pure returns (uint256 tokenAmountIn) {
uint256 weightRatio;
if (tokenWeightOut == tokenWeightIn) {
weightRatio = 1 * BONE;
} else if (tokenWeightOut >> 1 == tokenWeightIn) {
weightRatio = 2 * BONE;
} else {
weightRatio = tokenWeightOut.bdiv(tokenWeightIn);
}
uint256 diff = tokenBalanceOut.bsub(tokenAmountOut);
uint256 y = tokenBalanceOut.bdiv(diff);
uint256 foo;
if (tokenWeightOut == tokenWeightIn) {
foo = y;
} else if (tokenWeightOut >> 1 == tokenWeightIn) {
foo = y.bmul(y);
} else {
foo = y.bpow(weightRatio);
}
foo = foo.bsub(BONE);
tokenAmountIn = BONE.bsub(swapFee);
tokenAmountIn = tokenBalanceIn.bmul(foo).bdiv(tokenAmountIn);
return tokenAmountIn;
}
/**********************************************************************************************
// calcPoolOutGivenSingleIn //
// pAo = poolAmountOut / \ //
// tAi = tokenAmountIn /// / // wI \ \\ \ wI \ //
// wI = tokenWeightIn //| tAi *| 1 - || 1 - -- | * sF || + tBi \ -- \ //
// tW = totalWeight pAo=|| \ \ \\ tW / // | ^ tW | * pS - pS //
// tBi = tokenBalanceIn \\ ------------------------------------- / / //
// pS = poolSupply \\ tBi / / //
// sF = swapFee \ / //
**********************************************************************************************/
function calcPoolOutGivenSingleIn(
uint256 tokenBalanceIn,
uint256 tokenWeightIn,
uint256 poolSupply,
uint256 totalWeight,
uint256 tokenAmountIn,
uint256 swapFee
) public pure returns (uint256 poolAmountOut) {
// Charge the trading fee for the proportion of tokenAi
/// which is implicitly traded to the other pool tokens.
// That proportion is (1- weightTokenIn)
// tokenAiAfterFee = tAi * (1 - (1-weightTi) * poolFee);
uint256 normalizedWeight = tokenWeightIn.bdiv(totalWeight);
uint256 zaz = BONE.bsub(normalizedWeight).bmul(swapFee);
uint256 tokenAmountInAfterFee = tokenAmountIn.bmul(BONE.bsub(zaz));
uint256 newTokenBalanceIn = tokenBalanceIn.badd(tokenAmountInAfterFee);
uint256 tokenInRatio = newTokenBalanceIn.bdiv(tokenBalanceIn);
// uint newPoolSupply = (ratioTi ^ weightTi) * poolSupply;
uint256 poolRatio = tokenInRatio.bpow(normalizedWeight);
uint256 newPoolSupply = poolRatio.bmul(poolSupply);
poolAmountOut = newPoolSupply.bsub(poolSupply);
return poolAmountOut;
}
/**********************************************************************************************
// calcSingleOutGivenPoolIn //
// tAo = tokenAmountOut / / \\ //
// bO = tokenBalanceOut / // pS - (pAi * (1 - eF)) \ / 1 \ \\ //
// pAi = poolAmountIn | bO - || ----------------------- | ^ | --------- | * b0 || //
// ps = poolSupply \ \\ pS / \(wO / tW)/ // //
// wI = tokenWeightIn tAo = \ \ // //
// tW = totalWeight / / wO \ \ //
// sF = swapFee * | 1 - | 1 - ---- | * sF | //
// eF = exitFee \ \ tW / / //
**********************************************************************************************/
function calcSingleOutGivenPoolIn(
uint256 tokenBalanceOut,
uint256 tokenWeightOut,
uint256 poolSupply,
uint256 totalWeight,
uint256 poolAmountIn,
uint256 swapFee
) public pure returns (uint256 tokenAmountOut) {
uint256 normalizedWeight = tokenWeightOut.bdiv(totalWeight);
// charge exit fee on the pool token side
// pAiAfterExitFee = pAi*(1-exitFee)
uint256 poolAmountInAfterExitFee =
poolAmountIn.bmul(BONE.bsub(EXIT_ZERO_FEE));
uint256 newPoolSupply = poolSupply.bsub(poolAmountInAfterExitFee);
uint256 poolRatio = newPoolSupply.bdiv(poolSupply);
// newBalTo = poolRatio^(1/weightTo) * balTo;
uint256 tokenOutRatio = poolRatio.bpow(BONE.bdiv(normalizedWeight));
uint256 newTokenBalanceOut = tokenOutRatio.bmul(tokenBalanceOut);
uint256 tokenAmountOutBeforeSwapFee =
tokenBalanceOut.bsub(newTokenBalanceOut);
// charge swap fee on the output token side
//uint tAo = tAoBeforeSwapFee * (1 - (1-weightTo) * swapFee)
uint256 zaz = BONE.bsub(normalizedWeight).bmul(swapFee);
tokenAmountOut = tokenAmountOutBeforeSwapFee.bmul(BONE.bsub(zaz));
return tokenAmountOut;
}
}
// File: contracts/interface/IXConfig.sol
pragma solidity 0.5.17;
interface IXConfig {
function getCore() external view returns (address);
function getSAFU() external view returns (address);
function getMaxExitFee() external view returns (uint256);
function getSafuFee() external view returns (uint256);
function getSwapProxy() external view returns (address);
function dedupPool(address[] calldata tokens, uint256[] calldata denorms)
external
returns (bool exist, bytes32 sig);
function addPoolSig(bytes32 sig, address pool) external;
}
// File: contracts/XPool.sol
pragma solidity 0.5.17;
contract XPool is XApollo, XPToken, XConst {
using XNum for uint256;
//Swap Fees: 0.1%, 0.25%, 1%, 2.5%, 10%
uint256[5] public SWAP_FEES = [
BONE / 1000,
(25 * BONE) / 10000,
BONE / 100,
(25 * BONE) / 1000,
BONE / 10
];
struct Record {
bool bound; // is token bound to pool
uint256 index; // private
uint256 denorm; // denormalized weight
uint256 balance;
}
event LOG_SWAP(
address indexed caller,
address indexed tokenIn,
address indexed tokenOut,
uint256 tokenAmountIn,
uint256 tokenAmountOut
);
event LOG_REFER(
address indexed caller,
address indexed ref,
address indexed tokenIn,
uint256 fee
);
event LOG_JOIN(
address indexed caller,
address indexed tokenIn,
uint256 tokenAmountIn
);
event LOG_EXIT(
address indexed caller,
address indexed tokenOut,
uint256 tokenAmountOut
);
event LOG_BIND(
address indexed caller,
address indexed token,
uint256 denorm,
uint256 balance
);
event LOG_UPDATE_SAFU(address indexed safu, uint256 fee);
event LOG_EXIT_FEE(uint256 fee);
event LOG_FINAL(uint256 swapFee);
event SET_CONTROLLER(address indexed manager);
event UPDATE_FARM(address indexed caller, bool isFarm);
// anonymous event
event LOG_CALL(
bytes4 indexed sig,
address indexed caller,
bytes data
) anonymous;
modifier _logs_() {
emit LOG_CALL(msg.sig, msg.sender, msg.data);
_;
}
modifier _lock_() {
require(!_mutex, "ERR_REENTRY");
_mutex = true;
_;
_mutex = false;
}
modifier _viewlock_() {
require(!_mutex, "ERR_REENTRY");
_;
}
bool private _mutex;
address public controller; // has CONTROL role
// `finalize` require CONTROL, `finalize` sets `can SWAP and can JOIN`
bool public finalized;
uint256 public swapFee;
uint256 public exitFee;
// Pool Governance
address public SAFU;
uint256 public safuFee;
bool public isFarmPool;
address[] internal _tokens;
mapping(address => Record) internal _records;
uint256 private _totalWeight;
IXConfig public xconfig;
address public origin;
constructor(address _xconfig, address _controller) public {
controller = _controller;
origin = tx.origin;
swapFee = SWAP_FEES[1];
exitFee = EXIT_ZERO_FEE;
finalized = false;
xconfig = IXConfig(_xconfig);
SAFU = xconfig.getSAFU();
safuFee = xconfig.getSafuFee();
}
function isBound(address t) external view returns (bool) {
return _records[t].bound;
}
function getNumTokens() external view returns (uint256) {
return _tokens.length;
}
function getFinalTokens()
external
view
_viewlock_
returns (address[] memory tokens)
{
require(finalized, "ERR_NOT_FINALIZED");
return _tokens;
}
function getDenormalizedWeight(address token)
external
view
_viewlock_
returns (uint256)
{
require(_records[token].bound, "ERR_NOT_BOUND");
return _records[token].denorm;
}
function getTotalDenormalizedWeight()
external
view
_viewlock_
returns (uint256)
{
return _totalWeight;
}
function getNormalizedWeight(address token)
external
view
_viewlock_
returns (uint256)
{
require(_records[token].bound, "ERR_NOT_BOUND");
uint256 denorm = _records[token].denorm;
return denorm.bdiv(_totalWeight);
}
function getBalance(address token)
external
view
_viewlock_
returns (uint256)
{
require(_records[token].bound, "ERR_NOT_BOUND");
return _records[token].balance;
}
function setController(address manager) external _logs_ {
require(msg.sender == controller, "ERR_NOT_CONTROLLER");
require(manager != address(0), "ERR_ZERO_ADDR");
controller = manager;
emit SET_CONTROLLER(manager);
}
function setExitFee(uint256 fee) external {
require(!finalized, "ERR_IS_FINALIZED");
require(msg.sender == controller, "ERR_NOT_CONTROLLER");
require(fee <= xconfig.getMaxExitFee(), "INVALID_EXIT_FEE");
exitFee = fee;
emit LOG_EXIT_FEE(fee);
}
// allow SAFU address and SAFE FEE be updated by xconfig
function updateSafu(address safu, uint256 fee) external {
require(msg.sender == address(xconfig), "ERR_NOT_CONFIG");
require(safu != address(0), "ERR_ZERO_ADDR");
SAFU = safu;
safuFee = fee;
emit LOG_UPDATE_SAFU(safu, fee);
}
// allow isFarmPool be updated by xconfig
function updateFarm(bool isFarm) external {
require(msg.sender == address(xconfig), "ERR_NOT_CONFIG");
isFarmPool = isFarm;
emit UPDATE_FARM(msg.sender, isFarm);
}
function bind(address token, uint256 denorm) external _lock_ {
require(msg.sender == controller, "ERR_NOT_CONTROLLER");
require(!_records[token].bound, "ERR_IS_BOUND");
require(!finalized, "ERR_IS_FINALIZED");
require(_tokens.length < MAX_BOUND_TOKENS, "ERR_MAX_TOKENS");
require(denorm >= MIN_WEIGHT, "ERR_MIN_WEIGHT");
require(denorm <= MAX_WEIGHT, "ERR_MAX_WEIGHT");
uint256 balance = IERC20(token).balanceOf(address(this));
uint256 decimal = 10**uint256(IERC20(token).decimals());
require(decimal >= 10**6, "ERR_TOO_SMALL");
// 0.000001 TOKEN
require(balance >= decimal / MIN_BALANCE, "ERR_MIN_BALANCE");
_totalWeight = _totalWeight.badd(denorm);
require(_totalWeight <= MAX_TOTAL_WEIGHT, "ERR_MAX_TOTAL_WEIGHT");
_records[token] = Record({
bound: true,
index: _tokens.length,
denorm: denorm,
balance: balance
});
_tokens.push(token);
emit LOG_BIND(msg.sender, token, denorm, balance);
}
// _swapFee must be one of SWAP_FEES
function finalize(uint256 _swapFee) external _lock_ {
require(msg.sender == controller, "ERR_NOT_CONTROLLER");
require(!finalized, "ERR_IS_FINALIZED");
require(_tokens.length >= MIN_BOUND_TOKENS, "ERR_MIN_TOKENS");
require(_tokens.length <= MAX_BOUND_TOKENS, "ERR_MAX_TOKENS");
require(_swapFee >= SWAP_FEES[0], "ERR_MIN_FEE");
require(_swapFee <= SWAP_FEES[SWAP_FEES.length - 1], "ERR_MAX_FEE");
bool found = false;
for (uint256 i = 0; i < SWAP_FEES.length; i++) {
if (_swapFee == SWAP_FEES[i]) {
found = true;
break;
}
}
require(found, "ERR_INVALID_SWAP_FEE");
swapFee = _swapFee;
finalized = true;
_mintPoolShare(INIT_POOL_SUPPLY);
_pushPoolShare(msg.sender, INIT_POOL_SUPPLY);
emit LOG_FINAL(swapFee);
}
// Absorb any tokens that have been sent to this contract into the pool
function gulp(address token) external _logs_ _lock_ {
require(_records[token].bound, "ERR_NOT_BOUND");
_records[token].balance = IERC20(token).balanceOf(address(this));
}
function getSpotPrice(address tokenIn, address tokenOut)
external
view
_viewlock_
returns (uint256 spotPrice)
{
require(_records[tokenIn].bound, "ERR_NOT_BOUND");
require(_records[tokenOut].bound, "ERR_NOT_BOUND");
Record storage inRecord = _records[tokenIn];
Record storage outRecord = _records[tokenOut];
return
XMath.calcSpotPrice(
inRecord.balance,
inRecord.denorm,
outRecord.balance,
outRecord.denorm,
swapFee
);
}
function getSpotPriceSansFee(address tokenIn, address tokenOut)
external
view
_viewlock_
returns (uint256 spotPrice)
{
require(_records[tokenIn].bound, "ERR_NOT_BOUND");
require(_records[tokenOut].bound, "ERR_NOT_BOUND");
Record storage inRecord = _records[tokenIn];
Record storage outRecord = _records[tokenOut];
return
XMath.calcSpotPrice(
inRecord.balance,
inRecord.denorm,
outRecord.balance,
outRecord.denorm,
0
);
}
function joinPool(uint256 poolAmountOut, uint256[] calldata maxAmountsIn)
external
_lock_
{
require(finalized, "ERR_NOT_FINALIZED");
require(maxAmountsIn.length == _tokens.length, "ERR_LENGTH_MISMATCH");
uint256 poolTotal = totalSupply();
uint256 ratio = poolAmountOut.bdiv(poolTotal);
require(ratio != 0, "ERR_MATH_APPROX");
for (uint256 i = 0; i < _tokens.length; i++) {
address t = _tokens[i];
uint256 bal = _records[t].balance;
uint256 tokenAmountIn = ratio.bmul(bal);
require(tokenAmountIn != 0, "ERR_MATH_APPROX");
require(tokenAmountIn <= maxAmountsIn[i], "ERR_LIMIT_IN");
_records[t].balance = (_records[t].balance).badd(tokenAmountIn);
emit LOG_JOIN(msg.sender, t, tokenAmountIn);
_pullUnderlying(t, msg.sender, tokenAmountIn);
}
_mintPoolShare(poolAmountOut);
_pushPoolShare(msg.sender, poolAmountOut);
}
function exitPool(uint256 poolAmountIn, uint256[] calldata minAmountsOut)
external
_lock_
{
require(finalized, "ERR_NOT_FINALIZED");
require(minAmountsOut.length == _tokens.length, "ERR_LENGTH_MISMATCH");
// min pool amount
require(poolAmountIn >= MIN_POOL_AMOUNT, "ERR_MIN_AMOUNT");
uint256 poolTotal = totalSupply();
uint256 _exitFee = poolAmountIn.bmul(exitFee);
// never charge exitFee to pool origin
if (msg.sender == origin) {
_exitFee = 0;
}
uint256 pAiAfterExitFee = poolAmountIn.bsub(_exitFee);
uint256 ratio = pAiAfterExitFee.bdiv(poolTotal);
require(ratio != 0, "ERR_MATH_APPROX");
_pullPoolShare(msg.sender, poolAmountIn);
// send exitFee to origin
if (_exitFee > 0) {
_pushPoolShare(origin, _exitFee);
}
_burnPoolShare(pAiAfterExitFee);
for (uint256 i = 0; i < _tokens.length; i++) {
address t = _tokens[i];
uint256 bal = _records[t].balance;
uint256 tokenAmountOut = ratio.bmul(bal);
require(tokenAmountOut != 0, "ERR_MATH_APPROX");
require(tokenAmountOut >= minAmountsOut[i], "ERR_LIMIT_OUT");
_records[t].balance = (_records[t].balance).bsub(tokenAmountOut);
emit LOG_EXIT(msg.sender, t, tokenAmountOut);
_pushUnderlying(t, msg.sender, tokenAmountOut);
}
}
function swapExactAmountIn(
address tokenIn,
uint256 tokenAmountIn,
address tokenOut,
uint256 minAmountOut,
uint256 maxPrice
) external returns (uint256 tokenAmountOut, uint256 spotPriceAfter) {
return
swapExactAmountInRefer(
tokenIn,
tokenAmountIn,
tokenOut,
minAmountOut,
maxPrice,
address(0x0)
);
}
function swapExactAmountInRefer(
address tokenIn,
uint256 tokenAmountIn,
address tokenOut,
uint256 minAmountOut,
uint256 maxPrice,
address referrer
) public _lock_ returns (uint256 tokenAmountOut, uint256 spotPriceAfter) {
require(_records[tokenIn].bound, "ERR_NOT_BOUND");
require(_records[tokenOut].bound, "ERR_NOT_BOUND");
require(finalized, "ERR_NOT_FINALIZED");
Record storage inRecord = _records[address(tokenIn)];
Record storage outRecord = _records[address(tokenOut)];
require(
tokenAmountIn <= (inRecord.balance).bmul(MAX_IN_RATIO),
"ERR_MAX_IN_RATIO"
);
uint256 spotPriceBefore =
XMath.calcSpotPrice(
inRecord.balance,
inRecord.denorm,
outRecord.balance,
outRecord.denorm,
swapFee
);
require(spotPriceBefore <= maxPrice, "ERR_BAD_LIMIT_PRICE");
tokenAmountOut = calcOutGivenIn(
inRecord.balance,
inRecord.denorm,
outRecord.balance,
outRecord.denorm,
tokenAmountIn,
swapFee
);
require(tokenAmountOut >= minAmountOut, "ERR_LIMIT_OUT");
require(
spotPriceBefore <= tokenAmountIn.bdiv(tokenAmountOut),
"ERR_MATH_APPROX"
);
inRecord.balance = (inRecord.balance).badd(tokenAmountIn);
outRecord.balance = (outRecord.balance).bsub(tokenAmountOut);
spotPriceAfter = XMath.calcSpotPrice(
inRecord.balance,
inRecord.denorm,
outRecord.balance,
outRecord.denorm,
swapFee
);
require(spotPriceAfter >= spotPriceBefore, "ERR_MATH_APPROX");
require(spotPriceAfter <= maxPrice, "ERR_LIMIT_PRICE");
emit LOG_SWAP(
msg.sender,
tokenIn,
tokenOut,
tokenAmountIn,
tokenAmountOut
);
_pullUnderlying(tokenIn, msg.sender, tokenAmountIn);
uint256 _swapFee = tokenAmountIn.bmul(swapFee);
// to referral
uint256 _referFee = 0;
if (
referrer != address(0) &&
referrer != msg.sender &&
referrer != tx.origin
) {
_referFee = _swapFee / 5; // 20% to referrer
_pushUnderlying(tokenIn, referrer, _referFee);
inRecord.balance = (inRecord.balance).bsub(_referFee);
emit LOG_REFER(msg.sender, referrer, tokenIn, _referFee);
}
// to SAFU
uint256 _safuFee = tokenAmountIn.bmul(safuFee);
if (isFarmPool) {
_safuFee = _swapFee.bsub(_referFee);
}
require(_safuFee.badd(_referFee) <= _swapFee, "ERR_FEE_LIMIT");
_pushUnderlying(tokenIn, SAFU, _safuFee);
inRecord.balance = (inRecord.balance).bsub(_safuFee);
_pushUnderlying(tokenOut, msg.sender, tokenAmountOut);
return (tokenAmountOut, spotPriceAfter);
}
function swapExactAmountOut(
address tokenIn,
uint256 maxAmountIn,
address tokenOut,
uint256 tokenAmountOut,
uint256 maxPrice
) external returns (uint256 tokenAmountIn, uint256 spotPriceAfter) {
return
swapExactAmountOutRefer(
tokenIn,
maxAmountIn,
tokenOut,
tokenAmountOut,
maxPrice,
address(0x0)
);
}
function swapExactAmountOutRefer(
address tokenIn,
uint256 maxAmountIn,
address tokenOut,
uint256 tokenAmountOut,
uint256 maxPrice,
address referrer
) public _lock_ returns (uint256 tokenAmountIn, uint256 spotPriceAfter) {
require(_records[tokenIn].bound, "ERR_NOT_BOUND");
require(_records[tokenOut].bound, "ERR_NOT_BOUND");
require(finalized, "ERR_NOT_FINALIZED");
Record storage inRecord = _records[address(tokenIn)];
Record storage outRecord = _records[address(tokenOut)];
require(
tokenAmountOut <= (outRecord.balance).bmul(MAX_OUT_RATIO),
"ERR_MAX_OUT_RATIO"
);
uint256 spotPriceBefore =
XMath.calcSpotPrice(
inRecord.balance,
inRecord.denorm,
outRecord.balance,
outRecord.denorm,
swapFee
);
require(spotPriceBefore <= maxPrice, "ERR_BAD_LIMIT_PRICE");
tokenAmountIn = calcInGivenOut(
inRecord.balance,
inRecord.denorm,
outRecord.balance,
outRecord.denorm,
tokenAmountOut,
swapFee
);
require(tokenAmountIn <= maxAmountIn, "ERR_LIMIT_IN");
require(
spotPriceBefore <= tokenAmountIn.bdiv(tokenAmountOut),
"ERR_MATH_APPROX"
);
inRecord.balance = (inRecord.balance).badd(tokenAmountIn);
outRecord.balance = (outRecord.balance).bsub(tokenAmountOut);
spotPriceAfter = XMath.calcSpotPrice(
inRecord.balance,
inRecord.denorm,
outRecord.balance,
outRecord.denorm,
swapFee
);
require(spotPriceAfter >= spotPriceBefore, "ERR_MATH_APPROX");
require(spotPriceAfter <= maxPrice, "ERR_LIMIT_PRICE");
emit LOG_SWAP(
msg.sender,
tokenIn,
tokenOut,
tokenAmountIn,
tokenAmountOut
);
_pullUnderlying(tokenIn, msg.sender, tokenAmountIn);
uint256 _swapFee = tokenAmountIn.bmul(swapFee);
// to referral
uint256 _referFee = 0;
if (
referrer != address(0) &&
referrer != msg.sender &&
referrer != tx.origin
) {
_referFee = _swapFee / 5; // 20% to referrer
_pushUnderlying(tokenIn, referrer, _referFee);
inRecord.balance = (inRecord.balance).bsub(_referFee);
emit LOG_REFER(msg.sender, referrer, tokenIn, _referFee);
}
// to SAFU
uint256 _safuFee = tokenAmountIn.bmul(safuFee);
if (isFarmPool) {
_safuFee = _swapFee.bsub(_referFee);
}
require(_safuFee.badd(_referFee) <= _swapFee, "ERR_FEE_LIMIT");
_pushUnderlying(tokenIn, SAFU, _safuFee);
inRecord.balance = (inRecord.balance).bsub(_safuFee);
_pushUnderlying(tokenOut, msg.sender, tokenAmountOut);
return (tokenAmountIn, spotPriceAfter);
}
function joinswapExternAmountIn(
address tokenIn,
uint256 tokenAmountIn,
uint256 minPoolAmountOut
) external _lock_ returns (uint256 poolAmountOut) {
require(finalized, "ERR_NOT_FINALIZED");
require(_records[tokenIn].bound, "ERR_NOT_BOUND");
require(
tokenAmountIn <= (_records[tokenIn].balance).bmul(MAX_IN_RATIO),
"ERR_MAX_IN_RATIO"
);
_pullUnderlying(tokenIn, msg.sender, tokenAmountIn);
// to SAFU
uint256 _safuFee = tokenAmountIn.bmul(safuFee);
if (isFarmPool) {
_safuFee = tokenAmountIn.bmul(swapFee);
}
tokenAmountIn = tokenAmountIn.bsub(_safuFee);
Record storage inRecord = _records[tokenIn];
poolAmountOut = XMath.calcPoolOutGivenSingleIn(
inRecord.balance,
inRecord.denorm,
_totalSupply,
_totalWeight,
tokenAmountIn,
swapFee
);
require(poolAmountOut >= minPoolAmountOut, "ERR_LIMIT_OUT");
inRecord.balance = (inRecord.balance).badd(tokenAmountIn);
_pushUnderlying(tokenIn, SAFU, _safuFee);
emit LOG_JOIN(msg.sender, tokenIn, tokenAmountIn);
_mintPoolShare(poolAmountOut);
_pushPoolShare(msg.sender, poolAmountOut);
return poolAmountOut;
}
function exitswapPoolAmountIn(
address tokenOut,
uint256 poolAmountIn,
uint256 minAmountOut
) external _logs_ _lock_ returns (uint256 tokenAmountOut) {
require(finalized, "ERR_NOT_FINALIZED");
require(_records[tokenOut].bound, "ERR_NOT_BOUND");
require(poolAmountIn >= MIN_POOL_AMOUNT, "ERR_MIN_AMOUNT");
_pullPoolShare(msg.sender, poolAmountIn);
// exit fee to origin
if (exitFee > 0 && msg.sender != origin) {
uint256 _exitFee = poolAmountIn.bmul(exitFee);
_pushPoolShare(origin, _exitFee);
poolAmountIn = poolAmountIn.bsub(_exitFee);
}
_burnPoolShare(poolAmountIn);
Record storage outRecord = _records[tokenOut];
tokenAmountOut = XMath.calcSingleOutGivenPoolIn(
outRecord.balance,
outRecord.denorm,
_totalSupply,
_totalWeight,
poolAmountIn,
swapFee
);
require(tokenAmountOut >= minAmountOut, "ERR_LIMIT_OUT");
require(
tokenAmountOut <= (_records[tokenOut].balance).bmul(MAX_OUT_RATIO),
"ERR_MAX_OUT_RATIO"
);
outRecord.balance = (outRecord.balance).bsub(tokenAmountOut);
// to SAFU
uint256 _safuFee = tokenAmountOut.bmul(safuFee);
if (isFarmPool) {
_safuFee = tokenAmountOut.bmul(swapFee);
}
emit LOG_EXIT(msg.sender, tokenOut, tokenAmountOut);
_pushUnderlying(tokenOut, SAFU, _safuFee);
_pushUnderlying(tokenOut, msg.sender, tokenAmountOut.bsub(_safuFee));
return tokenAmountOut;
}
function calcOutGivenIn(
uint256 tokenBalanceIn,
uint256 tokenWeightIn,
uint256 tokenBalanceOut,
uint256 tokenWeightOut,
uint256 tokenAmountIn,
uint256 _swapFee
) public pure returns (uint256) {
return
XMath.calcOutGivenIn(
tokenBalanceIn,
tokenWeightIn,
tokenBalanceOut,
tokenWeightOut,
tokenAmountIn,
_swapFee
);
}
function calcInGivenOut(
uint256 tokenBalanceIn,
uint256 tokenWeightIn,
uint256 tokenBalanceOut,
uint256 tokenWeightOut,
uint256 tokenAmountOut,
uint256 _swapFee
) public pure returns (uint256) {
return
XMath.calcInGivenOut(
tokenBalanceIn,
tokenWeightIn,
tokenBalanceOut,
tokenWeightOut,
tokenAmountOut,
_swapFee
);
}
// ==
// 'Underlying' token-manipulation functions make external calls but are NOT locked
// You must `_lock_` or otherwise ensure reentry-safety
// Fixed ERC-20 transfer revert for some special token such as USDT
function _pullUnderlying(
address erc20,
address from,
uint256 amount
) internal {
// bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
(bool success, bytes memory data) =
erc20.call(
abi.encodeWithSelector(0x23b872dd, from, address(this), amount)
);
require(
success && (data.length == 0 || abi.decode(data, (bool))),
"ERC20_TRANSFER_FROM_FAILED"
);
}
function _pushUnderlying(
address erc20,
address to,
uint256 amount
) internal {
// bytes4(keccak256(bytes('transfer(address,uint256)')));
(bool success, bytes memory data) =
erc20.call(abi.encodeWithSelector(0xa9059cbb, to, amount));
require(
success && (data.length == 0 || abi.decode(data, (bool))),
"ERC20_TRANSFER_FAILED"
);
}
function _pullPoolShare(address from, uint256 amount) internal {
_move(from, address(this), amount);
}
function _pushPoolShare(address to, uint256 amount) internal {
_move(address(this), to, amount);
}
function _mintPoolShare(uint256 amount) internal {
_mint(amount);
}
function _burnPoolShare(uint256 amount) internal {
_burn(amount);
}
}