This commit is contained in:
Skye
2025-11-12 15:18:09 +08:00
parent 82733a61f3
commit 2f1bcdac7e
11 changed files with 1785 additions and 0 deletions

View File

@@ -0,0 +1,139 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.8.16;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {GSPTrader} from "./GSPTrader.sol";
import {GSPFunding} from "./GSPFunding.sol";
import {GSPVault} from "./GSPVault.sol";
/**
* @title DODO GasSavingPool
* @author DODO Breeder
*
* @notice DODO GasSavingPool initialization
*/
contract GSP is GSPTrader, GSPFunding {
/**
* @notice Function will be called in factory, init risk should not be included.
* @param maintainer The dodo's address, who can claim mtFee and own this pool
* @param admin oracle owner address, who can set price.
* @param baseTokenAddress The base token address
* @param quoteTokenAddress The quote token address
* @param lpFeeRate The rate of lp fee, with 18 decimal
* @param mtFeeRate The rate of mt fee, with 18 decimal
* @param i The oracle price, possible to be changed only by maintainer
* @param k The swap curve parameter
* @param priceLimit The limit of the setting range of the I
* @param isOpenTWAP Useless, always false, just for compatible with old version pool
*/
function init(
address maintainer,
address admin,
address baseTokenAddress,
address quoteTokenAddress,
uint256 lpFeeRate,
uint256 mtFeeRate,
uint256 i,
uint256 k,
uint256 priceLimit,
bool isOpenTWAP
) external {
// GSP can only be initialized once
require(!_GSP_INITIALIZED_, "GSP_INITIALIZED");
// _GSP_INITIALIZED_ is set to true after initialization
_GSP_INITIALIZED_ = true;
// baseTokenAddress and quoteTokenAddress should not be the same
require(baseTokenAddress != quoteTokenAddress, "BASE_QUOTE_CAN_NOT_BE_SAME");
// _BASE_TOKEN_ and _QUOTE_TOKEN_ should be valid ERC20 tokens
_BASE_TOKEN_ = IERC20(baseTokenAddress);
_QUOTE_TOKEN_ = IERC20(quoteTokenAddress);
// i should be greater than 0 and less than 10**36
require(i > 0 && i <= 10**36);
_I_ = i;
// k should be greater than 0 and less than 10**18
require(k <= 10**18);
_K_ = k;
// _LP_FEE_RATE_ is set when initialization
_LP_FEE_RATE_ = lpFeeRate;
// _MT_FEE_RATE_ is set when initialization
_MT_FEE_RATE_ = mtFeeRate;
// _MAINTAINER_ is set when initialization, the address receives the fee
_MAINTAINER_ = maintainer;
_ADMIN_ = admin;
_PRICE_LIMIT_ = priceLimit;
// _IS_OPEN_TWAP_ is always false
_IS_OPEN_TWAP_ = false;
string memory connect = "_";
string memory suffix = "GSP";
// name of the shares is the combination of suffix, connect and string of the GSP
name = string(abi.encodePacked(suffix, connect, addressToShortString(address(this))));
// symbol of the shares is GLP
symbol = "GLP";
// decimals of the shares is the same as the base token decimals
decimals = IERC20Metadata(baseTokenAddress).decimals();
// initialize DOMAIN_SEPARATOR
buildDomainSeparator();
// ==========================================================================
}
// ============================== Permit ====================================
/**
     * @notice DOMAIN_SEPARATOR is used for approve by signature
     */
function buildDomainSeparator() public returns (bytes32){
string memory connect = "_";
string memory suffix = "GSP";
// name of the shares is the combination of suffix, connect and string of the GSP
string memory name = string(abi.encodePacked(suffix, connect, addressToShortString(address(this))));
DOMAIN_SEPARATOR = keccak256(
abi.encode(
// keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f,
keccak256(bytes(name)),
keccak256(bytes("1")),
block.chainid,
address(this)
)
);
return DOMAIN_SEPARATOR;
}
/**
* @notice Convert the address to a shorter string
* @param _addr The address to convert
* @return A string representation of _addr in hexadecimal
*/
function addressToShortString(address _addr) public pure returns (string memory) {
bytes32 value = bytes32(uint256(uint160(_addr)));
bytes memory alphabet = "0123456789abcdef";
bytes memory str = new bytes(8);
for (uint256 i = 0; i < 4; i++) {
str[i * 2] = alphabet[uint8(value[i + 12] >> 4)];
str[1 + i * 2] = alphabet[uint8(value[i + 12] & 0x0f)];
}
return string(str);
}
// ============ Version Control ============
/**
* @notice Return the version of DODOGasSavingPool
* @return The current version is 1.0.1
*/
function version() external pure returns (string memory) {
return "GSP 1.0.1";
}
}

View File

