| -+ | /** | |
| *Submitted for verification at BscScan.com on 2021-03-14 | ||
| */ | ||
| // File: contracts/lib/SafeMath.sol | = | // File: contracts/lib/SafeMath.sol |
| /* | /* | |
| Copyright 2020 DODO ZOO. | Copyright 2020 DODO ZOO. | |
| SPDX-License-Identifier: Apache-2.0 | SPDX-License-Identifier: Apache-2.0 | |
| */ | */ | |
| pragma solidity 0.6.9; | pragma solidity 0.6.9; | |
| pragma experimental ABIEncoderV2; | pragma experimental ABIEncoderV2; | |
| /** | /** | |
| * @title SafeMath | * @title SafeMath | |
| * @author DODO Breeder | * @author DODO Breeder | |
| * | * | |
| * @notice Math operations with safety checks that revert on error | * @notice Math operations with safety checks that revert on error | |
| */ | */ | |
| library SafeMath { | library SafeMath { | |
| function mul(uint256 a, uint256 b) internal pure returns (uint256) { | function mul(uint256 a, uint256 b) internal pure returns (uint256) { | |
| if (a == 0) { | if (a == 0) { | |
| return 0; | return 0; | |
| } | } | |
| uint256 c = a * b; | uint256 c = a * b; | |
| require(c / a == b, "MUL_ERROR"); | require(c / a == b, "MUL_ERROR"); | |
| return c; | return c; | |
| } | } | |
| function div(uint256 a, uint256 b) internal pure returns (uint256) { | function div(uint256 a, uint256 b) internal pure returns (uint256) { | |
| require(b > 0, "DIVIDING_ERROR"); | require(b > 0, "DIVIDING_ERROR"); | |
| return a / b; | return a / b; | |
| } | } | |
| function divCeil(uint256 a, uint256 b) internal pure returns (uint256) { | function divCeil(uint256 a, uint256 b) internal pure returns (uint256) { | |
| uint256 quotient = div(a, b); | uint256 quotient = div(a, b); | |
| uint256 remainder = a - quotient * b; | uint256 remainder = a - quotient * b; | |
| if (remainder > 0) { | if (remainder > 0) { | |
| return quotient + 1; | return quotient + 1; | |
| } else { | } else { | |
| return quotient; | return quotient; | |
| } | } | |
| } | } | |
| function sub(uint256 a, uint256 b) internal pure returns (uint256) { | function sub(uint256 a, uint256 b) internal pure returns (uint256) { | |
| require(b <= a, "SUB_ERROR"); | require(b <= a, "SUB_ERROR"); | |
| return a - b; | return a - b; | |
| } | } | |
| function add(uint256 a, uint256 b) internal pure returns (uint256) { | function add(uint256 a, uint256 b) internal pure returns (uint256) { | |
| uint256 c = a + b; | uint256 c = a + b; | |
| require(c >= a, "ADD_ERROR"); | require(c >= a, "ADD_ERROR"); | |
| return c; | return c; | |
| } | } | |
| function sqrt(uint256 x) internal pure returns (uint256 y) { | function sqrt(uint256 x) internal pure returns (uint256 y) { | |
| uint256 z = x / 2 + 1; | uint256 z = x / 2 + 1; | |
| y = x; | y = x; | |
| while (z < y) { | while (z < y) { | |
| y = z; | y = z; | |
| z = (x / z + z) / 2; | z = (x / z + z) / 2; | |
| } | } | |
| } | } | |
| } | } | |
| // File: contracts/lib/DecimalMath.sol | // File: contracts/lib/DecimalMath.sol | |
| -+ | ||
| /** | = | /** |
| * @title DecimalMath | * @title DecimalMath | |
| * @author DODO Breeder | * @author DODO Breeder | |
| * | * | |
| * @notice Functions for fixed point number with 18 decimals | * @notice Functions for fixed point number with 18 decimals | |
| */ | */ | |
| library DecimalMath { | library DecimalMath { | |
| using SafeMath for uint256; | using SafeMath for uint256; | |
| uint256 internal constant ONE = 10**18; | uint256 internal constant ONE = 10**18; | |
| uint256 internal constant ONE2 = 10**36; | uint256 internal constant ONE2 = 10**36; | |
| function mulFloor(uint256 target, uint256 d) internal pure returns (uint256) { | function mulFloor(uint256 target, uint256 d) internal pure returns (uint256) { | |
| return target.mul(d) / (10**18); | return target.mul(d) / (10**18); | |
| } | } | |
| function mulCeil(uint256 target, uint256 d) internal pure returns (uint256) { | function mulCeil(uint256 target, uint256 d) internal pure returns (uint256) { | |
| return target.mul(d).divCeil(10**18); | return target.mul(d).divCeil(10**18); | |
| } | } | |
| function divFloor(uint256 target, uint256 d) internal pure returns (uint256) { | function divFloor(uint256 target, uint256 d) internal pure returns (uint256) { | |
| return target.mul(10**18).div(d); | return target.mul(10**18).div(d); | |
| } | } | |
| function divCeil(uint256 target, uint256 d) internal pure returns (uint256) { | function divCeil(uint256 target, uint256 d) internal pure returns (uint256) { | |
| return target.mul(10**18).divCeil(d); | return target.mul(10**18).divCeil(d); | |
| } | } | |
| function reciprocalFloor(uint256 target) internal pure returns (uint256) { | function reciprocalFloor(uint256 target) internal pure returns (uint256) { | |
| return uint256(10**36).div(target); | return uint256(10**36).div(target); | |
| } | } | |
| function reciprocalCeil(uint256 target) internal pure returns (uint256) { | function reciprocalCeil(uint256 target) internal pure returns (uint256) { | |
| return uint256(10**36).divCeil(target); | return uint256(10**36).divCeil(target); | |
| } | } | |
| +- | ||
| function powFloor(uint256 target, uint256 e) internal pure returns (uint256) { | ||
| if (e == 0) { | ||
| return 10 ** 18; | ||
| } else if (e == 1) { | ||
| return target; | ||
| } else { | ||
| uint p = powFloor(target, e.div(2)); | ||
| p = p.mul(p) / (10**18); | ||
| if (e % 2 == 1) { | ||
| p = p.mul(target) / (10**18); | ||
| } | ||
| return p; | ||
| } | ||
| } | ||
| } | = | } |
| // File: contracts/lib/Ownable.sol | // File: contracts/lib/Ownable.sol | |
| /** | /** | |
| * @title Ownable | * @title Ownable | |
| * @author DODO Breeder | * @author DODO Breeder | |
| * | * | |
| * @notice Ownership related functions | * @notice Ownership related functions | |
| */ | */ | |
| contract Ownable { | contract Ownable { | |
| address public _OWNER_; | address public _OWNER_; | |
| address public _NEW_OWNER_; | address public _NEW_OWNER_; | |
| // ============ Events ============ | // ============ Events ============ | |
| event OwnershipTransferPrepared(address indexed previousOwner, address indexed newOwner); | event OwnershipTransferPrepared(address indexed previousOwner, address indexed newOwner); | |
| event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); | |
| // ============ Modifiers ============ | // ============ Modifiers ============ | |
| modifier onlyOwner() { | modifier onlyOwner() { | |
| require(msg.sender == _OWNER_, "NOT_OWNER"); | require(msg.sender == _OWNER_, "NOT_OWNER"); | |
| _; | _; | |
| } | } | |
| // ============ Functions ============ | // ============ Functions ============ | |
| constructor() internal { | constructor() internal { | |
| _OWNER_ = msg.sender; | _OWNER_ = msg.sender; | |
| emit OwnershipTransferred(address(0), _OWNER_); | emit OwnershipTransferred(address(0), _OWNER_); | |
| } | } | |
| function transferOwnership(address newOwner) external virtual onlyOwner { | <> | function transferOwnership(address newOwner) external onlyOwner { |
| emit OwnershipTransferPrepared(_OWNER_, newOwner); | = | emit OwnershipTransferPrepared(_OWNER_, newOwner); |
| _NEW_OWNER_ = newOwner; | _NEW_OWNER_ = newOwner; | |
| } | } | |
| function claimOwnership() external { | function claimOwnership() external { | |
| require(msg.sender == _NEW_OWNER_, "INVALID_CLAIM"); | require(msg.sender == _NEW_OWNER_, "INVALID_CLAIM"); | |
| emit OwnershipTransferred(_OWNER_, _NEW_OWNER_); | emit OwnershipTransferred(_OWNER_, _NEW_OWNER_); | |
| _OWNER_ = _NEW_OWNER_; | _OWNER_ = _NEW_OWNER_; | |
| _NEW_OWNER_ = address(0); | _NEW_OWNER_ = address(0); | |
| } | } | |
| } | } | |
| // File: contracts/intf/IERC20.sol | // File: contracts/intf/IERC20.sol | |
| /** | /** | |
| * @dev Interface of the ERC20 standard as defined in the EIP. | * @dev Interface of the ERC20 standard as defined in the EIP. | |
| */ | */ | |
| interface IERC20 { | interface IERC20 { | |
| /** | /** | |
| * @dev Returns the amount of tokens in existence. | * @dev Returns the amount of tokens in existence. | |
| */ | */ | |
| function totalSupply() external view returns (uint256); | function totalSupply() external view returns (uint256); | |
| function decimals() external view returns (uint8); | function decimals() external view returns (uint8); | |
| function name() external view returns (string memory); | function name() external view returns (string memory); | |
| function symbol() external view returns (string memory); | function symbol() external view returns (string memory); | |
| /** | /** | |
| * @dev Returns the amount of tokens owned by `account`. | * @dev Returns the amount of tokens owned by `account`. | |
| */ | */ | |
| function balanceOf(address account) external view returns (uint256); | function balanceOf(address account) external view returns (uint256); | |
| /** | /** | |
| * @dev Moves `amount` tokens from the caller's account to `recipient`. | * @dev Moves `amount` tokens from the caller's account to `recipient`. | |
| * | * | |
| * Returns a boolean value indicating whether the operation succeeded. | * Returns a boolean value indicating whether the operation succeeded. | |
| * | * | |
| * Emits a {Transfer} event. | * Emits a {Transfer} event. | |
| */ | */ | |
| function transfer(address recipient, uint256 amount) external returns (bool); | function transfer(address recipient, uint256 amount) external returns (bool); | |
| /** | /** | |
| * @dev Returns the remaining number of tokens that `spender` will be | * @dev Returns the remaining number of tokens that `spender` will be | |
| * allowed to spend on behalf of `owner` through {transferFrom}. This is | * allowed to spend on behalf of `owner` through {transferFrom}. This is | |
| * zero by default. | * zero by default. | |
| * | * | |
| * This value changes when {approve} or {transferFrom} are called. | * This value changes when {approve} or {transferFrom} are called. | |
| */ | */ | |
| function allowance(address owner, address spender) external view returns (uint256); | function allowance(address owner, address spender) external view returns (uint256); | |
| /** | /** | |
| * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. | |
| * | * | |
| * Returns a boolean value indicating whether the operation succeeded. | * Returns a boolean value indicating whether the operation succeeded. | |
| * | * | |
| * IMPORTANT: Beware that changing an allowance with this method brings the risk | * IMPORTANT: Beware that changing an allowance with this method brings the risk | |
| * that someone may use both the old and the new allowance by unfortunate | * that someone may use both the old and the new allowance by unfortunate | |
| * transaction ordering. One possible solution to mitigate this race | * transaction ordering. One possible solution to mitigate this race | |
| * condition is to first reduce the spender's allowance to 0 and set the | * condition is to first reduce the spender's allowance to 0 and set the | |
| * desired value afterwards: | * desired value afterwards: | |
| * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 | |
| * | * | |
| * Emits an {Approval} event. | * Emits an {Approval} event. | |
| */ | */ | |
| function approve(address spender, uint256 amount) external returns (bool); | function approve(address spender, uint256 amount) external returns (bool); | |
| /** | /** | |
| * @dev Moves `amount` tokens from `sender` to `recipient` using the | * @dev Moves `amount` tokens from `sender` to `recipient` using the | |
| * allowance mechanism. `amount` is then deducted from the caller's | * allowance mechanism. `amount` is then deducted from the caller's | |
| * allowance. | * allowance. | |
| * | * | |
| * Returns a boolean value indicating whether the operation succeeded. | * Returns a boolean value indicating whether the operation succeeded. | |
| * | * | |
| * Emits a {Transfer} event. | * Emits a {Transfer} event. | |
| */ | */ | |
| function transferFrom( | function transferFrom( | |
| address sender, | address sender, | |
| address recipient, | address recipient, | |
| uint256 amount | uint256 amount | |
| ) external returns (bool); | ) external returns (bool); | |
| } | } | |
| // File: contracts/lib/SafeERC20.sol | // File: contracts/lib/SafeERC20.sol | |
| +- | ||
| /** | = | /** |
| * @title SafeERC20 | * @title SafeERC20 | |
| * @dev Wrappers around ERC20 operations that throw on failure (when the token | * @dev Wrappers around ERC20 operations that throw on failure (when the token | |
| * contract returns false). Tokens that return no value (and instead revert or | * contract returns false). Tokens that return no value (and instead revert or | |
| * throw on failure) are also supported, non-reverting calls are assumed to be | * throw on failure) are also supported, non-reverting calls are assumed to be | |
| * successful. | * successful. | |
| * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract, | * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract, | |
| * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. | * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. | |
| */ | */ | |
| library SafeERC20 { | library SafeERC20 { | |
| using SafeMath for uint256; | using SafeMath for uint256; | |
| function safeTransfer( | function safeTransfer( | |
| IERC20 token, | IERC20 token, | |
| address to, | address to, | |
| uint256 value | uint256 value | |
| ) internal { | ) internal { | |
| _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); | _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); | |
| } | } | |
| function safeTransferFrom( | function safeTransferFrom( | |
| IERC20 token, | IERC20 token, | |
| address from, | address from, | |
| address to, | address to, | |
| uint256 value | uint256 value | |
| ) internal { | ) internal { | |
| _callOptionalReturn( | _callOptionalReturn( | |
| token, | token, | |
| abi.encodeWithSelector(token.transferFrom.selector, from, to, value) | abi.encodeWithSelector(token.transferFrom.selector, from, to, value) | |
| ); | ); | |
| } | } | |
| function safeApprove( | function safeApprove( | |
| IERC20 token, | IERC20 token, | |
| address spender, | address spender, | |
| uint256 value | uint256 value | |
| ) internal { | ) internal { | |
| // safeApprove should only be called when setting an initial allowance, | // safeApprove should only be called when setting an initial allowance, | |
| // or when resetting it to zero. To increase and decrease it, use | // or when resetting it to zero. To increase and decrease it, use | |
| // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' | // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' | |
| // solhint-disable-next-line max-line-length | // solhint-disable-next-line max-line-length | |
| require( | require( | |
| (value == 0) || (token.allowance(address(this), spender) == 0), | (value == 0) || (token.allowance(address(this), spender) == 0), | |
| "SafeERC20: approve from non-zero to non-zero allowance" | "SafeERC20: approve from non-zero to non-zero allowance" | |
| ); | ); | |
| _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); | |
| } | } | |
| /** | /** | |
| * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement | * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement | |
| * on the return value: the return value is optional (but if data is returned, it must not be false). | * on the return value: the return value is optional (but if data is returned, it must not be false). | |
| * @param token The token targeted by the call. | * @param token The token targeted by the call. | |
| * @param data The call data (encoded using abi.encode or one of its variants). | * @param data The call data (encoded using abi.encode or one of its variants). | |
| */ | */ | |
| function _callOptionalReturn(IERC20 token, bytes memory data) private { | function _callOptionalReturn(IERC20 token, bytes memory data) private { | |
| // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since | // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since | |
| // we're implementing it ourselves. | // we're implementing it ourselves. | |
| // A Solidity high level call has three parts: | // A Solidity high level call has three parts: | |
| // 1. The target address is checked to verify it contains contract code | // 1. The target address is checked to verify it contains contract code | |
| // 2. The call itself is made, and success asserted | // 2. The call itself is made, and success asserted | |
| // 3. The return value is decoded, which in turn checks the size of the returned data. | // 3. The return value is decoded, which in turn checks the size of the returned data. | |
| // solhint-disable-next-line max-line-length | // solhint-disable-next-line max-line-length | |
| // solhint-disable-next-line avoid-low-level-calls | // solhint-disable-next-line avoid-low-level-calls | |
| (bool success, bytes memory returndata) = address(token).call(data); | (bool success, bytes memory returndata) = address(token).call(data); | |
| require(success, "SafeERC20: low-level call failed"); | require(success, "SafeERC20: low-level call failed"); | |
| if (returndata.length > 0) { | if (returndata.length > 0) { | |
| // Return data is optional | // Return data is optional | |
| // solhint-disable-next-line max-line-length | // solhint-disable-next-line max-line-length | |
| require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); | require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); | |
| } | } | |
| } | } | |
| } | } | |
| // File: contracts/DODOVendingMachine/intf/IDVM.sol | // File: contracts/DODOVendingMachine/intf/IDVM.sol | |
| interface IDVM { | interface IDVM { | |
| function init( | function init( | |
| address maintainer, | address maintainer, | |
| address baseTokenAddress, | address baseTokenAddress, | |
| address quoteTokenAddress, | address quoteTokenAddress, | |
| uint256 lpFeeRate, | uint256 lpFeeRate, | |
| address mtFeeRateModel, | address mtFeeRateModel, | |
| uint256 i, | uint256 i, | |
| uint256 k, | uint256 k, | |
| bool isOpenTWAP | bool isOpenTWAP | |
| ) external; | ) external; | |
| function _BASE_TOKEN_() external returns (address); | function _BASE_TOKEN_() external returns (address); | |
| function _QUOTE_TOKEN_() external returns (address); | function _QUOTE_TOKEN_() external returns (address); | |
| function _MT_FEE_RATE_MODEL_() external returns (address); | function _MT_FEE_RATE_MODEL_() external returns (address); | |
| function getVaultReserve() external returns (uint256 baseReserve, uint256 quoteReserve); | function getVaultReserve() external returns (uint256 baseReserve, uint256 quoteReserve); | |
| function sellBase(address to) external returns (uint256); | function sellBase(address to) external returns (uint256); | |
| function sellQuote(address to) external returns (uint256); | function sellQuote(address to) external returns (uint256); | |
| function buyShares(address to) external returns (uint256,uint256,uint256); | function buyShares(address to) external returns (uint256,uint256,uint256); | |
| function addressToShortString(address _addr) external pure returns (string memory); | +- | |
| function getMidPrice() external view returns (uint256 midPrice); | ||
| function sellShares( | ||
| uint256 shareAmount, | ||
| address to, | ||
| uint256 baseMinAmount, | ||
| uint256 quoteMinAmount, | ||
| bytes calldata data, | ||
| uint256 deadline | ||
| ) external returns (uint256 baseAmount, uint256 quoteAmount); | ||
| } | = | } |
| // File: contracts/lib/InitializableOwnable.sol | // File: contracts/lib/InitializableOwnable.sol | |
| /** | /** | |
| * @title Ownable | * @title Ownable | |
| * @author DODO Breeder | * @author DODO Breeder | |
| * | * | |
| * @notice Ownership related functions | * @notice Ownership related functions | |
| */ | */ | |
| contract InitializableOwnable { | contract InitializableOwnable { | |
| address public _OWNER_; | address public _OWNER_; | |
| address public _NEW_OWNER_; | address public _NEW_OWNER_; | |
| bool internal _INITIALIZED_; | bool internal _INITIALIZED_; | |
| // ============ Events ============ | // ============ Events ============ | |
| event OwnershipTransferPrepared(address indexed previousOwner, address indexed newOwner); | event OwnershipTransferPrepared(address indexed previousOwner, address indexed newOwner); | |
| event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); | |
| // ============ Modifiers ============ | // ============ Modifiers ============ | |
| modifier notInitialized() { | modifier notInitialized() { | |
| require(!_INITIALIZED_, "DODO_INITIALIZED"); | require(!_INITIALIZED_, "DODO_INITIALIZED"); | |
| _; | _; | |
| } | } | |
| modifier onlyOwner() { | modifier onlyOwner() { | |
| require(msg.sender == _OWNER_, "NOT_OWNER"); | require(msg.sender == _OWNER_, "NOT_OWNER"); | |
| _; | _; | |
| } | } | |
| // ============ Functions ============ | // ============ Functions ============ | |
| function initOwner(address newOwner) public notInitialized { | function initOwner(address newOwner) public notInitialized { | |
| _INITIALIZED_ = true; | _INITIALIZED_ = true; | |
| _OWNER_ = newOwner; | _OWNER_ = newOwner; | |
| } | } | |
| function transferOwnership(address newOwner) public onlyOwner { | function transferOwnership(address newOwner) public onlyOwner { | |
| emit OwnershipTransferPrepared(_OWNER_, newOwner); | emit OwnershipTransferPrepared(_OWNER_, newOwner); | |
| _NEW_OWNER_ = newOwner; | _NEW_OWNER_ = newOwner; | |
| } | } | |
| function claimOwnership() public { | function claimOwnership() public { | |
| require(msg.sender == _NEW_OWNER_, "INVALID_CLAIM"); | require(msg.sender == _NEW_OWNER_, "INVALID_CLAIM"); | |
| emit OwnershipTransferred(_OWNER_, _NEW_OWNER_); | emit OwnershipTransferred(_OWNER_, _NEW_OWNER_); | |
| _OWNER_ = _NEW_OWNER_; | _OWNER_ = _NEW_OWNER_; | |
| _NEW_OWNER_ = address(0); | _NEW_OWNER_ = address(0); | |
| } | } | |
| } | } | |
| // File: contracts/lib/CloneFactory.sol | // File: contracts/lib/CloneFactory.sol | |
| interface ICloneFactory { | interface ICloneFactory { | |
| function clone(address prototype) external returns (address proxy); | function clone(address prototype) external returns (address proxy); | |
| } | } | |
| // introduction of proxy mode design: https://docs.openzeppelin.com/upgrades/2.8/ | // introduction of proxy mode design: https://docs.openzeppelin.com/upgrades/2.8/ | |
| // minimum implementation of transparent proxy: https://eips.ethereum.org/EIPS/eip-1167 | // minimum implementation of transparent proxy: https://eips.ethereum.org/EIPS/eip-1167 | |
| contract CloneFactory is ICloneFactory { | contract CloneFactory is ICloneFactory { | |
| function clone(address prototype) external override returns (address proxy) { | function clone(address prototype) external override returns (address proxy) { | |
| bytes20 targetBytes = bytes20(prototype); | bytes20 targetBytes = bytes20(prototype); | |
| assembly { | assembly { | |
| let clone := mload(0x40) | let clone := mload(0x40) | |
| mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000) | mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000) | |
| mstore(add(clone, 0x14), targetBytes) | mstore(add(clone, 0x14), targetBytes) | |
| mstore( | mstore( | |
| add(clone, 0x28), | add(clone, 0x28), | |
| 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000 | 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000 | |
| ) | ) | |
| proxy := create(0, clone, 0x37) | proxy := create(0, clone, 0x37) | |
| } | } | |
| return proxy; | return proxy; | |
| } | } | |
| } | } | |
| // File: contracts/Factory/DVMFactory.sol | // File: contracts/Factory/DVMFactory.sol | |
| -+ | ||
| interface IDVMFactory { | = | interface IDVMFactory { |
| function createDODOVendingMachine( | function createDODOVendingMachine( | |
| address baseToken, | address baseToken, | |
| address quoteToken, | address quoteToken, | |
| uint256 lpFeeRate, | uint256 lpFeeRate, | |
| uint256 i, | uint256 i, | |
| uint256 k, | uint256 k, | |
| bool isOpenTWAP | bool isOpenTWAP | |
| ) external returns (address newVendingMachine); | ) external returns (address newVendingMachine); | |
| } | } | |
| /** | /** | |
| * @title DODO VendingMachine Factory | * @title DODO VendingMachine Factory | |
| * @author DODO Breeder | * @author DODO Breeder | |
| * | * | |
| * @notice Create And Register DVM Pools | * @notice Create And Register DVM Pools | |
| */ | */ | |
| contract DVMFactory is InitializableOwnable { | contract DVMFactory is InitializableOwnable { | |
| // ============ Templates ============ | // ============ Templates ============ | |
| address public immutable _CLONE_FACTORY_; | address public immutable _CLONE_FACTORY_; | |
| -+ | address public immutable _DEFAULT_MAINTAINER_; | |
| address public immutable _DEFAULT_MT_FEE_RATE_MODEL_; | = | address public immutable _DEFAULT_MT_FEE_RATE_MODEL_; |
| address public _DEFAULT_MAINTAINER_; | +- | |
| address public _DVM_TEMPLATE_; | = | address public _DVM_TEMPLATE_; |
| // ============ Registry ============ | // ============ Registry ============ | |
| // base -> quote -> DVM address list | // base -> quote -> DVM address list | |
| mapping(address => mapping(address => address[])) public _REGISTRY_; | mapping(address => mapping(address => address[])) public _REGISTRY_; | |
| // creator -> DVM address list | // creator -> DVM address list | |
| mapping(address => address[]) public _USER_REGISTRY_; | mapping(address => address[]) public _USER_REGISTRY_; | |
| // ============ Events ============ | // ============ Events ============ | |
| event NewDVM( | event NewDVM( | |
| address baseToken, | address baseToken, | |
| address quoteToken, | address quoteToken, | |
| address creator, | address creator, | |
| address dvm | address dvm | |
| ); | ); | |
| event RemoveDVM(address dvm); | event RemoveDVM(address dvm); | |
| // ============ Functions ============ | // ============ Functions ============ | |
| constructor( | constructor( | |
| address cloneFactory, | address cloneFactory, | |
| address dvmTemplate, | address dvmTemplate, | |
| address defaultMaintainer, | address defaultMaintainer, | |
| address defaultMtFeeRateModel | address defaultMtFeeRateModel | |
| ) public { | ) public { | |
| _CLONE_FACTORY_ = cloneFactory; | _CLONE_FACTORY_ = cloneFactory; | |
| _DVM_TEMPLATE_ = dvmTemplate; | _DVM_TEMPLATE_ = dvmTemplate; | |
| _DEFAULT_MAINTAINER_ = defaultMaintainer; | _DEFAULT_MAINTAINER_ = defaultMaintainer; | |
| _DEFAULT_MT_FEE_RATE_MODEL_ = defaultMtFeeRateModel; | _DEFAULT_MT_FEE_RATE_MODEL_ = defaultMtFeeRateModel; | |
| } | } | |
| function createDODOVendingMachine( | function createDODOVendingMachine( | |
| address baseToken, | address baseToken, | |
| address quoteToken, | address quoteToken, | |
| uint256 lpFeeRate, | uint256 lpFeeRate, | |
| uint256 i, | uint256 i, | |
| uint256 k, | uint256 k, | |
| bool isOpenTWAP | bool isOpenTWAP | |
| ) external returns (address newVendingMachine) { | ) external returns (address newVendingMachine) { | |
| newVendingMachine = ICloneFactory(_CLONE_FACTORY_).clone(_DVM_TEMPLATE_); | newVendingMachine = ICloneFactory(_CLONE_FACTORY_).clone(_DVM_TEMPLATE_); | |
| { | { | |
| IDVM(newVendingMachine).init( | IDVM(newVendingMachine).init( | |
| _DEFAULT_MAINTAINER_, | _DEFAULT_MAINTAINER_, | |
| baseToken, | baseToken, | |
| quoteToken, | quoteToken, | |
| lpFeeRate, | lpFeeRate, | |
| _DEFAULT_MT_FEE_RATE_MODEL_, | _DEFAULT_MT_FEE_RATE_MODEL_, | |
| i, | i, | |
| k, | k, | |
| isOpenTWAP | isOpenTWAP | |
| ); | ); | |
| } | } | |
| _REGISTRY_[baseToken][quoteToken].push(newVendingMachine); | _REGISTRY_[baseToken][quoteToken].push(newVendingMachine); | |
| _USER_REGISTRY_[tx.origin].push(newVendingMachine); | _USER_REGISTRY_[tx.origin].push(newVendingMachine); | |
| emit NewDVM(baseToken, quoteToken, tx.origin, newVendingMachine); | emit NewDVM(baseToken, quoteToken, tx.origin, newVendingMachine); | |
| } | } | |
| // ============ Admin Operation Functions ============ | // ============ Admin Operation Functions ============ | |
| function updateDvmTemplate(address _newDVMTemplate) external onlyOwner { | function updateDvmTemplate(address _newDVMTemplate) external onlyOwner { | |
| _DVM_TEMPLATE_ = _newDVMTemplate; | _DVM_TEMPLATE_ = _newDVMTemplate; | |
| } | +- | |
| function updateDefaultMaintainer(address _newMaintainer) external onlyOwner { | ||
| _DEFAULT_MAINTAINER_ = _newMaintainer; | ||
| } | = | } |
| function addPoolByAdmin( | function addPoolByAdmin( | |
| address creator, | address creator, | |
| address baseToken, | address baseToken, | |
| address quoteToken, | address quoteToken, | |
| address pool | address pool | |
| ) external onlyOwner { | ) external onlyOwner { | |
| _REGISTRY_[baseToken][quoteToken].push(pool); | _REGISTRY_[baseToken][quoteToken].push(pool); | |
| _USER_REGISTRY_[creator].push(pool); | _USER_REGISTRY_[creator].push(pool); | |
| emit NewDVM(baseToken, quoteToken, creator, pool); | emit NewDVM(baseToken, quoteToken, creator, pool); | |
| } | } | |
| function removePoolByAdmin( | function removePoolByAdmin( | |
| address creator, | address creator, | |
| address baseToken, | address baseToken, | |
| address quoteToken, | address quoteToken, | |
| address pool | address pool | |
| ) external onlyOwner { | ) external onlyOwner { | |
| address[] memory registryList = _REGISTRY_[baseToken][quoteToken]; | address[] memory registryList = _REGISTRY_[baseToken][quoteToken]; | |
| for (uint256 i = 0; i < registryList.length; i++) { | for (uint256 i = 0; i < registryList.length; i++) { | |
| if (registryList[i] == pool) { | if (registryList[i] == pool) { | |
| registryList[i] = registryList[registryList.length - 1]; | registryList[i] = registryList[registryList.length - 1]; | |
| break; | break; | |
| } | } | |
| } | } | |
| _REGISTRY_[baseToken][quoteToken] = registryList; | _REGISTRY_[baseToken][quoteToken] = registryList; | |
| _REGISTRY_[baseToken][quoteToken].pop(); | _REGISTRY_[baseToken][quoteToken].pop(); | |
| address[] memory userRegistryList = _USER_REGISTRY_[creator]; | address[] memory userRegistryList = _USER_REGISTRY_[creator]; | |
| for (uint256 i = 0; i < userRegistryList.length; i++) { | for (uint256 i = 0; i < userRegistryList.length; i++) { | |
| if (userRegistryList[i] == pool) { | if (userRegistryList[i] == pool) { | |
| userRegistryList[i] = userRegistryList[userRegistryList.length - 1]; | userRegistryList[i] = userRegistryList[userRegistryList.length - 1]; | |
| break; | break; | |
| } | } | |
| } | } | |
| _USER_REGISTRY_[creator] = userRegistryList; | _USER_REGISTRY_[creator] = userRegistryList; | |
| _USER_REGISTRY_[creator].pop(); | _USER_REGISTRY_[creator].pop(); | |
| emit RemoveDVM(pool); | emit RemoveDVM(pool); | |
| } | } | |
| // ============ View Functions ============ | // ============ View Functions ============ | |
| function getDODOPool(address baseToken, address quoteToken) | function getDODOPool(address baseToken, address quoteToken) | |
| external | external | |
| view | view | |
| returns (address[] memory machines) | returns (address[] memory machines) | |
| { | { | |
| return _REGISTRY_[baseToken][quoteToken]; | return _REGISTRY_[baseToken][quoteToken]; | |
| } | } | |
| function getDODOPoolBidirection(address token0, address token1) | function getDODOPoolBidirection(address token0, address token1) | |
| external | external | |
| view | view | |
| returns (address[] memory baseToken0Machines, address[] memory baseToken1Machines) | returns (address[] memory baseToken0Machines, address[] memory baseToken1Machines) | |
| { | { | |
| return (_REGISTRY_[token0][token1], _REGISTRY_[token1][token0]); | return (_REGISTRY_[token0][token1], _REGISTRY_[token1][token0]); | |
| } | } | |
| function getDODOPoolByUser(address user) | function getDODOPoolByUser(address user) | |
| external | external | |
| view | view | |
| returns (address[] memory machines) | returns (address[] memory machines) | |
| { | { | |
| return _USER_REGISTRY_[user]; | return _USER_REGISTRY_[user]; | |
| } | } | |
| } | } | |
| // File: contracts/lib/ReentrancyGuard.sol | // File: contracts/lib/ReentrancyGuard.sol | |
| /** | /** | |
| * @title ReentrancyGuard | * @title ReentrancyGuard | |
| * @author DODO Breeder | * @author DODO Breeder | |
| * | * | |
| * @notice Protect functions from Reentrancy Attack | * @notice Protect functions from Reentrancy Attack | |
| */ | */ | |
| contract ReentrancyGuard { | contract ReentrancyGuard { | |
| // https://solidity.readthedocs.io/en/latest/control-structures.html?highlight=zero-state#scoping-and-declarations | // https://solidity.readthedocs.io/en/latest/control-structures.html?highlight=zero-state#scoping-and-declarations | |
| // zero-state of _ENTERED_ is false | // zero-state of _ENTERED_ is false | |
| bool private _ENTERED_; | bool private _ENTERED_; | |
| modifier preventReentrant() { | modifier preventReentrant() { | |
| require(!_ENTERED_, "REENTRANT"); | require(!_ENTERED_, "REENTRANT"); | |
| _ENTERED_ = true; | _ENTERED_ = true; | |
| _; | _; | |
| _ENTERED_ = false; | _ENTERED_ = false; | |
| } | } | |
| } | } | |
| // File: contracts/lib/PermissionManager.sol | // File: contracts/lib/PermissionManager.sol | |
| interface IPermissionManager { | interface IPermissionManager { | |
| function initOwner(address) external; | function initOwner(address) external; | |
| function isAllowed(address) external view returns (bool); | function isAllowed(address) external view returns (bool); | |
| } | } | |
| contract PermissionManager is InitializableOwnable { | contract PermissionManager is InitializableOwnable { | |
| bool public _WHITELIST_MODE_ON_; | bool public _WHITELIST_MODE_ON_; | |
| mapping(address => bool) internal _whitelist_; | mapping(address => bool) internal _whitelist_; | |
| mapping(address => bool) internal _blacklist_; | mapping(address => bool) internal _blacklist_; | |
| function isAllowed(address account) external view returns (bool) { | function isAllowed(address account) external view returns (bool) { | |
| if (_WHITELIST_MODE_ON_) { | if (_WHITELIST_MODE_ON_) { | |
| return _whitelist_[account]; | return _whitelist_[account]; | |
| } else { | } else { | |
| return !_blacklist_[account]; | return !_blacklist_[account]; | |
| } | } | |
| } | } | |
| function openBlacklistMode() external onlyOwner { | function openBlacklistMode() external onlyOwner { | |
| _WHITELIST_MODE_ON_ = false; | _WHITELIST_MODE_ON_ = false; | |
| } | } | |
| function openWhitelistMode() external onlyOwner { | function openWhitelistMode() external onlyOwner { | |
| _WHITELIST_MODE_ON_ = true; | _WHITELIST_MODE_ON_ = true; | |
| } | } | |
| function addToWhitelist(address account) external onlyOwner { | function addToWhitelist(address account) external onlyOwner { | |
| _whitelist_[account] = true; | _whitelist_[account] = true; | |
| } | } | |
| function removeFromWhitelist(address account) external onlyOwner { | function removeFromWhitelist(address account) external onlyOwner { | |
| _whitelist_[account] = false; | _whitelist_[account] = false; | |
| } | } | |
| function addToBlacklist(address account) external onlyOwner { | function addToBlacklist(address account) external onlyOwner { | |
| _blacklist_[account] = true; | _blacklist_[account] = true; | |
| } | } | |
| function removeFromBlacklist(address account) external onlyOwner { | function removeFromBlacklist(address account) external onlyOwner { | |
| _blacklist_[account] = false; | _blacklist_[account] = false; | |
| } | } | |
| } | } | |
| // File: contracts/lib/FeeRateModel.sol | // File: contracts/lib/FeeRateModel.sol | |
| +- | ||
| interface IFeeRateImpl { | = | interface IFeeRateImpl { |
| function getFeeRate(address pool, address trader) external view returns (uint256); | function getFeeRate(address pool, address trader) external view returns (uint256); | |
| } | } | |
| interface IFeeRateModel { | interface IFeeRateModel { | |
| function getFeeRate(address trader) external view returns (uint256); | function getFeeRate(address trader) external view returns (uint256); | |
| } | } | |
| contract FeeRateModel is InitializableOwnable { | contract FeeRateModel is InitializableOwnable { | |
| address public feeRateImpl; | address public feeRateImpl; | |
| function setFeeProxy(address _feeRateImpl) public onlyOwner { | function setFeeProxy(address _feeRateImpl) public onlyOwner { | |
| feeRateImpl = _feeRateImpl; | feeRateImpl = _feeRateImpl; | |
| } | } | |
| function getFeeRate(address trader) external view returns (uint256) { | function getFeeRate(address trader) external view returns (uint256) { | |
| if(feeRateImpl == address(0)) | if(feeRateImpl == address(0)) | |
| return 0; | return 0; | |
| return IFeeRateImpl(feeRateImpl).getFeeRate(msg.sender,trader); | return IFeeRateImpl(feeRateImpl).getFeeRate(msg.sender,trader); | |
| } | } | |
| } | } | |
| // File: contracts/CrowdPooling/impl/CPStorage.sol | // File: contracts/CrowdPooling/impl/CPStorage.sol | |
| +- | ||
| contract CPStorage is InitializableOwnable, ReentrancyGuard { | = | contract CPStorage is InitializableOwnable, ReentrancyGuard { |
| using SafeMath for uint256; | using SafeMath for uint256; | |
| // ============ Constant ============ | // ============ Constant ============ | |
| uint256 internal constant _SETTLEMENT_EXPIRE_ = 86400 * 7; | uint256 internal constant _SETTLEMENT_EXPIRE_ = 86400 * 7; | |
| uint256 internal constant _SETTEL_FUND_ = 200 finney; | uint256 internal constant _SETTEL_FUND_ = 200 finney; | |
| bool public _IS_OPEN_TWAP_ = false; | bool public _IS_OPEN_TWAP_ = false; | |
| bool public _IS_OVERCAP_STOP = false; | +- | |
| bool public _FORCE_STOP_ = false; | ||
| = | ||
| // ============ Timeline ============ | // ============ Timeline ============ | |
| uint256 public _PHASE_BID_STARTTIME_; | uint256 public _PHASE_BID_STARTTIME_; | |
| uint256 public _PHASE_BID_ENDTIME_; | uint256 public _PHASE_BID_ENDTIME_; | |
| uint256 public _PHASE_CALM_ENDTIME_; | uint256 public _PHASE_CALM_ENDTIME_; | |
| uint256 public _SETTLED_TIME_; | uint256 public _SETTLED_TIME_; | |
| bool public _SETTLED_; | bool public _SETTLED_; | |
| // ============ Core Address ============ | // ============ Core Address ============ | |
| IERC20 public _BASE_TOKEN_; | IERC20 public _BASE_TOKEN_; | |
| IERC20 public _QUOTE_TOKEN_; | IERC20 public _QUOTE_TOKEN_; | |
| // ============ Distribution Parameters ============ | // ============ Distribution Parameters ============ | |
| uint256 public _TOTAL_BASE_; | uint256 public _TOTAL_BASE_; | |
| uint256 public _POOL_QUOTE_CAP_; | uint256 public _POOL_QUOTE_CAP_; | |
| // ============ Settlement ============ | // ============ Settlement ============ | |
| uint256 public _QUOTE_RESERVE_; | uint256 public _QUOTE_RESERVE_; | |
| uint256 public _UNUSED_BASE_; | uint256 public _UNUSED_BASE_; | |
| uint256 public _UNUSED_QUOTE_; | uint256 public _UNUSED_QUOTE_; | |
| uint256 public _TOTAL_SHARES_; | uint256 public _TOTAL_SHARES_; | |
| mapping(address => uint256) internal _SHARES_; | mapping(address => uint256) internal _SHARES_; | |
| mapping(address => bool) public _CLAIMED_QUOTE_; | <> | mapping(address => bool) public _CLAIMED_; |
| = | ||
| address public _POOL_FACTORY_; | address public _POOL_FACTORY_; | |
| address public _POOL_; | address public _POOL_; | |
| uint256 public _POOL_FEE_RATE_; | +- | |
| uint256 public _AVG_SETTLED_PRICE_; | = | uint256 public _AVG_SETTLED_PRICE_; |
| // ============ Advanced Control ============ | // ============ Advanced Control ============ | |
| address public _MAINTAINER_; | address public _MAINTAINER_; | |
| IFeeRateModel public _MT_FEE_RATE_MODEL_; | IFeeRateModel public _MT_FEE_RATE_MODEL_; | |
| IPermissionManager public _BIDDER_PERMISSION_; | IPermissionManager public _BIDDER_PERMISSION_; | |
| // ============ PMM Parameters ============ | // ============ PMM Parameters ============ | |
| uint256 public _K_; | uint256 public _K_; | |
| uint256 public _I_; | uint256 public _I_; | |
| // ============ LP Token Vesting && Claim Params ============ | <> | // ============ LP Token Vesting ============ |
| = | ||
| uint256 public _TOTAL_LP_AMOUNT_; | uint256 public _TOTAL_LP_AMOUNT_; | |
| uint256 public _FREEZE_DURATION_; | uint256 public _FREEZE_DURATION_; | |
| uint256 public _VESTING_DURATION_; | uint256 public _VESTING_DURATION_; | |
| uint256 public _CLIFF_RATE_; | uint256 public _CLIFF_RATE_; | |
| uint256 public _TOKEN_CLAIM_DURATION_; | +- | |
| uint256 public _TOKEN_VESTING_DURATION_; | ||
| uint256 public _TOKEN_CLIFF_RATE_; | ||
| mapping(address => uint256) _CLAIMED_BASE_TOKEN_; | ||
| // ============ Modifiers ============ | = | // ============ Modifiers ============ |
| modifier isForceStop() { | +- | |
| require(!_FORCE_STOP_, "FORCE_STOP"); | ||
| _; | ||
| } | ||
| = | ||
| modifier phaseBid() { | modifier phaseBid() { | |
| require( | require( | |
| block.timestamp >= _PHASE_BID_STARTTIME_ && block.timestamp < _PHASE_BID_ENDTIME_, | block.timestamp >= _PHASE_BID_STARTTIME_ && block.timestamp < _PHASE_BID_ENDTIME_, | |
| "NOT_PHASE_BID" | "NOT_PHASE_BID" | |
| ); | ); | |
| _; | _; | |
| } | } | |
| modifier phaseCalm() { | modifier phaseCalm() { | |
| require( | require( | |
| block.timestamp >= _PHASE_BID_ENDTIME_ && block.timestamp < _PHASE_CALM_ENDTIME_, | block.timestamp >= _PHASE_BID_ENDTIME_ && block.timestamp < _PHASE_CALM_ENDTIME_, | |
| "NOT_PHASE_CALM" | "NOT_PHASE_CALM" | |
| ); | ); | |
| _; | _; | |
| } | } | |
| modifier phaseBidOrCalm() { | modifier phaseBidOrCalm() { | |
| require( | require( | |
| block.timestamp >= _PHASE_BID_STARTTIME_ && block.timestamp < _PHASE_CALM_ENDTIME_, | block.timestamp >= _PHASE_BID_STARTTIME_ && block.timestamp < _PHASE_CALM_ENDTIME_, | |
| "NOT_PHASE_BID_OR_CALM" | "NOT_PHASE_BID_OR_CALM" | |
| ); | ); | |
| _; | _; | |
| } | } | |
| modifier phaseSettlement() { | modifier phaseSettlement() { | |
| require(block.timestamp >= _PHASE_CALM_ENDTIME_, "NOT_PHASE_EXE"); | require(block.timestamp >= _PHASE_CALM_ENDTIME_, "NOT_PHASE_EXE"); | |
| _; | _; | |
| } | } | |
| modifier phaseVesting() { | modifier phaseVesting() { | |
| require(_SETTLED_, "NOT_VESTING"); | require(_SETTLED_, "NOT_VESTING"); | |
| _; | _; | |
| } | +- | |
| function forceStop() external onlyOwner { | ||
| require(block.timestamp < _PHASE_BID_STARTTIME_, "CP_ALREADY_STARTED"); | ||
| _FORCE_STOP_ = true; | ||
| _TOTAL_BASE_ = 0; | ||
| uint256 baseAmount = _BASE_TOKEN_.balanceOf(address(this)); | ||
| _BASE_TOKEN_.transfer(_OWNER_, baseAmount); | ||
| } | = | } |
| } | } | |
| // File: contracts/lib/DODOMath.sol | // File: contracts/lib/DODOMath.sol | |
| /** | /** | |
| * @title DODOMath | * @title DODOMath | |
| * @author DODO Breeder | * @author DODO Breeder | |
| * | * | |
| * @notice Functions for complex calculating. Including ONE Integration and TWO Quadratic solutions | * @notice Functions for complex calculating. Including ONE Integration and TWO Quadratic solutions | |
| */ | */ | |
| library DODOMath { | library DODOMath { | |
| using SafeMath for uint256; | using SafeMath for uint256; | |
| /* | /* | |
| Integrate dodo curve from V1 to V2 | Integrate dodo curve from V1 to V2 | |
| require V0>=V1>=V2>0 | require V0>=V1>=V2>0 | |
| res = (1-k)i(V1-V2)+ikV0*V0(1/V2-1/V1) | res = (1-k)i(V1-V2)+ikV0*V0(1/V2-1/V1) | |
| let V1-V2=delta | let V1-V2=delta | |
| res = i*delta*(1-k+k(V0^2/V1/V2)) | res = i*delta*(1-k+k(V0^2/V1/V2)) | |
| i is the price of V-res trading pair | i is the price of V-res trading pair | |
| support k=1 & k=0 case | support k=1 & k=0 case | |
| [round down] | [round down] | |
| */ | */ | |
| function _GeneralIntegrate( | function _GeneralIntegrate( | |
| uint256 V0, | uint256 V0, | |
| uint256 V1, | uint256 V1, | |
| uint256 V2, | uint256 V2, | |
| uint256 i, | uint256 i, | |
| uint256 k | uint256 k | |
| ) internal pure returns (uint256) { | ) internal pure returns (uint256) { | |
| require(V0 > 0, "TARGET_IS_ZERO"); | require(V0 > 0, "TARGET_IS_ZERO"); | |
| uint256 fairAmount = i.mul(V1.sub(V2)); // i*delta | uint256 fairAmount = i.mul(V1.sub(V2)); // i*delta | |
| if (k == 0) { | if (k == 0) { | |
| return fairAmount.div(DecimalMath.ONE); | return fairAmount.div(DecimalMath.ONE); | |
| } | } | |
| uint256 V0V0V1V2 = DecimalMath.divFloor(V0.mul(V0).div(V1), V2); | uint256 V0V0V1V2 = DecimalMath.divFloor(V0.mul(V0).div(V1), V2); | |
| uint256 penalty = DecimalMath.mulFloor(k, V0V0V1V2); // k(V0^2/V1/V2) | uint256 penalty = DecimalMath.mulFloor(k, V0V0V1V2); // k(V0^2/V1/V2) | |
| return DecimalMath.ONE.sub(k).add(penalty).mul(fairAmount).div(DecimalMath.ONE2); | return DecimalMath.ONE.sub(k).add(penalty).mul(fairAmount).div(DecimalMath.ONE2); | |
| } | } | |
| /* | /* | |
| Follow the integration function above | Follow the integration function above | |
| i*deltaB = (Q2-Q1)*(1-k+kQ0^2/Q1/Q2) | i*deltaB = (Q2-Q1)*(1-k+kQ0^2/Q1/Q2) | |
| Assume Q2=Q0, Given Q1 and deltaB, solve Q0 | Assume Q2=Q0, Given Q1 and deltaB, solve Q0 | |
| i is the price of delta-V trading pair | i is the price of delta-V trading pair | |
| give out target of V | give out target of V | |
| support k=1 & k=0 case | support k=1 & k=0 case | |
| [round down] | [round down] | |
| */ | */ | |
| function _SolveQuadraticFunctionForTarget( | function _SolveQuadraticFunctionForTarget( | |
| uint256 V1, | uint256 V1, | |
| uint256 delta, | uint256 delta, | |
| uint256 i, | uint256 i, | |
| uint256 k | uint256 k | |
| ) internal pure returns (uint256) { | ) internal pure returns (uint256) { | |
| if (V1 == 0) { | if (V1 == 0) { | |
| return 0; | return 0; | |
| } | } | |
| if (k == 0) { | if (k == 0) { | |
| return V1.add(DecimalMath.mulFloor(i, delta)); | return V1.add(DecimalMath.mulFloor(i, delta)); | |
| } | } | |
| // V0 = V1*(1+(sqrt-1)/2k) | // V0 = V1*(1+(sqrt-1)/2k) | |
| // sqrt = √(1+4kidelta/V1) | // sqrt = √(1+4kidelta/V1) | |
| // premium = 1+(sqrt-1)/2k | // premium = 1+(sqrt-1)/2k | |
| // uint256 sqrt = (4 * k).mul(i).mul(delta).div(V1).add(DecimalMath.ONE2).sqrt(); | // uint256 sqrt = (4 * k).mul(i).mul(delta).div(V1).add(DecimalMath.ONE2).sqrt(); | |
| uint256 sqrt; | uint256 sqrt; | |
| uint256 ki = (4 * k).mul(i); | uint256 ki = (4 * k).mul(i); | |
| if (ki == 0) { | if (ki == 0) { | |
| sqrt = DecimalMath.ONE; | sqrt = DecimalMath.ONE; | |
| } else if ((ki * delta) / ki == delta) { | } else if ((ki * delta) / ki == delta) { | |
| sqrt = (ki * delta).div(V1).add(DecimalMath.ONE2).sqrt(); | sqrt = (ki * delta).div(V1).add(DecimalMath.ONE2).sqrt(); | |
| } else { | } else { | |
| sqrt = ki.div(V1).mul(delta).add(DecimalMath.ONE2).sqrt(); | sqrt = ki.div(V1).mul(delta).add(DecimalMath.ONE2).sqrt(); | |
| } | } | |
| uint256 premium = | uint256 premium = | |
| DecimalMath.divFloor(sqrt.sub(DecimalMath.ONE), k * 2).add(DecimalMath.ONE); | DecimalMath.divFloor(sqrt.sub(DecimalMath.ONE), k * 2).add(DecimalMath.ONE); | |
| // V0 is greater than or equal to V1 according to the solution | // V0 is greater than or equal to V1 according to the solution | |
| return DecimalMath.mulFloor(V1, premium); | return DecimalMath.mulFloor(V1, premium); | |
| } | } | |
| /* | /* | |
| Follow the integration expression above, we have: | Follow the integration expression above, we have: | |
| i*deltaB = (Q2-Q1)*(1-k+kQ0^2/Q1/Q2) | i*deltaB = (Q2-Q1)*(1-k+kQ0^2/Q1/Q2) | |
| Given Q1 and deltaB, solve Q2 | Given Q1 and deltaB, solve Q2 | |
| This is a quadratic function and the standard version is | This is a quadratic function and the standard version is | |
| aQ2^2 + bQ2 + c = 0, where | aQ2^2 + bQ2 + c = 0, where | |
| a=1-k | a=1-k | |
| -b=(1-k)Q1-kQ0^2/Q1+i*deltaB | -b=(1-k)Q1-kQ0^2/Q1+i*deltaB | |
| c=-kQ0^2 | c=-kQ0^2 | |
| and Q2=(-b+sqrt(b^2+4(1-k)kQ0^2))/2(1-k) | and Q2=(-b+sqrt(b^2+4(1-k)kQ0^2))/2(1-k) | |
| note: another root is negative, abondan | note: another root is negative, abondan | |
| if deltaBSig=true, then Q2>Q1, user sell Q and receive B | if deltaBSig=true, then Q2>Q1, user sell Q and receive B | |
| if deltaBSig=false, then Q2<Q1, user sell B and receive Q | if deltaBSig=false, then Q2<Q1, user sell B and receive Q | |
| return |Q1-Q2| | return |Q1-Q2| | |
| as we only support sell amount as delta, the deltaB is always negative | as we only support sell amount as delta, the deltaB is always negative | |
| the input ideltaB is actually -ideltaB in the equation | the input ideltaB is actually -ideltaB in the equation | |
| i is the price of delta-V trading pair | i is the price of delta-V trading pair | |
| support k=1 & k=0 case | support k=1 & k=0 case | |
| [round down] | [round down] | |
| */ | */ | |
| function _SolveQuadraticFunctionForTrade( | function _SolveQuadraticFunctionForTrade( | |
| uint256 V0, | uint256 V0, | |
| uint256 V1, | uint256 V1, | |
| uint256 delta, | uint256 delta, | |
| uint256 i, | uint256 i, | |
| uint256 k | uint256 k | |
| ) internal pure returns (uint256) { | ) internal pure returns (uint256) { | |
| require(V0 > 0, "TARGET_IS_ZERO"); | require(V0 > 0, "TARGET_IS_ZERO"); | |
| if (delta == 0) { | if (delta == 0) { | |
| return 0; | return 0; | |
| } | } | |
| if (k == 0) { | if (k == 0) { | |
| return DecimalMath.mulFloor(i, delta) > V1 ? V1 : DecimalMath.mulFloor(i, delta); | return DecimalMath.mulFloor(i, delta) > V1 ? V1 : DecimalMath.mulFloor(i, delta); | |
| } | } | |
| if (k == DecimalMath.ONE) { | if (k == DecimalMath.ONE) { | |
| // if k==1 | // if k==1 | |
| // Q2=Q1/(1+ideltaBQ1/Q0/Q0) | // Q2=Q1/(1+ideltaBQ1/Q0/Q0) | |
| // temp = ideltaBQ1/Q0/Q0 | // temp = ideltaBQ1/Q0/Q0 | |
| // Q2 = Q1/(1+temp) | // Q2 = Q1/(1+temp) | |
| // Q1-Q2 = Q1*(1-1/(1+temp)) = Q1*(temp/(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 = i.mul(delta).mul(V1).div(V0.mul(V0)); | |
| uint256 temp; | uint256 temp; | |
| uint256 idelta = i.mul(delta); | uint256 idelta = i.mul(delta); | |
| if (idelta == 0) { | if (idelta == 0) { | |
| temp = 0; | temp = 0; | |
| } else if ((idelta * V1) / idelta == V1) { | } else if ((idelta * V1) / idelta == V1) { | |
| temp = (idelta * V1).div(V0.mul(V0)); | temp = (idelta * V1).div(V0.mul(V0)); | |
| } else { | } else { | |
| temp = delta.mul(V1).div(V0).mul(i).div(V0); | temp = delta.mul(V1).div(V0).mul(i).div(V0); | |
| } | } | |
| return V1.mul(temp).div(temp.add(DecimalMath.ONE)); | return V1.mul(temp).div(temp.add(DecimalMath.ONE)); | |
| } | } | |
| // calculate -b value and sig | // calculate -b value and sig | |
| // b = kQ0^2/Q1-i*deltaB-(1-k)Q1 | // b = kQ0^2/Q1-i*deltaB-(1-k)Q1 | |
| // part1 = (1-k)Q1 >=0 | // part1 = (1-k)Q1 >=0 | |
| // part2 = kQ0^2/Q1-i*deltaB >=0 | // part2 = kQ0^2/Q1-i*deltaB >=0 | |
| // bAbs = abs(part1-part2) | // bAbs = abs(part1-part2) | |
| // if part1>part2 => b is negative => bSig is false | // if part1>part2 => b is negative => bSig is false | |
| // if part2>part1 => b is positive => bSig is true | // if part2>part1 => b is positive => bSig is true | |
| uint256 part2 = k.mul(V0).div(V1).mul(V0).add(i.mul(delta)); // kQ0^2/Q1-i*deltaB | uint256 part2 = k.mul(V0).div(V1).mul(V0).add(i.mul(delta)); // kQ0^2/Q1-i*deltaB | |
| uint256 bAbs = DecimalMath.ONE.sub(k).mul(V1); // (1-k)Q1 | uint256 bAbs = DecimalMath.ONE.sub(k).mul(V1); // (1-k)Q1 | |
| bool bSig; | bool bSig; | |
| if (bAbs >= part2) { | if (bAbs >= part2) { | |
| bAbs = bAbs - part2; | bAbs = bAbs - part2; | |
| bSig = false; | bSig = false; | |
| } else { | } else { | |
| bAbs = part2 - bAbs; | bAbs = part2 - bAbs; | |
| bSig = true; | bSig = true; | |
| } | } | |
| bAbs = bAbs.div(DecimalMath.ONE); | bAbs = bAbs.div(DecimalMath.ONE); | |
| // calculate sqrt | // calculate sqrt | |
| uint256 squareRoot = | uint256 squareRoot = | |
| DecimalMath.mulFloor( | DecimalMath.mulFloor( | |
| DecimalMath.ONE.sub(k).mul(4), | DecimalMath.ONE.sub(k).mul(4), | |
| DecimalMath.mulFloor(k, V0).mul(V0) | DecimalMath.mulFloor(k, V0).mul(V0) | |
| ); // 4(1-k)kQ0^2 | ); // 4(1-k)kQ0^2 | |
| squareRoot = bAbs.mul(bAbs).add(squareRoot).sqrt(); // sqrt(b*b+4(1-k)kQ0*Q0) | squareRoot = bAbs.mul(bAbs).add(squareRoot).sqrt(); // sqrt(b*b+4(1-k)kQ0*Q0) | |
| // final res | // final res | |
| uint256 denominator = DecimalMath.ONE.sub(k).mul(2); // 2(1-k) | uint256 denominator = DecimalMath.ONE.sub(k).mul(2); // 2(1-k) | |
| uint256 numerator; | uint256 numerator; | |
| if (bSig) { | if (bSig) { | |
| numerator = squareRoot.sub(bAbs); | numerator = squareRoot.sub(bAbs); | |
| } else { | } else { | |
| numerator = bAbs.add(squareRoot); | numerator = bAbs.add(squareRoot); | |
| } | } | |
| uint256 V2 = DecimalMath.divCeil(numerator, denominator); | uint256 V2 = DecimalMath.divCeil(numerator, denominator); | |
| if (V2 > V1) { | if (V2 > V1) { | |
| return 0; | return 0; | |
| } else { | } else { | |
| return V1 - V2; | return V1 - V2; | |
| } | } | |
| } | } | |
| } | } | |
| // File: contracts/lib/PMMPricing.sol | // File: contracts/lib/PMMPricing.sol | |
| /** | /** | |
| * @title Pricing | * @title Pricing | |
| * @author DODO Breeder | * @author DODO Breeder | |
| * | * | |
| * @notice DODO Pricing model | * @notice DODO Pricing model | |
| */ | */ | |
| library PMMPricing { | library PMMPricing { | |
| using SafeMath for uint256; | using SafeMath for uint256; | |
| enum RState {ONE, ABOVE_ONE, BELOW_ONE} | enum RState {ONE, ABOVE_ONE, BELOW_ONE} | |
| struct PMMState { | struct PMMState { | |
| uint256 i; | uint256 i; | |
| uint256 K; | uint256 K; | |
| uint256 B; | uint256 B; | |
| uint256 Q; | uint256 Q; | |
| uint256 B0; | uint256 B0; | |
| uint256 Q0; | uint256 Q0; | |
| RState R; | RState R; | |
| } | } | |
| // ============ buy & sell ============ | // ============ buy & sell ============ | |
| function sellBaseToken(PMMState memory state, uint256 payBaseAmount) | function sellBaseToken(PMMState memory state, uint256 payBaseAmount) | |
| internal | internal | |
| pure | pure | |
| returns (uint256 receiveQuoteAmount, RState newR) | returns (uint256 receiveQuoteAmount, RState newR) | |
| { | { | |
| if (state.R == RState.ONE) { | if (state.R == RState.ONE) { | |
| // case 1: R=1 | // case 1: R=1 | |
| // R falls below one | // R falls below one | |
| receiveQuoteAmount = _ROneSellBaseToken(state, payBaseAmount); | receiveQuoteAmount = _ROneSellBaseToken(state, payBaseAmount); | |
| newR = RState.BELOW_ONE; | newR = RState.BELOW_ONE; | |
| } else if (state.R == RState.ABOVE_ONE) { | } else if (state.R == RState.ABOVE_ONE) { | |
| uint256 backToOnePayBase = state.B0.sub(state.B); | uint256 backToOnePayBase = state.B0.sub(state.B); | |
| uint256 backToOneReceiveQuote = state.Q.sub(state.Q0); | uint256 backToOneReceiveQuote = state.Q.sub(state.Q0); | |
| // case 2: R>1 | // case 2: R>1 | |
| // complex case, R status depends on trading amount | // complex case, R status depends on trading amount | |
| if (payBaseAmount < backToOnePayBase) { | if (payBaseAmount < backToOnePayBase) { | |
| // case 2.1: R status do not change | // case 2.1: R status do not change | |
| receiveQuoteAmount = _RAboveSellBaseToken(state, payBaseAmount); | receiveQuoteAmount = _RAboveSellBaseToken(state, payBaseAmount); | |
| newR = RState.ABOVE_ONE; | newR = RState.ABOVE_ONE; | |
| if (receiveQuoteAmount > backToOneReceiveQuote) { | if (receiveQuoteAmount > backToOneReceiveQuote) { | |
| // [Important corner case!] may enter this branch when some precision problem happens. And consequently contribute to negative spare quote amount | // [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 | // to make sure spare quote>=0, mannually set receiveQuote=backToOneReceiveQuote | |
| receiveQuoteAmount = backToOneReceiveQuote; | receiveQuoteAmount = backToOneReceiveQuote; | |
| } | } | |
| } else if (payBaseAmount == backToOnePayBase) { | } else if (payBaseAmount == backToOnePayBase) { | |
| // case 2.2: R status changes to ONE | // case 2.2: R status changes to ONE | |
| receiveQuoteAmount = backToOneReceiveQuote; | receiveQuoteAmount = backToOneReceiveQuote; | |
| newR = RState.ONE; | newR = RState.ONE; | |
| } else { | } else { | |
| // case 2.3: R status changes to BELOW_ONE | // case 2.3: R status changes to BELOW_ONE | |
| receiveQuoteAmount = backToOneReceiveQuote.add( | receiveQuoteAmount = backToOneReceiveQuote.add( | |
| _ROneSellBaseToken(state, payBaseAmount.sub(backToOnePayBase)) | _ROneSellBaseToken(state, payBaseAmount.sub(backToOnePayBase)) | |
| ); | ); | |
| newR = RState.BELOW_ONE; | newR = RState.BELOW_ONE; | |
| } | } | |
| } else { | } else { | |
| // state.R == RState.BELOW_ONE | // state.R == RState.BELOW_ONE | |
| // case 3: R<1 | // case 3: R<1 | |
| receiveQuoteAmount = _RBelowSellBaseToken(state, payBaseAmount); | receiveQuoteAmount = _RBelowSellBaseToken(state, payBaseAmount); | |
| newR = RState.BELOW_ONE; | newR = RState.BELOW_ONE; | |
| } | } | |
| } | } | |
| function sellQuoteToken(PMMState memory state, uint256 payQuoteAmount) | function sellQuoteToken(PMMState memory state, uint256 payQuoteAmount) | |
| internal | internal | |
| pure | pure | |
| returns (uint256 receiveBaseAmount, RState newR) | returns (uint256 receiveBaseAmount, RState newR) | |
| { | { | |
| if (state.R == RState.ONE) { | if (state.R == RState.ONE) { | |
| receiveBaseAmount = _ROneSellQuoteToken(state, payQuoteAmount); | receiveBaseAmount = _ROneSellQuoteToken(state, payQuoteAmount); | |
| newR = RState.ABOVE_ONE; | newR = RState.ABOVE_ONE; | |
| } else if (state.R == RState.ABOVE_ONE) { | } else if (state.R == RState.ABOVE_ONE) { | |
| receiveBaseAmount = _RAboveSellQuoteToken(state, payQuoteAmount); | receiveBaseAmount = _RAboveSellQuoteToken(state, payQuoteAmount); | |
| newR = RState.ABOVE_ONE; | newR = RState.ABOVE_ONE; | |
| } else { | } else { | |
| uint256 backToOnePayQuote = state.Q0.sub(state.Q); | uint256 backToOnePayQuote = state.Q0.sub(state.Q); | |
| uint256 backToOneReceiveBase = state.B.sub(state.B0); | uint256 backToOneReceiveBase = state.B.sub(state.B0); | |
| if (payQuoteAmount < backToOnePayQuote) { | if (payQuoteAmount < backToOnePayQuote) { | |
| receiveBaseAmount = _RBelowSellQuoteToken(state, payQuoteAmount); | receiveBaseAmount = _RBelowSellQuoteToken(state, payQuoteAmount); | |
| newR = RState.BELOW_ONE; | newR = RState.BELOW_ONE; | |
| if (receiveBaseAmount > backToOneReceiveBase) { | if (receiveBaseAmount > backToOneReceiveBase) { | |
| receiveBaseAmount = backToOneReceiveBase; | receiveBaseAmount = backToOneReceiveBase; | |
| } | } | |
| } else if (payQuoteAmount == backToOnePayQuote) { | } else if (payQuoteAmount == backToOnePayQuote) { | |
| receiveBaseAmount = backToOneReceiveBase; | receiveBaseAmount = backToOneReceiveBase; | |
| newR = RState.ONE; | newR = RState.ONE; | |
| } else { | } else { | |
| receiveBaseAmount = backToOneReceiveBase.add( | receiveBaseAmount = backToOneReceiveBase.add( | |
| _ROneSellQuoteToken(state, payQuoteAmount.sub(backToOnePayQuote)) | _ROneSellQuoteToken(state, payQuoteAmount.sub(backToOnePayQuote)) | |
| ); | ); | |
| newR = RState.ABOVE_ONE; | newR = RState.ABOVE_ONE; | |
| } | } | |
| } | } | |
| } | } | |
| // ============ R = 1 cases ============ | // ============ R = 1 cases ============ | |
| function _ROneSellBaseToken(PMMState memory state, uint256 payBaseAmount) | function _ROneSellBaseToken(PMMState memory state, uint256 payBaseAmount) | |
| internal | internal | |
| pure | pure | |
| returns ( | returns ( | |
| uint256 // receiveQuoteToken | uint256 // receiveQuoteToken | |
| ) | ) | |
| { | { | |
| // in theory Q2 <= targetQuoteTokenAmount | // in theory Q2 <= targetQuoteTokenAmount | |
| // however when amount is close to 0, precision problems may cause Q2 > targetQuoteTokenAmount | // however when amount is close to 0, precision problems may cause Q2 > targetQuoteTokenAmount | |
| return | return | |
| DODOMath._SolveQuadraticFunctionForTrade( | DODOMath._SolveQuadraticFunctionForTrade( | |
| state.Q0, | state.Q0, | |
| state.Q0, | state.Q0, | |
| payBaseAmount, | payBaseAmount, | |
| state.i, | state.i, | |
| state.K | state.K | |
| ); | ); | |
| } | } | |
| function _ROneSellQuoteToken(PMMState memory state, uint256 payQuoteAmount) | function _ROneSellQuoteToken(PMMState memory state, uint256 payQuoteAmount) | |
| internal | internal | |
| pure | pure | |
| returns ( | returns ( | |
| uint256 // receiveBaseToken | uint256 // receiveBaseToken | |
| ) | ) | |
| { | { | |
| return | return | |
| DODOMath._SolveQuadraticFunctionForTrade( | DODOMath._SolveQuadraticFunctionForTrade( | |
| state.B0, | state.B0, | |
| state.B0, | state.B0, | |
| payQuoteAmount, | payQuoteAmount, | |
| DecimalMath.reciprocalFloor(state.i), | DecimalMath.reciprocalFloor(state.i), | |
| state.K | state.K | |
| ); | ); | |
| } | } | |
| // ============ R < 1 cases ============ | // ============ R < 1 cases ============ | |
| function _RBelowSellQuoteToken(PMMState memory state, uint256 payQuoteAmount) | function _RBelowSellQuoteToken(PMMState memory state, uint256 payQuoteAmount) | |
| internal | internal | |
| pure | pure | |
| returns ( | returns ( | |
| uint256 // receiveBaseToken | uint256 // receiveBaseToken | |
| ) | ) | |
| { | { | |
| return | return | |
| DODOMath._GeneralIntegrate( | DODOMath._GeneralIntegrate( | |
| state.Q0, | state.Q0, | |
| state.Q.add(payQuoteAmount), | state.Q.add(payQuoteAmount), | |
| state.Q, | state.Q, | |
| DecimalMath.reciprocalFloor(state.i), | DecimalMath.reciprocalFloor(state.i), | |
| state.K | state.K | |
| ); | ); | |
| } | } | |
| function _RBelowSellBaseToken(PMMState memory state, uint256 payBaseAmount) | function _RBelowSellBaseToken(PMMState memory state, uint256 payBaseAmount) | |
| internal | internal | |
| pure | pure | |
| returns ( | returns ( | |
| uint256 // receiveQuoteToken | uint256 // receiveQuoteToken | |
| ) | ) | |
| { | { | |
| return | return | |
| DODOMath._SolveQuadraticFunctionForTrade( | DODOMath._SolveQuadraticFunctionForTrade( | |
| state.Q0, | state.Q0, | |
| state.Q, | state.Q, | |
| payBaseAmount, | payBaseAmount, | |
| state.i, | state.i, | |
| state.K | state.K | |
| ); | ); | |
| } | } | |
| // ============ R > 1 cases ============ | // ============ R > 1 cases ============ | |
| function _RAboveSellBaseToken(PMMState memory state, uint256 payBaseAmount) | function _RAboveSellBaseToken(PMMState memory state, uint256 payBaseAmount) | |
| internal | internal | |
| pure | pure | |
| returns ( | returns ( | |
| uint256 // receiveQuoteToken | uint256 // receiveQuoteToken | |
| ) | ) | |
| { | { | |
| return | return | |
| DODOMath._GeneralIntegrate( | DODOMath._GeneralIntegrate( | |
| state.B0, | state.B0, | |
| state.B.add(payBaseAmount), | state.B.add(payBaseAmount), | |
| state.B, | state.B, | |
| state.i, | state.i, | |
| state.K | state.K | |
| ); | ); | |
| } | } | |
| function _RAboveSellQuoteToken(PMMState memory state, uint256 payQuoteAmount) | function _RAboveSellQuoteToken(PMMState memory state, uint256 payQuoteAmount) | |
| internal | internal | |
| pure | pure | |
| returns ( | returns ( | |
| uint256 // receiveBaseToken | uint256 // receiveBaseToken | |
| ) | ) | |
| { | { | |
| return | return | |
| DODOMath._SolveQuadraticFunctionForTrade( | DODOMath._SolveQuadraticFunctionForTrade( | |
| state.B0, | state.B0, | |
| state.B, | state.B, | |
| payQuoteAmount, | payQuoteAmount, | |
| DecimalMath.reciprocalFloor(state.i), | DecimalMath.reciprocalFloor(state.i), | |
| state.K | state.K | |
| ); | ); | |
| } | } | |
| // ============ Helper functions ============ | // ============ Helper functions ============ | |
| function adjustedTarget(PMMState memory state) internal pure { | function adjustedTarget(PMMState memory state) internal pure { | |
| if (state.R == RState.BELOW_ONE) { | if (state.R == RState.BELOW_ONE) { | |
| state.Q0 = DODOMath._SolveQuadraticFunctionForTarget( | state.Q0 = DODOMath._SolveQuadraticFunctionForTarget( | |
| state.Q, | state.Q, | |
| state.B.sub(state.B0), | state.B.sub(state.B0), | |
| state.i, | state.i, | |
| state.K | state.K | |
| ); | ); | |
| } else if (state.R == RState.ABOVE_ONE) { | } else if (state.R == RState.ABOVE_ONE) { | |
| state.B0 = DODOMath._SolveQuadraticFunctionForTarget( | state.B0 = DODOMath._SolveQuadraticFunctionForTarget( | |
| state.B, | state.B, | |
| state.Q.sub(state.Q0), | state.Q.sub(state.Q0), | |
| DecimalMath.reciprocalFloor(state.i), | DecimalMath.reciprocalFloor(state.i), | |
| state.K | state.K | |
| ); | ); | |
| } | } | |
| } | } | |
| function getMidPrice(PMMState memory state) internal pure returns (uint256) { | function getMidPrice(PMMState memory state) internal pure returns (uint256) { | |
| if (state.R == RState.BELOW_ONE) { | if (state.R == RState.BELOW_ONE) { | |
| uint256 R = DecimalMath.divFloor(state.Q0.mul(state.Q0).div(state.Q), state.Q); | uint256 R = DecimalMath.divFloor(state.Q0.mul(state.Q0).div(state.Q), state.Q); | |
| R = DecimalMath.ONE.sub(state.K).add(DecimalMath.mulFloor(state.K, R)); | R = DecimalMath.ONE.sub(state.K).add(DecimalMath.mulFloor(state.K, R)); | |
| return DecimalMath.divFloor(state.i, R); | return DecimalMath.divFloor(state.i, R); | |
| } else { | } else { | |
| uint256 R = DecimalMath.divFloor(state.B0.mul(state.B0).div(state.B), state.B); | uint256 R = DecimalMath.divFloor(state.B0.mul(state.B0).div(state.B), state.B); | |
| R = DecimalMath.ONE.sub(state.K).add(DecimalMath.mulFloor(state.K, R)); | R = DecimalMath.ONE.sub(state.K).add(DecimalMath.mulFloor(state.K, R)); | |
| return DecimalMath.mulFloor(state.i, R); | return DecimalMath.mulFloor(state.i, R); | |
| } | } | |
| } | } | |
| } | } | |
| // File: contracts/intf/IDODOCallee.sol | // File: contracts/intf/IDODOCallee.sol | |
| interface IDODOCallee { | interface IDODOCallee { | |
| function DVMSellShareCall( | function DVMSellShareCall( | |
| address sender, | address sender, | |
| uint256 burnShareAmount, | uint256 burnShareAmount, | |
| uint256 baseAmount, | uint256 baseAmount, | |
| uint256 quoteAmount, | uint256 quoteAmount, | |
| bytes calldata data | bytes calldata data | |
| ) external; | ) external; | |
| function DVMFlashLoanCall( | function DVMFlashLoanCall( | |
| address sender, | address sender, | |
| uint256 baseAmount, | uint256 baseAmount, | |
| uint256 quoteAmount, | uint256 quoteAmount, | |
| bytes calldata data | bytes calldata data | |
| ) external; | ) external; | |
| function DPPFlashLoanCall( | function DPPFlashLoanCall( | |
| address sender, | address sender, | |
| uint256 baseAmount, | uint256 baseAmount, | |
| uint256 quoteAmount, | uint256 quoteAmount, | |
| bytes calldata data | bytes calldata data | |
| ) external; | ) external; | |
| function DSPFlashLoanCall( | +- | |
| address sender, | ||
| uint256 baseAmount, | ||
| uint256 quoteAmount, | ||
| bytes calldata data | ||
| ) external; | ||
| function CPCancelCall( | = | function CPCancelCall( |
| address sender, | address sender, | |
| uint256 amount, | uint256 amount, | |
| bytes calldata data | bytes calldata data | |
| ) external; | ) external; | |
| function CPClaimBidCall( | function CPClaimBidCall( | |
| address sender, | address sender, | |
| uint256 baseAmount, | uint256 baseAmount, | |
| uint256 quoteAmount, | uint256 quoteAmount, | |
| bytes calldata data | bytes calldata data | |
| ) external; | ) external; | |
| +- | ||
| function NFTRedeemCall( | ||
| address payable assetTo, | ||
| uint256 quoteAmount, | ||
| bytes calldata | ||
| ) external; | ||
| } | = | } |
| // File: contracts/CrowdPooling/impl/CPFunding.sol | // File: contracts/CrowdPooling/impl/CPFunding.sol | |
| -+ | ||
| = | ||
| contract CPFunding is CPStorage { | contract CPFunding is CPStorage { | |
| using SafeERC20 for IERC20; | using SafeERC20 for IERC20; | |
| // ============ Events ============ | // ============ Events ============ | |
| event Bid(address to, uint256 amount, uint256 fee); | event Bid(address to, uint256 amount, uint256 fee); | |
| event Cancel(address to,uint256 amount); | event Cancel(address to,uint256 amount); | |
| event Settle(); | event Settle(); | |
| // ============ BID & CALM PHASE ============ | // ============ BID & CALM PHASE ============ | |
| modifier isBidderAllow(address bidder) { | modifier isBidderAllow(address bidder) { | |
| require(_BIDDER_PERMISSION_.isAllowed(bidder), "BIDDER_NOT_ALLOWED"); | require(_BIDDER_PERMISSION_.isAllowed(bidder), "BIDDER_NOT_ALLOWED"); | |
| if(_IS_OVERCAP_STOP) { | +- | |
| require(_QUOTE_TOKEN_.balanceOf(address(this)) < _POOL_QUOTE_CAP_, "ALREADY_OVER_CAP"); | ||
| } | ||
| _; | = | _; |
| } | } | |
| function bid(address to) external isForceStop phaseBid preventReentrant isBidderAllow(to) { | <> | function bid(address to) external phaseBid preventReentrant isBidderAllow(to) { |
| uint256 input = _getQuoteInput(); | = | uint256 input = _getQuoteInput(); |
| uint256 mtFee = DecimalMath.mulFloor(input, _MT_FEE_RATE_MODEL_.getFeeRate(to)); | uint256 mtFee = DecimalMath.mulFloor(input, _MT_FEE_RATE_MODEL_.getFeeRate(to)); | |
| _transferQuoteOut(_MAINTAINER_, mtFee); | _transferQuoteOut(_MAINTAINER_, mtFee); | |
| _mintShares(to, input.sub(mtFee)); | _mintShares(to, input.sub(mtFee)); | |
| _sync(); | _sync(); | |
| emit Bid(to, input, mtFee); | emit Bid(to, input, mtFee); | |
| } | } | |
| function cancel(address to, uint256 amount, bytes calldata data) external phaseBidOrCalm preventReentrant { | function cancel(address to, uint256 amount, bytes calldata data) external phaseBidOrCalm preventReentrant { | |
| require(_SHARES_[msg.sender] >= amount, "SHARES_NOT_ENOUGH"); | require(_SHARES_[msg.sender] >= amount, "SHARES_NOT_ENOUGH"); | |
| _burnShares(msg.sender, amount); | _burnShares(msg.sender, amount); | |
| _transferQuoteOut(to, amount); | _transferQuoteOut(to, amount); | |
| _sync(); | _sync(); | |
| if(data.length > 0){ | if(data.length > 0){ | |
| IDODOCallee(to).CPCancelCall(msg.sender,amount,data); | IDODOCallee(to).CPCancelCall(msg.sender,amount,data); | |
| } | } | |
| emit Cancel(msg.sender,amount); | emit Cancel(msg.sender,amount); | |
| } | } | |
| function _mintShares(address to, uint256 amount) internal { | function _mintShares(address to, uint256 amount) internal { | |
| _SHARES_[to] = _SHARES_[to].add(amount); | _SHARES_[to] = _SHARES_[to].add(amount); | |
| _TOTAL_SHARES_ = _TOTAL_SHARES_.add(amount); | _TOTAL_SHARES_ = _TOTAL_SHARES_.add(amount); | |
| } | } | |
| function _burnShares(address from, uint256 amount) internal { | function _burnShares(address from, uint256 amount) internal { | |
| _SHARES_[from] = _SHARES_[from].sub(amount); | _SHARES_[from] = _SHARES_[from].sub(amount); | |
| _TOTAL_SHARES_ = _TOTAL_SHARES_.sub(amount); | _TOTAL_SHARES_ = _TOTAL_SHARES_.sub(amount); | |
| } | } | |
| // ============ SETTLEMENT ============ | // ============ SETTLEMENT ============ | |
| function settle() external isForceStop phaseSettlement preventReentrant { | <> | function settle() external phaseSettlement preventReentrant { |
| _settle(); | = | _settle(); |
| (uint256 poolBase, uint256 poolQuote, uint256 poolI, uint256 unUsedBase, uint256 unUsedQuote) = getSettleResult(); | (uint256 poolBase, uint256 poolQuote, uint256 poolI, uint256 unUsedBase, uint256 unUsedQuote) = getSettleResult(); | |
| _UNUSED_BASE_ = unUsedBase; | _UNUSED_BASE_ = unUsedBase; | |
| _UNUSED_QUOTE_ = unUsedQuote; | _UNUSED_QUOTE_ = unUsedQuote; | |
| address _poolBaseToken; | address _poolBaseToken; | |
| address _poolQuoteToken; | address _poolQuoteToken; | |
| if (_UNUSED_BASE_ > poolBase) { | if (_UNUSED_BASE_ > poolBase) { | |
| _poolBaseToken = address(_QUOTE_TOKEN_); | _poolBaseToken = address(_QUOTE_TOKEN_); | |
| _poolQuoteToken = address(_BASE_TOKEN_); | _poolQuoteToken = address(_BASE_TOKEN_); | |
| } else { | } else { | |
| _poolBaseToken = address(_BASE_TOKEN_); | _poolBaseToken = address(_BASE_TOKEN_); | |
| _poolQuoteToken = address(_QUOTE_TOKEN_); | _poolQuoteToken = address(_QUOTE_TOKEN_); | |
| } | } | |
| _POOL_ = IDVMFactory(_POOL_FACTORY_).createDODOVendingMachine( | _POOL_ = IDVMFactory(_POOL_FACTORY_).createDODOVendingMachine( | |
| _poolBaseToken, | _poolBaseToken, | |
| _poolQuoteToken, | _poolQuoteToken, | |
| _POOL_FEE_RATE_, | <> | 3e15, // 0.3% lp feeRate |
| poolI, | = | poolI, |
| DecimalMath.ONE, | DecimalMath.ONE, | |
| _IS_OPEN_TWAP_ | _IS_OPEN_TWAP_ | |
| ); | ); | |
| uint256 avgPrice = unUsedBase == 0 ? _I_ : DecimalMath.divCeil(poolQuote, unUsedBase); | uint256 avgPrice = unUsedBase == 0 ? _I_ : DecimalMath.divCeil(poolQuote, unUsedBase); | |
| _AVG_SETTLED_PRICE_ = avgPrice; | _AVG_SETTLED_PRICE_ = avgPrice; | |
| _transferBaseOut(_POOL_, poolBase); | _transferBaseOut(_POOL_, poolBase); | |
| _transferQuoteOut(_POOL_, poolQuote); | _transferQuoteOut(_POOL_, poolQuote); | |
| (_TOTAL_LP_AMOUNT_, ,) = IDVM(_POOL_).buyShares(address(this)); | (_TOTAL_LP_AMOUNT_, ,) = IDVM(_POOL_).buyShares(address(this)); | |
| msg.sender.transfer(_SETTEL_FUND_); | msg.sender.transfer(_SETTEL_FUND_); | |
| emit Settle(); | emit Settle(); | |
| } | } | |
| // in case something wrong with base token contract | // in case something wrong with base token contract | |
| function emergencySettle() external isForceStop phaseSettlement preventReentrant { | <> | function emergencySettle() external phaseSettlement preventReentrant { |
| require(block.timestamp >= _PHASE_CALM_ENDTIME_.add(_SETTLEMENT_EXPIRE_), "NOT_EMERGENCY"); | = | require(block.timestamp >= _PHASE_CALM_ENDTIME_.add(_SETTLEMENT_EXPIRE_), "NOT_EMERGENCY"); |
| _settle(); | _settle(); | |
| _UNUSED_QUOTE_ = _QUOTE_TOKEN_.balanceOf(address(this)); | _UNUSED_QUOTE_ = _QUOTE_TOKEN_.balanceOf(address(this)); | |
| } | } | |
| function _settle() internal { | function _settle() internal { | |
| require(!_SETTLED_, "ALREADY_SETTLED"); | require(!_SETTLED_, "ALREADY_SETTLED"); | |
| _SETTLED_ = true; | _SETTLED_ = true; | |
| _SETTLED_TIME_ = block.timestamp; | _SETTLED_TIME_ = block.timestamp; | |
| } | } | |
| // ============ Pricing ============ | // ============ Pricing ============ | |
| function getSettleResult() public view returns (uint256 poolBase, uint256 poolQuote, uint256 poolI, uint256 unUsedBase, uint256 unUsedQuote) { | function getSettleResult() public view returns (uint256 poolBase, uint256 poolQuote, uint256 poolI, uint256 unUsedBase, uint256 unUsedQuote) { | |
| poolQuote = _QUOTE_TOKEN_.balanceOf(address(this)); | poolQuote = _QUOTE_TOKEN_.balanceOf(address(this)); | |
| if (poolQuote > _POOL_QUOTE_CAP_) { | if (poolQuote > _POOL_QUOTE_CAP_) { | |
| poolQuote = _POOL_QUOTE_CAP_; | poolQuote = _POOL_QUOTE_CAP_; | |
| } | } | |
| (uint256 soldBase,) = PMMPricing.sellQuoteToken(_getPMMState(), poolQuote); | (uint256 soldBase,) = PMMPricing.sellQuoteToken(_getPMMState(), poolQuote); | |
| poolBase = _TOTAL_BASE_.sub(soldBase); | poolBase = _TOTAL_BASE_.sub(soldBase); | |
| unUsedQuote = _QUOTE_TOKEN_.balanceOf(address(this)).sub(poolQuote); | unUsedQuote = _QUOTE_TOKEN_.balanceOf(address(this)).sub(poolQuote); | |
| unUsedBase = _BASE_TOKEN_.balanceOf(address(this)).sub(poolBase); | unUsedBase = _BASE_TOKEN_.balanceOf(address(this)).sub(poolBase); | |
| // Try to make midPrice equal to avgPrice | // Try to make midPrice equal to avgPrice | |
| // k=1, If quote and base are not balanced, one side must be cut off | // k=1, If quote and base are not balanced, one side must be cut off | |
| // DVM truncated quote, but if more quote than base entering the pool, we need set the quote to the base | // DVM truncated quote, but if more quote than base entering the pool, we need set the quote to the base | |
| // m = avgPrice | // m = avgPrice | |
| // i = m (1-quote/(m*base)) | // i = m (1-quote/(m*base)) | |
| // if quote = m*base i = 1 | // if quote = m*base i = 1 | |
| // if quote > m*base reverse | // if quote > m*base reverse | |
| uint256 avgPrice = unUsedBase == 0 ? _I_ : DecimalMath.divCeil(poolQuote, unUsedBase); | uint256 avgPrice = unUsedBase == 0 ? _I_ : DecimalMath.divCeil(poolQuote, unUsedBase); | |
| uint256 baseDepth = DecimalMath.mulFloor(avgPrice, poolBase); | uint256 baseDepth = DecimalMath.mulFloor(avgPrice, poolBase); | |
| if (poolQuote == 0) { | if (poolQuote == 0) { | |
| // ask side only DVM | // ask side only DVM | |
| poolI = _I_; | poolI = _I_; | |
| } else if (unUsedBase== poolBase) { | } else if (unUsedBase== poolBase) { | |
| // standard bonding curve | // standard bonding curve | |
| poolI = 1; | poolI = 1; | |
| } else if (unUsedBase < poolBase) { | } else if (unUsedBase < poolBase) { | |
| // poolI up round | // poolI up round | |
| uint256 ratio = DecimalMath.ONE.sub(DecimalMath.divFloor(poolQuote, baseDepth)); | uint256 ratio = DecimalMath.ONE.sub(DecimalMath.divFloor(poolQuote, baseDepth)); | |
| poolI = avgPrice.mul(ratio).mul(ratio).divCeil(DecimalMath.ONE2); | poolI = avgPrice.mul(ratio).mul(ratio).divCeil(DecimalMath.ONE2); | |
| } else if (unUsedBase > poolBase) { | } else if (unUsedBase > poolBase) { | |
| // poolI down round | // poolI down round | |
| uint256 ratio = DecimalMath.ONE.sub(DecimalMath.divCeil(baseDepth, poolQuote)); | uint256 ratio = DecimalMath.ONE.sub(DecimalMath.divCeil(baseDepth, poolQuote)); | |
| poolI = ratio.mul(ratio).div(avgPrice); | poolI = ratio.mul(ratio).div(avgPrice); | |
| } | } | |
| } | } | |
| function _getPMMState() internal view returns (PMMPricing.PMMState memory state) { | function _getPMMState() internal view returns (PMMPricing.PMMState memory state) { | |
| state.i = _I_; | state.i = _I_; | |
| state.K = _K_; | state.K = _K_; | |
| state.B = _TOTAL_BASE_; | state.B = _TOTAL_BASE_; | |
| state.Q = 0; | state.Q = 0; | |
| state.B0 = state.B; | state.B0 = state.B; | |
| state.Q0 = 0; | state.Q0 = 0; | |
| state.R = PMMPricing.RState.ONE; | state.R = PMMPricing.RState.ONE; | |
| } | } | |
| function getExpectedAvgPrice() external view returns (uint256) { | function getExpectedAvgPrice() external view returns (uint256) { | |
| require(!_SETTLED_, "ALREADY_SETTLED"); | require(!_SETTLED_, "ALREADY_SETTLED"); | |
| (uint256 poolBase, uint256 poolQuote, , , ) = getSettleResult(); | (uint256 poolBase, uint256 poolQuote, , , ) = getSettleResult(); | |
| return DecimalMath.divCeil(poolQuote, _BASE_TOKEN_.balanceOf(address(this)).sub(poolBase)); | return DecimalMath.divCeil(poolQuote, _BASE_TOKEN_.balanceOf(address(this)).sub(poolBase)); | |
| } | } | |
| // ============ Asset In ============ | // ============ Asset In ============ | |
| function _getQuoteInput() internal view returns (uint256 input) { | function _getQuoteInput() internal view returns (uint256 input) { | |
| return _QUOTE_TOKEN_.balanceOf(address(this)).sub(_QUOTE_RESERVE_); | return _QUOTE_TOKEN_.balanceOf(address(this)).sub(_QUOTE_RESERVE_); | |
| } | } | |
| // ============ Set States ============ | // ============ Set States ============ | |
| function _sync() internal { | function _sync() internal { | |
| uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); | uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); | |
| if (quoteBalance != _QUOTE_RESERVE_) { | if (quoteBalance != _QUOTE_RESERVE_) { | |
| _QUOTE_RESERVE_ = quoteBalance; | _QUOTE_RESERVE_ = quoteBalance; | |
| } | } | |
| } | } | |
| // ============ Asset Out ============ | // ============ Asset Out ============ | |
| function _transferBaseOut(address to, uint256 amount) internal { | function _transferBaseOut(address to, uint256 amount) internal { | |
| if (amount > 0) { | if (amount > 0) { | |
| _BASE_TOKEN_.safeTransfer(to, amount); | _BASE_TOKEN_.safeTransfer(to, amount); | |
| } | } | |
| } | } | |
| function _transferQuoteOut(address to, uint256 amount) internal { | function _transferQuoteOut(address to, uint256 amount) internal { | |
| if (amount > 0) { | if (amount > 0) { | |
| _QUOTE_TOKEN_.safeTransfer(to, amount); | _QUOTE_TOKEN_.safeTransfer(to, amount); | |
| } | } | |
| } | } | |
| function getShares(address user) external view returns (uint256) { | function getShares(address user) external view returns (uint256) { | |
| return _SHARES_[user]; | return _SHARES_[user]; | |
| } | } | |
| } | } | |
| // File: contracts/CrowdPooling/impl/CPVesting.sol | // File: contracts/CrowdPooling/impl/CPVesting.sol | |
| -+ | ||
| /** | = | /** |
| * @title CPVesting | * @title CPVesting | |
| * @author DODO Breeder | * @author DODO Breeder | |
| * | * | |
| * @notice Lock Token and release it linearly | * @notice Lock Token and release it linearly | |
| */ | */ | |
| contract CPVesting is CPFunding { | contract CPVesting is CPFunding { | |
| using SafeMath for uint256; | using SafeMath for uint256; | |
| using SafeERC20 for IERC20; | using SafeERC20 for IERC20; | |
| // ============ Events ============ | // ============ Events ============ | |
| event ClaimBaseToken(address user, uint256 baseAmount); | <> | |
| event ClaimQuoteToken(address user, uint256 quoteAmount); | event Claim(address user, uint256 baseAmount, uint256 quoteAmount); | |
| event ClaimLP(uint256 amount); | = | event ClaimLP(uint256 amount); |
| // ================ Modifiers ================ | // ================ Modifiers ================ | |
| modifier afterSettlement() { | modifier afterSettlement() { | |
| require(_SETTLED_, "NOT_SETTLED"); | require(_SETTLED_, "NOT_SETTLED"); | |
| _; | _; | |
| } | } | |
| modifier afterFreeze() { | modifier afterFreeze() { | |
| require(_SETTLED_ && block.timestamp >= _SETTLED_TIME_.add(_FREEZE_DURATION_), "FREEZED"); | require(_SETTLED_ && block.timestamp >= _SETTLED_TIME_.add(_FREEZE_DURATION_), "FREEZED"); | |
| _; | _; | |
| } | } | |
| modifier afterClaimFreeze() { | +- | |
| require(_SETTLED_ && block.timestamp >= _SETTLED_TIME_.add(_TOKEN_CLAIM_DURATION_), "CLAIM_FREEZED"); | ||
| _; | ||
| } | ||
| // ============ Bidder Functions ============ | = | // ============ Bidder Functions ============ |
| function claimQuoteToken(address to,bytes calldata data) external afterSettlement { | <> | function bidderClaim(address to,bytes calldata data) external afterSettlement { |
| require(!_CLAIMED_QUOTE_[msg.sender], "ALREADY_CLAIMED_FUND"); | require(!_CLAIMED_[msg.sender], "ALREADY_CLAIMED"); | |
| _CLAIMED_QUOTE_[msg.sender] = true; | _CLAIMED_[msg.sender] = true; | |
| = | ||
| -+ | uint256 baseAmount = _UNUSED_BASE_.mul(_SHARES_[msg.sender]).div(_TOTAL_SHARES_); | |
| uint256 quoteAmount = _UNUSED_QUOTE_.mul(_SHARES_[msg.sender]).div(_TOTAL_SHARES_); | = | uint256 quoteAmount = _UNUSED_QUOTE_.mul(_SHARES_[msg.sender]).div(_TOTAL_SHARES_); |
| -+ | _transferBaseOut(to, baseAmount); | |
| _transferQuoteOut(to, quoteAmount); | = | _transferQuoteOut(to, quoteAmount); |
| if(data.length>0){ | if(data.length>0){ | |
| IDODOCallee(to).CPClaimBidCall(msg.sender,0,quoteAmount,data); | <> | IDODOCallee(to).CPClaimBidCall(msg.sender,baseAmount,quoteAmount,data); |
| } | = | } |
| emit ClaimQuoteToken(msg.sender, quoteAmount); | <> | emit Claim(msg.sender, baseAmount, quoteAmount); |
| } | ||
| function claimBaseToken() external afterClaimFreeze { | ||
| uint256 claimableBaseAmount = getClaimableBaseToken(msg.sender); | ||
| _transferBaseOut(msg.sender, claimableBaseAmount); | ||
| _CLAIMED_BASE_TOKEN_[msg.sender] = _CLAIMED_BASE_TOKEN_[msg.sender].add(claimableBaseAmount); | ||
| emit ClaimBaseToken(msg.sender, claimableBaseAmount); | ||
| } | ||
| function getClaimableBaseToken(address user) public view afterClaimFreeze returns (uint256) { | ||
| uint256 baseTotalAmount = _UNUSED_BASE_.mul(_SHARES_[user]).div(_TOTAL_SHARES_); | ||
| uint256 remainingBaseToken = DecimalMath.mulFloor( | ||
| getRemainingBaseTokenRatio(block.timestamp), | ||
| baseTotalAmount | ||
| ); | ||
| return baseTotalAmount.sub(remainingBaseToken).sub(_CLAIMED_BASE_TOKEN_[user]); | ||
| } | ||
| function getRemainingBaseTokenRatio(uint256 timestamp) public view afterClaimFreeze returns (uint256) { | ||
| uint256 timePast = timestamp.sub(_SETTLED_TIME_.add(_TOKEN_CLAIM_DURATION_)); | ||
| if (timePast < _TOKEN_VESTING_DURATION_) { | ||
| uint256 remainingTime = _TOKEN_VESTING_DURATION_.sub(timePast); | ||
| return DecimalMath.ONE.sub(_TOKEN_CLIFF_RATE_).mul(remainingTime).div(_TOKEN_VESTING_DURATION_); | ||
| } else { | ||
| return 0; | ||
| } | ||
| } | = | } |
| // ============ Owner Functions ============ | // ============ Owner Functions ============ | |
| function claimLPToken() external onlyOwner afterFreeze { | function claimLPToken() external onlyOwner afterFreeze { | |
| uint256 lpAmount = getClaimableLPToken(); | uint256 lpAmount = getClaimableLPToken(); | |
| IERC20(_POOL_).safeTransfer(_OWNER_, lpAmount); | IERC20(_POOL_).safeTransfer(_OWNER_, lpAmount); | |
| emit ClaimLP(lpAmount); | emit ClaimLP(lpAmount); | |
| } | } | |
| function getClaimableLPToken() public view afterFreeze returns (uint256) { | function getClaimableLPToken() public view afterFreeze returns (uint256) { | |
| uint256 remainingLPToken = DecimalMath.mulFloor( | uint256 remainingLPToken = DecimalMath.mulFloor( | |
| getRemainingLPRatio(block.timestamp), | getRemainingLPRatio(block.timestamp), | |
| _TOTAL_LP_AMOUNT_ | _TOTAL_LP_AMOUNT_ | |
| ); | ); | |
| return IERC20(_POOL_).balanceOf(address(this)).sub(remainingLPToken); | return IERC20(_POOL_).balanceOf(address(this)).sub(remainingLPToken); | |
| } | } | |
| function getRemainingLPRatio(uint256 timestamp) public view afterFreeze returns (uint256) { | function getRemainingLPRatio(uint256 timestamp) public view afterFreeze returns (uint256) { | |
| uint256 timePast = timestamp.sub(_SETTLED_TIME_.add(_FREEZE_DURATION_)); | uint256 timePast = timestamp.sub(_SETTLED_TIME_.add(_FREEZE_DURATION_)); | |
| if (timePast < _VESTING_DURATION_) { | if (timePast < _VESTING_DURATION_) { | |
| uint256 remainingTime = _VESTING_DURATION_.sub(timePast); | uint256 remainingTime = _VESTING_DURATION_.sub(timePast); | |
| return DecimalMath.ONE.sub(_CLIFF_RATE_).mul(remainingTime).div(_VESTING_DURATION_); | return DecimalMath.ONE.sub(_CLIFF_RATE_).mul(remainingTime).div(_VESTING_DURATION_); | |
| } else { | } else { | |
| return 0; | return 0; | |
| } | } | |
| } | } | |
| } | } | |
| // File: contracts/CrowdPooling/impl/CP.sol | // File: contracts/CrowdPooling/impl/CP.sol | |
| -+ | ||
| /** | = | /** |
| * @title DODO CrowdPooling | * @title DODO CrowdPooling | |
| * @author DODO Breeder | * @author DODO Breeder | |
| * | * | |
| * @notice CrowdPooling initialization | * @notice CrowdPooling initialization | |
| */ | */ | |
| contract CP is CPVesting { | contract CP is CPVesting { | |
| using SafeMath for uint256; | using SafeMath for uint256; | |
| receive() external payable { | receive() external payable { | |
| require(_INITIALIZED_ == false, "WE_NOT_SAVE_ETH_AFTER_INIT"); | require(_INITIALIZED_ == false, "WE_NOT_SAVE_ETH_AFTER_INIT"); | |
| } | } | |
| function init( | function init( | |
| address[] calldata addressList, | address[] calldata addressList, | |
| uint256[] calldata timeLine, | uint256[] calldata timeLine, | |
| uint256[] calldata valueList, | uint256[] calldata valueList, | |
| bool[] calldata switches //0 isOverCapStop 1 isOpenTWAP | <> | bool isOpenTWAP |
| ) external { | = | ) external { |
| /* | /* | |
| Address List | Address List | |
| 0. owner | 0. owner | |
| 1. maintainer | 1. maintainer | |
| 2. baseToken | 2. baseToken | |
| 3. quoteToken | 3. quoteToken | |
| 4. permissionManager | 4. permissionManager | |
| 5. feeRateModel | 5. feeRateModel | |
| 6. poolFactory | 6. poolFactory | |
| */ | */ | |
| require(addressList.length == 7, "LIST_LENGTH_WRONG"); | require(addressList.length == 7, "LIST_LENGTH_WRONG"); | |
| initOwner(addressList[0]); | initOwner(addressList[0]); | |
| _MAINTAINER_ = addressList[1]; | _MAINTAINER_ = addressList[1]; | |
| _BASE_TOKEN_ = IERC20(addressList[2]); | _BASE_TOKEN_ = IERC20(addressList[2]); | |
| _QUOTE_TOKEN_ = IERC20(addressList[3]); | _QUOTE_TOKEN_ = IERC20(addressList[3]); | |
| _BIDDER_PERMISSION_ = IPermissionManager(addressList[4]); | _BIDDER_PERMISSION_ = IPermissionManager(addressList[4]); | |
| _MT_FEE_RATE_MODEL_ = IFeeRateModel(addressList[5]); | _MT_FEE_RATE_MODEL_ = IFeeRateModel(addressList[5]); | |
| _POOL_FACTORY_ = addressList[6]; | _POOL_FACTORY_ = addressList[6]; | |
| /* | /* | |
| Time Line | Time Line | |
| 0. phase bid starttime | 0. phase bid starttime | |
| 1. phase bid duration | 1. phase bid duration | |
| 2. phase calm duration | 2. phase calm duration | |
| 3. freeze duration | 3. freeze duration | |
| 4. vesting duration | 4. vesting duration | |
| 5. claim freeze duration | +- | |
| 6. claim vesting duration | ||
| */ | = | */ |
| require(timeLine.length == 7, "LIST_LENGTH_WRONG"); | <> | require(timeLine.length == 5, "LIST_LENGTH_WRONG"); |
| = | ||
| _PHASE_BID_STARTTIME_ = timeLine[0]; | _PHASE_BID_STARTTIME_ = timeLine[0]; | |
| _PHASE_BID_ENDTIME_ = _PHASE_BID_STARTTIME_.add(timeLine[1]); | _PHASE_BID_ENDTIME_ = _PHASE_BID_STARTTIME_.add(timeLine[1]); | |
| _PHASE_CALM_ENDTIME_ = _PHASE_BID_ENDTIME_.add(timeLine[2]); | _PHASE_CALM_ENDTIME_ = _PHASE_BID_ENDTIME_.add(timeLine[2]); | |
| _FREEZE_DURATION_ = timeLine[3]; | _FREEZE_DURATION_ = timeLine[3]; | |
| _VESTING_DURATION_ = timeLine[4]; | _VESTING_DURATION_ = timeLine[4]; | |
| _TOKEN_CLAIM_DURATION_ = timeLine[5]; | <> | |
| _TOKEN_VESTING_DURATION_ = timeLine[6]; | ||
| require(block.timestamp <= _PHASE_BID_STARTTIME_, "TIMELINE_WRONG"); | = | require(block.timestamp <= _PHASE_BID_STARTTIME_, "TIMELINE_WRONG"); |
| /* | /* | |
| Value List | Value List | |
| 0. pool quote cap | 0. pool quote cap | |
| 1. k | 1. k | |
| 2. i | 2. i | |
| 3. lp cliff rate | <> | 3. cliff rate |
| 4. base token cliff rate | ||
| 5. lp fee rate | ||
| */ | = | */ |
| require(valueList.length == 6, "LIST_LENGTH_WRONG"); | <> | require(valueList.length == 4, "LIST_LENGTH_WRONG"); |
| = | ||
| _POOL_QUOTE_CAP_ = valueList[0]; | _POOL_QUOTE_CAP_ = valueList[0]; | |
| _K_ = valueList[1]; | _K_ = valueList[1]; | |
| _I_ = valueList[2]; | _I_ = valueList[2]; | |
| _CLIFF_RATE_ = valueList[3]; | _CLIFF_RATE_ = valueList[3]; | |
| _TOKEN_CLIFF_RATE_ = valueList[4]; | +- | |
| _POOL_FEE_RATE_ = valueList[5]; | ||
| = | ||
| require(_I_ > 0 && _I_ <= 1e36, "I_VALUE_WRONG"); | require(_I_ > 0 && _I_ <= 1e36, "I_VALUE_WRONG"); | |
| require(_K_ <= 1e18, "K_VALUE_WRONG"); | require(_K_ <= 1e18, "K_VALUE_WRONG"); | |
| require(_CLIFF_RATE_ <= 1e18, "CLIFF_RATE_WRONG"); | require(_CLIFF_RATE_ <= 1e18, "CLIFF_RATE_WRONG"); | |
| require(_TOKEN_CLIFF_RATE_ <= 1e18, "TOKEN_CLIFF_RATE_WRONG"); | +- | |
| = | ||
| _TOTAL_BASE_ = _BASE_TOKEN_.balanceOf(address(this)); | _TOTAL_BASE_ = _BASE_TOKEN_.balanceOf(address(this)); | |
| _IS_OVERCAP_STOP = switches[0]; | <> | |
| _IS_OPEN_TWAP_ = switches[1]; | _IS_OPEN_TWAP_ = isOpenTWAP; | |
| = | ||
| require(address(this).balance == _SETTEL_FUND_, "SETTLE_FUND_NOT_MATCH"); | require(address(this).balance == _SETTEL_FUND_, "SETTLE_FUND_NOT_MATCH"); | |
| } | } | |
| +- | ||
| // ============ Version Control ============ | ||
| function version() virtual external pure returns (string memory) { | ||
| return "CP 2.0.0"; | ||
| } | ||
| } | = | } |