@@ -0,0 +1,153 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.8.16;
import {GSPVault} from "./GSPVault.sol";
import {DecimalMath} from "../../lib/DecimalMath.sol";
import {IDODOCallee} from "../../intf/IDODOCallee.sol";
/// @notice this part focus on Lp tokens, mint and burn
contract GSPFunding is GSPVault {
// ============ Events ============
event BuyShares(address to, uint256 increaseShares, uint256 totalShares);
event SellShares(address payer, address to, uint256 decreaseShares, uint256 totalShares);
// ============ Buy & Sell Shares ============
/// @notice User mint Lp token and deposit tokens, the result is rounded down
/// @dev User first transfer baseToken and quoteToken to GSP, then call buyShares
/// @param to The address will receive shares
/// @return shares The amount of shares user will receive
/// @return baseInput The amount of baseToken user transfer to GSP
/// @return quoteInput The amount of quoteToken user transfer to GSP
function buyShares(address to)
external
nonReentrant
returns (
uint256 shares,
uint256 baseInput,
uint256 quoteInput
)
{
// The balance of baseToken and quoteToken should be the balance minus the fee
uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)) - _MT_FEE_BASE_;
uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)) - _MT_FEE_QUOTE_;
// The reserve of baseToken and quoteToken
uint256 baseReserve = _BASE_RESERVE_;
uint256 quoteReserve = _QUOTE_RESERVE_;
// The amount of baseToken and quoteToken user transfer to GSP
baseInput = baseBalance - baseReserve;
quoteInput = quoteBalance - quoteReserve;
// BaseToken should be transferred to GSP before calling buyShares
require(baseInput > 0, "NO_BASE_INPUT");
// Round down when withdrawing. Therefore, never be a situation occuring balance is 0 but totalsupply is not 0
// But May Happenreserve >0 But totalSupply = 0
if (totalSupply == 0) {
// case 1. initial supply
require(quoteBalance > 0, "ZERO_QUOTE_AMOUNT");
// The shares will be minted to user
shares = quoteBalance < DecimalMath.mulFloor(baseBalance, _I_)
? DecimalMath.divFloor(quoteBalance, _I_)
: baseBalance;
// The target will be updated
_BASE_TARGET_ = uint112(shares);
_QUOTE_TARGET_ = uint112(DecimalMath.mulFloor(shares, _I_));
require(_QUOTE_TARGET_ > 0, "QUOTE_TARGET_IS_ZERO");
// Lock 1001 shares permanently in first deposit
require(shares > 2001, "MINT_AMOUNT_NOT_ENOUGH");
_mint(address(0), 1001);
shares -= 1001;
} else if (baseReserve > 0 && quoteReserve > 0) {
// case 2. normal case
uint256 baseInputRatio = DecimalMath.divFloor(baseInput, baseReserve);
uint256 quoteInputRatio = DecimalMath.divFloor(quoteInput, quoteReserve);
uint256 mintRatio = quoteInputRatio < baseInputRatio ? quoteInputRatio : baseInputRatio;
// The shares will be minted to user
shares = DecimalMath.mulFloor(totalSupply, mintRatio);
// The target will be updated
_BASE_TARGET_ = uint112(uint256(_BASE_TARGET_) + (DecimalMath.mulFloor(uint256(_BASE_TARGET_), mintRatio)));
_QUOTE_TARGET_ = uint112(uint256(_QUOTE_TARGET_) + (DecimalMath.mulFloor(uint256(_QUOTE_TARGET_), mintRatio)));
}
// The shares will be minted to user
// The reserve will be updated
_mint(to, shares);
_setReserve(baseBalance, quoteBalance);
emit BuyShares(to, shares, _SHARES_[to]);
}
/// @notice User burn their lp and withdraw their tokens, the result is rounded down
/// @dev User call sellShares, the calculated baseToken and quoteToken amount should geater than minBaseToken and minQuoteToken
/// @param shareAmount The amount of shares user want to sell
/// @param to The address will receive baseToken and quoteToken
/// @param baseMinAmount The minimum amount of baseToken user want to receive
/// @param quoteMinAmount The minimum amount of quoteToken user want to receive
/// @param data The data will be passed to callee contract
/// @param deadline The deadline of this transaction
function sellShares(
uint256 shareAmount,
address to,
uint256 baseMinAmount,
uint256 quoteMinAmount,
bytes calldata data,
uint256 deadline
) external nonReentrant returns (uint256 baseAmount, uint256 quoteAmount) {
// The deadline should be greater than current timestamp
require(deadline >= block.timestamp, "TIME_EXPIRED");
// The amount of shares user want to sell should be less than user's balance
require(shareAmount <= _SHARES_[msg.sender], "GLP_NOT_ENOUGH");
// The balance of baseToken and quoteToken should be the balance minus the fee
uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)) - _MT_FEE_BASE_;
uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)) - _MT_FEE_QUOTE_;
// The total shares of GSP
uint256 totalShares = totalSupply;
// The amount of baseToken and quoteToken user will receive is calculated by the ratio of user's shares to total shares
baseAmount = baseBalance * shareAmount / totalShares;
quoteAmount = quoteBalance * shareAmount / totalShares;
// The target will be updated
_BASE_TARGET_ = uint112(uint256(_BASE_TARGET_) - DecimalMath._divCeil((uint256(_BASE_TARGET_) * (shareAmount)), totalShares));
_QUOTE_TARGET_ = uint112(uint256(_QUOTE_TARGET_) - DecimalMath._divCeil((uint256(_QUOTE_TARGET_) * (shareAmount)), totalShares));
// The calculated baseToken and quoteToken amount should geater than minBaseToken and minQuoteToken
require(
baseAmount >= baseMinAmount && quoteAmount >= quoteMinAmount,
"WITHDRAW_NOT_ENOUGH"
);
// The shares will be burned from user
// The baseToken and quoteToken will be transferred to user
// The reserve will be synced
_burn(msg.sender, shareAmount);
_transferBaseOut(to, baseAmount);
_transferQuoteOut(to, quoteAmount);
_sync();
// If the data is not empty, the callee contract will be called
if (data.length > 0) {
//Same as DVM
IDODOCallee(to).DVMSellShareCall(
msg.sender,
shareAmount,
baseAmount,
quoteAmount,
data
);
}
emit SellShares(msg.sender, to, shareAmount, _SHARES_[msg.sender]);
}
}

View File

@@ -0,0 +1,148 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.8.16;
import {DODOMath} from "../../lib/DODOMath.sol";
import {DecimalMath} from "../../lib/DecimalMath.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {PMMPricing} from "../../lib/PMMPricing.sol";
/// @notice this contract is used for store state and read state
contract GSPStorage is ReentrancyGuard {
// ============ Storage for Setup ============
// _GSP_INITIALIZED_ will be set to true when the init function is called
bool internal _GSP_INITIALIZED_;
// GSP does not open TWAP by default
// _IS_OPEN_TWAP_ can be set to true when the init function is called
bool public _IS_OPEN_TWAP_ = false;
// ============ Core Address ============
// _MAINTAINER_ is the maintainer of GSP
address public _MAINTAINER_;
// _ADMIN_ can set price
address public _ADMIN_;
// _BASE_TOKEN_ and _QUOTE_TOKEN_ should be ERC20 token
IERC20 public _BASE_TOKEN_;
IERC20 public _QUOTE_TOKEN_;
// _BASE_RESERVE_ and _QUOTE_RESERVE_ are the current reserves of the GSP
uint112 public _BASE_RESERVE_;
uint112 public _QUOTE_RESERVE_;
// _BLOCK_TIMESTAMP_LAST_ is used when calculating TWAP
uint32 public _BLOCK_TIMESTAMP_LAST_;
// _BASE_PRICE_CUMULATIVE_LAST_ is used when calculating TWAP
uint256 public _BASE_PRICE_CUMULATIVE_LAST_;
// _BASE_TARGET_ and _QUOTE_TARGET_ are recalculated when the pool state changes
uint112 public _BASE_TARGET_;
uint112 public _QUOTE_TARGET_;
// _RState_ is the current R state of the GSP
uint32 public _RState_;
// ============ Shares (ERC20) ============
// symbol is the symbol of the shares
string public symbol;
// decimals is the decimals of the shares
uint8 public decimals;
// name is the name of the shares
string public name;
// totalSupply is the total supply of the shares
uint256 public totalSupply;
// _SHARES_ is the mapping from account to share balance, record the share balance of each account
mapping(address => uint256) internal _SHARES_;
mapping(address => mapping(address => uint256)) internal _ALLOWED_;
// ================= Permit ======================
bytes32 public DOMAIN_SEPARATOR;
// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
bytes32 public constant PERMIT_TYPEHASH =
0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
mapping(address => uint256) public nonces;
// ============ Variables for Pricing ============
// _MT_FEE_RATE_ is the fee rate of mt fee
uint256 public _MT_FEE_RATE_;
// _LP_FEE_RATE_ is the fee rate of lp fee
uint256 public _LP_FEE_RATE_;
uint256 public _K_;
uint256 public _I_;
// _PRICE_LIMIT_ is used to limit the setting range of I
uint256 public _PRICE_LIMIT_;
// ============ Mt Fee ============
// _MT_FEE_BASE_ represents the mt fee in base token
uint256 public _MT_FEE_BASE_;
// _MT_FEE_QUOTE_ represents the mt fee in quote token
uint256 public _MT_FEE_QUOTE_;
// _MT_FEE_RATE_MODEL_ is useless, just for compatible with old version pool
address public _MT_FEE_RATE_MODEL_ = address(0);
// ============ Helper Functions ============
/// @notice Return the PMM state of the pool from inner or outside
/// @dev B0 and Q0 are calculated in adjustedTarget
/// @return state The current PMM state
function getPMMState() public view returns (PMMPricing.PMMState memory state) {
state.i = _I_;
state.K = _K_;
state.B = _BASE_RESERVE_;
state.Q = _QUOTE_RESERVE_;
state.B0 = _BASE_TARGET_; // will be calculated in adjustedTarget
state.Q0 = _QUOTE_TARGET_;
state.R = PMMPricing.RState(_RState_);
PMMPricing.adjustedTarget(state);
}
/// @notice Return the PMM state variables used for routeHelpers
/// @return i The price index
/// @return K The K value
/// @return B The base token reserve
/// @return Q The quote token reserve
/// @return B0 The base token target
/// @return Q0 The quote token target
/// @return R The R state of the pool
function getPMMStateForCall()
external
view
returns (
uint256 i,
uint256 K,
uint256 B,
uint256 Q,
uint256 B0,
uint256 Q0,
uint256 R
)
{
PMMPricing.PMMState memory state = getPMMState();
i = state.i;
K = state.K;
B = state.B;
Q = state.Q;
B0 = state.B0;
Q0 = state.Q0;
R = uint256(state.R);
}
/// @notice Return the adjusted mid price
/// @return midPrice The current mid price
function getMidPrice() public view returns (uint256 midPrice) {
return PMMPricing.getMidPrice(getPMMState());
}
/// @notice Return the total mt fee maintainer can claim
/// @dev The total mt fee is represented in two types: in base token and in quote token
/// @return mtFeeBase The total mt fee in base token
/// @return mtFeeQuote The total mt fee in quote token
function getMtFeeTotal() public view returns (uint256 mtFeeBase, uint256 mtFeeQuote) {
mtFeeBase = _MT_FEE_BASE_;
mtFeeQuote = _MT_FEE_QUOTE_;
}
}

View File

@@ -0,0 +1,275 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.8.16;
import {GSPVault} from "./GSPVault.sol";
import {DecimalMath} from "../../lib/DecimalMath.sol";
import {PMMPricing} from "../../lib/PMMPricing.sol";
import {IDODOCallee} from "../../intf/IDODOCallee.sol";
/// @notice this contract deal with swap
contract GSPTrader is GSPVault {
// ============ Events ============
event DODOSwap(
address fromToken,
address toToken,
uint256 fromAmount,
uint256 toAmount,
address trader,
address receiver
);
event DODOFlashLoan(address borrower, address assetTo, uint256 baseAmount, uint256 quoteAmount);
event RChange(PMMPricing.RState newRState);
// ============ Trade Functions ============
/**
* @notice User sell base tokens, user pay tokens first. Must be used with a router
* @dev The base token balance is the actual balance minus the mt fee
* @param to The recipient of the output
* @return receiveQuoteAmount Amount of quote token received
*/
function sellBase(address to) external nonReentrant returns (uint256 receiveQuoteAmount) {
uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)) - _MT_FEE_BASE_;
uint256 baseInput = baseBalance - uint256(_BASE_RESERVE_);
uint256 mtFee;
uint256 newBaseTarget;
PMMPricing.RState newRState;
// calculate the amount of quote token to receive and mt fee
(receiveQuoteAmount, mtFee, newRState, newBaseTarget) = querySellBase(tx.origin, baseInput);
// transfer quote token to recipient
_transferQuoteOut(to, receiveQuoteAmount);
// update mt fee in quote token
_MT_FEE_QUOTE_ = _MT_FEE_QUOTE_ + mtFee;
// update TARGET
if (_RState_ != uint32(newRState)) {
require(newBaseTarget <= type(uint112).max, "OVERFLOW");
_BASE_TARGET_ = uint112(newBaseTarget);
_RState_ = uint32(newRState);
emit RChange(newRState);
}
// update reserve
_setReserve(baseBalance, _QUOTE_TOKEN_.balanceOf(address(this)) - _MT_FEE_QUOTE_);
emit DODOSwap(
address(_BASE_TOKEN_),
address(_QUOTE_TOKEN_),
baseInput,
receiveQuoteAmount,
msg.sender,
to
);
}
/**
* @notice User sell quote tokens, user pay tokens first. Must be used with a router
* @param to The recipient of the output
* @return receiveBaseAmount Amount of base token received
*/
function sellQuote(address to) external nonReentrant returns (uint256 receiveBaseAmount) {
uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)) - _MT_FEE_QUOTE_;
uint256 quoteInput = quoteBalance - uint256(_QUOTE_RESERVE_);
uint256 mtFee;
uint256 newQuoteTarget;
PMMPricing.RState newRState;
// calculate the amount of base token to receive and mt fee
(receiveBaseAmount, mtFee, newRState, newQuoteTarget) = querySellQuote(
tx.origin,
quoteInput
);
// transfer base token to recipient
_transferBaseOut(to, receiveBaseAmount);
// update mt fee in base token
_MT_FEE_BASE_ = _MT_FEE_BASE_ + mtFee;
// update TARGET
if (_RState_ != uint32(newRState)) {
require(newQuoteTarget <= type(uint112).max, "OVERFLOW");
_QUOTE_TARGET_ = uint112(newQuoteTarget);
_RState_ = uint32(newRState);
emit RChange(newRState);
}
// update reserve
_setReserve((_BASE_TOKEN_.balanceOf(address(this)) - _MT_FEE_BASE_), quoteBalance);
emit DODOSwap(
address(_QUOTE_TOKEN_),
address(_BASE_TOKEN_),
quoteInput,
receiveBaseAmount,
msg.sender,
to
);
}
/**
* @notice inner flashloan, pay tokens out first, call external contract and check tokens left
* @param baseAmount The base token amount user require
* @param quoteAmount The quote token amount user require
* @param assetTo The address who uses above tokens
* @param data The external contract's callData
*/
function flashLoan(
uint256 baseAmount,
uint256 quoteAmount,
address assetTo,
bytes calldata data
) external nonReentrant {
_transferBaseOut(assetTo, baseAmount);
_transferQuoteOut(assetTo, quoteAmount);
if (data.length > 0)
IDODOCallee(assetTo).DSPFlashLoanCall(msg.sender, baseAmount, quoteAmount, data);
uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)) - _MT_FEE_BASE_;
uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)) - _MT_FEE_QUOTE_;
// no input -> pure loss
require(
baseBalance >= _BASE_RESERVE_ || quoteBalance >= _QUOTE_RESERVE_,
"FLASH_LOAN_FAILED"
);
// sell quote case
// quote input + base output
if (baseBalance < _BASE_RESERVE_) {
uint256 quoteInput = quoteBalance - uint256(_QUOTE_RESERVE_);
(
uint256 receiveBaseAmount,
uint256 mtFee,
PMMPricing.RState newRState,
uint256 newQuoteTarget
) = querySellQuote(tx.origin, quoteInput); // revert if quoteBalance<quoteReserve
require(
(uint256(_BASE_RESERVE_) - baseBalance) <= receiveBaseAmount,
"FLASH_LOAN_FAILED"
);
_MT_FEE_BASE_ = _MT_FEE_BASE_ + mtFee;
if (_RState_ != uint32(newRState)) {
require(newQuoteTarget <= type(uint112).max, "OVERFLOW");
_QUOTE_TARGET_ = uint112(newQuoteTarget);
_RState_ = uint32(newRState);
emit RChange(newRState);
}
emit DODOSwap(
address(_QUOTE_TOKEN_),
address(_BASE_TOKEN_),
quoteInput,
receiveBaseAmount,
msg.sender,
assetTo
);
}
// sell base case
// base input + quote output
if (quoteBalance < _QUOTE_RESERVE_) {
uint256 baseInput = baseBalance - uint256(_BASE_RESERVE_);
(
uint256 receiveQuoteAmount,
uint256 mtFee,
PMMPricing.RState newRState,
uint256 newBaseTarget
) = querySellBase(tx.origin, baseInput); // revert if baseBalance<baseReserve
require(
(uint256(_QUOTE_RESERVE_) - quoteBalance) <= receiveQuoteAmount,
"FLASH_LOAN_FAILED"
);
_MT_FEE_QUOTE_ = _MT_FEE_QUOTE_ + mtFee;
if (_RState_ != uint32(newRState)) {
require(newBaseTarget <= type(uint112).max, "OVERFLOW");
_BASE_TARGET_ = uint112(newBaseTarget);
_RState_ = uint32(newRState);
emit RChange(newRState);
}
emit DODOSwap(
address(_BASE_TOKEN_),
address(_QUOTE_TOKEN_),
baseInput,
receiveQuoteAmount,
msg.sender,
assetTo
);
}
_sync();
emit DODOFlashLoan(msg.sender, assetTo, baseAmount, quoteAmount);
}
// ============ Query Functions ============
/**
* @notice Return swap result, for query, sellBase side.
* @param trader Useless, just to keep the same interface with old version pool
* @param payBaseAmount The amount of base token user want to sell
* @return receiveQuoteAmount The amount of quote token user will receive
* @return mtFee The amount of mt fee charged
* @return newRState The new RState after swap
* @return newBaseTarget The new base target after swap
*/
function querySellBase(address trader, uint256 payBaseAmount)
public
view
returns (
uint256 receiveQuoteAmount,
uint256 mtFee,
PMMPricing.RState newRState,
uint256 newBaseTarget
)
{
PMMPricing.PMMState memory state = getPMMState();
(receiveQuoteAmount, newRState) = PMMPricing.sellBaseToken(state, payBaseAmount);
uint256 lpFeeRate = _LP_FEE_RATE_;
uint256 mtFeeRate = _MT_FEE_RATE_;
mtFee = DecimalMath.mulFloor(receiveQuoteAmount, mtFeeRate);
receiveQuoteAmount = receiveQuoteAmount
- DecimalMath.mulFloor(receiveQuoteAmount, lpFeeRate)
- mtFee;
newBaseTarget = state.B0;
}
/**
* @notice Return swap result, for query, sellQuote side
* @param trader Useless, just for keeping the same interface with old version pool
* @param payQuoteAmount The amount of quote token user want to sell
* @return receiveBaseAmount The amount of base token user will receive
* @return mtFee The amount of mt fee charged
* @return newRState The new RState after swap
* @return newQuoteTarget The new quote target after swap
*/
function querySellQuote(address trader, uint256 payQuoteAmount)
public
view
returns (
uint256 receiveBaseAmount,
uint256 mtFee,
PMMPricing.RState newRState,
uint256 newQuoteTarget
)
{
PMMPricing.PMMState memory state = getPMMState();
(receiveBaseAmount, newRState) = PMMPricing.sellQuoteToken(state, payQuoteAmount);
uint256 lpFeeRate = _LP_FEE_RATE_;
uint256 mtFeeRate = _MT_FEE_RATE_;
mtFee = DecimalMath.mulFloor(receiveBaseAmount, mtFeeRate);
receiveBaseAmount = receiveBaseAmount
- DecimalMath.mulFloor(receiveBaseAmount, lpFeeRate)
- mtFee;
newQuoteTarget = state.Q0;
}
}

View File

@@ -0,0 +1,369 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.8.16;
import {DecimalMath} from "../../lib/DecimalMath.sol";
import {PMMPricing} from "../../lib/PMMPricing.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {GSPStorage} from "./GSPStorage.sol";
contract GSPVault is GSPStorage {
using SafeERC20 for IERC20;
// ============ Modifiers ============
/// @notice Check whether the caller is maintainer
modifier onlyMaintainer() {
require(msg.sender == _MAINTAINER_, "ACCESS_DENIED");
_;
}
/// @notice Check whether the caller is admin
modifier onlyAdmin() {
require(msg.sender == _ADMIN_, "ADMIN_ACCESS_DENIED");
_;
}
// ============ Events ============
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
event Mint(address indexed user, uint256 value);
event Burn(address indexed user, uint256 value);
event MtFeeRateChange(uint256 newMtFee);
event LpFeeRateChange(uint256 newLpFee);
event IChange(uint256 newI);
event KChange(uint256 newK);
event WithdrawMtFee(address indexed token, uint256 amount);
// ============ View Functions ============
/**
* @notice Get the reserves of the pool
* @return baseReserve The base token reserve
* @return quoteReserve The quote token reserve
*/
function getVaultReserve() external view returns (uint256 baseReserve, uint256 quoteReserve) {
baseReserve = _BASE_RESERVE_;
quoteReserve = _QUOTE_RESERVE_;
}
/**
* @notice Get the fee rate of the pool
* @param user Useless, just keep the same interface with old version pool
* @return lpFeeRate The lp fee rate
* @return mtFeeRate The mt fee rate
*/
function getUserFeeRate(address user)
external
view
returns (uint256 lpFeeRate, uint256 mtFeeRate)
{
lpFeeRate = _LP_FEE_RATE_;
mtFeeRate = _MT_FEE_RATE_;
}
// ============ Asset In ============
/**
* @notice Get the amount of base token transferred in
* @dev The amount of base token input should be the base token reserve minus the mt fee in base token
* @return input The amount of base token transferred in
*/
function getBaseInput() public view returns (uint256 input) {
return _BASE_TOKEN_.balanceOf(address(this)) - uint256(_BASE_RESERVE_) - uint256(_MT_FEE_BASE_);
}
/**
* @notice Get the amount of quote token transferred in
* @dev The amount of quote token input should be the quote token reserve minus the mt fee in quote token
* @return input The amount of quote token transferred in
*/
function getQuoteInput() public view returns (uint256 input) {
return _QUOTE_TOKEN_.balanceOf(address(this)) - uint256(_QUOTE_RESERVE_) - uint256(_MT_FEE_QUOTE_);
}
// ============ Set States ============
/**
* @notice Set the reserves of the pool, internal use only
* @param baseReserve The base token reserve
* @param quoteReserve The quote token reserve
*/
function _setReserve(uint256 baseReserve, uint256 quoteReserve) internal {
// the reserves should be less than the max uint112
require(baseReserve <= type(uint112).max && quoteReserve <= type(uint112).max, "OVERFLOW");
_BASE_RESERVE_ = uint112(baseReserve);
_QUOTE_RESERVE_ = uint112(quoteReserve);
}
/**
* @notice Sync the reserves of the pool, internal use only
* @dev The balances of the pool should be actual balances minus the mt fee
*/
function _sync() internal {
uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)) - uint256(_MT_FEE_BASE_);
uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)) - uint256(_MT_FEE_QUOTE_);
// the reserves should be less than the max uint112
require(baseBalance <= type(uint112).max && quoteBalance <= type(uint112).max, "OVERFLOW");
if (baseBalance != _BASE_RESERVE_) {
_BASE_RESERVE_ = uint112(baseBalance);
}
if (quoteBalance != _QUOTE_RESERVE_) {
_QUOTE_RESERVE_ = uint112(quoteBalance);
}
}
/// @notice Sync the reserves of the pool
function sync() external nonReentrant {
_sync();
}
/// @notice Correct the rState of the pool, details in pmm algorithm
function correctRState() public {
if (_RState_ == uint32(PMMPricing.RState.BELOW_ONE) && _BASE_RESERVE_<_BASE_TARGET_) {
_RState_ = uint32(PMMPricing.RState.ONE);
_BASE_TARGET_ = _BASE_RESERVE_;
_QUOTE_TARGET_ = _QUOTE_RESERVE_;
}
if (_RState_ == uint32(PMMPricing.RState.ABOVE_ONE) && _QUOTE_RESERVE_<_QUOTE_TARGET_) {
_RState_ = uint32(PMMPricing.RState.ONE);
_BASE_TARGET_ = _BASE_RESERVE_;
_QUOTE_TARGET_ = _QUOTE_RESERVE_;
}
}
/**
* @notice PriceLimit is used for oracle change protection
* @notice It sets a ratio where the relative deviation between the new price and the old price cannot exceed this ratio.
* @dev The default priceLimit is 1e3, the decimals of priceLimit is 1e6
* @param priceLimit The new price limit
*/
function adjustPriceLimit(uint256 priceLimit) external onlyAdmin {
// the default priceLimit is 1e3
require(priceLimit <= 1e6, "INVALID_PRICE_LIMIT");
_PRICE_LIMIT_ = priceLimit;
}
/**
* @notice Adjust oricle price i, only for admin
*/
function adjustPrice(uint256 i) external onlyAdmin {
// the difference between i and _I_ should be less than priceLimit
uint256 offset = i > _I_ ? i - _I_ : _I_ - i;
require((offset * 1e6 / _I_) <= _PRICE_LIMIT_, "EXCEED_PRICE_LIMIT");
_I_ = i;
emit IChange(i);
}
/**
* @notice Adjust mtFee rate, only for maintainer
* @dev The decimals of mtFee rate is 1e18
* @param mtFeeRate The new mtFee rate
*/
function adjustMtFeeRate(uint256 mtFeeRate) external onlyMaintainer {
require(mtFeeRate <= 10**18, "INVALID_MT_FEE_RATE");
_MT_FEE_RATE_ = mtFeeRate;
emit MtFeeRateChange(mtFeeRate);
}
/**
* @notice Adjust lpFee rate, only for maintainer
* @dev The decimals of lpFee rate is 1e18
* @param lpFeeRate The new lpFee rate
*/
function adjustLpFeeRate(uint256 lpFeeRate) external onlyMaintainer {
require(lpFeeRate <= 10**18, "INVALID_LP_FEE_RATE");
_LP_FEE_RATE_ = lpFeeRate;
emit LpFeeRateChange(lpFeeRate);
}
/**
* @notice Adjust swap curve parameter k, only for maintainer
* @dev The decimals of k is 1e18
* @param k The new swap curve parameter k
*/
function adjustK(uint256 k) external onlyMaintainer {
require(k <= 10**18, "INVALID_K");
_K_ = k;
emit KChange(k);
}
// ============ Asset Out ============
/**
* @notice Transfer base token out, internal use only
* @param to The address of the receiver
* @param amount The amount of base token to transfer out
*/
function _transferBaseOut(address to, uint256 amount) internal {
if (amount > 0) {
_BASE_TOKEN_.safeTransfer(to, amount);
}
}
/**
* @notice Transfer quote token out, internal use only
* @param to The address of the receiver
* @param amount The amount of quote token to transfer out
*/
function _transferQuoteOut(address to, uint256 amount) internal {
if (amount > 0) {
_QUOTE_TOKEN_.safeTransfer(to, amount);
}
}
/// @notice Maintainer withdraw mtFee, only for maintainer
function withdrawMtFeeTotal() external nonReentrant onlyMaintainer {
uint256 mtFeeQuote = _MT_FEE_QUOTE_;
uint256 mtFeeBase = _MT_FEE_BASE_;
_MT_FEE_QUOTE_ = 0;
_transferQuoteOut(_MAINTAINER_, mtFeeQuote);
_MT_FEE_BASE_ = 0;
_transferBaseOut(_MAINTAINER_, mtFeeBase);
emit WithdrawMtFee(address(_QUOTE_TOKEN_), mtFeeQuote);
emit WithdrawMtFee(address(_BASE_TOKEN_), mtFeeBase);
}
// ============ Shares (ERC20) ============
/**
* @dev Transfer token for a specified address
* @param to The address to transfer to.
* @param amount The amount to be transferred.
*/
function transfer(address to, uint256 amount) public returns (bool) {
require(amount <= _SHARES_[msg.sender], "BALANCE_NOT_ENOUGH");
_SHARES_[msg.sender] = _SHARES_[msg.sender] - (amount);
_SHARES_[to] = _SHARES_[to] + amount;
emit Transfer(msg.sender, to, amount);
return true;
}
/**
* @dev Gets the balance of the specified address.
* @param owner The address to query the the balance of.
* @return balance An uint256 representing the amount owned by the passed address.
*/
function balanceOf(address owner) external view returns (uint256 balance) {
return _SHARES_[owner];
}
/**
* @dev Transfer tokens from one address to another
* @param from address The address which you want to send tokens from
* @param to address The address which you want to transfer to
* @param amount uint256 the amount of tokens to be transferred
*/
function transferFrom(
address from,
address to,
uint256 amount
) public returns (bool) {
require(amount <= _SHARES_[from], "BALANCE_NOT_ENOUGH");
require(amount <= _ALLOWED_[from][msg.sender], "ALLOWANCE_NOT_ENOUGH");
_SHARES_[from] = _SHARES_[from] - amount;
_SHARES_[to] = _SHARES_[to] + amount;
_ALLOWED_[from][msg.sender] = _ALLOWED_[from][msg.sender] - amount;
emit Transfer(from, to, amount);
return true;
}
/**
* @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
* @param spender The address which will spend the funds.
* @param amount The amount of tokens to be spent.
*/
function approve(address spender, uint256 amount) public returns (bool) {
_approve(msg.sender, spender, amount);
return true;
}
function _approve(
address owner,
address spender,
uint256 amount
) private {
_ALLOWED_[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @dev Function to check the amount of tokens that an owner _ALLOWED_ to a spender.
* @param owner address The address which owns the funds.
* @param spender address The address which will spend the funds.
* @return A uint256 specifying the amount of tokens still available for the spender.
*/
function allowance(address owner, address spender) public view returns (uint256) {
return _ALLOWED_[owner][spender];
}
function _mint(address user, uint256 value) internal {
require(value > 1000, "MINT_AMOUNT_NOT_ENOUGH");
_SHARES_[user] = _SHARES_[user] + value;
totalSupply = totalSupply + value;
emit Mint(user, value);
emit Transfer(address(0), user, value);
}
function _burn(address user, uint256 value) internal {
_SHARES_[user] = _SHARES_[user] - value;
totalSupply = totalSupply - value;
emit Burn(user, value);
emit Transfer(user, address(0), value);
}
// ============================ Permit ======================================
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
require(deadline >= block.timestamp, "DODO_GSP_LP: EXPIRED");
bytes32 digest =
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
keccak256(
abi.encode(
PERMIT_TYPEHASH,
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
);
address recoveredAddress = ecrecover(digest, v, r, s);
require(
recoveredAddress != address(0) && recoveredAddress == owner,
"DODO_GSP_LP: INVALID_SIGNATURE"
);
_approve(owner, spender, value);
}
}

View File

@@ -0,0 +1,45 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.8.16;
interface IGSP {
function init(
address maintainer,
address admin,
address baseTokenAddress,
address quoteTokenAddress,
uint256 lpFeeRate,
uint256 mtFeeRate,
uint256 i,
uint256 k,
uint256 priceLimit,
bool isOpenTWAP
) external;
function _BASE_TOKEN_() external view returns (address);
function _QUOTE_TOKEN_() external view returns (address);
function _I_() external view returns (uint256);
function _MT_FEE_RATE_MODEL_() external view returns (address); // Useless, just for compatibility
function getVaultReserve() external view returns (uint256 baseReserve, uint256 quoteReserve);
function getUserFeeRate(address user) external view returns (uint256 lpFeeRate, uint256 mtFeeRate);
function getMtFeeTotal() external view returns (uint256 mtFeeBase, uint256 mtFeeQuote);
function sellBase(address to) external returns (uint256);
function sellQuote(address to) external returns (uint256);
function buyShares(address to) external returns (uint256 shares, uint256 baseInput, uint256 quoteInput);
function sellShares(uint256 shareAmount, address to, uint256 baseMinAmount, uint256 quoteMinAmount, bytes calldata data, uint256 deadline) external returns (uint256 baseAmount, uint256 quoteAmount);
}

View File

@@ -0,0 +1,59 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.8.16;
pragma experimental ABIEncoderV2;
interface IDODOCallee {
function DVMSellShareCall(
address sender,
uint256 burnShareAmount,
uint256 baseAmount,
uint256 quoteAmount,
bytes calldata data
) external;
function DVMFlashLoanCall(
address sender,
uint256 baseAmount,
uint256 quoteAmount,
bytes calldata data
) external;
function DPPFlashLoanCall(
address sender,
uint256 baseAmount,
uint256 quoteAmount,
bytes calldata data
) external;
function DSPFlashLoanCall(
address sender,
uint256 baseAmount,
uint256 quoteAmount,
bytes calldata data
) external;
function CPCancelCall(
address sender,
uint256 amount,
bytes calldata data
) external;
function CPClaimBidCall(
address sender,
uint256 baseAmount,
uint256 quoteAmount,
bytes calldata data
) external;
function NFTRedeemCall(
address payable assetTo,
uint256 quoteAmount,
bytes calldata
) external;
}

View File

@@ -0,0 +1,195 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.16;
pragma experimental ABIEncoderV2;
import {DecimalMath} from "./DecimalMath.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
/**
* @title DODOMath
* @author DODO Breeder
*
* @notice Functions for complex calculating. Including ONE Integration and TWO Quadratic solutions
*/
library DODOMath {
using Math for uint256;
/*
Integrate dodo curve from V1 to V2
require V0>=V1>=V2>0
res = (1-k)i(V1-V2)+ikV0*V0(1/V2-1/V1)
let V1-V2=delta
res = i*delta*(1-k+k(V0^2/V1/V2))
i is the price of V-res trading pair
support k=1 & k=0 case
[round down]
*/
function _GeneralIntegrate(
uint256 V0,
uint256 V1,
uint256 V2,
uint256 i,
uint256 k
) internal pure returns (uint256) {
require(V0 > 0, "TARGET_IS_ZERO");
uint256 fairAmount = i * (V1 - V2); // i*delta
if (k == 0) {
return fairAmount / DecimalMath.ONE;
}
uint256 V0V0V1V2 = DecimalMath.divFloor(V0 * V0 / V1, V2);
uint256 penalty = DecimalMath.mulFloor(k, V0V0V1V2); // k(V0^2/V1/V2)
return (DecimalMath.ONE - k + penalty) * fairAmount / DecimalMath.ONE2;
}
/*
Follow the integration function above
i*deltaB = (Q2-Q1)*(1-k+kQ0^2/Q1/Q2)
Assume Q2=Q0, Given Q1 and deltaB, solve Q0
i is the price of delta-V trading pair
give out target of V
support k=1 & k=0 case
[round down]
*/
function _SolveQuadraticFunctionForTarget(
uint256 V1,
uint256 delta,
uint256 i,
uint256 k
) internal pure returns (uint256) {
if (k == 0) {
return V1 + DecimalMath.mulFloor(i, delta);
}
// V0 = V1*(1+(sqrt-1)/2k)
// sqrt = √(1+4kidelta/V1)
// premium = 1+(sqrt-1)/2k
// uint256 sqrt = (4 * k).mul(i).mul(delta).div(V1).add(DecimalMath.ONE2).sqrt();
if (V1 == 0) {
return 0;
}
uint256 sqrt;
uint256 ki = 4 * k * i;
if (ki == 0) {
sqrt = DecimalMath.ONE;
} else if ((ki * delta) / ki == delta) {
sqrt =((ki * delta) / V1 + DecimalMath.ONE2).sqrt();
} else {
sqrt = (ki / V1 * delta + DecimalMath.ONE2).sqrt();
}
uint256 premium =
DecimalMath.divFloor(sqrt - DecimalMath.ONE, k * 2) + DecimalMath.ONE;
// V0 is greater than or equal to V1 according to the solution
return DecimalMath.mulFloor(V1, premium);
}
/*
Follow the integration expression above, we have:
i*deltaB = (Q2-Q1)*(1-k+kQ0^2/Q1/Q2)
Given Q1 and deltaB, solve Q2
This is a quadratic function and the standard version is
aQ2^2 + bQ2 + c = 0, where
a=1-k
-b=(1-k)Q1-kQ0^2/Q1+i*deltaB
c=-kQ0^2
and Q2=(-b+sqrt(b^2+4(1-k)kQ0^2))/2(1-k)
note: another root is negative, abondan
if deltaBSig=true, then Q2>Q1, user sell Q and receive B
if deltaBSig=false, then Q2<Q1, user sell B and receive Q
return |Q1-Q2|
as we only support sell amount as delta, the deltaB is always negative
the input ideltaB is actually -ideltaB in the equation
i is the price of delta-V trading pair
support k=1 & k=0 case
[round down]
*/
function _SolveQuadraticFunctionForTrade(
uint256 V0,
uint256 V1,
uint256 delta,
uint256 i,
uint256 k
) internal pure returns (uint256) {
require(V0 > 0, "TARGET_IS_ZERO");
if (delta == 0) {
return 0;
}
if (k == 0) {
// why v1
return DecimalMath.mulFloor(i, delta) > V1 ? V1 : DecimalMath.mulFloor(i, delta);
}
if (k == DecimalMath.ONE) {
// if k==1
// Q2=Q1/(1+ideltaBQ1/Q0/Q0)
// temp = ideltaBQ1/Q0/Q0
// Q2 = Q1/(1+temp)
// Q1-Q2 = Q1*(1-1/(1+temp)) = Q1*(temp/(1+temp))
// uint256 temp = i.mul(delta).mul(V1).div(V0.mul(V0));
uint256 temp;
uint256 idelta = i * (delta);
if (idelta == 0) {
temp = 0;
} else if ((idelta * V1) / idelta == V1) {
temp = (idelta * V1) / (V0 * V0);
} else {
temp = delta * (V1) / (V0) * (i) / (V0);
}
return V1 * (temp) / (temp + (DecimalMath.ONE));
}
// calculate -b value and sig
// b = kQ0^2/Q1-i*deltaB-(1-k)Q1
// part1 = (1-k)Q1 >=0
// part2 = kQ0^2/Q1-i*deltaB >=0
// bAbs = abs(part1-part2)
// if part1>part2 => b is negative => bSig is false
// if part2>part1 => b is positive => bSig is true
uint256 part2 = k * (V0) / (V1) * (V0) + (i * (delta)); // kQ0^2/Q1-i*deltaB
uint256 bAbs = (DecimalMath.ONE - k) * (V1); // (1-k)Q1
bool bSig;
if (bAbs >= part2) {
bAbs = bAbs - part2;
bSig = false;
} else {
bAbs = part2 - bAbs;
bSig = true;
}
bAbs = bAbs / (DecimalMath.ONE);
// calculate sqrt
uint256 squareRoot = DecimalMath.mulFloor((DecimalMath.ONE - k) * (4), DecimalMath.mulFloor(k, V0) * (V0)); // 4(1-k)kQ0^2
squareRoot = Math.sqrt((bAbs * bAbs) + squareRoot); // sqrt(b*b+4(1-k)kQ0*Q0)
// final res
uint256 denominator = (DecimalMath.ONE - k) * 2; // 2(1-k)
uint256 numerator;
if (bSig) {
numerator = squareRoot - bAbs;
if (numerator == 0) {
revert("DODOMath: should not be 0");
}
} else {
numerator = bAbs + squareRoot;
}
uint256 V2 = DecimalMath.divCeil(numerator, denominator);
if (V2 > V1) {
return 0;
} else {
return V1 - V2;
}
}
}

View File

@@ -0,0 +1,78 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.16;
pragma experimental ABIEncoderV2;
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
/**
* @title DecimalMath
* @author DODO Breeder
*
* @notice Functions for fixed point number with 18 decimals
*/
library DecimalMath {
uint256 internal constant ONE = 10 ** 18;
uint256 internal constant ONE2 = 10 ** 36;
function mul(uint256 target, uint256 d) internal pure returns (uint256) {
return target * d / (10 ** 18);
}
function mulFloor(uint256 target, uint256 d) internal pure returns (uint256) {
return target * d / (10 ** 18);
}
function mulCeil(uint256 target, uint256 d) internal pure returns (uint256) {
return _divCeil(target * d, 10 ** 18);
}
function div(uint256 target, uint256 d) internal pure returns (uint256) {
return target * (10 ** 18) / d;
}
function divFloor(uint256 target, uint256 d) internal pure returns (uint256) {
return target * (10 ** 18) / d;
}
function divCeil(uint256 target, uint256 d) internal pure returns (uint256) {
return _divCeil(target * (10 ** 18), d);
}
function reciprocalFloor(uint256 target) internal pure returns (uint256) {
return uint256(10 ** 36) / target;
}
function reciprocalCeil(uint256 target) internal pure returns (uint256) {
return _divCeil(uint256(10 ** 36), target);
}
function sqrt(uint256 target) internal pure returns (uint256) {
return Math.sqrt(target * ONE);
}
function powFloor(uint256 target, uint256 e) internal pure returns (uint256) {
if (e == 0) {
return 10 ** 18;
} else if (e == 1) {
return target;
} else {
uint256 p = powFloor(target, e / 2);
p = p * p / (10 ** 18);
if (e % 2 == 1) {
p = p * target / (10 ** 18);
}
return p;
}
}
function _divCeil(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 quotient = a / b;
uint256 remainder = a - quotient * b;
if (remainder > 0) {
return quotient + 1;
} else {
return quotient;
}
}
}

View File

@@ -0,0 +1,58 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.8.16;
pragma experimental ABIEncoderV2;
/**
* @title Ownable
* @author DODO Breeder
*
* @notice Ownership related functions
*/
contract InitializableOwnable {
address public _OWNER_;
address public _NEW_OWNER_;
bool internal _INITIALIZED_;
// ============ Events ============
event OwnershipTransferPrepared(address indexed previousOwner, address indexed newOwner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
// ============ Modifiers ============
modifier notInitialized() {
require(!_INITIALIZED_, "DODO_INITIALIZED");
_;
}
modifier onlyOwner() {
require(msg.sender == _OWNER_, "NOT_OWNER");
_;
}
// ============ Functions ============
function initOwner(address newOwner) public notInitialized {
_INITIALIZED_ = true;
_OWNER_ = newOwner;
}
function transferOwnership(address newOwner) public onlyOwner {
emit OwnershipTransferPrepared(_OWNER_, newOwner);
_NEW_OWNER_ = newOwner;
}
function claimOwnership() public {
require(msg.sender == _NEW_OWNER_, "INVALID_CLAIM");
emit OwnershipTransferred(_OWNER_, _NEW_OWNER_);
_OWNER_ = _NEW_OWNER_;
_NEW_OWNER_ = address(0);
}
}

View File

@@ -0,0 +1,266 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.8.16;
pragma experimental ABIEncoderV2;
import {DecimalMath} from "../lib/DecimalMath.sol";
import {DODOMath} from "../lib/DODOMath.sol";
/**
* @title Pricing
* @author DODO Breeder
*
* @notice DODO Pricing model
*/
library PMMPricing {
enum RState {ONE, ABOVE_ONE, BELOW_ONE}
struct PMMState {
uint256 i;
uint256 K;
uint256 B;
uint256 Q;
uint256 B0;
uint256 Q0;
RState R;
}
// ============ buy & sell ============
/**
* @notice Inner calculation based on pmm algorithm, sell base
* @param state The current PMM state
* @param payBaseAmount The amount of base token user want to sell
* @return receiveQuoteAmount The amount of quote token user will receive
* @return newR The new R status after swap
*/
function sellBaseToken(PMMState memory state, uint256 payBaseAmount)
internal
pure
returns (uint256 receiveQuoteAmount, RState newR)
{
if (state.R == RState.ONE) {
// case 1: R=1
// R falls below one
receiveQuoteAmount = _ROneSellBaseToken(state, payBaseAmount);
newR = RState.BELOW_ONE;
} else if (state.R == RState.ABOVE_ONE) {
uint256 backToOnePayBase = state.B0 - state.B;
uint256 backToOneReceiveQuote = state.Q - state.Q0;
// case 2: R>1
// complex case, R status depends on trading amount
if (payBaseAmount < backToOnePayBase) {
// case 2.1: R status do not change
receiveQuoteAmount = _RAboveSellBaseToken(state, payBaseAmount);
newR = RState.ABOVE_ONE;
if (receiveQuoteAmount > backToOneReceiveQuote) {
// [Important corner case!] may enter this branch when some precision problem happens. And consequently contribute to negative spare quote amount
// to make sure spare quote>=0, mannually set receiveQuote=backToOneReceiveQuote
receiveQuoteAmount = backToOneReceiveQuote;
}
} else if (payBaseAmount == backToOnePayBase) {
// case 2.2: R status changes to ONE
receiveQuoteAmount = backToOneReceiveQuote;
newR = RState.ONE;
} else {
// case 2.3: R status changes to BELOW_ONE
receiveQuoteAmount = backToOneReceiveQuote + (
_ROneSellBaseToken(state, (payBaseAmount - backToOnePayBase))
);
newR = RState.BELOW_ONE;
}
} else {
// state.R == RState.BELOW_ONE
// case 3: R<1
receiveQuoteAmount = _RBelowSellBaseToken(state, payBaseAmount);
newR = RState.BELOW_ONE;
}
}
/**
* @notice Inner calculation based on pmm algorithm, sell quote
* @param state The current PMM state
* @param payQuoteAmount The amount of quote token user want to sell
* @return receiveBaseAmount The amount of base token user will receive
* @return newR The new R status after swap
*/
function sellQuoteToken(PMMState memory state, uint256 payQuoteAmount)
internal
pure
returns (uint256 receiveBaseAmount, RState newR)
{
if (state.R == RState.ONE) {
receiveBaseAmount = _ROneSellQuoteToken(state, payQuoteAmount);
newR = RState.ABOVE_ONE;
} else if (state.R == RState.ABOVE_ONE) {
receiveBaseAmount = _RAboveSellQuoteToken(state, payQuoteAmount);
newR = RState.ABOVE_ONE;
} else {
uint256 backToOnePayQuote = state.Q0 - state.Q;
uint256 backToOneReceiveBase = state.B - state.B0;
if (payQuoteAmount < backToOnePayQuote) {
receiveBaseAmount = _RBelowSellQuoteToken(state, payQuoteAmount);
newR = RState.BELOW_ONE;
if (receiveBaseAmount > backToOneReceiveBase) {
receiveBaseAmount = backToOneReceiveBase;
}
} else if (payQuoteAmount == backToOnePayQuote) {
receiveBaseAmount = backToOneReceiveBase;
newR = RState.ONE;
} else {
receiveBaseAmount = backToOneReceiveBase + (
_ROneSellQuoteToken(state, payQuoteAmount - backToOnePayQuote)
);
newR = RState.ABOVE_ONE;
}
}
}
// ============ R = 1 cases ============
function _ROneSellBaseToken(PMMState memory state, uint256 payBaseAmount)
internal
pure
returns (
uint256 // receiveQuoteToken
)
{
// in theory Q2 <= targetQuoteTokenAmount
// however when amount is close to 0, precision problems may cause Q2 > targetQuoteTokenAmount
return
DODOMath._SolveQuadraticFunctionForTrade(
state.Q0,
state.Q0,
payBaseAmount,
state.i,
state.K
);
}
function _ROneSellQuoteToken(PMMState memory state, uint256 payQuoteAmount)
internal
pure
returns (
uint256 // receiveBaseToken
)
{
return
DODOMath._SolveQuadraticFunctionForTrade(
state.B0,
state.B0,
payQuoteAmount,
DecimalMath.reciprocalFloor(state.i),
state.K
);
}
// ============ R < 1 cases ============
function _RBelowSellQuoteToken(PMMState memory state, uint256 payQuoteAmount)
internal
pure
returns (
uint256 // receiveBaseToken
)
{
return
DODOMath._GeneralIntegrate(
state.Q0,
state.Q + payQuoteAmount,
state.Q,
DecimalMath.reciprocalFloor(state.i),
state.K
);
}
function _RBelowSellBaseToken(PMMState memory state, uint256 payBaseAmount)
internal
pure
returns (
uint256 // receiveQuoteToken
)
{
return
DODOMath._SolveQuadraticFunctionForTrade(
state.Q0,
state.Q,
payBaseAmount,
state.i,
state.K
);
}
// ============ R > 1 cases ============
function _RAboveSellBaseToken(PMMState memory state, uint256 payBaseAmount)
internal
pure
returns (
uint256 // receiveQuoteToken
)
{
return
DODOMath._GeneralIntegrate(
state.B0,
state.B + payBaseAmount,
state.B,
state.i,
state.K
);
}
function _RAboveSellQuoteToken(PMMState memory state, uint256 payQuoteAmount)
internal
pure
returns (
uint256 // receiveBaseToken
)
{
return
DODOMath._SolveQuadraticFunctionForTrade(
state.B0,
state.B,
payQuoteAmount,
DecimalMath.reciprocalFloor(state.i),
state.K
);
}
// ============ Helper functions ============
function adjustedTarget(PMMState memory state) internal pure {
if (state.R == RState.BELOW_ONE) {
state.Q0 = DODOMath._SolveQuadraticFunctionForTarget(
state.Q,
state.B - state.B0,
state.i,
state.K
);
} else if (state.R == RState.ABOVE_ONE) {
state.B0 = DODOMath._SolveQuadraticFunctionForTarget(
state.B,
state.Q - state.Q0,
DecimalMath.reciprocalFloor(state.i),
state.K
);
}
}
function getMidPrice(PMMState memory state) internal pure returns (uint256) {
if (state.R == RState.BELOW_ONE) {
uint256 R = DecimalMath.divFloor(state.Q0 * state.Q0 / state.Q, state.Q);
R = DecimalMath.ONE - state.K + (DecimalMath.mulFloor(state.K, R));
return DecimalMath.divFloor(state.i, R);
} else {
uint256 R = DecimalMath.divFloor(state.B0 * state.B0 / state.B, state.B);
R = DecimalMath.ONE - state.K + (DecimalMath.mulFloor(state.K, R));
return DecimalMath.mulFloor(state.i, R);
}
}
}