Contract
0xf1791e48b9d7f7b39036d5ae9601b45c9694539e
1
Contract Overview
Balance:
0 ETH
ETH Value:
$0.00
My Name Tag:
Not Available
Txn Hash | Method |
Block
|
From
|
To
|
Value | [Txn Fee] | |||
---|---|---|---|---|---|---|---|---|---|
0xd62a442735cf0bb7f9f9c6acf8f5a12c7501fd8881c8ed19b67da5e081b89f5a | 0x60806040 | 2901739 | 451 days 7 hrs ago | MUX Protocol: Deployer | IN | Create: LiquidityPool | 0 ETH | 0.244300879777 ETH |
[ Download CSV Export ]
Latest 25 internal transaction
[ Download CSV Export ]
This contract contains unverified libraries: AMMModule
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.
Contract Name:
LiquidityPool
Compiler Version
v0.7.4+commit.3f05b770
Optimization Enabled:
Yes with 1000 runs
Other Settings:
default evmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.7.4; pragma experimental ABIEncoderV2; import "@openzeppelin/contracts-upgradeable/utils/EnumerableSetUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/SafeCastUpgradeable.sol"; import "./interface/ILiquidityPool.sol"; import "./module/AMMModule.sol"; import "./module/LiquidityPoolModule.sol"; import "./module/PerpetualModule.sol"; import "./Getter.sol"; import "./Governance.sol"; import "./LibraryEvents.sol"; import "./Perpetual.sol"; import "./Storage.sol"; import "./Type.sol"; contract LiquidityPool is Storage, Perpetual, Getter, Governance, LibraryEvents, ILiquidityPool { using EnumerableSetUpgradeable for EnumerableSetUpgradeable.Bytes32Set; using SafeCastUpgradeable for uint256; using PerpetualModule for PerpetualStorage; using LiquidityPoolModule for LiquidityPoolStorage; using AMMModule for LiquidityPoolStorage; receive() external payable { revert("contract does not accept ether"); } /** * @notice Initialize the liquidity pool and set up its configuration * * @param operator The address of operator which should be current pool creator. * @param collateral The address of collateral token. * @param collateralDecimals The decimals of collateral token, to support token without decimals interface. * @param governor The address of governor, who is able to call governance methods. * @param initData A bytes array contains data to initialize new created liquidity pool. */ function initialize( address operator, address collateral, uint256 collateralDecimals, address governor, bytes calldata initData ) external override initializer { _liquidityPool.initialize( _msgSender(), collateral, collateralDecimals, operator, governor, initData ); } /** * @notice Create new perpetual of the liquidity pool. * The operator can create perpetual only when the pool is not running or isFastCreationEnabled is true. * Otherwise a perpetual can only be create by governor (say, through voting). * * @param oracle The oracle's address of the perpetual. * @param baseParams The base parameters of the perpetual, see whitepaper for details. * @param riskParams The risk parameters of the perpetual, * Must be within range [minRiskParamValues, maxRiskParamValues]. * @param minRiskParamValues The minimum values of risk parameters. * @param maxRiskParamValues The maximum values of risk parameters. */ function createPerpetual( address oracle, int256[9] calldata baseParams, int256[9] calldata riskParams, int256[9] calldata minRiskParamValues, int256[9] calldata maxRiskParamValues ) external onlyNotUniverseSettled { if (!_liquidityPool.isRunning || _liquidityPool.isFastCreationEnabled) { require( _msgSender() == _liquidityPool.getOperator(), "only operator can create perpetual" ); } else { require(_msgSender() == _liquidityPool.governor, "only governor can create perpetual"); } _liquidityPool.createPerpetual( oracle, baseParams, riskParams, minRiskParamValues, maxRiskParamValues ); } /** * @notice Set the liquidity pool to running state. Can be call only once by operater.m n */ function runLiquidityPool() external override onlyOperator { require(!_liquidityPool.isRunning, "already running"); _liquidityPool.runLiquidityPool(); } /** * @notice If you want to get the real-time data, call this function first */ function forceToSyncState() public override syncState(false) {} /** * @notice Add liquidity to the liquidity pool. * Liquidity provider deposits collaterals then gets share tokens back. * The ratio of added cash to share token is determined by current liquidity. * Can only called when the pool is running. * * @param cashToAdd The amount of cash to add. always use decimals 18. */ function addLiquidity(int256 cashToAdd) external override onlyNotUniverseSettled syncState(false) nonReentrant { require(_liquidityPool.isRunning, "pool is not running"); _liquidityPool.addLiquidity(_msgSender(), cashToAdd); } /** * @notice Remove liquidity from the liquidity pool. * Liquidity providers redeems share token then gets collateral back. * The amount of collateral retrieved may differ from the amount when adding liquidity, * The index price, trading fee and positions holding by amm will affect the profitability of providers. * Can only called when the pool is running. * * @param shareToRemove The amount of share token to remove. The amount always use decimals 18. * @param cashToReturn The amount of cash(collateral) to return. The amount always use decimals 18. */ function removeLiquidity(int256 shareToRemove, int256 cashToReturn) external override nonReentrant syncState(false) { require(_liquidityPool.isRunning, "pool is not running"); if (IPoolCreatorFull(_liquidityPool.creator).isUniverseSettled()) { require( _liquidityPool.isAllPerpetualIn(PerpetualState.CLEARED), "all perpetual must be cleared" ); } _liquidityPool.removeLiquidity(_msgSender(), shareToRemove, cashToReturn); } /** * @notice Donate collateral to the insurance fund of the pool. * Can only called when the pool is running. * Donated collateral is not withdrawable but can be used to improve security. * Unexpected loss (bankrupt) will be deducted from insurance fund then donated insurance fund. * Until donated insurance fund is drained, the perpetual will not enter emergency state and shutdown. * * @param amount The amount of collateral to donate. The amount always use decimals 18. */ function donateInsuranceFund(int256 amount) external nonReentrant { require(_liquidityPool.isRunning, "pool is not running"); _liquidityPool.donateInsuranceFund(_msgSender(), amount); } /** * @notice Add liquidity to the liquidity pool without getting shares. * * @param cashToAdd The amount of cash to add. The amount always use decimals 18. */ function donateLiquidity(int256 cashToAdd) external nonReentrant { require(_liquidityPool.isRunning, "pool is not running"); _liquidityPool.donateLiquidity(_msgSender(), cashToAdd); } bytes32[50] private __gap; }
// SPDX-License-Identifier: MIT pragma solidity >=0.6.0 <0.8.0; /** * @dev Library for managing * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive * types. * * Sets have the following properties: * * - Elements are added, removed, and checked for existence in constant time * (O(1)). * - Elements are enumerated in O(n). No guarantees are made on the ordering. * * ``` * contract Example { * // Add the library methods * using EnumerableSet for EnumerableSet.AddressSet; * * // Declare a set state variable * EnumerableSet.AddressSet private mySet; * } * ``` * * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`) * and `uint256` (`UintSet`) are supported. */ library EnumerableSetUpgradeable { // To implement this library for multiple types with as little code // repetition as possible, we write it in terms of a generic Set type with // bytes32 values. // The Set implementation uses private functions, and user-facing // implementations (such as AddressSet) are just wrappers around the // underlying Set. // This means that we can only create new EnumerableSets for types that fit // in bytes32. struct Set { // Storage of set values bytes32[] _values; // Position of the value in the `values` array, plus 1 because index 0 // means a value is not in the set. mapping (bytes32 => uint256) _indexes; } /** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function _add(Set storage set, bytes32 value) private returns (bool) { if (!_contains(set, value)) { set._values.push(value); // The value is stored at length-1, but we add 1 to all indexes // and use 0 as a sentinel value set._indexes[value] = set._values.length; return true; } else { return false; } } /** * @dev Removes a value from a set. O(1). * * Returns true if the value was removed from the set, that is if it was * present. */ function _remove(Set storage set, bytes32 value) private returns (bool) { // We read and store the value's index to prevent multiple reads from the same storage slot uint256 valueIndex = set._indexes[value]; if (valueIndex != 0) { // Equivalent to contains(set, value) // To delete an element from the _values array in O(1), we swap the element to delete with the last one in // the array, and then remove the last element (sometimes called as 'swap and pop'). // This modifies the order of the array, as noted in {at}. uint256 toDeleteIndex = valueIndex - 1; uint256 lastIndex = set._values.length - 1; // When the value to delete is the last one, the swap operation is unnecessary. However, since this occurs // so rarely, we still do the swap anyway to avoid the gas cost of adding an 'if' statement. bytes32 lastvalue = set._values[lastIndex]; // Move the last value to the index where the value to delete is set._values[toDeleteIndex] = lastvalue; // Update the index for the moved value set._indexes[lastvalue] = toDeleteIndex + 1; // All indexes are 1-based // Delete the slot where the moved value was stored set._values.pop(); // Delete the index for the deleted slot delete set._indexes[value]; return true; } else { return false; } } /** * @dev Returns true if the value is in the set. O(1). */ function _contains(Set storage set, bytes32 value) private view returns (bool) { return set._indexes[value] != 0; } /** * @dev Returns the number of values on the set. O(1). */ function _length(Set storage set) private view returns (uint256) { return set._values.length; } /** * @dev Returns the value stored at position `index` in the set. O(1). * * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function _at(Set storage set, uint256 index) private view returns (bytes32) { require(set._values.length > index, "EnumerableSet: index out of bounds"); return set._values[index]; } // Bytes32Set struct Bytes32Set { Set _inner; } /** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function add(Bytes32Set storage set, bytes32 value) internal returns (bool) { return _add(set._inner, value); } /** * @dev Removes a value from a set. O(1). * * Returns true if the value was removed from the set, that is if it was * present. */ function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) { return _remove(set._inner, value); } /** * @dev Returns true if the value is in the set. O(1). */ function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) { return _contains(set._inner, value); } /** * @dev Returns the number of values in the set. O(1). */ function length(Bytes32Set storage set) internal view returns (uint256) { return _length(set._inner); } /** * @dev Returns the value stored at position `index` in the set. O(1). * * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) { return _at(set._inner, index); } // AddressSet struct AddressSet { Set _inner; } /** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function add(AddressSet storage set, address value) internal returns (bool) { return _add(set._inner, bytes32(uint256(uint160(value)))); } /** * @dev Removes a value from a set. O(1). * * Returns true if the value was removed from the set, that is if it was * present. */ function remove(AddressSet storage set, address value) internal returns (bool) { return _remove(set._inner, bytes32(uint256(uint160(value)))); } /** * @dev Returns true if the value is in the set. O(1). */ function contains(AddressSet storage set, address value) internal view returns (bool) { return _contains(set._inner, bytes32(uint256(uint160(value)))); } /** * @dev Returns the number of values in the set. O(1). */ function length(AddressSet storage set) internal view returns (uint256) { return _length(set._inner); } /** * @dev Returns the value stored at position `index` in the set. O(1). * * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function at(AddressSet storage set, uint256 index) internal view returns (address) { return address(uint160(uint256(_at(set._inner, index)))); } // UintSet struct UintSet { Set _inner; } /** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function add(UintSet storage set, uint256 value) internal returns (bool) { return _add(set._inner, bytes32(value)); } /** * @dev Removes a value from a set. O(1). * * Returns true if the value was removed from the set, that is if it was * present. */ function remove(UintSet storage set, uint256 value) internal returns (bool) { return _remove(set._inner, bytes32(value)); } /** * @dev Returns true if the value is in the set. O(1). */ function contains(UintSet storage set, uint256 value) internal view returns (bool) { return _contains(set._inner, bytes32(value)); } /** * @dev Returns the number of values on the set. O(1). */ function length(UintSet storage set) internal view returns (uint256) { return _length(set._inner); } /** * @dev Returns the value stored at position `index` in the set. O(1). * * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function at(UintSet storage set, uint256 index) internal view returns (uint256) { return uint256(_at(set._inner, index)); } }
// SPDX-License-Identifier: MIT pragma solidity >=0.6.0 <0.8.0; /** * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow * checks. * * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can * easily result in undesired exploitation or bugs, since developers usually * assume that overflows raise errors. `SafeCast` restores this intuition by * reverting the transaction when such an operation overflows. * * Using this library instead of the unchecked operations eliminates an entire * class of bugs, so it's recommended to use it always. * * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing * all math on `uint256` and `int256` and then downcasting. */ library SafeCastUpgradeable { /** * @dev Returns the downcasted uint128 from uint256, reverting on * overflow (when the input is greater than largest uint128). * * Counterpart to Solidity's `uint128` operator. * * Requirements: * * - input must fit into 128 bits */ function toUint128(uint256 value) internal pure returns (uint128) { require(value < 2**128, "SafeCast: value doesn\'t fit in 128 bits"); return uint128(value); } /** * @dev Returns the downcasted uint64 from uint256, reverting on * overflow (when the input is greater than largest uint64). * * Counterpart to Solidity's `uint64` operator. * * Requirements: * * - input must fit into 64 bits */ function toUint64(uint256 value) internal pure returns (uint64) { require(value < 2**64, "SafeCast: value doesn\'t fit in 64 bits"); return uint64(value); } /** * @dev Returns the downcasted uint32 from uint256, reverting on * overflow (when the input is greater than largest uint32). * * Counterpart to Solidity's `uint32` operator. * * Requirements: * * - input must fit into 32 bits */ function toUint32(uint256 value) internal pure returns (uint32) { require(value < 2**32, "SafeCast: value doesn\'t fit in 32 bits"); return uint32(value); } /** * @dev Returns the downcasted uint16 from uint256, reverting on * overflow (when the input is greater than largest uint16). * * Counterpart to Solidity's `uint16` operator. * * Requirements: * * - input must fit into 16 bits */ function toUint16(uint256 value) internal pure returns (uint16) { require(value < 2**16, "SafeCast: value doesn\'t fit in 16 bits"); return uint16(value); } /** * @dev Returns the downcasted uint8 from uint256, reverting on * overflow (when the input is greater than largest uint8). * * Counterpart to Solidity's `uint8` operator. * * Requirements: * * - input must fit into 8 bits. */ function toUint8(uint256 value) internal pure returns (uint8) { require(value < 2**8, "SafeCast: value doesn\'t fit in 8 bits"); return uint8(value); } /** * @dev Converts a signed int256 into an unsigned uint256. * * Requirements: * * - input must be greater than or equal to 0. */ function toUint256(int256 value) internal pure returns (uint256) { require(value >= 0, "SafeCast: value must be positive"); return uint256(value); } /** * @dev Returns the downcasted int128 from int256, reverting on * overflow (when the input is less than smallest int128 or * greater than largest int128). * * Counterpart to Solidity's `int128` operator. * * Requirements: * * - input must fit into 128 bits * * _Available since v3.1._ */ function toInt128(int256 value) internal pure returns (int128) { require(value >= -2**127 && value < 2**127, "SafeCast: value doesn\'t fit in 128 bits"); return int128(value); } /** * @dev Returns the downcasted int64 from int256, reverting on * overflow (when the input is less than smallest int64 or * greater than largest int64). * * Counterpart to Solidity's `int64` operator. * * Requirements: * * - input must fit into 64 bits * * _Available since v3.1._ */ function toInt64(int256 value) internal pure returns (int64) { require(value >= -2**63 && value < 2**63, "SafeCast: value doesn\'t fit in 64 bits"); return int64(value); } /** * @dev Returns the downcasted int32 from int256, reverting on * overflow (when the input is less than smallest int32 or * greater than largest int32). * * Counterpart to Solidity's `int32` operator. * * Requirements: * * - input must fit into 32 bits * * _Available since v3.1._ */ function toInt32(int256 value) internal pure returns (int32) { require(value >= -2**31 && value < 2**31, "SafeCast: value doesn\'t fit in 32 bits"); return int32(value); } /** * @dev Returns the downcasted int16 from int256, reverting on * overflow (when the input is less than smallest int16 or * greater than largest int16). * * Counterpart to Solidity's `int16` operator. * * Requirements: * * - input must fit into 16 bits * * _Available since v3.1._ */ function toInt16(int256 value) internal pure returns (int16) { require(value >= -2**15 && value < 2**15, "SafeCast: value doesn\'t fit in 16 bits"); return int16(value); } /** * @dev Returns the downcasted int8 from int256, reverting on * overflow (when the input is less than smallest int8 or * greater than largest int8). * * Counterpart to Solidity's `int8` operator. * * Requirements: * * - input must fit into 8 bits. * * _Available since v3.1._ */ function toInt8(int256 value) internal pure returns (int8) { require(value >= -2**7 && value < 2**7, "SafeCast: value doesn\'t fit in 8 bits"); return int8(value); } /** * @dev Converts an unsigned uint256 into a signed int256. * * Requirements: * * - input must be less than or equal to maxInt256. */ function toInt256(uint256 value) internal pure returns (int256) { require(value < 2**255, "SafeCast: value doesn't fit in an int256"); return int256(value); } }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.7.4; pragma experimental ABIEncoderV2; import "../Type.sol"; interface ILiquidityPool { /** * @notice Initialize the liquidity pool and set up its configuration. * * @param operator The operator's address of the liquidity pool. * @param collateral The collateral's address of the liquidity pool. * @param collateralDecimals The collateral's decimals of the liquidity pool. * @param governor The governor's address of the liquidity pool. * @param initData A bytes array contains data to initialize new created liquidity pool. */ function initialize( address operator, address collateral, uint256 collateralDecimals, address governor, bytes calldata initData ) external; /** * @notice Set the liquidity pool to running state. Can be call only once by operater.m n */ function runLiquidityPool() external; /** * @notice If you want to get the real-time data, call this function first */ function forceToSyncState() external; /** * @notice Add liquidity to the liquidity pool. * Liquidity provider deposits collaterals then gets share tokens back. * The ratio of added cash to share token is determined by current liquidity. * Can only called when the pool is running. * * @param cashToAdd The amount of cash to add. always use decimals 18. */ function addLiquidity(int256 cashToAdd) external; /** * @notice Remove liquidity from the liquidity pool. * Liquidity providers redeems share token then gets collateral back. * The amount of collateral retrieved may differ from the amount when adding liquidity, * The index price, trading fee and positions holding by amm will affect the profitability of providers. * Can only called when the pool is running. * * @param shareToRemove The amount of share token to remove. The amount always use decimals 18. * @param cashToReturn The amount of cash(collateral) to return. The amount always use decimals 18. */ function removeLiquidity(int256 shareToRemove, int256 cashToReturn) external; }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.7.4; pragma experimental ABIEncoderV2; import "@openzeppelin/contracts-upgradeable/utils/SafeCastUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/math/SignedSafeMathUpgradeable.sol"; import "../libraries/Constant.sol"; import "../libraries/Math.sol"; import "../libraries/SafeMathExt.sol"; import "../libraries/Utils.sol"; import "../module/MarginAccountModule.sol"; import "../module/PerpetualModule.sol"; import "../Type.sol"; library AMMModule { using Math for int256; using SafeMathExt for int256; using SignedSafeMathUpgradeable for int256; using SafeCastUpgradeable for uint256; using MarginAccountModule for PerpetualStorage; using PerpetualModule for PerpetualStorage; struct Context { int256 indexPrice; int256 position; int256 positionValue; // squareValue is 10^36, others are 10^18 int256 squareValue; int256 positionMargin; int256 availableCash; } /** * @dev Get the trading result when trader trades with AMM, divided into two parts: * - AMM closes its position * - AMM opens its position. * * @param liquidityPool The liquidity pool object of AMM. * @param perpetualIndex The index of the perpetual in the liquidity pool to trade. * @param tradeAmount The trading amount of position, positive if AMM longs, negative if AMM shorts. * @param partialFill Whether to allow partially trading. Set to true when liquidation trading, * set to false when normal trading. * @return deltaCash The update cash(collateral) of AMM after the trade. * @return deltaPosition The update position of AMM after the trade. */ function queryTradeWithAMM( LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex, int256 tradeAmount, bool partialFill ) public view returns (int256 deltaCash, int256 deltaPosition) { require(tradeAmount != 0, "trading amount is zero"); Context memory context = prepareContext(liquidityPool, perpetualIndex); PerpetualStorage storage perpetual = liquidityPool.perpetuals[perpetualIndex]; (int256 closePosition, int256 openPosition) = Utils.splitAmount( context.position, tradeAmount ); // AMM close position int256 closeBestPrice; (deltaCash, closeBestPrice) = ammClosePosition(context, perpetual, closePosition); context.availableCash = context.availableCash.add(deltaCash); context.position = context.position.add(closePosition); // AMM open position (int256 openDeltaCash, int256 openDeltaPosition, int256 openBestPrice) = ammOpenPosition( context, perpetual, openPosition, partialFill ); deltaCash = deltaCash.add(openDeltaCash); deltaPosition = closePosition.add(openDeltaPosition); int256 bestPrice = closePosition != 0 ? closeBestPrice : openBestPrice; // If price is better(for trader) than best price, change price to best price deltaCash = deltaCash.max(bestPrice.wmul(deltaPosition).neg()); } /** * @dev Calculate the amount of share token to mint when liquidity provider adds liquidity to the liquidity pool. * If adding liquidity at first time, which means total supply of share token is zero, * the amount of share token to mint equals to the pool margin after adding liquidity. * * @param liquidityPool The liquidity pool object of AMM. * @param shareTotalSupply The total supply of the share token before adding liquidity. * @param cashToAdd The amount of cash(collateral) added to the liquidity pool. * @return shareToMint The amount of share token to mint. * @return addedPoolMargin The added amount of pool margin after adding liquidity. */ function getShareToMint( LiquidityPoolStorage storage liquidityPool, int256 shareTotalSupply, int256 cashToAdd ) public view returns (int256 shareToMint, int256 addedPoolMargin) { Context memory context = prepareContext(liquidityPool); (int256 poolMargin, ) = getPoolMargin(context); context.availableCash = context.availableCash.add(cashToAdd); (int256 newPoolMargin, ) = getPoolMargin(context); require( liquidityPool.liquidityCap == 0 || newPoolMargin <= liquidityPool.liquidityCap.toInt256(), "liquidity reaches cap" ); addedPoolMargin = newPoolMargin.sub(poolMargin); if (shareTotalSupply == 0) { // first time, if there is pool margin left in pool, it belongs to the first person who adds liquidity shareToMint = newPoolMargin; } else { // If share token's total supply is not zero and there is no money in pool, // these share tokens have no value. This case should be avoided. require(poolMargin > 0, "share token has no value"); shareToMint = newPoolMargin.sub(poolMargin).wfrac(shareTotalSupply, poolMargin); } } /** * @dev Calculate the amount of cash to add when liquidity provider adds liquidity to the liquidity pool. * If adding liquidity at first time, which means total supply of share token is zero, * the amount of cash to add equals to the share amount to mint minus pool margin before adding liquidity. * * @param liquidityPool The liquidity pool object of AMM. * @param shareTotalSupply The total supply of the share token before adding liquidity. * @param shareToMint The amount of share token to mint. * @return cashToAdd The amount of cash(collateral) to add to the liquidity pool. */ function getCashToAdd( LiquidityPoolStorage storage liquidityPool, int256 shareTotalSupply, int256 shareToMint ) public view returns (int256 cashToAdd) { Context memory context = prepareContext(liquidityPool); (int256 poolMargin, ) = getPoolMargin(context); if (shareTotalSupply == 0) { // first time, if there is pool margin left in pool, it belongs to the first person who adds liquidity cashToAdd = shareToMint.sub(poolMargin).max(0); int256 newPoolMargin = cashToAdd.add(poolMargin); require( liquidityPool.liquidityCap == 0 || newPoolMargin <= liquidityPool.liquidityCap.toInt256(), "liquidity reaches cap" ); } else { // If share token's total supply is not zero and there is no money in pool, // these share tokens have no value. This case should be avoided. require(poolMargin > 0, "share token has no value"); int256 newPoolMargin = shareTotalSupply.add(shareToMint).wfrac( poolMargin, shareTotalSupply ); require( liquidityPool.liquidityCap == 0 || newPoolMargin <= liquidityPool.liquidityCap.toInt256(), "liquidity reaches cap" ); int256 minPoolMargin = context.squareValue.div(2).sqrt(); int256 newCash; if (newPoolMargin <= minPoolMargin) { // pool is still unsafe after adding liquidity newCash = newPoolMargin.mul(2).sub(context.positionValue); } else { // context.squareValue is 10^36, so use div instead of wdiv newCash = context.squareValue.div(newPoolMargin).div(2).add(newPoolMargin).sub( context.positionValue ); } cashToAdd = newCash.sub(context.availableCash); } } /** * @dev Calculate the amount of cash(collateral) to return when liquidity provider removes liquidity from the liquidity pool. * Removing liquidity is forbidden at several cases: * 1. AMM is unsafe before removing liquidity * 2. AMM is unsafe after removing liquidity * 3. AMM will offer negative price at any perpetual after removing liquidity * 4. AMM will exceed maximum leverage at any perpetual after removing liquidity * * @param liquidityPool The liquidity pool object of AMM. * @param shareTotalSupply The total supply of the share token before removing liquidity. * @param shareToRemove The amount of share token to redeem. * @return cashToReturn The amount of cash(collateral) to return. * @return removedInsuranceFund The part of insurance fund returned to LP if all perpetuals are in CLEARED state. * @return removedDonatedInsuranceFund The part of donated insurance fund returned to LP if all perpetuals are in CLEARED state. * @return removedPoolMargin The removed amount of pool margin after removing liquidity. */ function getCashToReturn( LiquidityPoolStorage storage liquidityPool, int256 shareTotalSupply, int256 shareToRemove ) public view returns ( int256 cashToReturn, int256 removedInsuranceFund, int256 removedDonatedInsuranceFund, int256 removedPoolMargin ) { require( shareTotalSupply > 0, "total supply of share token is zero when removing liquidity" ); Context memory context = prepareContext(liquidityPool); require(isAMMSafe(context, 0), "AMM is unsafe before removing liquidity"); removedPoolMargin = calculatePoolMarginWhenSafe(context, 0); require(removedPoolMargin > 0, "pool margin must be positive"); int256 poolMargin = shareTotalSupply.sub(shareToRemove).wfrac( removedPoolMargin, shareTotalSupply ); removedPoolMargin = removedPoolMargin.sub(poolMargin); { int256 minPoolMargin = context.squareValue.div(2).sqrt(); require(poolMargin >= minPoolMargin, "AMM is unsafe after removing liquidity"); } cashToReturn = calculateCashToReturn(context, poolMargin); require(cashToReturn >= 0, "received margin is negative"); uint256 length = liquidityPool.perpetualCount; bool allCleared = true; for (uint256 i = 0; i < length; i++) { PerpetualStorage storage perpetual = liquidityPool.perpetuals[i]; if (perpetual.state != PerpetualState.CLEARED) { allCleared = false; } if (perpetual.state != PerpetualState.NORMAL) { continue; } // prevent AMM offering negative price require( perpetual.getPosition(address(this)) <= poolMargin.wdiv(perpetual.openSlippageFactor.value).wdiv( perpetual.getIndexPrice() ), "AMM is unsafe after removing liquidity" ); } // prevent AMM exceeding max leverage require( context.availableCash.add(context.positionValue).sub(cashToReturn) >= context.positionMargin, "AMM exceeds max leverage after removing liquidity" ); if (allCleared) { // get insurance fund proportionally removedInsuranceFund = liquidityPool.insuranceFund.wfrac( shareToRemove, shareTotalSupply, Round.FLOOR ); removedDonatedInsuranceFund = liquidityPool.donatedInsuranceFund.wfrac( shareToRemove, shareTotalSupply, Round.FLOOR ); cashToReturn = cashToReturn.add(removedInsuranceFund).add(removedDonatedInsuranceFund); } } /** * @dev Calculate the amount of share token to redeem when liquidity provider removes liquidity from the liquidity pool. * Removing liquidity is forbidden at several cases: * 1. AMM is unsafe before removing liquidity * 2. AMM is unsafe after removing liquidity * 3. AMM will offer negative price at any perpetual after removing liquidity * 4. AMM will exceed maximum leverage at any perpetual after removing liquidity * * @param liquidityPool The liquidity pool object of AMM. * @param shareTotalSupply The total supply of the share token before removing liquidity. * @param cashToReturn The cash(collateral) to return. * @return shareToRemove The amount of share token to redeem. * @return removedInsuranceFund The part of insurance fund returned to LP if all perpetuals are in CLEARED state. * @return removedDonatedInsuranceFund The part of donated insurance fund returned to LP if all perpetuals are in CLEARED state. * @return removedPoolMargin The removed amount of pool margin after removing liquidity. */ function getShareToRemove( LiquidityPoolStorage storage liquidityPool, int256 shareTotalSupply, int256 cashToReturn ) public view returns ( int256 shareToRemove, int256 removedInsuranceFund, int256 removedDonatedInsuranceFund, int256 removedPoolMargin ) { require( shareTotalSupply > 0, "total supply of share token is zero when removing liquidity" ); Context memory context = prepareContext(liquidityPool); require(isAMMSafe(context, 0), "AMM is unsafe before removing liquidity"); int256 poolMargin = calculatePoolMarginWhenSafe(context, 0); context.availableCash = context.availableCash.sub(cashToReturn); require(isAMMSafe(context, 0), "AMM is unsafe after removing liquidity"); int256 newPoolMargin = calculatePoolMarginWhenSafe(context, 0); removedPoolMargin = poolMargin.sub(newPoolMargin); shareToRemove = poolMargin.sub(newPoolMargin).wfrac(shareTotalSupply, poolMargin); uint256 length = liquidityPool.perpetualCount; bool allCleared = true; for (uint256 i = 0; i < length; i++) { PerpetualStorage storage perpetual = liquidityPool.perpetuals[i]; if (perpetual.state != PerpetualState.CLEARED) { allCleared = false; } if (perpetual.state != PerpetualState.NORMAL) { continue; } // prevent AMM offering negative price require( perpetual.getPosition(address(this)) <= newPoolMargin.wdiv(perpetual.openSlippageFactor.value).wdiv( perpetual.getIndexPrice() ), "AMM is unsafe after removing liquidity" ); } // prevent AMM exceeding max leverage require( context.availableCash.add(context.positionValue) >= context.positionMargin, "AMM exceeds max leverage after removing liquidity" ); if (allCleared) { // get insurance fund proportionally ( shareToRemove, removedInsuranceFund, removedDonatedInsuranceFund, removedPoolMargin ) = getShareToRemoveWhenAllCleared( liquidityPool, cashToReturn, poolMargin, shareTotalSupply ); } } /** * @dev Calculate the amount of share token to redeem when liquidity provider removes liquidity from the liquidity pool. * Only called when all perpetuals in the liquidity pool are in CLEARED state. * * @param liquidityPool The liquidity pool object of AMM. * @param cashToReturn The cash(collateral) to return. * @param poolMargin The pool margin before removing liquidity. * @param shareTotalSupply The total supply of the share token before removing liquidity. * @return shareToRemove The amount of share token to redeem. * @return removedInsuranceFund The part of insurance fund returned to LP if all perpetuals are in CLEARED state. * @return removedDonatedInsuranceFund The part of donated insurance fund returned to LP if all perpetuals are in CLEARED state. * @return removedPoolMargin The part of pool margin returned to LP if all perpetuals are in CLEARED state. */ function getShareToRemoveWhenAllCleared( LiquidityPoolStorage storage liquidityPool, int256 cashToReturn, int256 poolMargin, int256 shareTotalSupply ) public view returns ( int256 shareToRemove, int256 removedInsuranceFund, int256 removedDonatedInsuranceFund, int256 removedPoolMargin ) { // get insurance fund proportionally require( poolMargin.add(liquidityPool.insuranceFund).add(liquidityPool.donatedInsuranceFund) > 0, "all cleared, insufficient liquidity" ); shareToRemove = shareTotalSupply.wfrac( cashToReturn, poolMargin.add(liquidityPool.insuranceFund).add(liquidityPool.donatedInsuranceFund) ); removedInsuranceFund = liquidityPool.insuranceFund.wfrac( shareToRemove, shareTotalSupply, Round.FLOOR ); removedDonatedInsuranceFund = liquidityPool.donatedInsuranceFund.wfrac( shareToRemove, shareTotalSupply, Round.FLOOR ); removedPoolMargin = poolMargin.wfrac(shareToRemove, shareTotalSupply, Round.FLOOR); } /** * @dev Calculate the pool margin of AMM when AMM is safe. * Pool margin is how much collateral of the pool considering the AMM's positions of perpetuals. * * @param context Context object of AMM, but current perpetual is not included. * @param slippageFactor The slippage factor of current perpetual. * @return poolMargin The pool margin of AMM. */ function calculatePoolMarginWhenSafe(Context memory context, int256 slippageFactor) internal pure returns (int256 poolMargin) { // The context doesn't include the current perpetual, add them. int256 positionValue = context.indexPrice.wmul(context.position); int256 margin = positionValue.add(context.positionValue).add(context.availableCash); // 10^36, the same as context.squareValue int256 tmp = positionValue.wmul(positionValue).mul(slippageFactor).add(context.squareValue); int256 beforeSqrt = margin.mul(margin).sub(tmp.mul(2)); require(beforeSqrt >= 0, "AMM is unsafe when calculating pool margin"); poolMargin = beforeSqrt.sqrt().add(margin).div(2); require(poolMargin >= 0, "pool margin is negative when calculating pool margin"); } /** * @dev Check if AMM is safe * @param context Context object of AMM, but current perpetual is not included. * @param slippageFactor The slippage factor of current perpetual. * @return bool True if AMM is safe. */ function isAMMSafe(Context memory context, int256 slippageFactor) internal pure returns (bool) { int256 positionValue = context.indexPrice.wmul(context.position); // 10^36, the same as context.squareValue int256 minAvailableCash = positionValue.wmul(positionValue).mul(slippageFactor); minAvailableCash = minAvailableCash.add(context.squareValue).mul(2).sqrt().sub( context.positionValue.add(positionValue) ); return context.availableCash >= minAvailableCash; } /** * @dev Get the trading result when AMM closes its position. * If the AMM is unsafe, the trading price is the best price. * If trading price is too bad, it will be limited to index price * (1 +/- max close price discount) * * @param context Context object of AMM, but current perpetual is not included. * @param perpetual The perpetual object to trade. * @param tradeAmount The amount of position to trade. * Positive for long and negative for short from AMM's perspective. * @return deltaCash The update cash(collateral) of AMM after the trade. * @return bestPrice The best price, is used for clipping to spread price if needed outside. * If AMM is safe, best price = middle price * (1 +/- half spread). * If AMM is unsafe and normal case, best price = index price. */ function ammClosePosition( Context memory context, PerpetualStorage storage perpetual, int256 tradeAmount ) internal view returns (int256 deltaCash, int256 bestPrice) { if (tradeAmount == 0) { return (0, 0); } int256 positionBefore = context.position; int256 indexPrice = context.indexPrice; int256 slippageFactor = perpetual.closeSlippageFactor.value; int256 maxClosePriceDiscount = perpetual.maxClosePriceDiscount.value; int256 halfSpread = tradeAmount < 0 ? perpetual.halfSpread.value : perpetual.halfSpread.value.neg(); if (isAMMSafe(context, slippageFactor)) { int256 poolMargin = calculatePoolMarginWhenSafe(context, slippageFactor); require(poolMargin > 0, "pool margin must be positive"); bestPrice = getMidPrice(poolMargin, indexPrice, positionBefore, slippageFactor).wmul( halfSpread.add(Constant.SIGNED_ONE) ); deltaCash = getDeltaCash( poolMargin, positionBefore, positionBefore.add(tradeAmount), indexPrice, slippageFactor ); } else { bestPrice = indexPrice; deltaCash = bestPrice.wmul(tradeAmount).neg(); } int256 priceLimit = tradeAmount > 0 ? Constant.SIGNED_ONE.add(maxClosePriceDiscount) : Constant.SIGNED_ONE.sub(maxClosePriceDiscount); // prevent too bad price deltaCash = deltaCash.max(indexPrice.wmul(priceLimit).wmul(tradeAmount).neg()); // prevent negative price require( !Utils.hasTheSameSign(deltaCash, tradeAmount), "price is negative when AMM closes position" ); } /** * @dev Get the trading result when AMM opens its position. * AMM can't open position when unsafe and can't open position to exceed the maximum position * * @param context Context object of AMM, but current perpetual is not included. * @param perpetual The perpetual object to trade * @param tradeAmount The trading amount of position, positive if AMM longs, negative if AMM shorts * @param partialFill Whether to allow partially trading. Set to true when liquidation trading, * set to false when normal trading * @return deltaCash The update cash(collateral) of AMM after the trade * @return deltaPosition The update position of AMM after the trade * @return bestPrice The best price, is used for clipping to spread price if needed outside. * Equal to middle price * (1 +/- half spread) */ function ammOpenPosition( Context memory context, PerpetualStorage storage perpetual, int256 tradeAmount, bool partialFill ) internal view returns ( int256 deltaCash, int256 deltaPosition, int256 bestPrice ) { if (tradeAmount == 0) { return (0, 0, 0); } int256 slippageFactor = perpetual.openSlippageFactor.value; if (!isAMMSafe(context, slippageFactor)) { require(partialFill, "AMM is unsafe when open"); return (0, 0, 0); } int256 poolMargin = calculatePoolMarginWhenSafe(context, slippageFactor); require(poolMargin > 0, "pool margin must be positive"); int256 indexPrice = context.indexPrice; int256 positionBefore = context.position; int256 positionAfter = positionBefore.add(tradeAmount); int256 maxPosition = getMaxPosition( context, poolMargin, perpetual.ammMaxLeverage.value, slippageFactor, positionAfter > 0 ); if (positionAfter.abs() > maxPosition.abs()) { require(partialFill, "trade amount exceeds max amount"); // trade to max position if partialFill deltaPosition = maxPosition.sub(positionBefore); // current position already exeeds max position before trade, can't open if (Utils.hasTheSameSign(deltaPosition, tradeAmount.neg())) { return (0, 0, 0); } positionAfter = maxPosition; } else { deltaPosition = tradeAmount; } deltaCash = getDeltaCash( poolMargin, positionBefore, positionAfter, indexPrice, slippageFactor ); // prevent negative price require( !Utils.hasTheSameSign(deltaCash, deltaPosition), "price is negative when AMM opens position" ); int256 halfSpread = tradeAmount < 0 ? perpetual.halfSpread.value : perpetual.halfSpread.value.neg(); bestPrice = getMidPrice(poolMargin, indexPrice, positionBefore, slippageFactor).wmul( halfSpread.add(Constant.SIGNED_ONE) ); } /** * @dev Calculate the status of AMM * * @param liquidityPool The reference of liquidity pool storage. * @return context Context object of AMM, but current perpetual is not included. */ function prepareContext(LiquidityPoolStorage storage liquidityPool) internal view returns (Context memory context) { context = prepareContext(liquidityPool, liquidityPool.perpetualCount); } /** * @dev Calculate the status of AMM, but specified perpetual index is not included. * * @param liquidityPool The reference of liquidity pool storage. * @param perpetualIndex The index of the perpetual in the liquidity pool to distinguish, * set to liquidityPool.perpetualCount to skip distinguishing. * @return context Context object of AMM. */ function prepareContext(LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex) internal view returns (Context memory context) { int256 maintenanceMargin; uint256 length = liquidityPool.perpetualCount; for (uint256 i = 0; i < length; i++) { PerpetualStorage storage perpetual = liquidityPool.perpetuals[i]; // only involve normal market if (perpetual.state != PerpetualState.NORMAL) { continue; } int256 position = perpetual.getPosition(address(this)); int256 indexPrice = perpetual.getIndexPrice(); require(indexPrice > 0, "index price must be positive"); context.availableCash = context.availableCash.add( perpetual.getAvailableCash(address(this)) ); maintenanceMargin = maintenanceMargin.add( indexPrice.wmul(position).wmul(perpetual.maintenanceMarginRate).abs() ); if (i == perpetualIndex) { context.indexPrice = indexPrice; context.position = position; } else { // To avoid returning more cash than pool has because of precision error, // cashToReturn should be smaller, which means positionValue should be smaller, squareValue should be bigger context.positionValue = context.positionValue.add( indexPrice.wmul(position, Round.FLOOR) ); // 10^36 context.squareValue = context.squareValue.add( position .wmul(position, Round.CEIL) .wmul(indexPrice, Round.CEIL) .wmul(indexPrice, Round.CEIL) .mul(perpetual.openSlippageFactor.value) ); context.positionMargin = context.positionMargin.add( indexPrice.wmul(position).abs().wdiv(perpetual.ammMaxLeverage.value) ); } } context.availableCash = context.availableCash.add(liquidityPool.poolCash); // prevent margin balance < maintenance margin. // call setEmergencyState(SET_ALL_PERPETUALS_TO_EMERGENCY_STATE) when AMM is maintenance margin unsafe require( context.availableCash.add(context.positionValue).add( context.indexPrice.wmul(context.position) ) >= maintenanceMargin, "AMM is mm unsafe" ); } /** * @dev Calculate the cash(collateral) to return when removing liquidity. * * @param context Context object of AMM, but current perpetual is not included. * @param poolMargin The pool margin of AMM before removing liquidity. * @return cashToReturn The cash(collateral) to return. */ function calculateCashToReturn(Context memory context, int256 poolMargin) public pure returns (int256 cashToReturn) { if (poolMargin == 0) { // remove all return context.availableCash; } require(poolMargin > 0, "pool margin must be positive when removing liquidity"); // context.squareValue is 10^36, so use div instead of wdiv cashToReturn = context.squareValue.div(poolMargin).div(2).add(poolMargin).sub( context.positionValue ); cashToReturn = context.availableCash.sub(cashToReturn); } /** * @dev Get the middle price offered by AMM * * @param poolMargin The pool margin of AMM. * @param indexPrice The index price of the perpetual. * @param position The position of AMM in the perpetual. * @param slippageFactor The slippage factor of AMM in the perpetual. * @return midPrice A middle price offered by AMM. */ function getMidPrice( int256 poolMargin, int256 indexPrice, int256 position, int256 slippageFactor ) internal pure returns (int256 midPrice) { midPrice = Constant .SIGNED_ONE .sub(indexPrice.wmul(position).wfrac(slippageFactor, poolMargin)) .wmul(indexPrice); } /** * @dev Get update cash(collateral) of AMM if trader trades against AMM. * * @param poolMargin The pool margin of AMM. * @param positionBefore The position of AMM in the perpetual before trading. * @param positionAfter The position of AMM in the perpetual after trading. * @param indexPrice The index price of the perpetual. * @param slippageFactor The slippage factor of AMM in the perpetual. * @return deltaCash The update cash(collateral) of AMM after trading. */ function getDeltaCash( int256 poolMargin, int256 positionBefore, int256 positionAfter, int256 indexPrice, int256 slippageFactor ) internal pure returns (int256 deltaCash) { deltaCash = positionAfter.add(positionBefore).wmul(indexPrice).div(2).wfrac( slippageFactor, poolMargin ); deltaCash = Constant.SIGNED_ONE.sub(deltaCash).wmul(indexPrice).wmul( positionBefore.sub(positionAfter) ); } /** * @dev Get the max position of AMM in the perpetual when AMM is opening position, calculated by three restrictions: * 1. AMM must be safe after the trade. * 2. AMM mustn't exceed maximum leverage in any perpetual after the trade. * 3. AMM must offer positive price in any perpetual after the trade. It's easy to prove that, in the * perpetual, AMM definitely offers positive price when AMM holds short position. * * @param context Context object of AMM, but current perpetual is not included. * @param poolMargin The pool margin of AMM. * @param ammMaxLeverage The max leverage of AMM in the perpetual. * @param slippageFactor The slippage factor of AMM in the perpetual. * @return maxPosition The max position of AMM in the perpetual. */ function getMaxPosition( Context memory context, int256 poolMargin, int256 ammMaxLeverage, int256 slippageFactor, bool isLongSide ) internal pure returns (int256 maxPosition) { int256 indexPrice = context.indexPrice; int256 beforeSqrt = poolMargin.mul(poolMargin).mul(2).sub(context.squareValue).wdiv( slippageFactor ); if (beforeSqrt <= 0) { // 1. already unsafe, can't open position // 2. initial AMM is also this case, position = 0, available cash = 0, pool margin = 0 return 0; } int256 maxPosition3 = beforeSqrt.sqrt().wdiv(indexPrice); int256 maxPosition2; // context.squareValue is 10^36, so use div instead of wdiv beforeSqrt = poolMargin.sub(context.positionMargin).add( context.squareValue.div(poolMargin).div(2) ); beforeSqrt = beforeSqrt.wmul(ammMaxLeverage).wmul(ammMaxLeverage).wmul(slippageFactor); beforeSqrt = poolMargin.sub(beforeSqrt.mul(2)); if (beforeSqrt < 0) { // never exceed max leverage maxPosition2 = type(int256).max; } else { // might be negative, clip to zero maxPosition2 = poolMargin.sub(beforeSqrt.mul(poolMargin).sqrt()).max(0); maxPosition2 = maxPosition2.wdiv(ammMaxLeverage).wdiv(slippageFactor).wdiv(indexPrice); } maxPosition = maxPosition3.min(maxPosition2); if (isLongSide) { // long side has one more restriction than short side int256 maxPosition1 = poolMargin.wdiv(slippageFactor).wdiv(indexPrice); maxPosition = maxPosition.min(maxPosition1); } else { maxPosition = maxPosition.neg(); } } /** * @dev Get pool margin of AMM, equal to 1/2 margin of AMM when AMM is unsafe. * Marin of AMM: cash + index price1 * position1 + index price2 * position2 + ... * * @param context Context object of AMM, but current perpetual is not included. * @return poolMargin The pool margin of AMM. * @return isSafe True if AMM is safe or false. */ function getPoolMargin(Context memory context) internal pure returns (int256 poolMargin, bool isSafe) { isSafe = isAMMSafe(context, 0); if (isSafe) { poolMargin = calculatePoolMarginWhenSafe(context, 0); } else { poolMargin = context.availableCash.add(context.positionValue).div(2); require(poolMargin >= 0, "pool margin is negative when getting pool margin"); } } /** * @dev Get pool margin of AMM, prepare context first. * @param liquidityPool The liquidity pool object * @return int256 The pool margin of AMM * @return bool True if AMM is safe */ function getPoolMargin(LiquidityPoolStorage storage liquidityPool) public view returns (int256, bool) { return getPoolMargin(prepareContext(liquidityPool)); } }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.7.4; pragma experimental ABIEncoderV2; import "@openzeppelin/contracts-upgradeable/math/SignedSafeMathUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/SafeCastUpgradeable.sol"; import "../interface/IAccessControl.sol"; import "../interface/IGovernor.sol"; import "../interface/IPoolCreatorFull.sol"; import "../interface/ISymbolService.sol"; import "../libraries/SafeMathExt.sol"; import "../libraries/OrderData.sol"; import "../libraries/Utils.sol"; import "./AMMModule.sol"; import "./CollateralModule.sol"; import "./MarginAccountModule.sol"; import "./PerpetualModule.sol"; import "../Type.sol"; library LiquidityPoolModule { using SafeCastUpgradeable for uint256; using SafeCastUpgradeable for int256; using SafeMathExt for int256; using SafeMathExt for uint256; using SafeMathUpgradeable for uint256; using SignedSafeMathUpgradeable for int256; using EnumerableSetUpgradeable for EnumerableSetUpgradeable.AddressSet; using OrderData for uint32; using AMMModule for LiquidityPoolStorage; using CollateralModule for LiquidityPoolStorage; using MarginAccountModule for PerpetualStorage; using PerpetualModule for PerpetualStorage; uint256 public constant OPERATOR_CHECK_IN_TIMEOUT = 10 days; uint256 public constant MAX_PERPETUAL_COUNT = 48; event AddLiquidity( address indexed trader, int256 addedCash, int256 mintedShare, int256 addedPoolMargin ); event RemoveLiquidity( address indexed trader, int256 returnedCash, int256 burnedShare, int256 removedPoolMargin ); event UpdatePoolMargin(int256 poolMargin); event TransferOperatorTo(address indexed newOperator); event ClaimOperator(address indexed newOperator); event RevokeOperator(); event SetLiquidityPoolParameter(int256[4] value); event CreatePerpetual( uint256 perpetualIndex, address governor, address shareToken, address operator, address oracle, address collateral, int256[9] baseParams, int256[9] riskParams ); event RunLiquidityPool(); event OperatorCheckIn(address indexed operator); event DonateInsuranceFund(int256 amount); event TransferExcessInsuranceFundToLP(int256 amount); event SetTargetLeverage(uint256 perpetualIndex, address indexed trader, int256 targetLeverage); event AddAMMKeeper(uint256 perpetualIndex, address indexed keeper); event RemoveAMMKeeper(uint256 perpetualIndex, address indexed keeper); event AddTraderKeeper(uint256 perpetualIndex, address indexed keeper); event RemoveTraderKeeper(uint256 perpetualIndex, address indexed keeper); /** * @dev Get the vault's address of the liquidity pool * * @param liquidityPool The reference of liquidity pool storage. * @return vault The vault's address of the liquidity pool */ function getVault(LiquidityPoolStorage storage liquidityPool) public view returns (address vault) { vault = IPoolCreatorFull(liquidityPool.creator).getVault(); } function getShareTransferDelay(LiquidityPoolStorage storage liquidityPool) public view returns (uint256 delay) { delay = liquidityPool.shareTransferDelay.max(1); } function getOperator(LiquidityPoolStorage storage liquidityPool) internal view returns (address) { return block.timestamp <= liquidityPool.operatorExpiration ? liquidityPool.operator : address(0); } function getTransferringOperator(LiquidityPoolStorage storage liquidityPool) internal view returns (address) { return block.timestamp <= liquidityPool.operatorExpiration ? liquidityPool.transferringOperator : address(0); } /** * @dev Get the vault fee rate of the liquidity pool * * @param liquidityPool The reference of liquidity pool storage. * @return vaultFeeRate The vault fee rate. */ function getVaultFeeRate(LiquidityPoolStorage storage liquidityPool) public view returns (int256 vaultFeeRate) { vaultFeeRate = IPoolCreatorFull(liquidityPool.creator).getVaultFeeRate(); } /** * @dev Get the available pool cash(collateral) of the liquidity pool excluding the specific perpetual. Available cash * in a perpetual means: margin - initial margin * * @param liquidityPool The reference of liquidity pool storage. * @param exclusiveIndex The index of perpetual in the liquidity pool to exclude, * set to liquidityPool.perpetualCount to skip excluding. * @return availablePoolCash The available pool cash(collateral) of the liquidity pool excluding the specific perpetual */ function getAvailablePoolCash( LiquidityPoolStorage storage liquidityPool, uint256 exclusiveIndex ) public view returns (int256 availablePoolCash) { uint256 length = liquidityPool.perpetualCount; for (uint256 i = 0; i < length; i++) { PerpetualStorage storage perpetual = liquidityPool.perpetuals[i]; if (i == exclusiveIndex || perpetual.state != PerpetualState.NORMAL) { continue; } int256 markPrice = perpetual.getMarkPrice(); availablePoolCash = availablePoolCash.add( perpetual.getMargin(address(this), markPrice).sub( perpetual.getInitialMargin(address(this), markPrice) ) ); } return availablePoolCash.add(liquidityPool.poolCash); } /** * @dev Get the available pool cash(collateral) of the liquidity pool. * Sum of available cash of AMM in every perpetual in the liquidity pool, and add the pool cash. * * @param liquidityPool The reference of liquidity pool storage. * @return availablePoolCash The available pool cash(collateral) of the liquidity pool */ function getAvailablePoolCash(LiquidityPoolStorage storage liquidityPool) public view returns (int256 availablePoolCash) { return getAvailablePoolCash(liquidityPool, liquidityPool.perpetualCount); } /** * @dev Check if Trader is maintenance margin safe in the perpetual. * * @param liquidityPool The reference of liquidity pool storage. * @param perpetualIndex The index of the perpetual in the liquidity pool. * @param trader The address of the trader * @param tradeAmount The amount of positions actually traded in the transaction * @return isSafe True if Trader is maintenance margin safe in the perpetual. */ function isTraderMarginSafe( LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex, address trader, int256 tradeAmount ) public view returns (bool isSafe) { PerpetualStorage storage perpetual = liquidityPool.perpetuals[perpetualIndex]; bool hasOpened = Utils.hasOpenedPosition(perpetual.getPosition(trader), tradeAmount); int256 markPrice = perpetual.getMarkPrice(); return hasOpened ? perpetual.isInitialMarginSafe(trader, markPrice) : perpetual.isMarginSafe(trader, markPrice); } /** * @dev Initialize the liquidity pool and set up its configuration. * * @param liquidityPool The reference of liquidity pool storage. * @param collateral The collateral's address of the liquidity pool. * @param collateralDecimals The collateral's decimals of the liquidity pool. * @param operator The operator's address of the liquidity pool. * @param governor The governor's address of the liquidity pool. * @param initData The byte array contains data to initialize new created liquidity pool. */ function initialize( LiquidityPoolStorage storage liquidityPool, address creator, address collateral, uint256 collateralDecimals, address operator, address governor, bytes memory initData ) public { require(collateral != address(0), "collateral is invalid"); require(governor != address(0), "governor is invalid"); ( bool isFastCreationEnabled, int256 insuranceFundCap, uint256 liquidityCap, uint256 shareTransferDelay ) = abi.decode(initData, (bool, int256, uint256, uint256)); require(liquidityCap >= 0, "liquidity cap should be greater than 0"); require(shareTransferDelay >= 1, "share transfer delay should be at lease 1"); liquidityPool.initializeCollateral(collateral, collateralDecimals); liquidityPool.creator = creator; liquidityPool.accessController = IPoolCreatorFull(creator).getAccessController(); liquidityPool.operator = operator; liquidityPool.operatorExpiration = block.timestamp.add(OPERATOR_CHECK_IN_TIMEOUT); liquidityPool.governor = governor; liquidityPool.shareToken = governor; liquidityPool.isFastCreationEnabled = isFastCreationEnabled; liquidityPool.insuranceFundCap = insuranceFundCap; liquidityPool.liquidityCap = liquidityCap; liquidityPool.shareTransferDelay = shareTransferDelay; } /** * @dev Create and initialize new perpetual in the liquidity pool. Can only called by the operator * if the liquidity pool is running or isFastCreationEnabled is set to true. * Otherwise can only called by the governor * @param liquidityPool The reference of liquidity pool storage. * @param oracle The oracle's address of the perpetual * @param baseParams The base parameters of the perpetual * @param riskParams The risk parameters of the perpetual, must between minimum value and maximum value * @param minRiskParamValues The risk parameters' minimum values of the perpetual * @param maxRiskParamValues The risk parameters' maximum values of the perpetual */ function createPerpetual( LiquidityPoolStorage storage liquidityPool, address oracle, int256[9] calldata baseParams, int256[9] calldata riskParams, int256[9] calldata minRiskParamValues, int256[9] calldata maxRiskParamValues ) public { require( liquidityPool.perpetualCount < MAX_PERPETUAL_COUNT, "perpetual count exceeds limit" ); uint256 perpetualIndex = liquidityPool.perpetualCount; PerpetualStorage storage perpetual = liquidityPool.perpetuals[perpetualIndex]; perpetual.initialize( perpetualIndex, oracle, baseParams, riskParams, minRiskParamValues, maxRiskParamValues ); ISymbolService service = ISymbolService( IPoolCreatorFull(liquidityPool.creator).getSymbolService() ); service.allocateSymbol(address(this), perpetualIndex); if (liquidityPool.isRunning) { perpetual.setNormalState(); } liquidityPool.perpetualCount++; emit CreatePerpetual( perpetualIndex, liquidityPool.governor, liquidityPool.shareToken, getOperator(liquidityPool), oracle, liquidityPool.collateralToken, baseParams, riskParams ); } /** * @dev Run the liquidity pool. Can only called by the operator. The operator can create new perpetual before running * or after running if isFastCreationEnabled is set to true * * @param liquidityPool The reference of liquidity pool storage. */ function runLiquidityPool(LiquidityPoolStorage storage liquidityPool) public { uint256 length = liquidityPool.perpetualCount; require(length > 0, "there should be at least 1 perpetual to run"); for (uint256 i = 0; i < length; i++) { liquidityPool.perpetuals[i].setNormalState(); } liquidityPool.isRunning = true; emit RunLiquidityPool(); } /** * @dev Set the parameter of the liquidity pool. Can only called by the governor. * * @param liquidityPool The reference of liquidity pool storage. * @param params The new value of the parameter */ function setLiquidityPoolParameter( LiquidityPoolStorage storage liquidityPool, int256[4] memory params ) public { validateLiquidityPoolParameter(params); liquidityPool.isFastCreationEnabled = (params[0] != 0); liquidityPool.insuranceFundCap = params[1]; liquidityPool.liquidityCap = uint256(params[2]); liquidityPool.shareTransferDelay = uint256(params[3]); emit SetLiquidityPoolParameter(params); } /** * @dev Validate the liquidity pool parameter: * 1. insurance fund cap >= 0 * @param liquidityPoolParams The parameters of the liquidity pool. */ function validateLiquidityPoolParameter(int256[4] memory liquidityPoolParams) public pure { require(liquidityPoolParams[1] >= 0, "insuranceFundCap < 0"); require(liquidityPoolParams[2] >= 0, "liquidityCap < 0"); require(liquidityPoolParams[3] >= 1, "shareTransferDelay < 1"); } /** * @dev Add an account to the whitelist, accounts in the whitelist is allowed to call `liquidateByAMM`. * If never called, the whitelist in poolCreator will be used instead. * Once called, the local whitelist will be used and the the whitelist in poolCreator will be ignored. * * @param keeper The account of keeper. * @param perpetualIndex The index of perpetual in the liquidity pool */ function addAMMKeeper( LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex, address keeper ) public { require(perpetualIndex < liquidityPool.perpetualCount, "perpetual index out of range"); EnumerableSetUpgradeable.AddressSet storage whitelist = liquidityPool .perpetuals[perpetualIndex] .ammKeepers; require(!whitelist.contains(keeper), "keeper is already added"); bool success = whitelist.add(keeper); require(success, "fail to add keeper to whitelist"); emit AddAMMKeeper(perpetualIndex, keeper); } /** * @dev Remove an account from the `liquidateByAMM` whitelist. * * @param keeper The account of keeper. * @param perpetualIndex The index of perpetual in the liquidity pool */ function removeAMMKeeper( LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex, address keeper ) public { require(perpetualIndex < liquidityPool.perpetualCount, "perpetual index out of range"); EnumerableSetUpgradeable.AddressSet storage whitelist = liquidityPool .perpetuals[perpetualIndex] .ammKeepers; require(whitelist.contains(keeper), "keeper is not added"); bool success = whitelist.remove(keeper); require(success, "fail to remove keeper from whitelist"); emit RemoveAMMKeeper(perpetualIndex, keeper); } function setPerpetualOracle( LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex, address newOracle ) public { require(perpetualIndex < liquidityPool.perpetualCount, "perpetual index out of range"); PerpetualStorage storage perpetual = liquidityPool.perpetuals[perpetualIndex]; perpetual.setOracle(newOracle); } /** * @dev Set the base parameter of the perpetual. Can only called by the governor * @param liquidityPool The reference of liquidity pool storage. * @param perpetualIndex The index of perpetual in the liquidity pool * @param baseParams The new value of the base parameter */ function setPerpetualBaseParameter( LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex, int256[9] memory baseParams ) public { require(perpetualIndex < liquidityPool.perpetualCount, "perpetual index out of range"); PerpetualStorage storage perpetual = liquidityPool.perpetuals[perpetualIndex]; perpetual.setBaseParameter(baseParams); } /** * @dev Set the risk parameter of the perpetual, including minimum value and maximum value. * Can only called by the governor * @param liquidityPool The reference of liquidity pool storage. * @param perpetualIndex The index of perpetual in the liquidity pool * @param riskParams The new value of the risk parameter, must between minimum value and maximum value * @param minRiskParamValues The minimum value of the risk parameter * @param maxRiskParamValues The maximum value of the risk parameter */ function setPerpetualRiskParameter( LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex, int256[9] memory riskParams, int256[9] memory minRiskParamValues, int256[9] memory maxRiskParamValues ) public { require(perpetualIndex < liquidityPool.perpetualCount, "perpetual index out of range"); PerpetualStorage storage perpetual = liquidityPool.perpetuals[perpetualIndex]; perpetual.setRiskParameter(riskParams, minRiskParamValues, maxRiskParamValues); } /** * @dev Set the risk parameter of the perpetual. Can only called by the governor * @param liquidityPool The reference of liquidity pool storage. * @param perpetualIndex The index of perpetual in the liquidity pool * @param riskParams The new value of the risk parameter, must between minimum value and maximum value */ function updatePerpetualRiskParameter( LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex, int256[9] memory riskParams ) public { require(perpetualIndex < liquidityPool.perpetualCount, "perpetual index out of range"); PerpetualStorage storage perpetual = liquidityPool.perpetuals[perpetualIndex]; perpetual.updateRiskParameter(riskParams); } /** * @dev Set the state of the perpetual to "EMERGENCY". Must rebalance first. * After that the perpetual is not allowed to trade, deposit and withdraw. * The price of the perpetual is freezed to the settlement price * @param liquidityPool The reference of liquidity pool storage. * @param perpetualIndex The index of the perpetual in the liquidity pool */ function setEmergencyState(LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex) public { require(perpetualIndex < liquidityPool.perpetualCount, "perpetual index out of range"); rebalance(liquidityPool, perpetualIndex); liquidityPool.perpetuals[perpetualIndex].setEmergencyState(); if (!isAnyPerpetualIn(liquidityPool, PerpetualState.NORMAL)) { refundDonatedInsuranceFund(liquidityPool); } } /** * @dev Check if all the perpetuals in the liquidity pool are not in a state. */ function isAnyPerpetualIn(LiquidityPoolStorage storage liquidityPool, PerpetualState state) internal view returns (bool) { uint256 length = liquidityPool.perpetualCount; for (uint256 i = 0; i < length; i++) { if (liquidityPool.perpetuals[i].state == state) { return true; } } return false; } /** * @dev Check if all the perpetuals in the liquidity pool are not in normal state. */ function isAllPerpetualIn(LiquidityPoolStorage storage liquidityPool, PerpetualState state) internal view returns (bool) { uint256 length = liquidityPool.perpetualCount; for (uint256 i = 0; i < length; i++) { if (liquidityPool.perpetuals[i].state != state) { return false; } } return true; } /** * @dev Refund donated insurance fund to current operator. * - If current operator address is non-zero, all the donated funds will be forward to the operator address; * - If no operator, the donated funds will be dispatched to the LPs according to the ratio of owned shares. */ function refundDonatedInsuranceFund(LiquidityPoolStorage storage liquidityPool) internal { address operator = getOperator(liquidityPool); if (liquidityPool.donatedInsuranceFund > 0 && operator != address(0)) { int256 toRefund = liquidityPool.donatedInsuranceFund; liquidityPool.donatedInsuranceFund = 0; liquidityPool.transferToUser(operator, toRefund); } } /** * @dev Set the state of all the perpetuals to "EMERGENCY". Use special type of rebalance. * After rebalance, pool cash >= 0 and margin / initialMargin is the same in all perpetuals. * Can only called when AMM is not maintenance margin safe in all perpetuals. * After that all the perpetuals are not allowed to trade, deposit and withdraw. * The price of every perpetual is freezed to the settlement price * @param liquidityPool The reference of liquidity pool storage. */ function setAllPerpetualsToEmergencyState(LiquidityPoolStorage storage liquidityPool) public { require(liquidityPool.perpetualCount > 0, "no perpetual to settle"); int256 margin; int256 maintenanceMargin; int256 initialMargin; uint256 length = liquidityPool.perpetualCount; for (uint256 i = 0; i < length; i++) { PerpetualStorage storage perpetual = liquidityPool.perpetuals[i]; if (perpetual.state != PerpetualState.NORMAL) { continue; } int256 markPrice = perpetual.getMarkPrice(); maintenanceMargin = maintenanceMargin.add( perpetual.getMaintenanceMargin(address(this), markPrice) ); initialMargin = initialMargin.add(perpetual.getInitialMargin(address(this), markPrice)); margin = margin.add(perpetual.getMargin(address(this), markPrice)); } margin = margin.add(liquidityPool.poolCash); require( margin < maintenanceMargin || IPoolCreatorFull(liquidityPool.creator).isUniverseSettled(), "AMM's margin >= maintenance margin or not universe settled" ); // rebalance for settle all perps // Floor to make sure poolCash >= 0 int256 rate = initialMargin != 0 ? margin.wdiv(initialMargin, Round.FLOOR) : 0; for (uint256 i = 0; i < length; i++) { PerpetualStorage storage perpetual = liquidityPool.perpetuals[i]; if (perpetual.state != PerpetualState.NORMAL) { continue; } int256 markPrice = perpetual.getMarkPrice(); // Floor to make sure poolCash >= 0 int256 newMargin = perpetual.getInitialMargin(address(this), markPrice).wmul( rate, Round.FLOOR ); margin = perpetual.getMargin(address(this), markPrice); int256 deltaMargin = newMargin.sub(margin); if (deltaMargin > 0) { // from pool to perp perpetual.updateCash(address(this), deltaMargin); transferFromPoolToPerpetual(liquidityPool, i, deltaMargin); } else if (deltaMargin < 0) { // from perp to pool perpetual.updateCash(address(this), deltaMargin); transferFromPerpetualToPool(liquidityPool, i, deltaMargin.neg()); } liquidityPool.perpetuals[i].setEmergencyState(); } require(liquidityPool.poolCash >= 0, "negative poolCash after settle all"); refundDonatedInsuranceFund(liquidityPool); } /** * @dev Set the state of the perpetual to "CLEARED". Add the collateral of AMM in the perpetual to the pool cash. * Can only called when all the active accounts in the perpetual are cleared * @param liquidityPool The reference of liquidity pool storage. * @param perpetualIndex The index of the perpetual in the liquidity pool */ function setClearedState(LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex) public { require(perpetualIndex < liquidityPool.perpetualCount, "perpetual index out of range"); PerpetualStorage storage perpetual = liquidityPool.perpetuals[perpetualIndex]; perpetual.countMargin(address(this)); perpetual.setClearedState(); int256 marginToReturn = perpetual.settle(address(this)); transferFromPerpetualToPool(liquidityPool, perpetualIndex, marginToReturn); } /** * @dev Specify a new address to be operator. See transferOperator in Governance.sol. * @param liquidityPool The liquidity pool storage. * @param newOperator The address of new operator to transfer to */ function transferOperator(LiquidityPoolStorage storage liquidityPool, address newOperator) public { require(newOperator != address(0), "new operator is invalid"); require(newOperator != getOperator(liquidityPool), "cannot transfer to current operator"); liquidityPool.transferringOperator = newOperator; liquidityPool.operatorExpiration = block.timestamp.add(OPERATOR_CHECK_IN_TIMEOUT); emit TransferOperatorTo(newOperator); } /** * @dev A lease mechanism to check if the operator is alive as the pool manager. * When called the operatorExpiration will be extended according to OPERATOR_CHECK_IN_TIMEOUT. * After OPERATOR_CHECK_IN_TIMEOUT, the operator will no longer be the operator. * New operator will only be raised by voting. * Transfer operator to another account will renew the expiration. * * @param liquidityPool The liquidity pool storage. */ function checkIn(LiquidityPoolStorage storage liquidityPool) public { liquidityPool.operatorExpiration = block.timestamp.add(OPERATOR_CHECK_IN_TIMEOUT); emit OperatorCheckIn(getOperator(liquidityPool)); } /** * @dev Claim the ownership of the liquidity pool to claimer. See `transferOperator` in Governance.sol. * @param liquidityPool The liquidity pool storage. * @param claimer The address of claimer */ function claimOperator(LiquidityPoolStorage storage liquidityPool, address claimer) public { require(claimer == getTransferringOperator(liquidityPool), "caller is not qualified"); liquidityPool.operator = claimer; liquidityPool.operatorExpiration = block.timestamp.add(OPERATOR_CHECK_IN_TIMEOUT); liquidityPool.transferringOperator = address(0); IPoolCreatorFull(liquidityPool.creator).registerOperatorOfLiquidityPool( address(this), claimer ); emit ClaimOperator(claimer); } /** * @dev Revoke operator of the liquidity pool. * @param liquidityPool The liquidity pool object */ function revokeOperator(LiquidityPoolStorage storage liquidityPool) public { liquidityPool.operator = address(0); IPoolCreatorFull(liquidityPool.creator).registerOperatorOfLiquidityPool( address(this), address(0) ); emit RevokeOperator(); } /** * @dev Update the funding state of each perpetual of the liquidity pool. Funding payment of every account in the * liquidity pool is updated * @param liquidityPool The reference of liquidity pool storage. * @param currentTime The current timestamp */ function updateFundingState(LiquidityPoolStorage storage liquidityPool, uint256 currentTime) public { if (liquidityPool.fundingTime >= currentTime) { // invalid time return; } int256 timeElapsed = currentTime.sub(liquidityPool.fundingTime).toInt256(); uint256 length = liquidityPool.perpetualCount; for (uint256 i = 0; i < length; i++) { PerpetualStorage storage perpetual = liquidityPool.perpetuals[i]; if (perpetual.state != PerpetualState.NORMAL) { continue; } perpetual.updateFundingState(timeElapsed); } liquidityPool.fundingTime = currentTime; } /** * @dev Update the funding rate of each perpetual of the liquidity pool * @param liquidityPool The reference of liquidity pool storage. */ function updateFundingRate(LiquidityPoolStorage storage liquidityPool) public { (int256 poolMargin, bool isAMMSafe) = liquidityPool.getPoolMargin(); emit UpdatePoolMargin(poolMargin); if (!isAMMSafe) { poolMargin = 0; } uint256 length = liquidityPool.perpetualCount; for (uint256 i = 0; i < length; i++) { PerpetualStorage storage perpetual = liquidityPool.perpetuals[i]; if (perpetual.state != PerpetualState.NORMAL) { continue; } perpetual.updateFundingRate(poolMargin); } } /** * @dev Update the oracle price of each perpetual of the liquidity pool. * If oracle is terminated, set market to EMERGENCY. * * @param liquidityPool The liquidity pool object * @param ignoreTerminated Ignore terminated oracle if set to True. */ function updatePrice(LiquidityPoolStorage storage liquidityPool, bool ignoreTerminated) public { uint256 length = liquidityPool.perpetualCount; for (uint256 i = 0; i < length; i++) { PerpetualStorage storage perpetual = liquidityPool.perpetuals[i]; if (perpetual.state != PerpetualState.NORMAL) { continue; } perpetual.updatePrice(); if (IOracle(perpetual.oracle).isTerminated() && !ignoreTerminated) { setEmergencyState(liquidityPool, perpetual.id); } } } /** * @dev Donate collateral to the insurance fund of the liquidity pool to make the liquidity pool safe. * * @param liquidityPool The liquidity pool object * @param amount The amount of collateral to donate */ function donateInsuranceFund( LiquidityPoolStorage storage liquidityPool, address donator, int256 amount ) public { require(amount > 0, "invalid amount"); liquidityPool.transferFromUser(donator, amount); liquidityPool.donatedInsuranceFund = liquidityPool.donatedInsuranceFund.add(amount); emit DonateInsuranceFund(amount); } /** * @dev Update the collateral of the insurance fund in the liquidity pool. * If the collateral of the insurance fund exceeds the cap, the extra part of collateral belongs to LP. * If the collateral of the insurance fund < 0, the donated insurance fund will cover it. * * @param liquidityPool The liquidity pool object * @param deltaFund The update collateral amount of the insurance fund in the perpetual * @return penaltyToLP The extra part of collateral if the collateral of the insurance fund exceeds the cap */ function updateInsuranceFund(LiquidityPoolStorage storage liquidityPool, int256 deltaFund) public returns (int256 penaltyToLP) { if (deltaFund != 0) { int256 newInsuranceFund = liquidityPool.insuranceFund.add(deltaFund); if (deltaFund > 0) { if (newInsuranceFund > liquidityPool.insuranceFundCap) { penaltyToLP = newInsuranceFund.sub(liquidityPool.insuranceFundCap); newInsuranceFund = liquidityPool.insuranceFundCap; emit TransferExcessInsuranceFundToLP(penaltyToLP); } } else { if (newInsuranceFund < 0) { liquidityPool.donatedInsuranceFund = liquidityPool.donatedInsuranceFund.add( newInsuranceFund ); require( liquidityPool.donatedInsuranceFund >= 0, "negative donated insurance fund" ); newInsuranceFund = 0; } } liquidityPool.insuranceFund = newInsuranceFund; } } /** * @dev Deposit collateral to the trader's account of the perpetual. The trader's cash will increase. * Activate the perpetual for the trader if the account in the perpetual is empty before depositing. * Empty means cash and position are zero. * * @param liquidityPool The liquidity pool object * @param perpetualIndex The index of the perpetual in the liquidity pool * @param trader The address of the trader * @param amount The amount of collateral to deposit */ function deposit( LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex, address trader, int256 amount ) public { require(perpetualIndex < liquidityPool.perpetualCount, "perpetual index out of range"); transferFromUserToPerpetual(liquidityPool, perpetualIndex, trader, amount); if (liquidityPool.perpetuals[perpetualIndex].deposit(trader, amount)) { IPoolCreatorFull(liquidityPool.creator).activatePerpetualFor(trader, perpetualIndex); } } /** * @dev Withdraw collateral from the trader's account of the perpetual. The trader's cash will decrease. * Trader must be initial margin safe in the perpetual after withdrawing. * Deactivate the perpetual for the trader if the account in the perpetual is empty after withdrawing. * Empty means cash and position are zero. * * @param liquidityPool The liquidity pool object * @param perpetualIndex The index of the perpetual in the liquidity pool * @param trader The address of the trader * @param amount The amount of collateral to withdraw */ function withdraw( LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex, address trader, int256 amount ) public { require(perpetualIndex < liquidityPool.perpetualCount, "perpetual index out of range"); PerpetualStorage storage perpetual = liquidityPool.perpetuals[perpetualIndex]; rebalance(liquidityPool, perpetualIndex); if (perpetual.withdraw(trader, amount)) { IPoolCreatorFull(liquidityPool.creator).deactivatePerpetualFor(trader, perpetualIndex); } transferFromPerpetualToUser(liquidityPool, perpetualIndex, trader, amount); } /** * @dev If the state of the perpetual is "CLEARED", anyone authorized withdraw privilege by trader can settle * trader's account in the perpetual. Which means to calculate how much the collateral should be returned * to the trader, return it to trader's wallet and clear the trader's cash and position in the perpetual. * * @param liquidityPool The reference of liquidity pool storage. * @param perpetualIndex The index of the perpetual in the liquidity pool. * @param trader The address of the trader. */ function settle( LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex, address trader ) public { require(trader != address(0), "invalid trader"); int256 marginToReturn = liquidityPool.perpetuals[perpetualIndex].settle(trader); require(marginToReturn > 0, "no margin to settle"); transferFromPerpetualToUser(liquidityPool, perpetualIndex, trader, marginToReturn); } /** * @dev Clear the next active account of the perpetual which state is "EMERGENCY" and send gas reward of collateral * to sender. If all active accounts are cleared, the clear progress is done and the perpetual's state will * change to "CLEARED". Active means the trader's account is not empty in the perpetual. * Empty means cash and position are zero. * * @param liquidityPool The reference of liquidity pool storage. * @param perpetualIndex The index of the perpetual in the liquidity pool */ function clear( LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex, address trader ) public { PerpetualStorage storage perpetual = liquidityPool.perpetuals[perpetualIndex]; if ( perpetual.keeperGasReward > 0 && perpetual.totalCollateral >= perpetual.keeperGasReward ) { transferFromPerpetualToUser( liquidityPool, perpetualIndex, trader, perpetual.keeperGasReward ); } if ( perpetual.activeAccounts.length() == 0 || perpetual.clear(perpetual.getNextActiveAccount()) ) { setClearedState(liquidityPool, perpetualIndex); } } /** * @dev Add collateral to the liquidity pool and get the minted share tokens. * The share token is the credential and use to get the collateral back when removing liquidity. * Can only called when at least 1 perpetual is in NORMAL state. * * @param liquidityPool The reference of liquidity pool storage. * @param trader The address of the trader that adding liquidity * @param cashToAdd The cash(collateral) to add */ function addLiquidity( LiquidityPoolStorage storage liquidityPool, address trader, int256 cashToAdd ) public { require(cashToAdd > 0, "cash amount must be positive"); uint256 length = liquidityPool.perpetualCount; bool allowAdd; for (uint256 i = 0; i < length; i++) { if (liquidityPool.perpetuals[i].state == PerpetualState.NORMAL) { allowAdd = true; break; } } require(allowAdd, "all perpetuals are NOT in NORMAL state"); liquidityPool.transferFromUser(trader, cashToAdd); IGovernor shareToken = IGovernor(liquidityPool.shareToken); int256 shareTotalSupply = shareToken.totalSupply().toInt256(); (int256 shareToMint, int256 addedPoolMargin) = liquidityPool.getShareToMint( shareTotalSupply, cashToAdd ); require(shareToMint > 0, "received share must be positive"); // pool cash cannot be added before calculation, DO NOT use transferFromUserToPool increasePoolCash(liquidityPool, cashToAdd); shareToken.mint(trader, shareToMint.toUint256()); emit AddLiquidity(trader, cashToAdd, shareToMint, addedPoolMargin); } /** * @dev Remove collateral from the liquidity pool and redeem the share tokens when the liquidity pool is running. * Only one of shareToRemove or cashToReturn may be non-zero. * * @param liquidityPool The reference of liquidity pool storage. * @param trader The address of the trader that removing liquidity. * @param shareToRemove The amount of the share token to redeem. * @param cashToReturn The amount of cash(collateral) to return. */ function removeLiquidity( LiquidityPoolStorage storage liquidityPool, address trader, int256 shareToRemove, int256 cashToReturn ) public { IGovernor shareToken = IGovernor(liquidityPool.shareToken); int256 shareTotalSupply = shareToken.totalSupply().toInt256(); int256 removedInsuranceFund; int256 removedDonatedInsuranceFund; int256 removedPoolMargin; if (cashToReturn == 0 && shareToRemove > 0) { ( cashToReturn, removedInsuranceFund, removedDonatedInsuranceFund, removedPoolMargin ) = liquidityPool.getCashToReturn(shareTotalSupply, shareToRemove); require(cashToReturn > 0, "cash to return must be positive"); } else if (cashToReturn > 0 && shareToRemove == 0) { ( shareToRemove, removedInsuranceFund, removedDonatedInsuranceFund, removedPoolMargin ) = liquidityPool.getShareToRemove(shareTotalSupply, cashToReturn); require(shareToRemove > 0, "share to remove must be positive"); } else { revert("invalid parameter"); } require( shareToRemove.toUint256() <= shareToken.balanceOf(trader), "insufficient share balance" ); int256 removedCashFromPool = cashToReturn.sub(removedInsuranceFund).sub( removedDonatedInsuranceFund ); require( removedCashFromPool <= getAvailablePoolCash(liquidityPool), "insufficient pool cash" ); shareToken.burn(trader, shareToRemove.toUint256()); liquidityPool.transferToUser(trader, cashToReturn); liquidityPool.insuranceFund = liquidityPool.insuranceFund.sub(removedInsuranceFund); liquidityPool.donatedInsuranceFund = liquidityPool.donatedInsuranceFund.sub( removedDonatedInsuranceFund ); decreasePoolCash(liquidityPool, removedCashFromPool); emit RemoveLiquidity(trader, cashToReturn, shareToRemove, removedPoolMargin); } /** * @dev Add collateral to the liquidity pool without getting share tokens. * * @param liquidityPool The reference of liquidity pool storage. * @param trader The address of the trader that adding liquidity * @param cashToAdd The cash(collateral) to add */ function donateLiquidity( LiquidityPoolStorage storage liquidityPool, address trader, int256 cashToAdd ) public { require(cashToAdd > 0, "cash amount must be positive"); (, int256 addedPoolMargin) = liquidityPool.getShareToMint(0, cashToAdd); liquidityPool.transferFromUser(trader, cashToAdd); // pool cash cannot be added before calculation, DO NOT use transferFromUserToPool increasePoolCash(liquidityPool, cashToAdd); emit AddLiquidity(trader, cashToAdd, 0, addedPoolMargin); } /** * @dev To keep the AMM's margin equal to initial margin in the perpetual as possible. * Transfer collateral between the perpetual and the liquidity pool's cash, then * update the AMM's cash in perpetual. The liquidity pool's cash can be negative, * but the available cash can't. If AMM need to transfer and the available cash * is not enough, transfer all the rest available cash of collateral. * * @param liquidityPool The reference of liquidity pool storage. * @param perpetualIndex The index of the perpetual in the liquidity pool * @return The amount of rebalanced margin. A positive amount indicates the collaterals * are moved from perpetual to pool, and a negative amount indicates the opposite. * 0 means no rebalance happened. */ function rebalance(LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex) public returns (int256) { require(perpetualIndex < liquidityPool.perpetualCount, "perpetual index out of range"); PerpetualStorage storage perpetual = liquidityPool.perpetuals[perpetualIndex]; if (perpetual.state != PerpetualState.NORMAL) { return 0; } int256 rebalanceMargin = perpetual.getRebalanceMargin(); if (rebalanceMargin == 0) { // nothing to rebalance return 0; } else if (rebalanceMargin > 0) { // from perp to pool rebalanceMargin = rebalanceMargin.min(perpetual.totalCollateral); perpetual.updateCash(address(this), rebalanceMargin.neg()); transferFromPerpetualToPool(liquidityPool, perpetualIndex, rebalanceMargin); } else { // from pool to perp int256 availablePoolCash = getAvailablePoolCash(liquidityPool, perpetualIndex); if (availablePoolCash <= 0) { // pool has no more collateral, nothing to rebalance return 0; } rebalanceMargin = rebalanceMargin.abs().min(availablePoolCash); perpetual.updateCash(address(this), rebalanceMargin); transferFromPoolToPerpetual(liquidityPool, perpetualIndex, rebalanceMargin); } return rebalanceMargin; } /** * @dev Increase the liquidity pool's cash(collateral). * @param liquidityPool The reference of liquidity pool storage. * @param amount The amount of cash(collateral) to increase. */ function increasePoolCash(LiquidityPoolStorage storage liquidityPool, int256 amount) internal { require(amount >= 0, "increase negative pool cash"); liquidityPool.poolCash = liquidityPool.poolCash.add(amount); } /** * @dev Decrease the liquidity pool's cash(collateral). * @param liquidityPool The reference of liquidity pool storage. * @param amount The amount of cash(collateral) to decrease. */ function decreasePoolCash(LiquidityPoolStorage storage liquidityPool, int256 amount) internal { require(amount >= 0, "decrease negative pool cash"); liquidityPool.poolCash = liquidityPool.poolCash.sub(amount); } // user <=> pool (addLiquidity/removeLiquidity) function transferFromUserToPool( LiquidityPoolStorage storage liquidityPool, address account, int256 amount ) public { liquidityPool.transferFromUser(account, amount); increasePoolCash(liquidityPool, amount); } function transferFromPoolToUser( LiquidityPoolStorage storage liquidityPool, address account, int256 amount ) public { if (amount == 0) { return; } liquidityPool.transferToUser(account, amount); decreasePoolCash(liquidityPool, amount); } // user <=> perpetual (deposit/withdraw) function transferFromUserToPerpetual( LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex, address account, int256 amount ) public { liquidityPool.transferFromUser(account, amount); liquidityPool.perpetuals[perpetualIndex].increaseTotalCollateral(amount); } function transferFromPerpetualToUser( LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex, address account, int256 amount ) public { if (amount == 0) { return; } liquidityPool.transferToUser(account, amount); liquidityPool.perpetuals[perpetualIndex].decreaseTotalCollateral(amount); } // pool <=> perpetual (fee/rebalance) function transferFromPerpetualToPool( LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex, int256 amount ) public { if (amount == 0) { return; } liquidityPool.perpetuals[perpetualIndex].decreaseTotalCollateral(amount); increasePoolCash(liquidityPool, amount); } function transferFromPoolToPerpetual( LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex, int256 amount ) public { if (amount == 0) { return; } liquidityPool.perpetuals[perpetualIndex].increaseTotalCollateral(amount); decreasePoolCash(liquidityPool, amount); } /** * @dev Check if the trader is authorized the privilege by the grantee. Any trader is authorized by himself * @param liquidityPool The reference of liquidity pool storage. * @param trader The address of the trader * @param grantee The address of the grantee * @param privilege The privilege * @return isGranted True if the trader is authorized */ function isAuthorized( LiquidityPoolStorage storage liquidityPool, address trader, address grantee, uint256 privilege ) public view returns (bool isGranted) { isGranted = trader == grantee || IAccessControl(liquidityPool.accessController).isGranted(trader, grantee, privilege); } /** * @dev Deposit or withdraw to let effective leverage == target leverage * * @param liquidityPool The reference of liquidity pool storage. * @param perpetualIndex The index of the perpetual in the liquidity pool. * @param trader The address of the trader. * @param deltaPosition The update position of the trader's account in the perpetual. * @param deltaCash The update cash(collateral) of the trader's account in the perpetual. * @param totalFee The total fee collected from the trader after the trade. * @param flags The flags of the trade. */ function adjustMarginLeverage( LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex, address trader, int256 deltaPosition, int256 deltaCash, int256 totalFee, uint32 flags ) public { PerpetualStorage storage perpetual = liquidityPool.perpetuals[perpetualIndex]; // read perp int256 position = perpetual.getPosition(trader); int256 adjustCollateral; (int256 closePosition, int256 openPosition) = Utils.splitAmount( position.sub(deltaPosition), deltaPosition ); if (closePosition != 0 && openPosition == 0) { // close only adjustCollateral = adjustClosedMargin( perpetual, trader, closePosition, deltaCash, totalFee ); } else { // open only or close + open adjustCollateral = adjustOpenedMargin( perpetual, trader, deltaPosition, deltaCash, closePosition, openPosition, totalFee, flags ); } // real deposit/withdraw if (adjustCollateral > 0) { deposit(liquidityPool, perpetualIndex, trader, adjustCollateral); } else if (adjustCollateral < 0) { withdraw(liquidityPool, perpetualIndex, trader, adjustCollateral.neg()); } } function adjustClosedMargin( PerpetualStorage storage perpetual, address trader, int256 closePosition, int256 deltaCash, int256 totalFee ) public view returns (int256 adjustCollateral) { int256 markPrice = perpetual.getMarkPrice(); int256 position2 = perpetual.getPosition(trader); if (position2 == 0) { // close all, withdraw all return perpetual.getAvailableCash(trader).neg().min(0); } // when close, keep the margin ratio // -withdraw == (availableCash2 * close - (deltaCash - fee) * position2 + reservedValue) / position1 // reservedValue = 0 if position2 == 0 else keeperGasReward * (-deltaPos) adjustCollateral = perpetual.getAvailableCash(trader).wmul(closePosition); adjustCollateral = adjustCollateral.sub(deltaCash.sub(totalFee).wmul(position2)); if (position2 != 0) { adjustCollateral = adjustCollateral.sub(perpetual.keeperGasReward.wmul(closePosition)); } adjustCollateral = adjustCollateral.wdiv(position2.sub(closePosition)); // withdraw only when IM is satisfied adjustCollateral = adjustCollateral.max( perpetual.getAvailableMargin(trader, markPrice).neg() ); // never deposit when close positions adjustCollateral = adjustCollateral.min(0); } // open only or close + open function adjustOpenedMargin( PerpetualStorage storage perpetual, address trader, int256 deltaPosition, int256 deltaCash, int256 closePosition, int256 openPosition, int256 totalFee, uint32 flags ) public view returns (int256 adjustCollateral) { int256 markPrice = perpetual.getMarkPrice(); int256 oldMargin = perpetual.getMargin(trader, markPrice); { int256 leverage = perpetual.getTargetLeverageWithFlags(trader, flags); require(leverage > 0, "target leverage = 0"); // openPositionMargin adjustCollateral = openPosition.abs().wfrac(markPrice, leverage); } if (perpetual.getPosition(trader).sub(deltaPosition) != 0 && closePosition == 0) { // open from non-zero position // adjustCollateral = openPositionMargin + fee - pnl adjustCollateral = adjustCollateral .add(totalFee) .sub(markPrice.wmul(deltaPosition)) .sub(deltaCash); } else { // open from 0 or close + open adjustCollateral = adjustCollateral.add(perpetual.keeperGasReward).sub(oldMargin); } // make sure after adjust: trader is initial margin safe adjustCollateral = adjustCollateral.max( perpetual.getAvailableMargin(trader, markPrice).neg() ); } // deprecated function setTargetLeverage( LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex, address trader, int256 targetLeverage ) public { PerpetualStorage storage perpetual = liquidityPool.perpetuals[perpetualIndex]; require(perpetual.initialMarginRate != 0, "initialMarginRate is not set"); require( targetLeverage != perpetual.marginAccounts[trader].targetLeverage, "targetLeverage is already set" ); int256 maxLeverage = Constant.SIGNED_ONE.wdiv(perpetual.initialMarginRate); require(targetLeverage <= maxLeverage, "targetLeverage exceeds maxLeverage"); perpetual.setTargetLeverage(trader, targetLeverage); emit SetTargetLeverage(perpetualIndex, trader, targetLeverage); } // A readonly version of adjustMarginLeverage. This function was written post-audit. So there's a lot of repeated logic here. function readonlyAdjustMarginLeverage( PerpetualStorage storage perpetual, MarginAccount memory trader, int256 deltaPosition, int256 deltaCash, int256 totalFee, uint32 flags ) public view returns (int256 adjustCollateral) { // read perp (int256 closePosition, int256 openPosition) = Utils.splitAmount( trader.position.sub(deltaPosition), deltaPosition ); if (closePosition != 0 && openPosition == 0) { // close only adjustCollateral = readonlyAdjustClosedMargin( perpetual, trader, closePosition, deltaCash, totalFee ); } else { // open only or close + open adjustCollateral = readonlyAdjustOpenedMargin( perpetual, trader, deltaPosition, deltaCash, closePosition, openPosition, totalFee, flags ); } } // A readonly version of adjustClosedMargin. This function was written post-audit. So there's a lot of repeated logic here. function readonlyAdjustClosedMargin( PerpetualStorage storage perpetual, MarginAccount memory trader, int256 closePosition, int256 deltaCash, int256 totalFee ) public view returns (int256 adjustCollateral) { int256 markPrice = perpetual.getMarkPrice(); int256 position2 = trader.position; // was perpetual.getAvailableCash(trader) adjustCollateral = trader.cash.sub(position2.wmul(perpetual.unitAccumulativeFunding)); if (position2 == 0) { // close all, withdraw all return adjustCollateral.neg().min(0); } // was adjustClosedMargin adjustCollateral = adjustCollateral.wmul(closePosition); adjustCollateral = adjustCollateral.sub(deltaCash.sub(totalFee).wmul(position2)); if (position2 != 0) { adjustCollateral = adjustCollateral.sub(perpetual.keeperGasReward.wmul(closePosition)); } adjustCollateral = adjustCollateral.wdiv(position2.sub(closePosition)); // withdraw only when IM is satisfied adjustCollateral = adjustCollateral.max( readonlyGetAvailableMargin(perpetual, trader, markPrice).neg() ); // never deposit when close positions adjustCollateral = adjustCollateral.min(0); } // A readonly version of adjustOpenedMargin. This function was written post-audit. So there's a lot of repeated logic here. function readonlyAdjustOpenedMargin( PerpetualStorage storage perpetual, MarginAccount memory trader, int256 deltaPosition, int256 deltaCash, int256 closePosition, int256 openPosition, int256 totalFee, uint32 flags ) public view returns (int256 adjustCollateral) { int256 markPrice = perpetual.getMarkPrice(); int256 oldMargin = readonlyGetMargin(perpetual, trader, markPrice); // was perpetual.getTargetLeverageWithFlags int256 leverage; { bool _oldUseTargetLeverage = flags.oldUseTargetLeverage(); bool _newUseTargetLeverage = flags.newUseTargetLeverage(); require(!(_oldUseTargetLeverage && _newUseTargetLeverage), "invalid flags"); if (_oldUseTargetLeverage) { leverage = trader.targetLeverage; } else { leverage = flags.getTargetLeverageByFlags(); } require(perpetual.initialMarginRate != 0, "initialMarginRate is not set"); int256 maxLeverage = Constant.SIGNED_ONE.wdiv(perpetual.initialMarginRate); leverage = leverage == 0 ? perpetual.defaultTargetLeverage.value : leverage; leverage = leverage.min(maxLeverage); } require(leverage > 0, "target leverage = 0"); // openPositionMargin adjustCollateral = openPosition.abs().wfrac(markPrice, leverage); if (trader.position.sub(deltaPosition) != 0 && closePosition == 0) { // open from non-zero position // adjustCollateral = openPositionMargin + fee - pnl adjustCollateral = adjustCollateral .add(totalFee) .sub(markPrice.wmul(deltaPosition)) .sub(deltaCash); } else { // open from 0 or close + open adjustCollateral = adjustCollateral.add(perpetual.keeperGasReward).sub(oldMargin); } // make sure after adjust: trader is initial margin safe adjustCollateral = adjustCollateral.max( readonlyGetAvailableMargin(perpetual, trader, markPrice).neg() ); } // A readonly version of getMargin. This function was written post-audit. So there's a lot of repeated logic here. function readonlyGetMargin( PerpetualStorage storage perpetual, MarginAccount memory account, int256 price ) public view returns (int256 margin) { margin = account.position.wmul(price).add( account.cash.sub(account.position.wmul(perpetual.unitAccumulativeFunding)) ); } // A readonly version of getAvailableMargin. This function was written post-audit. So there's a lot of repeated logic here. function readonlyGetAvailableMargin( PerpetualStorage storage perpetual, MarginAccount memory account, int256 price ) public view returns (int256 availableMargin) { int256 threshold = account.position == 0 ? 0 // was getInitialMargin : account.position.wmul(price).wmul(perpetual.initialMarginRate).abs().add( perpetual.keeperGasReward ); // was getAvailableMargin availableMargin = readonlyGetMargin(perpetual, account, price).sub(threshold); } }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.7.4; pragma experimental ABIEncoderV2; import "@openzeppelin/contracts-upgradeable/math/SignedSafeMathUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/EnumerableSetUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/SafeCastUpgradeable.sol"; import "../interface/IOracle.sol"; import "../libraries/SafeMathExt.sol"; import "../libraries/Utils.sol"; import "../Type.sol"; import "./MarginAccountModule.sol"; library PerpetualModule { using SafeMathUpgradeable for uint256; using SafeMathExt for int256; using SafeCastUpgradeable for int256; using SafeCastUpgradeable for uint256; using AddressUpgradeable for address; using SignedSafeMathUpgradeable for int256; using EnumerableSetUpgradeable for EnumerableSetUpgradeable.AddressSet; using MarginAccountModule for PerpetualStorage; int256 constant FUNDING_INTERVAL = 3600 * 8; uint256 internal constant INDEX_INITIAL_MARGIN_RATE = 0; uint256 internal constant INDEX_MAINTENANCE_MARGIN_RATE = 1; uint256 internal constant INDEX_OPERATOR_FEE_RATE = 2; uint256 internal constant INDEX_LP_FEE_RATE = 3; uint256 internal constant INDEX_REFERRAL_REBATE_RATE = 4; uint256 internal constant INDEX_LIQUIDATION_PENALTY_RATE = 5; uint256 internal constant INDEX_KEEPER_GAS_REWARD = 6; uint256 internal constant INDEX_INSURANCE_FUND_RATE = 7; uint256 internal constant INDEX_MAX_OPEN_INTEREST_RATE = 8; uint256 internal constant INDEX_HALF_SPREAD = 0; uint256 internal constant INDEX_OPEN_SLIPPAGE_FACTOR = 1; uint256 internal constant INDEX_CLOSE_SLIPPAGE_FACTOR = 2; uint256 internal constant INDEX_FUNDING_RATE_LIMIT = 3; uint256 internal constant INDEX_AMM_MAX_LEVERAGE = 4; uint256 internal constant INDEX_AMM_CLOSE_PRICE_DISCOUNT = 5; uint256 internal constant INDEX_FUNDING_RATE_FACTOR = 6; uint256 internal constant INDEX_DEFAULT_TARGET_LEVERAGE = 7; uint256 internal constant INDEX_BASE_FUNDING_RATE = 8; event Deposit(uint256 perpetualIndex, address indexed trader, int256 amount); event Withdraw(uint256 perpetualIndex, address indexed trader, int256 amount); event Clear(uint256 perpetualIndex, address indexed trader); event Settle(uint256 perpetualIndex, address indexed trader, int256 amount); event SetNormalState(uint256 perpetualIndex); event SetEmergencyState(uint256 perpetualIndex, int256 settlementPrice, uint256 settlementTime); event SetClearedState(uint256 perpetualIndex); event UpdateUnitAccumulativeFunding(uint256 perpetualIndex, int256 unitAccumulativeFunding); event SetPerpetualBaseParameter(uint256 perpetualIndex, int256[9] baseParams); event SetPerpetualRiskParameter( uint256 perpetualIndex, int256[9] riskParams, int256[9] minRiskParamValues, int256[9] maxRiskParamValues ); event UpdatePerpetualRiskParameter(uint256 perpetualIndex, int256[9] riskParams); event SetOracle(uint256 perpetualIndex, address indexed oldOracle, address indexed newOracle); event UpdatePrice( uint256 perpetualIndex, address indexed oracle, int256 markPrice, uint256 markPriceUpdateTime, int256 indexPrice, uint256 indexPriceUpdateTime ); event UpdateFundingRate(uint256 perpetualIndex, int256 fundingRate); /** * @dev Get the mark price of the perpetual. If the state of the perpetual is not "NORMAL", * return the settlement price * @param perpetual The reference of perpetual storage. * @return markPrice The mark price of current perpetual. */ function getMarkPrice(PerpetualStorage storage perpetual) internal view returns (int256 markPrice) { markPrice = perpetual.state == PerpetualState.NORMAL ? perpetual.markPriceData.price : perpetual.settlementPriceData.price; } /** * @dev Get the index price of the perpetual. If the state of the perpetual is not "NORMAL", * return the settlement price * @param perpetual The reference of perpetual storage. * @return indexPrice The index price of current perpetual. */ function getIndexPrice(PerpetualStorage storage perpetual) internal view returns (int256 indexPrice) { indexPrice = perpetual.state == PerpetualState.NORMAL ? perpetual.indexPriceData.price : perpetual.settlementPriceData.price; } /** * @dev Get the margin to rebalance in the perpetual. * Margin to rebalance = margin - initial margin * @param perpetual The perpetual object * @return marginToRebalance The margin to rebalance in the perpetual */ function getRebalanceMargin(PerpetualStorage storage perpetual) public view returns (int256 marginToRebalance) { int256 price = getMarkPrice(perpetual); marginToRebalance = perpetual.getMargin(address(this), price).sub( perpetual.getInitialMargin(address(this), price) ); } /** * @dev Initialize the perpetual. Set up its configuration and validate parameters. * If the validation passed, set the state of perpetual to "INITIALIZING" * [minRiskParamValues, maxRiskParamValues] represents the range that the operator could * update directly without proposal. * * @param perpetual The reference of perpetual storage. * @param id The id of the perpetual (currently the index of perpetual) * @param oracle The address of oracle contract. * @param baseParams An int array of base parameter values. * @param riskParams An int array of risk parameter values. * @param minRiskParamValues An int array of minimal risk parameter values. * @param maxRiskParamValues An int array of maximum risk parameter values. */ function initialize( PerpetualStorage storage perpetual, uint256 id, address oracle, int256[9] calldata baseParams, int256[9] calldata riskParams, int256[9] calldata minRiskParamValues, int256[9] calldata maxRiskParamValues ) public { perpetual.id = id; setOracle(perpetual, oracle); setBaseParameter(perpetual, baseParams); setRiskParameter(perpetual, riskParams, minRiskParamValues, maxRiskParamValues); perpetual.state = PerpetualState.INITIALIZING; } /** * @dev Set oracle address of perpetual. New oracle must be different from the old one. * * @param perpetual The reference of perpetual storage. * @param newOracle The address of new oracle contract. */ function setOracle(PerpetualStorage storage perpetual, address newOracle) public { require(newOracle != perpetual.oracle, "oracle not changed"); validateOracle(newOracle); emit SetOracle(perpetual.id, perpetual.oracle, newOracle); perpetual.oracle = newOracle; } /** * @dev Set the base parameter of the perpetual. Can only called by the governor * @param perpetual The perpetual object * @param baseParams The new value of the base parameter */ function setBaseParameter(PerpetualStorage storage perpetual, int256[9] memory baseParams) public { validateBaseParameters(perpetual, baseParams); perpetual.initialMarginRate = baseParams[INDEX_INITIAL_MARGIN_RATE]; perpetual.maintenanceMarginRate = baseParams[INDEX_MAINTENANCE_MARGIN_RATE]; perpetual.operatorFeeRate = baseParams[INDEX_OPERATOR_FEE_RATE]; perpetual.lpFeeRate = baseParams[INDEX_LP_FEE_RATE]; perpetual.referralRebateRate = baseParams[INDEX_REFERRAL_REBATE_RATE]; perpetual.liquidationPenaltyRate = baseParams[INDEX_LIQUIDATION_PENALTY_RATE]; perpetual.keeperGasReward = baseParams[INDEX_KEEPER_GAS_REWARD]; perpetual.insuranceFundRate = baseParams[INDEX_INSURANCE_FUND_RATE]; perpetual.maxOpenInterestRate = baseParams[INDEX_MAX_OPEN_INTEREST_RATE]; emit SetPerpetualBaseParameter(perpetual.id, baseParams); } /** * @dev Set the risk parameter of the perpetual. New parameters will be validate first to apply. * Using group set instead of one-by-one set to avoid revert due to constrains between values. * * @param perpetual The reference of perpetual storage. * @param riskParams An int array of risk parameter values. * @param minRiskParamValues An int array of minimal risk parameter values. * @param maxRiskParamValues An int array of maximum risk parameter values. */ function setRiskParameter( PerpetualStorage storage perpetual, int256[9] memory riskParams, int256[9] memory minRiskParamValues, int256[9] memory maxRiskParamValues ) public { validateRiskParameters(perpetual, riskParams); setOption( perpetual.halfSpread, riskParams[INDEX_HALF_SPREAD], minRiskParamValues[INDEX_HALF_SPREAD], maxRiskParamValues[INDEX_HALF_SPREAD] ); setOption( perpetual.openSlippageFactor, riskParams[INDEX_OPEN_SLIPPAGE_FACTOR], minRiskParamValues[INDEX_OPEN_SLIPPAGE_FACTOR], maxRiskParamValues[INDEX_OPEN_SLIPPAGE_FACTOR] ); setOption( perpetual.closeSlippageFactor, riskParams[INDEX_CLOSE_SLIPPAGE_FACTOR], minRiskParamValues[INDEX_CLOSE_SLIPPAGE_FACTOR], maxRiskParamValues[INDEX_CLOSE_SLIPPAGE_FACTOR] ); setOption( perpetual.fundingRateLimit, riskParams[INDEX_FUNDING_RATE_LIMIT], minRiskParamValues[INDEX_FUNDING_RATE_LIMIT], maxRiskParamValues[INDEX_FUNDING_RATE_LIMIT] ); setOption( perpetual.ammMaxLeverage, riskParams[INDEX_AMM_MAX_LEVERAGE], minRiskParamValues[INDEX_AMM_MAX_LEVERAGE], maxRiskParamValues[INDEX_AMM_MAX_LEVERAGE] ); setOption( perpetual.maxClosePriceDiscount, riskParams[INDEX_AMM_CLOSE_PRICE_DISCOUNT], minRiskParamValues[INDEX_AMM_CLOSE_PRICE_DISCOUNT], maxRiskParamValues[INDEX_AMM_CLOSE_PRICE_DISCOUNT] ); setOption( perpetual.fundingRateFactor, riskParams[INDEX_FUNDING_RATE_FACTOR], minRiskParamValues[INDEX_FUNDING_RATE_FACTOR], maxRiskParamValues[INDEX_FUNDING_RATE_FACTOR] ); setOption( perpetual.defaultTargetLeverage, riskParams[INDEX_DEFAULT_TARGET_LEVERAGE], minRiskParamValues[INDEX_DEFAULT_TARGET_LEVERAGE], maxRiskParamValues[INDEX_DEFAULT_TARGET_LEVERAGE] ); setOption( perpetual.baseFundingRate, riskParams[INDEX_BASE_FUNDING_RATE], minRiskParamValues[INDEX_BASE_FUNDING_RATE], maxRiskParamValues[INDEX_BASE_FUNDING_RATE] ); emit SetPerpetualRiskParameter( perpetual.id, riskParams, minRiskParamValues, maxRiskParamValues ); } /** * @dev Adjust the risk parameter. New values should always satisfied the constrains and min/max limit. * * @param perpetual The reference of perpetual storage. * @param riskParams An int array of risk parameter values. */ function updateRiskParameter(PerpetualStorage storage perpetual, int256[9] memory riskParams) public { validateRiskParameters(perpetual, riskParams); updateOption(perpetual.halfSpread, riskParams[INDEX_HALF_SPREAD]); updateOption(perpetual.openSlippageFactor, riskParams[INDEX_OPEN_SLIPPAGE_FACTOR]); updateOption(perpetual.closeSlippageFactor, riskParams[INDEX_CLOSE_SLIPPAGE_FACTOR]); updateOption(perpetual.fundingRateLimit, riskParams[INDEX_FUNDING_RATE_LIMIT]); updateOption(perpetual.ammMaxLeverage, riskParams[INDEX_AMM_MAX_LEVERAGE]); updateOption(perpetual.maxClosePriceDiscount, riskParams[INDEX_AMM_CLOSE_PRICE_DISCOUNT]); updateOption(perpetual.fundingRateFactor, riskParams[INDEX_FUNDING_RATE_FACTOR]); updateOption(perpetual.defaultTargetLeverage, riskParams[INDEX_DEFAULT_TARGET_LEVERAGE]); updateOption(perpetual.baseFundingRate, riskParams[INDEX_BASE_FUNDING_RATE]); emit UpdatePerpetualRiskParameter(perpetual.id, riskParams); } /** * @dev Update the unitAccumulativeFunding variable in perpetual. * After that, funding payment of every account in the perpetual is updated, * * nextUnitAccumulativeFunding = unitAccumulativeFunding * + index * fundingRate * elapsedTime / fundingInterval * * @param perpetual The reference of perpetual storage. * @param timeElapsed The elapsed time since last update. */ function updateFundingState(PerpetualStorage storage perpetual, int256 timeElapsed) public { int256 deltaUnitLoss = timeElapsed .mul(getIndexPrice(perpetual)) .wmul(perpetual.fundingRate) .div(FUNDING_INTERVAL); perpetual.unitAccumulativeFunding = perpetual.unitAccumulativeFunding.add(deltaUnitLoss); emit UpdateUnitAccumulativeFunding(perpetual.id, perpetual.unitAccumulativeFunding); } /** * @dev Update the funding rate of the perpetual. * * - funding rate = - index * position * limit / pool margin * - funding rate = (+/-)limit when * - pool margin = 0 and position != 0 * - abs(funding rate) > limit * * @param perpetual The reference of perpetual storage. * @param poolMargin The pool margin of liquidity pool. */ function updateFundingRate(PerpetualStorage storage perpetual, int256 poolMargin) public { int256 position = perpetual.getPosition(address(this)); int256 newFundingRate; if ( ((perpetual.baseFundingRate.value > 0 && position <= 0) || (perpetual.baseFundingRate.value < 0 && position >= 0)) && perpetual.openInterest != 0 ) { newFundingRate = perpetual.baseFundingRate.value; } else { newFundingRate = 0; } if (position != 0) { int256 fundingRateLimit = perpetual.fundingRateLimit.value; if (poolMargin != 0) { newFundingRate = newFundingRate.add( getIndexPrice(perpetual).wfrac(position, poolMargin).neg().wmul( perpetual.fundingRateFactor.value ) ); newFundingRate = newFundingRate.min(fundingRateLimit).max(fundingRateLimit.neg()); } else if (position > 0) { newFundingRate = fundingRateLimit.neg(); } else { newFundingRate = fundingRateLimit; } } perpetual.fundingRate = newFundingRate; emit UpdateFundingRate(perpetual.id, newFundingRate); } /** * @dev Update the oracle price of the perpetual, including the index price and the mark price * @param perpetual The reference of perpetual storage. */ function updatePrice(PerpetualStorage storage perpetual) internal { IOracle oracle = IOracle(perpetual.oracle); updatePriceData(perpetual.markPriceData, oracle.priceTWAPLong); updatePriceData(perpetual.indexPriceData, oracle.priceTWAPShort); emit UpdatePrice( perpetual.id, address(oracle), perpetual.markPriceData.price, perpetual.markPriceData.time, perpetual.indexPriceData.price, perpetual.indexPriceData.time ); } /** * @dev Set the state of the perpetual to "NORMAL". The state must be "INITIALIZING" before * @param perpetual The reference of perpetual storage. */ function setNormalState(PerpetualStorage storage perpetual) public { require( perpetual.state == PerpetualState.INITIALIZING, "perpetual should be in initializing state" ); perpetual.state = PerpetualState.NORMAL; emit SetNormalState(perpetual.id); } /** * @dev Set the state of the perpetual to "EMERGENCY". The state must be "NORMAL" before. * The settlement price is the mark price at this time * @param perpetual The reference of perpetual storage. */ function setEmergencyState(PerpetualStorage storage perpetual) public { require(perpetual.state == PerpetualState.NORMAL, "perpetual should be in NORMAL state"); // use mark price as final price when emergency perpetual.settlementPriceData = perpetual.markPriceData; perpetual.totalAccount = perpetual.activeAccounts.length(); perpetual.state = PerpetualState.EMERGENCY; emit SetEmergencyState( perpetual.id, perpetual.settlementPriceData.price, perpetual.settlementPriceData.time ); } /** * @dev Set the state of the perpetual to "CLEARED". The state must be "EMERGENCY" before. * And settle the collateral of the perpetual, which means * determining how much collateral should returned to every account. * @param perpetual The reference of perpetual storage. */ function setClearedState(PerpetualStorage storage perpetual) public { require( perpetual.state == PerpetualState.EMERGENCY, "perpetual should be in emergency state" ); settleCollateral(perpetual); perpetual.state = PerpetualState.CLEARED; emit SetClearedState(perpetual.id); } /** * @dev Deposit collateral to the trader's account of the perpetual, that will increase the cash amount in * trader's margin account. * * If this is the first time the trader deposits in current perpetual, the address of trader will be * push to a list, then the trader is defined as an 'Active' trader for this perpetual. * List of active traders will be used during clearing. * * @param perpetual The reference of perpetual storage. * @param trader The address of the trader. * @param amount The amount of collateral to deposit. * @return isInitialDeposit True if the trader's account is empty before depositing. */ function deposit( PerpetualStorage storage perpetual, address trader, int256 amount ) public returns (bool isInitialDeposit) { require(amount > 0, "amount should greater than 0"); perpetual.updateCash(trader, amount); isInitialDeposit = registerActiveAccount(perpetual, trader); emit Deposit(perpetual.id, trader, amount); } /** * @dev Withdraw collateral from the trader's account of the perpetual, that will increase the cash amount in * trader's margin account. * * Trader must be initial margin safe in the perpetual after withdrawing. * Making the margin account 'Empty' will mark this account as a 'Deactive' trader then be removed from * list of active traders. * * @param perpetual The reference of perpetual storage. * @param trader The address of the trader. * @param amount The amount of collateral to withdraw. * @return isLastWithdrawal True if the trader's account is empty after withdrawing. */ function withdraw( PerpetualStorage storage perpetual, address trader, int256 amount ) public returns (bool isLastWithdrawal) { require( perpetual.getPosition(trader) == 0 || !IOracle(perpetual.oracle).isMarketClosed(), "market is closed" ); require(amount > 0, "amount should greater than 0"); perpetual.updateCash(trader, amount.neg()); int256 markPrice = getMarkPrice(perpetual); require( perpetual.isInitialMarginSafe(trader, markPrice), "margin is unsafe after withdrawal" ); isLastWithdrawal = perpetual.isEmptyAccount(trader); if (isLastWithdrawal) { deregisterActiveAccount(perpetual, trader); } emit Withdraw(perpetual.id, trader, amount); } /** * @dev Clear the active account of the perpetual which state is "EMERGENCY" and send gas reward of collateral * to sender. * If all active accounts are cleared, the clear progress is done and the perpetual's state will * change to "CLEARED". * * @param perpetual The reference of perpetual storage. * @param trader The address of the trader to clear. * @return isAllCleared True if all the active accounts are cleared. */ function clear(PerpetualStorage storage perpetual, address trader) public returns (bool isAllCleared) { require(perpetual.activeAccounts.length() > 0, "no account to clear"); require( perpetual.activeAccounts.contains(trader), "account cannot be cleared or already cleared" ); countMargin(perpetual, trader); perpetual.activeAccounts.remove(trader); isAllCleared = (perpetual.activeAccounts.length() == 0); emit Clear(perpetual.id, trader); } /** * @dev Check the margin balance of trader's account, update total margin. * If the margin of the trader's account is not positive, it will be counted as 0. * * @param perpetual The reference of perpetual storage. * @param trader The address of the trader to be counted. */ function countMargin(PerpetualStorage storage perpetual, address trader) public { int256 margin = perpetual.getMargin(trader, getMarkPrice(perpetual)); if (margin <= 0) { return; } if (perpetual.getPosition(trader) != 0) { perpetual.totalMarginWithPosition = perpetual.totalMarginWithPosition.add(margin); } else { perpetual.totalMarginWithoutPosition = perpetual.totalMarginWithoutPosition.add(margin); } } /** * @dev Get the address of the next active account in the perpetual. * * @param perpetual The reference of perpetual storage. * @return account The address of the next active account. */ function getNextActiveAccount(PerpetualStorage storage perpetual) public view returns (address account) { require(perpetual.activeAccounts.length() > 0, "no active account"); account = perpetual.activeAccounts.at(0); } /** * @dev If the state of the perpetual is "CLEARED". * The traders is able to settle all margin balance left in account. * How much collateral can be returned is determined by the ratio of margin balance left in account to the * total amount of collateral in perpetual. * The priority is: * - accounts withou position; * - accounts with positions; * - accounts with negative margin balance will get nothing back. * * @param perpetual The reference of perpetual storage. * @param trader The address of the trader to settle. * @param marginToReturn The actual collateral will be returned to the trader. */ function settle(PerpetualStorage storage perpetual, address trader) public returns (int256 marginToReturn) { int256 price = getMarkPrice(perpetual); marginToReturn = perpetual.getSettleableMargin(trader, price); perpetual.resetAccount(trader); emit Settle(perpetual.id, trader, marginToReturn); } /** * @dev Settle the total collateral of the perpetual, which means update redemptionRateWithPosition * and redemptionRateWithoutPosition variables. * If the total collateral is not enough for the accounts without position, * all the total collateral is given to them proportionally. * If the total collateral is more than the accounts without position needs, * the extra part of collateral is given to the accounts with position proportionally. * * @param perpetual The reference of perpetual storage. */ function settleCollateral(PerpetualStorage storage perpetual) public { int256 totalCollateral = perpetual.totalCollateral; // 2. cover margin without position if (totalCollateral < perpetual.totalMarginWithoutPosition) { // margin without positions get balance / total margin // smaller rate to make sure total redemption margin < total collateral of perpetual perpetual.redemptionRateWithoutPosition = perpetual.totalMarginWithoutPosition > 0 ? totalCollateral.wdiv(perpetual.totalMarginWithoutPosition, Round.FLOOR) : 0; // margin with positions will get nothing perpetual.redemptionRateWithPosition = 0; } else { // 3. covere margin with position perpetual.redemptionRateWithoutPosition = Constant.SIGNED_ONE; // smaller rate to make sure total redemption margin < total collateral of perpetual perpetual.redemptionRateWithPosition = perpetual.totalMarginWithPosition > 0 ? totalCollateral.sub(perpetual.totalMarginWithoutPosition).wdiv( perpetual.totalMarginWithPosition, Round.FLOOR ) : 0; } } /** * @dev Register the trader's account to the active accounts in the perpetual * @param perpetual The reference of perpetual storage. * @param trader The address of the trader. * @return True if the trader is added to account for the first time. */ function registerActiveAccount(PerpetualStorage storage perpetual, address trader) internal returns (bool) { return perpetual.activeAccounts.add(trader); } /** * @dev Deregister the trader's account from the active accounts in the perpetual * @param perpetual The reference of perpetual storage. * @param trader The address of the trader. * @return True if the trader is removed to account for the first time. */ function deregisterActiveAccount(PerpetualStorage storage perpetual, address trader) internal returns (bool) { return perpetual.activeAccounts.remove(trader); } /** * @dev Update the price data, which means the price and the update time * @param priceData The price data to update. * @param priceGetter The function pointer to retrieve current price data. */ function updatePriceData( OraclePriceData storage priceData, function() external returns (int256, uint256) priceGetter ) internal { (int256 price, uint256 time) = priceGetter(); require(price > 0 && time != 0, "invalid price data"); if (time >= priceData.time) { priceData.price = price; priceData.time = time; } } /** * @dev Increase the total collateral of the perpetual * @param perpetual The reference of perpetual storage. * @param amount The amount of collateral to increase */ function increaseTotalCollateral(PerpetualStorage storage perpetual, int256 amount) internal { require(amount >= 0, "amount is negative"); perpetual.totalCollateral = perpetual.totalCollateral.add(amount); } /** * @dev Decrease the total collateral of the perpetual * @param perpetual The reference of perpetual storage. * @param amount The amount of collateral to decrease */ function decreaseTotalCollateral(PerpetualStorage storage perpetual, int256 amount) internal { require(amount >= 0, "amount is negative"); perpetual.totalCollateral = perpetual.totalCollateral.sub(amount); require(perpetual.totalCollateral >= 0, "collateral is negative"); } /** * @dev Update the option * @param option The option to update * @param newValue The new value of the option, must between the minimum value and the maximum value */ function updateOption(Option storage option, int256 newValue) internal { require(newValue >= option.minValue && newValue <= option.maxValue, "value out of range"); option.value = newValue; } /** * @dev Set the option value, with constraints that newMinValue <= newValue <= newMaxValue. * * @param option The reference of option storage. * @param newValue The new value of the option, must be within range of [newMinValue, newMaxValue]. * @param newMinValue The minimum value of the option. * @param newMaxValue The maximum value of the option. */ function setOption( Option storage option, int256 newValue, int256 newMinValue, int256 newMaxValue ) internal { require(newValue >= newMinValue && newValue <= newMaxValue, "value out of range"); option.value = newValue; option.minValue = newMinValue; option.maxValue = newMaxValue; } /** * @dev Validate oracle contract, including each method of oracle * * @param oracle The address of oracle contract. */ function validateOracle(address oracle) public { require(oracle != address(0), "invalid oracle address"); require(oracle.isContract(), "oracle must be contract"); bool success; bytes memory data; (success, data) = oracle.call(abi.encodeWithSignature("isMarketClosed()")); require(success && data.length == 32, "invalid function: isMarketClosed"); (success, data) = oracle.call(abi.encodeWithSignature("isTerminated()")); require(success && data.length == 32, "invalid function: isTerminated"); require(!abi.decode(data, (bool)), "oracle is terminated"); (success, data) = oracle.call(abi.encodeWithSignature("collateral()")); require(success && data.length > 0, "invalid function: collateral"); string memory result; result = abi.decode(data, (string)); require(keccak256(bytes(result)) != keccak256(bytes("")), "oracle's collateral is empty"); (success, data) = oracle.call(abi.encodeWithSignature("underlyingAsset()")); require(success && data.length > 0, "invalid function: underlyingAsset"); result = abi.decode(data, (string)); require( keccak256(bytes(result)) != keccak256(bytes("")), "oracle's underlyingAsset is empty" ); (success, data) = oracle.call(abi.encodeWithSignature("priceTWAPLong()")); require(success && data.length > 0, "invalid function: priceTWAPLong"); (int256 price, uint256 timestamp) = abi.decode(data, (int256, uint256)); require(price > 0 && timestamp > 0, "oracle's twap long price is not updated"); (success, data) = oracle.call(abi.encodeWithSignature("priceTWAPShort()")); require(success && data.length > 0, "invalid function: priceTWAPShort"); (price, timestamp) = abi.decode(data, (int256, uint256)); require(price > 0 && timestamp > 0, "oracle's twap short price is not updated"); } /** * @dev Validate the base parameters of the perpetual: * 1. initial margin rate > 0 * 2. 0 < maintenance margin rate <= initial margin rate * 3. 0 <= operator fee rate <= 0.01 * 4. 0 <= lp fee rate <= 0.01 * 5. 0 <= liquidation penalty rate < maintenance margin rate * 6. keeper gas reward >= 0 * * @param perpetual The reference of perpetual storage. * @param baseParams The base parameters of the perpetual. */ function validateBaseParameters(PerpetualStorage storage perpetual, int256[9] memory baseParams) public view { require(baseParams[INDEX_INITIAL_MARGIN_RATE] > 0, "initialMarginRate <= 0"); require( perpetual.initialMarginRate == 0 || baseParams[INDEX_INITIAL_MARGIN_RATE] <= perpetual.initialMarginRate, "cannot increase initialMarginRate" ); int256 maxLeverage = Constant.SIGNED_ONE.wdiv(baseParams[INDEX_INITIAL_MARGIN_RATE]); require( perpetual.defaultTargetLeverage.value <= maxLeverage, "default target leverage exceeds max leverage" ); require( perpetual.maintenanceMarginRate == 0 || baseParams[INDEX_MAINTENANCE_MARGIN_RATE] <= perpetual.maintenanceMarginRate, "cannot increase maintenanceMarginRate" ); require(baseParams[INDEX_MAINTENANCE_MARGIN_RATE] > 0, "maintenanceMarginRate <= 0"); require( baseParams[INDEX_MAINTENANCE_MARGIN_RATE] <= baseParams[INDEX_INITIAL_MARGIN_RATE], "maintenanceMarginRate > initialMarginRate" ); require(baseParams[INDEX_OPERATOR_FEE_RATE] >= 0, "operatorFeeRate < 0"); require( baseParams[INDEX_OPERATOR_FEE_RATE] <= (Constant.SIGNED_ONE / 100), "operatorFeeRate > 1%" ); require(baseParams[INDEX_LP_FEE_RATE] >= 0, "lpFeeRate < 0"); require(baseParams[INDEX_LP_FEE_RATE] <= (Constant.SIGNED_ONE / 100), "lpFeeRate > 1%"); require(baseParams[INDEX_REFERRAL_REBATE_RATE] >= 0, "referralRebateRate < 0"); require( baseParams[INDEX_REFERRAL_REBATE_RATE] <= Constant.SIGNED_ONE, "referralRebateRate > 100%" ); require(baseParams[INDEX_LIQUIDATION_PENALTY_RATE] >= 0, "liquidationPenaltyRate < 0"); require( baseParams[INDEX_LIQUIDATION_PENALTY_RATE] <= baseParams[INDEX_MAINTENANCE_MARGIN_RATE], "liquidationPenaltyRate > maintenanceMarginRate" ); require(baseParams[INDEX_KEEPER_GAS_REWARD] >= 0, "keeperGasReward < 0"); require(baseParams[INDEX_INSURANCE_FUND_RATE] >= 0, "insuranceFundRate < 0"); require(baseParams[INDEX_MAX_OPEN_INTEREST_RATE] > 0, "maxOpenInterestRate <= 0"); } /** * @dev alidate the risk parameters of the perpetual * 1. 0 <= half spread < 1 * 2. open slippage factor > 0 * 3. 0 < close slippage factor <= open slippage factor * 4. funding rate limit >= 0 * 5. AMM max leverage > 0 * 6. 0 <= max close price discount < 1 * * @param perpetual The reference of perpetual storage. */ function validateRiskParameters(PerpetualStorage storage perpetual, int256[9] memory riskParams) public view { // must set risk parameters after setting base parameters require(perpetual.initialMarginRate > 0, "need to set base parameters first"); require(riskParams[INDEX_HALF_SPREAD] >= 0, "halfSpread < 0"); require(riskParams[INDEX_HALF_SPREAD] < Constant.SIGNED_ONE, "halfSpread >= 100%"); require(riskParams[INDEX_OPEN_SLIPPAGE_FACTOR] > 0, "openSlippageFactor < 0"); require(riskParams[INDEX_CLOSE_SLIPPAGE_FACTOR] > 0, "closeSlippageFactor < 0"); require( riskParams[INDEX_CLOSE_SLIPPAGE_FACTOR] <= riskParams[INDEX_OPEN_SLIPPAGE_FACTOR], "closeSlippageFactor > openSlippageFactor" ); require(riskParams[INDEX_FUNDING_RATE_FACTOR] >= 0, "fundingRateFactor < 0"); require(riskParams[INDEX_FUNDING_RATE_LIMIT] >= 0, "fundingRateLimit < 0"); require(riskParams[INDEX_AMM_MAX_LEVERAGE] >= 0, "ammMaxLeverage < 0"); require( riskParams[INDEX_AMM_MAX_LEVERAGE] <= Constant.SIGNED_ONE.wdiv(perpetual.initialMarginRate, Round.FLOOR), "ammMaxLeverage > 1 / initialMarginRate" ); require(riskParams[INDEX_AMM_CLOSE_PRICE_DISCOUNT] >= 0, "maxClosePriceDiscount < 0"); require( riskParams[INDEX_AMM_CLOSE_PRICE_DISCOUNT] < Constant.SIGNED_ONE, "maxClosePriceDiscount >= 100%" ); require(perpetual.initialMarginRate != 0, "initialMarginRate is not set"); int256 maxLeverage = Constant.SIGNED_ONE.wdiv(perpetual.initialMarginRate); require( riskParams[INDEX_DEFAULT_TARGET_LEVERAGE] <= maxLeverage, "default target leverage exceeds max leverage" ); } }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.7.4; import "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/EnumerableSetUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/SafeCastUpgradeable.sol"; import "./interface/ILiquidityPoolGetter.sol"; import "./libraries/SafeMathExt.sol"; import "./libraries/Utils.sol"; import "./module/MarginAccountModule.sol"; import "./module/PerpetualModule.sol"; import "./module/LiquidityPoolModule.sol"; import "./module/TradeModule.sol"; import "./module/AMMModule.sol"; import "./Type.sol"; import "./Storage.sol"; /** * @notice Getter is a helper to help getting status of liquidity from external. */ contract Getter is Storage, ILiquidityPoolGetter { using SafeMathUpgradeable for uint256; using SafeCastUpgradeable for uint256; using SafeMathExt for int256; using SafeMathExt for uint256; using Utils for EnumerableSetUpgradeable.AddressSet; using EnumerableSetUpgradeable for EnumerableSetUpgradeable.AddressSet; using MarginAccountModule for PerpetualStorage; using PerpetualModule for PerpetualStorage; using LiquidityPoolModule for LiquidityPoolStorage; using TradeModule for LiquidityPoolStorage; using AMMModule for LiquidityPoolStorage; /** * @notice Get the info of the liquidity pool. * WARN: the result of this function is base on current storage of liquidityPool, not the latest. * To get the latest status, call `syncState` first. * * @return isRunning True if the liquidity pool is running. * @return isFastCreationEnabled True if the operator of the liquidity pool is allowed to create new perpetual * when the liquidity pool is running. * @return addresses The related addresses of the liquidity pool. * @return intNums An fixed length array of int type properties, see comments for details. * @return uintNums An fixed length array of uint type properties, see comments for details. */ function getLiquidityPoolInfo() external view override returns ( bool isRunning, bool isFastCreationEnabled, // [0] creator, // [1] operator, // [2] transferringOperator, // [3] governor, // [4] shareToken, // [5] collateralToken, // [6] vault, address[7] memory addresses, // [0] vaultFeeRate, // [1] poolCash, // [2] insuranceFundCap, // [3] insuranceFund, // [4] donatedInsuranceFund, int256[5] memory intNums, // [0] collateralDecimals, // [1] perpetualCount, // [2] fundingTime, // [3] operatorExpiration, // [4] liquidityCap, // [5] shareTransferDelay, uint256[6] memory uintNums ) { isRunning = _liquidityPool.isRunning; isFastCreationEnabled = _liquidityPool.isFastCreationEnabled; addresses = [ _liquidityPool.creator, _liquidityPool.getOperator(), _liquidityPool.getTransferringOperator(), _liquidityPool.governor, _liquidityPool.shareToken, _liquidityPool.collateralToken, _liquidityPool.getVault() ]; intNums[0] = _liquidityPool.getVaultFeeRate(); intNums[1] = _liquidityPool.poolCash; intNums[2] = _liquidityPool.insuranceFundCap; intNums[3] = _liquidityPool.insuranceFund; intNums[4] = _liquidityPool.donatedInsuranceFund; uintNums[0] = _liquidityPool.collateralDecimals; uintNums[1] = _liquidityPool.perpetualCount; uintNums[2] = _liquidityPool.fundingTime; uintNums[3] = _liquidityPool.operatorExpiration; uintNums[4] = _liquidityPool.liquidityCap; uintNums[5] = _liquidityPool.getShareTransferDelay(); } /** * @notice Get the info of the perpetual. Need to update the funding state and the oracle price * of each perpetual before and update the funding rate of each perpetual after. * WARN: the result of this function is base on current storage of liquidityPool, not the latest. * To get the latest status, call `syncState` first. * * @param perpetualIndex The index of the perpetual in the liquidity pool. * @return state The state of the perpetual. * @return oracle The address of the current oracle in perpetual. * @return nums An fixed length array of uint type properties, see comments for details. */ function getPerpetualInfo(uint256 perpetualIndex) external view override onlyExistedPerpetual(perpetualIndex) returns ( PerpetualState state, address oracle, // [0] totalCollateral // [1] markPrice, (return settlementPrice if it is in EMERGENCY state) // [2] indexPrice, // [3] fundingRate, // [4] unitAccumulativeFunding, // [5] initialMarginRate, // [6] maintenanceMarginRate, // [7] operatorFeeRate, // [8] lpFeeRate, // [9] referralRebateRate, // [10] liquidationPenaltyRate, // [11] keeperGasReward, // [12] insuranceFundRate, // [13-15] halfSpread value, min, max, // [16-18] openSlippageFactor value, min, max, // [19-21] closeSlippageFactor value, min, max, // [22-24] fundingRateLimit value, min, max, // [25-27] ammMaxLeverage value, min, max, // [28-30] maxClosePriceDiscount value, min, max, // [31] openInterest, // [32] maxOpenInterestRate, // [33-35] fundingRateFactor value, min, max, // [36-38] defaultTargetLeverage value, min, max, // [39-41] baseFundingRate value, min, max, int256[42] memory nums ) { PerpetualStorage storage perpetual = _liquidityPool.perpetuals[perpetualIndex]; state = perpetual.state; oracle = perpetual.oracle; nums = [ // [0] perpetual.totalCollateral, perpetual.getMarkPrice(), perpetual.getIndexPrice(), perpetual.fundingRate, perpetual.unitAccumulativeFunding, perpetual.initialMarginRate, perpetual.maintenanceMarginRate, perpetual.operatorFeeRate, perpetual.lpFeeRate, perpetual.referralRebateRate, // [10] perpetual.liquidationPenaltyRate, perpetual.keeperGasReward, perpetual.insuranceFundRate, perpetual.halfSpread.value, perpetual.halfSpread.minValue, perpetual.halfSpread.maxValue, perpetual.openSlippageFactor.value, perpetual.openSlippageFactor.minValue, perpetual.openSlippageFactor.maxValue, perpetual.closeSlippageFactor.value, // [20] perpetual.closeSlippageFactor.minValue, perpetual.closeSlippageFactor.maxValue, perpetual.fundingRateLimit.value, perpetual.fundingRateLimit.minValue, perpetual.fundingRateLimit.maxValue, perpetual.ammMaxLeverage.value, perpetual.ammMaxLeverage.minValue, perpetual.ammMaxLeverage.maxValue, perpetual.maxClosePriceDiscount.value, perpetual.maxClosePriceDiscount.minValue, // [30] perpetual.maxClosePriceDiscount.maxValue, perpetual.openInterest, perpetual.maxOpenInterestRate, perpetual.fundingRateFactor.value, perpetual.fundingRateFactor.minValue, perpetual.fundingRateFactor.maxValue, perpetual.defaultTargetLeverage.value, perpetual.defaultTargetLeverage.minValue, perpetual.defaultTargetLeverage.maxValue, perpetual.baseFundingRate.value, // [40] perpetual.baseFundingRate.minValue, perpetual.baseFundingRate.maxValue ]; } /** * @notice Get the account info of the trader. Need to update the funding state and the oracle price * of each perpetual before and update the funding rate of each perpetual after * WARN: the result of this function is base on current storage of liquidityPool, not the latest. * To get the latest status, call `syncState` first. * * @param perpetualIndex The index of the perpetual in the liquidity pool. * @param trader The address of the trader. * When trader == liquidityPool, isSafe are meanless. Do not forget to sum * poolCash and availableCash of all perpetuals in a liquidityPool when * calculating AMM margin * @return cash The cash of the account. * @return position The position of the account. * @return availableMargin The available margin of the account. * @return margin The margin of the account. * @return settleableMargin The settleable margin of the account. * @return isInitialMarginSafe True if the account is initial margin safe. * @return isMaintenanceMarginSafe True if the account is maintenance margin safe. * @return isMarginSafe True if the total value of margin account is beyond 0. * @return targetLeverage The target leverage for openning position. */ function getMarginAccount(uint256 perpetualIndex, address trader) external view override onlyExistedPerpetual(perpetualIndex) returns ( int256 cash, int256 position, int256 availableMargin, int256 margin, int256 settleableMargin, bool isInitialMarginSafe, bool isMaintenanceMarginSafe, bool isMarginSafe, int256 targetLeverage ) { PerpetualStorage storage perpetual = _liquidityPool.perpetuals[perpetualIndex]; MarginAccount storage account = perpetual.marginAccounts[trader]; int256 markPrice = perpetual.getMarkPrice(); cash = account.cash; position = account.position; availableMargin = perpetual.getAvailableMargin(trader, markPrice); margin = perpetual.getMargin(trader, markPrice); settleableMargin = perpetual.getSettleableMargin(trader, markPrice); isInitialMarginSafe = perpetual.isInitialMarginSafe(trader, markPrice); isMaintenanceMarginSafe = perpetual.isMaintenanceMarginSafe(trader, markPrice); isMarginSafe = perpetual.isMarginSafe(trader, markPrice); targetLeverage = perpetual.getTargetLeverage(trader); } /** * @notice Get the number of active accounts in the given perpetual. * Active means the trader has margin (margin != 0) in the margin account. * @param perpetualIndex The index of the perpetual in liquidity pool. * @return activeAccountCount The number of active accounts in the perpetual. */ function getActiveAccountCount(uint256 perpetualIndex) external view override onlyExistedPerpetual(perpetualIndex) returns (uint256 activeAccountCount) { activeAccountCount = _liquidityPool.perpetuals[perpetualIndex].activeAccounts.length(); } /** * @notice Get the active accounts in the perpetual whose index with range [begin, end). * @param perpetualIndex The index of the perpetual in the liquidity pool. * @param begin The begin index of account to retrieve. * @param end The end index of account, exclusive. * @return result An array of active addresses. */ function listActiveAccounts( uint256 perpetualIndex, uint256 begin, uint256 end ) external view override onlyExistedPerpetual(perpetualIndex) returns (address[] memory result) { PerpetualStorage storage perpetual = _liquidityPool.perpetuals[perpetualIndex]; result = perpetual.activeAccounts.toArray(begin, end); } /** * @notice Get the progress of clearing active accounts. * @param perpetualIndex The index of the perpetual in the liquidity pool * @return left Number of left active accounts. * @return total Number of total active accounts. */ function getClearProgress(uint256 perpetualIndex) external view override onlyExistedPerpetual(perpetualIndex) returns (uint256 left, uint256 total) { PerpetualStorage storage perpetual = _liquidityPool.perpetuals[perpetualIndex]; left = perpetual.activeAccounts.length(); total = perpetual.state == PerpetualState.NORMAL ? perpetual.activeAccounts.length() : perpetual.totalAccount; } /** * @notice Get the pool margin of the liquidity pool. * Pool margin is how much collateral of the pool considering the AMM's positions of perpetuals * WARN: the result of this function is base on current storage of liquidityPool, not the latest. * To get the latest status, call `syncState` first. * @return poolMargin The pool margin of the liquidity pool * @return isAMMSafe True if AMM is safe */ function getPoolMargin() external view override returns (int256 poolMargin, bool isAMMSafe) { (poolMargin, isAMMSafe) = _liquidityPool.getPoolMargin(); } /** * @notice Query the price, fees and cost when trade agaist amm. * The trading price is determined by the AMM based on the index price of the perpetual. * This method should returns the same result as a 'read-only' trade. * WARN: the result of this function is base on current storage of liquidityPool, not the latest. * To get the latest status, call `syncState` first. * * Flags is a 32 bit uint value which indicates: (from highest bit) * - close only only close position during trading; * - market order do not check limit price during trading; * - stop loss only available in brokerTrade mode; * - take profit only available in brokerTrade mode; * For stop loss and take profit, see `validateTriggerPrice` in OrderModule.sol for details. * * @param perpetualIndex The index of the perpetual in liquidity pool. * @param trader The address of trader. * @param amount The amount of position to trader, positive for buying and negative for selling. The amount always use decimals 18. * @param referrer The address of referrer who will get rebate from the deal. * @param flags The flags of the trade. * @return tradePrice The average fill price. * @return totalFee The total fee collected from the trader after the trade. * @return cost Deposit or withdraw to let effective leverage == targetLeverage if flags contain USE_TARGET_LEVERAGE. > 0 if deposit, < 0 if withdraw. */ function queryTrade( uint256 perpetualIndex, address trader, int256 amount, address referrer, uint32 flags ) external override returns ( int256 tradePrice, int256 totalFee, int256 cost ) { require(trader != address(0), "invalid trader"); require(amount != 0, "invalid amount"); require( _liquidityPool.perpetuals[perpetualIndex].state == PerpetualState.NORMAL, "perpetual should be in NORMAL state" ); return _liquidityPool.queryTrade(perpetualIndex, trader, amount, referrer, flags); } /** * @notice Query cash to add / share to mint when adding liquidity to the liquidity pool. * Only one of cashToAdd or shareToMint may be non-zero. * Can only called when the pool is running. * * @param cashToAdd The amount of cash to add, always use decimals 18. * @param shareToMint The amount of share token to mint, always use decimals 18. * @return cashToAddResult The amount of cash to add, always use decimals 18. Equal to cashToAdd if cashToAdd is non-zero. * @return shareToMintResult The amount of cash to add, always use decimals 18. Equal to shareToMint if shareToMint is non-zero. */ function queryAddLiquidity(int256 cashToAdd, int256 shareToMint) external view override returns (int256 cashToAddResult, int256 shareToMintResult) { require(_liquidityPool.isRunning, "pool is not running"); int256 shareTotalSupply = IGovernor(_liquidityPool.shareToken).totalSupply().toInt256(); if (cashToAdd > 0 && shareToMint == 0) { (shareToMintResult, ) = _liquidityPool.getShareToMint(shareTotalSupply, cashToAdd); cashToAddResult = cashToAdd; } else if (cashToAdd == 0 && shareToMint > 0) { cashToAddResult = _liquidityPool.getCashToAdd(shareTotalSupply, shareToMint); shareToMintResult = shareToMint; } else { revert("invalid parameter"); } } /** * @notice Query cash to return / share to redeem when removing liquidity from the liquidity pool. * Only one of shareToRemove or cashToReturn may be non-zero. * Can only called when the pool is running. * * @param shareToRemove The amount of share token to redeem, always use decimals 18. * @param cashToReturn The amount of cash to return, always use decimals 18. * @return shareToRemoveResult The amount of share token to redeem, always use decimals 18. Equal to shareToRemove if shareToRemove is non-zero. * @return cashToReturnResult The amount of cash to return, always use decimals 18. Equal to cashToReturn if cashToReturn is non-zero. */ function queryRemoveLiquidity(int256 shareToRemove, int256 cashToReturn) external view override returns (int256 shareToRemoveResult, int256 cashToReturnResult) { require(_liquidityPool.isRunning, "pool is not running"); int256 shareTotalSupply = IGovernor(_liquidityPool.shareToken).totalSupply().toInt256(); if (shareToRemove > 0 && cashToReturn == 0) { (cashToReturnResult, , , ) = _liquidityPool.getCashToReturn( shareTotalSupply, shareToRemove ); shareToRemoveResult = shareToRemove; } else if (shareToRemove == 0 && cashToReturn > 0) { (shareToRemoveResult, , , ) = _liquidityPool.getShareToRemove( shareTotalSupply, cashToReturn ); cashToReturnResult = cashToReturn; } else { revert("invalid parameter"); } } /** * @notice List all local keepers who are able to call `liquidateByAMM`. * @param perpetualIndex The index of the perpetual in the liquidity pool. * @param begin The begin index of keeper to retrieve. * @param end The end index of keeper, exclusive. * @return result An array of keeper addresses. */ function listByAMMKeepers( uint256 perpetualIndex, uint256 begin, uint256 end ) external view onlyExistedPerpetual(perpetualIndex) returns (address[] memory result) { result = _liquidityPool.perpetuals[perpetualIndex].ammKeepers.toArray(begin, end); } bytes32[50] private __gap; }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.7.4; import "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; import "./module/PerpetualModule.sol"; import "./Type.sol"; import "./Storage.sol"; import "./interface/ILiquidityPoolGovernance.sol"; // @title Governance is the contract to maintain liquidityPool parameters. contract Governance is Storage, ILiquidityPoolGovernance { using SafeMathUpgradeable for uint256; using PerpetualModule for PerpetualStorage; using MarginAccountModule for PerpetualStorage; using LiquidityPoolModule for LiquidityPoolStorage; modifier onlyGovernor() { require(_msgSender() == _liquidityPool.governor, "only governor is allowed"); _; } modifier onlyOperator() { require(_msgSender() == _liquidityPool.getOperator(), "only operator is allowed"); _; } modifier onlyOperatorOrGovernor() { address operator = _liquidityPool.getOperator(); if (operator != address(0)) { // has operator require(_msgSender() == operator, "can only be initiated by operator"); } else { require(_msgSender() == _liquidityPool.governor, "can only be initiated by governor"); } _; } /** @notice */ function checkIn() public onlyOperator { _liquidityPool.checkIn(); } /** * @notice Use in a two phase operator transfer design: * 1. transfer operator to new operator; * 2. new operator claim to finish transfer. * Before claimOperator is called, operator wil remain to be the previous address. * * There are condition when calling transferring operator: * 1. when operator exists, only operator is able to call transfer; * 2. when operator not exists, call should be from a succeeded governor proposal. * @param newOperator The address of new operator to transfer to. */ function transferOperator(address newOperator) external onlyOperatorOrGovernor { require(newOperator != address(0), "new operator is zero address"); _liquidityPool.transferOperator(newOperator); } /** * @notice Claim the ownership of the liquidity pool to sender. See `transferOperator` for details. * The caller must be the one specified by `transferOperator` first. */ function claimOperator() public { _liquidityPool.claimOperator(_msgSender()); } /** * @notice Revoke the operator of the liquidity pool. Can only called by the operator. */ function revokeOperator() public onlyOperator { _liquidityPool.revokeOperator(); } /** * @notice Set the parameter of the liquidity pool. Can only called by the governor. * @param params New values of parameter set. */ function setLiquidityPoolParameter(int256[4] calldata params) public onlyGovernor { _liquidityPool.setLiquidityPoolParameter(params); } function setOracle(uint256 perpetualIndex, address oracle) public onlyGovernor { _liquidityPool.setPerpetualOracle(perpetualIndex, oracle); } /** * @notice Set the base parameter of the perpetual. Can only called by the governor. * @param perpetualIndex The index of the perpetual in liquidity pool. * @param baseParams Values of new base parameter set */ function setPerpetualBaseParameter(uint256 perpetualIndex, int256[9] calldata baseParams) public onlyGovernor { _liquidityPool.setPerpetualBaseParameter(perpetualIndex, baseParams); } /** * @notice Set the risk parameter and adjust range of the perpetual. Can only called by the governor. * @param perpetualIndex The index of the perpetual in liquidity pool. * @param riskParams Values of new risk parameter set, each should be within range of related [min, max]. * @param minRiskParamValues Min values of new risk parameter. * @param maxRiskParamValues Max values of new risk parameter. */ function setPerpetualRiskParameter( uint256 perpetualIndex, int256[9] calldata riskParams, int256[9] calldata minRiskParamValues, int256[9] calldata maxRiskParamValues ) external onlyGovernor { _liquidityPool.setPerpetualRiskParameter( perpetualIndex, riskParams, minRiskParamValues, maxRiskParamValues ); } /** * @notice Update the risk parameter of the perpetual. Can only called by the operator * @param perpetualIndex The index of the perpetual in liquidity pool. * @param riskParams The new value of the risk parameter, must between minimum value and maximum value */ function updatePerpetualRiskParameter(uint256 perpetualIndex, int256[9] calldata riskParams) external onlyOperator { _liquidityPool.updatePerpetualRiskParameter(perpetualIndex, riskParams); } /** * @dev Add an account to the whitelist, accounts in the whitelist is allowed to call `liquidateByAMM`. * If never called, the whitelist in poolCreator will be used instead. * Once called, the local whitelist will be used and the the whitelist in poolCreator will be ignored. * * @param keeper The account of keeper. * @param perpetualIndex The index of perpetual in the liquidity pool */ function addAMMKeeper(uint256 perpetualIndex, address keeper) external onlyOperatorOrGovernor { _liquidityPool.addAMMKeeper(perpetualIndex, keeper); } /** * @dev Remove an account from the `liquidateByAMM` whitelist. * * @param keeper The account of keeper. * @param perpetualIndex The index of perpetual in the liquidity pool */ function removeAMMKeeper(uint256 perpetualIndex, address keeper) external onlyOperatorOrGovernor { _liquidityPool.removeAMMKeeper(perpetualIndex, keeper); } /** * @notice Force to set the state of the perpetual to "EMERGENCY" and set the settlement price. * Can only called by the governor. * @param perpetualIndex The index of the perpetual in liquidity pool. */ function forceToSetEmergencyState(uint256 perpetualIndex, int256 settlementPrice) external syncState(true) onlyGovernor { require(settlementPrice >= 0, "negative settlement price"); OraclePriceData memory settlementPriceData = OraclePriceData({ price: settlementPrice, time: block.timestamp }); PerpetualStorage storage perpetual = _liquidityPool.perpetuals[perpetualIndex]; perpetual.markPriceData = settlementPriceData; perpetual.indexPriceData = settlementPriceData; _liquidityPool.setEmergencyState(perpetualIndex); } /** * @notice Set perpetual into "EMERGENCY" state. * 1. if the oracle contract declares itself as "terminated", call setEmergencyState(index). * 2. if the AMM is maintenance margin unsafe, call * setEmergencyState(SET_ALL_PERPETUALS_TO_EMERGENCY_STATE). * @param perpetualIndex The index of the perpetual in liquidity pool or * SET_ALL_PERPETUALS_TO_EMERGENCY_STATE to settle the whole pool */ function setEmergencyState(uint256 perpetualIndex) public override syncState(true) { if (perpetualIndex == Constant.SET_ALL_PERPETUALS_TO_EMERGENCY_STATE) { _liquidityPool.setAllPerpetualsToEmergencyState(); } else { PerpetualStorage storage perpetual = _liquidityPool.perpetuals[perpetualIndex]; require(IOracle(perpetual.oracle).isTerminated(), "prerequisite not met"); _liquidityPool.setEmergencyState(perpetualIndex); } } bytes32[50] private __gap; }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.7.4; pragma experimental ABIEncoderV2; /** * @notice The libraryEvents defines events that will be raised from modules (contract/modules). * @dev DO REMEMBER to add new events in modules here. */ contract LibraryEvents { // PerpetualModule event Deposit(uint256 perpetualIndex, address indexed trader, int256 amount); event Withdraw(uint256 perpetualIndex, address indexed trader, int256 amount); event Clear(uint256 perpetualIndex, address indexed trader); event Settle(uint256 perpetualIndex, address indexed trader, int256 amount); event SetNormalState(uint256 perpetualIndex); event SetEmergencyState(uint256 perpetualIndex, int256 settlementPrice, uint256 settlementTime); event SetClearedState(uint256 perpetualIndex); event UpdateUnitAccumulativeFunding(uint256 perpetualIndex, int256 unitAccumulativeFunding); event SetPerpetualBaseParameter(uint256 perpetualIndex, int256[9] baseParams); event SetPerpetualRiskParameter( uint256 perpetualIndex, int256[9] riskParams, int256[9] minRiskParamValues, int256[9] maxRiskParamValues ); event UpdatePerpetualRiskParameter(uint256 perpetualIndex, int256[9] riskParams); event SetOracle(uint256 perpetualIndex, address indexed oldOracle, address indexed newOracle); event UpdatePrice( uint256 perpetualIndex, address indexed oracle, int256 markPrice, uint256 markPriceUpdateTime, int256 indexPrice, uint256 indexPriceUpdateTime ); event UpdateFundingRate(uint256 perpetualIndex, int256 fundingRate); // LiquidityPoolModule event AddLiquidity( address indexed trader, int256 addedCash, int256 mintedShare, int256 addedPoolMargin ); event RemoveLiquidity( address indexed trader, int256 returnedCash, int256 burnedShare, int256 removedPoolMargin ); event UpdatePoolMargin(int256 poolMargin); event TransferOperatorTo(address indexed newOperator); event ClaimOperator(address indexed newOperator); event RevokeOperator(); event SetLiquidityPoolParameter(int256[4] value); event CreatePerpetual( uint256 perpetualIndex, address governor, address shareToken, address operator, address oracle, address collateral, int256[9] baseParams, int256[9] riskParams ); event RunLiquidityPool(); event OperatorCheckIn(address indexed operator); event DonateInsuranceFund(int256 amount); event TransferExcessInsuranceFundToLP(int256 amount); event SetTargetLeverage(uint256 perpetualIndex, address indexed trader, int256 targetLeverage); event AddAMMKeeper(uint256 perpetualIndex, address indexed keeper); event RemoveAMMKeeper(uint256 perpetualIndex, address indexed keeper); event AddTraderKeeper(uint256 perpetualIndex, address indexed keeper); event RemoveTraderKeeper(uint256 perpetualIndex, address indexed keeper); // TradeModule event Trade( uint256 perpetualIndex, address indexed trader, int256 position, int256 price, int256 fee, int256 lpFee ); event Liquidate( uint256 perpetualIndex, address indexed liquidator, address indexed trader, int256 amount, int256 price, int256 penalty, int256 penaltyToLP ); event TransferFeeToVault( uint256 perpetualIndex, address indexed trader, address indexed vault, int256 vaultFee ); event TransferFeeToOperator( uint256 perpetualIndex, address indexed trader, address indexed operator, int256 operatorFee ); event TransferFeeToReferrer( uint256 perpetualIndex, address indexed trader, address indexed referrer, int256 referralRebate ); }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.7.4; pragma experimental ABIEncoderV2; import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/EnumerableSetUpgradeable.sol"; import "./interface/IPerpetual.sol"; import "./libraries/Constant.sol"; import "./libraries/OrderData.sol"; import "./module/TradeModule.sol"; import "./module/OrderModule.sol"; import "./module/LiquidityPoolModule.sol"; import "./Storage.sol"; import "./Type.sol"; contract Perpetual is Storage, ReentrancyGuardUpgradeable, IPerpetual { using OrderData for bytes; using OrderData for uint32; using OrderModule for LiquidityPoolStorage; using TradeModule for LiquidityPoolStorage; using LiquidityPoolModule for LiquidityPoolStorage; using EnumerableSetUpgradeable for EnumerableSetUpgradeable.AddressSet; // deprecated function setTargetLeverage( uint256 perpetualIndex, address trader, int256 targetLeverage ) external onlyAuthorized( trader, Constant.PRIVILEGE_TRADE | Constant.PRIVILEGE_DEPOSIT | Constant.PRIVILEGE_WITHDRAW ) { require(trader != address(0), "invalid trader"); require(targetLeverage % Constant.SIGNED_ONE == 0, "targetLeverage must be integer"); require(targetLeverage > 0, "targetLeverage is negative"); _liquidityPool.setTargetLeverage(perpetualIndex, trader, targetLeverage); } /** * @notice Deposit collateral to the perpetual. * Can only called when the perpetual's state is "NORMAL". * This method will always increase `cash` amount in trader's margin account. * * @param perpetualIndex The index of the perpetual in the liquidity pool. * @param trader The address of the trader. * @param amount The amount of collateral to deposit. The amount always use decimals 18. */ function deposit( uint256 perpetualIndex, address trader, int256 amount ) external override nonReentrant onlyNotUniverseSettled onlyAuthorized(trader, Constant.PRIVILEGE_DEPOSIT) { require( _liquidityPool.perpetuals[perpetualIndex].state == PerpetualState.NORMAL, "perpetual should be in NORMAL state" ); require(trader != address(0), "invalid trader"); require(amount > 0, "invalid amount"); _liquidityPool.deposit(perpetualIndex, trader, amount); } /** * @notice Withdraw collateral from the trader's account of the perpetual. * After withdrawn, trader shall at least has maintenance margin left in account. * Can only called when the perpetual's state is "NORMAL". * Margin account must at least keep * The trader's cash will decrease in the perpetual. * Need to update the funding state and the oracle price of each perpetual before * and update the funding rate of each perpetual after * * @param perpetualIndex The index of the perpetual in the liquidity pool. * @param trader The address of the trader. * @param amount The amount of collateral to withdraw. The amount always use decimals 18. */ function withdraw( uint256 perpetualIndex, address trader, int256 amount ) external override nonReentrant onlyNotUniverseSettled syncState(false) onlyAuthorized(trader, Constant.PRIVILEGE_WITHDRAW) { require( _liquidityPool.perpetuals[perpetualIndex].state == PerpetualState.NORMAL, "perpetual should be in NORMAL state" ); require(trader != address(0), "invalid trader"); require(amount > 0, "invalid amount"); _liquidityPool.withdraw(perpetualIndex, trader, amount); } /** * @notice If the state of the perpetual is "CLEARED", anyone authorized withdraw privilege by trader can settle * trader's account in the perpetual. Which means to calculate how much the collateral should be returned * to the trader, return it to trader's wallet and clear the trader's cash and position in the perpetual. * * @param perpetualIndex The index of the perpetual in the liquidity pool * @param trader The address of the trader. */ function settle(uint256 perpetualIndex, address trader) external override onlyAuthorized(trader, Constant.PRIVILEGE_WITHDRAW) nonReentrant { require(trader != address(0), "invalid trader"); require( _liquidityPool.perpetuals[perpetualIndex].state == PerpetualState.CLEARED, "perpetual should be in CLEARED state" ); _liquidityPool.settle(perpetualIndex, trader); } /** * @notice Clear the next active account of the perpetual which state is "EMERGENCY" and send gas reward of collateral * to sender. If all active accounts are cleared, the clear progress is done and the perpetual's state will * change to "CLEARED". Active means the trader's account is not empty in the perpetual. * Empty means cash and position are zero * * @param perpetualIndex The index of the perpetual in the liquidity pool. */ function clear(uint256 perpetualIndex) external override nonReentrant { require( _liquidityPool.perpetuals[perpetualIndex].state == PerpetualState.EMERGENCY, "perpetual should be in EMERGENCY state" ); _liquidityPool.clear(perpetualIndex, _msgSender()); } /** * @notice Trade with AMM in the perpetual, require sender is granted the trade privilege by the trader. * The trading price is determined by the AMM based on the index price of the perpetual. * A successful trade should: * - The trade transaction not exceeds deadline; * - Current liquidity of amm is enough to make the deal; * - to open position: * - Trader's margin balance must be greater then or equal to initial margin after trading; * - Full trading fee will be charged if trader is opening position. * - to close position: * - Trader's margin balance must be greater then or equal to 0 after trading; * - Trader need to pay the trading fee as much as possible before all the margin balance drained. * If one trade transaction does close and open at same time (Open positions in the opposite direction) * It will be treat as opening position. * * * Flags is a 32 bit uint value which indicates: (from highest bit) * 31 27 26 7 6 0 * +---+---+---+---+---+------------------------+----------------+ * | C | M | S | T | R | Target leverage 20bits | Reserved 7bits | * +---+---+---+---+---+------------------------+----------------+ * | | | | | ` Target leverage Fixed-point decimal with 2 decimal digits. * | | | | | 0 means don't automatically deposit / withdraw. * | | | | `--- Reserved * | | | `------- Take profit Only available in brokerTrade mode. * | | `----------- Stop loss Only available in brokerTrade mode. * | `--------------- Market order Do not check limit price during trading. * `------------------- Close only Only close position during trading. * For stop loss and take profit, see `validateTriggerPrice` in OrderModule.sol for details. * * @param perpetualIndex The index of the perpetual in liquidity pool. * @param trader The address of trader. * @param amount The amount of position to trader, positive for buying and negative for selling. The amount always use decimals 18. * @param limitPrice The worst price the trader accepts. * @param deadline The deadline of trade transaction. * @param referrer The address of referrer who will get rebate from the deal. * @param flags The flags of the trade. * @return tradeAmount The amount of positions actually traded in the transaction. The amount always use decimals 18. */ function trade( uint256 perpetualIndex, address trader, int256 amount, int256 limitPrice, uint256 deadline, address referrer, uint32 flags ) external override onlyAuthorized( trader, flags.useTargetLeverage() ? Constant.PRIVILEGE_TRADE | Constant.PRIVILEGE_DEPOSIT | Constant.PRIVILEGE_WITHDRAW : Constant.PRIVILEGE_TRADE ) syncState(false) returns (int256 tradeAmount) { require(trader != address(0), "invalid trader"); require(amount != 0, "invalid amount"); require(deadline >= block.timestamp, "deadline exceeded"); tradeAmount = _trade(perpetualIndex, trader, amount, limitPrice, referrer, flags); } /** * @notice Trade with AMM by the order, initiated by the broker. order is passed in through packed data structure. * All the fields of order are verified by signature. * See `trade` for details. * @param orderData The order data object * @param amount The amount of position to trader, positive for buying and negative for selling. * This amount should be lower then or equal to amount in `orderData`. The amount always use decimals 18. * @return tradeAmount The amount of positions actually traded in the transaction. The amount always use decimals 18. */ function brokerTrade(bytes memory orderData, int256 amount) external override syncState(false) returns (int256 tradeAmount) { Order memory order = orderData.decodeOrderData(); bytes memory signature = orderData.decodeSignature(); _liquidityPool.validateSignature(order, signature); _liquidityPool.validateOrder(order, amount); _liquidityPool.validateTriggerPrice(order); tradeAmount = _trade( order.perpetualIndex, order.trader, amount, order.limitPrice, order.referrer, order.flags ); } /** * @notice Liquidate the trader if the trader's margin balance is lower than maintenance margin (unsafe). * Liquidate can be considered as a forced trading between AMM and unsafe margin account; * Based on current liquidity of AMM, it may take positions up to an amount equal to all the position * of the unsafe account. Besides the position, trader need to pay an extra penalty to AMM * for taking the unsafe assets. See TradeModule.sol for ehe strategy of penalty. * * The liquidate price will be determined by AMM. * Caller of this method can be anyone, then get a reward to make up for transaction gas fee. * * If a trader's margin balance is lower than 0 (bankrupt), insurance fund will be use to fill the loss * to make the total profit and loss balanced. (first the `insuranceFund` then the `donatedInsuranceFund`) * * If insurance funds are drained, the state of perpetual will turn to enter "EMERGENCY" than shutdown. * Can only liquidate when the perpetual's state is "NORMAL". * * @param perpetualIndex The index of the perpetual in liquidity pool * @param trader The address of trader to be liquidated. * @return liquidationAmount The amount of positions actually liquidated in the transaction. The amount always use decimals 18. */ function liquidateByAMM(uint256 perpetualIndex, address trader) external override nonReentrant onlyNotUniverseSettled syncState(false) returns (int256 liquidationAmount) { require(_isAMMKeeper(perpetualIndex, _msgSender()), "caller must be keeper"); require( _liquidityPool.perpetuals[perpetualIndex].state == PerpetualState.NORMAL, "perpetual should be in NORMAL state" ); require(trader != address(0), "invalid trader"); require(trader != address(this), "cannot liquidate AMM"); liquidationAmount = _liquidityPool.liquidateByAMM(perpetualIndex, _msgSender(), trader); } /** * @notice This method is generally consistent with `liquidateByAMM` function, but there some difference: * - The liquidation price is no longer determined by AMM, but the mark price; * - The penalty is taken by trader who takes position but AMM; * * @param perpetualIndex The index of the perpetual in liquidity pool. * @param liquidator The address of liquidator to receive the liquidated position. * @param trader The address of trader to be liquidated. * @param amount The amount of position to be taken from liquidated trader. The amount always use decimals 18. * @param limitPrice The worst price liquidator accepts. * @param deadline The deadline of transaction. * @return liquidationAmount The amount of positions actually liquidated in the transaction. */ function liquidateByTrader( uint256 perpetualIndex, address liquidator, address trader, int256 amount, int256 limitPrice, uint256 deadline ) external override nonReentrant onlyNotUniverseSettled onlyAuthorized(liquidator, Constant.PRIVILEGE_LIQUIDATE) syncState(false) returns (int256 liquidationAmount) { require( _liquidityPool.perpetuals[perpetualIndex].state == PerpetualState.NORMAL, "perpetual should be in NORMAL state" ); require(trader != address(0), "invalid trader"); require(trader != address(this), "cannot liquidate AMM"); require(amount != 0, "invalid amount"); require(limitPrice >= 0, "invalid limit price"); require(deadline >= block.timestamp, "deadline exceeded"); liquidationAmount = _liquidityPool.liquidateByTrader( perpetualIndex, liquidator, trader, amount, limitPrice ); } function _trade( uint256 perpetualIndex, address trader, int256 amount, int256 limitPrice, address referrer, uint32 flags ) internal onlyNotUniverseSettled returns (int256 tradeAmount) { require( _liquidityPool.perpetuals[perpetualIndex].state == PerpetualState.NORMAL, "perpetual should be in NORMAL state" ); tradeAmount = _liquidityPool.trade( perpetualIndex, trader, amount, limitPrice, referrer, flags ); } function _isAMMKeeper(uint256 perpetualIndex, address liquidator) internal view returns (bool) { EnumerableSetUpgradeable.AddressSet storage whitelist = _liquidityPool .perpetuals[perpetualIndex] .ammKeepers; if (whitelist.length() == 0) { return IPoolCreatorFull(_liquidityPool.creator).isKeeper(liquidator); } else { return whitelist.contains(liquidator); } } bytes32[50] private __gap; }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.7.4; import "@openzeppelin/contracts-upgradeable/GSN/ContextUpgradeable.sol"; import "./interface/IPoolCreatorFull.sol"; import "./module/LiquidityPoolModule.sol"; import "./Type.sol"; contract Storage is ContextUpgradeable { using LiquidityPoolModule for LiquidityPoolStorage; LiquidityPoolStorage internal _liquidityPool; modifier onlyNotUniverseSettled() { require(!IPoolCreatorFull(_liquidityPool.creator).isUniverseSettled(), "universe settled"); _; } modifier onlyExistedPerpetual(uint256 perpetualIndex) { require(perpetualIndex < _liquidityPool.perpetualCount, "perpetual not exist"); _; } modifier syncState(bool ignoreTerminated) { uint256 currentTime = block.timestamp; _liquidityPool.updateFundingState(currentTime); _liquidityPool.updatePrice(ignoreTerminated); _; _liquidityPool.updateFundingRate(); } modifier onlyAuthorized(address trader, uint256 privilege) { require( _liquidityPool.isAuthorized(trader, _msgSender(), privilege), "unauthorized caller" ); _; } bytes32[28] private __gap; }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.7.4; import "@openzeppelin/contracts-upgradeable/utils/EnumerableSetUpgradeable.sol"; /** * @notice Perpetual state: * - INVALID: Uninitialized or not non-existent perpetual; * - INITIALIZING: Only when LiquidityPoolStorage.isRunning == false. Traders cannot perform operations; * - NORMAL: Full functional state. Traders is able to perform all operations; * - EMERGENCY: Perpetual is unsafe and only clear is available; * - CLEARED: All margin account is cleared. Trade could withdraw remaining margin balance. */ enum PerpetualState { INVALID, INITIALIZING, NORMAL, EMERGENCY, CLEARED } enum OrderType { LIMIT, MARKET, STOP } /** * @notice Data structure to store risk parameter value. */ struct Option { int256 value; int256 minValue; int256 maxValue; } /** * @notice Data structure to store oracle price data. */ struct OraclePriceData { int256 price; uint256 time; } /** * @notice Data structure to store user margin information. See MarginAccountModule.sol for details. */ struct MarginAccount { int256 cash; int256 position; int256 targetLeverage; } /** * @notice Data structure of an order object. */ struct Order { address trader; address broker; address relayer; address referrer; address liquidityPool; int256 minTradeAmount; int256 amount; int256 limitPrice; int256 triggerPrice; uint256 chainID; uint64 expiredAt; uint32 perpetualIndex; uint32 brokerFeeLimit; uint32 flags; uint32 salt; } /** * @notice Core data structure, a core . */ struct LiquidityPoolStorage { bool isRunning; bool isFastCreationEnabled; // addresses address creator; address operator; address transferringOperator; address governor; address shareToken; address accessController; bool reserved3; // isWrapped uint256 scaler; uint256 collateralDecimals; address collateralToken; // pool attributes int256 poolCash; uint256 fundingTime; uint256 reserved5; uint256 operatorExpiration; mapping(address => int256) reserved1; bytes32[] reserved2; // perpetuals uint256 perpetualCount; mapping(uint256 => PerpetualStorage) perpetuals; // insurance fund int256 insuranceFundCap; int256 insuranceFund; int256 donatedInsuranceFund; address reserved4; uint256 liquidityCap; uint256 shareTransferDelay; // reserved slot for future upgrade bytes32[14] reserved; } /** * @notice Core data structure, storing perpetual information. */ struct PerpetualStorage { uint256 id; PerpetualState state; address oracle; int256 totalCollateral; int256 openInterest; // prices OraclePriceData indexPriceData; OraclePriceData markPriceData; OraclePriceData settlementPriceData; // funding state int256 fundingRate; int256 unitAccumulativeFunding; // base parameters int256 initialMarginRate; int256 maintenanceMarginRate; int256 operatorFeeRate; int256 lpFeeRate; int256 referralRebateRate; int256 liquidationPenaltyRate; int256 keeperGasReward; int256 insuranceFundRate; int256 reserved1; int256 maxOpenInterestRate; // risk parameters Option halfSpread; Option openSlippageFactor; Option closeSlippageFactor; Option fundingRateLimit; Option fundingRateFactor; Option ammMaxLeverage; Option maxClosePriceDiscount; // users uint256 totalAccount; int256 totalMarginWithoutPosition; int256 totalMarginWithPosition; int256 redemptionRateWithoutPosition; int256 redemptionRateWithPosition; EnumerableSetUpgradeable.AddressSet activeAccounts; // insurance fund int256 reserved2; int256 reserved3; // accounts mapping(address => MarginAccount) marginAccounts; Option defaultTargetLeverage; // keeper address reserved4; EnumerableSetUpgradeable.AddressSet ammKeepers; EnumerableSetUpgradeable.AddressSet reserved5; Option baseFundingRate; // reserved slot for future upgrade bytes32[9] reserved; }
// SPDX-License-Identifier: MIT pragma solidity >=0.6.0 <0.8.0; /** * @title SignedSafeMath * @dev Signed math operations with safety checks that revert on error. */ library SignedSafeMathUpgradeable { int256 constant private _INT256_MIN = -2**255; /** * @dev Returns the multiplication of two signed integers, reverting on * overflow. * * Counterpart to Solidity's `*` operator. * * Requirements: * * - Multiplication cannot overflow. */ function mul(int256 a, int256 b) internal pure returns (int256) { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 if (a == 0) { return 0; } require(!(a == -1 && b == _INT256_MIN), "SignedSafeMath: multiplication overflow"); int256 c = a * b; require(c / a == b, "SignedSafeMath: multiplication overflow"); return c; } /** * @dev Returns the integer division of two signed integers. Reverts on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. Note: this function uses a * `revert` opcode (which leaves remaining gas untouched) while Solidity * uses an invalid opcode to revert (consuming all remaining gas). * * Requirements: * * - The divisor cannot be zero. */ function div(int256 a, int256 b) internal pure returns (int256) { require(b != 0, "SignedSafeMath: division by zero"); require(!(b == -1 && a == _INT256_MIN), "SignedSafeMath: division overflow"); int256 c = a / b; return c; } /** * @dev Returns the subtraction of two signed integers, reverting on * overflow. * * Counterpart to Solidity's `-` operator. * * Requirements: * * - Subtraction cannot overflow. */ function sub(int256 a, int256 b) internal pure returns (int256) { int256 c = a - b; require((b >= 0 && c <= a) || (b < 0 && c > a), "SignedSafeMath: subtraction overflow"); return c; } /** * @dev Returns the addition of two signed integers, reverting on * overflow. * * Counterpart to Solidity's `+` operator. * * Requirements: * * - Addition cannot overflow. */ function add(int256 a, int256 b) internal pure returns (int256) { int256 c = a + b; require((b >= 0 && c >= a) || (b < 0 && c < a), "SignedSafeMath: addition overflow"); return c; } }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.7.4; library Constant { address internal constant INVALID_ADDRESS = address(0); int256 internal constant SIGNED_ONE = 10**18; uint256 internal constant UNSIGNED_ONE = 10**18; uint256 internal constant PRIVILEGE_DEPOSIT = 0x1; uint256 internal constant PRIVILEGE_WITHDRAW = 0x2; uint256 internal constant PRIVILEGE_TRADE = 0x4; uint256 internal constant PRIVILEGE_LIQUIDATE = 0x8; uint256 internal constant PRIVILEGE_GUARD = PRIVILEGE_DEPOSIT | PRIVILEGE_WITHDRAW | PRIVILEGE_TRADE | PRIVILEGE_LIQUIDATE; // max number of uint256 uint256 internal constant SET_ALL_PERPETUALS_TO_EMERGENCY_STATE = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity >=0.7.4; library Math { /** * @dev Get the most significant bit of the number, example: 0 ~ 1 => 0, 2 ~ 3 => 1, 4 ~ 7 => 2, 8 ~ 15 => 3, about use 606 ~ 672 gas * @param x The number * @return uint8 The significant bit of the number */ function mostSignificantBit(uint256 x) internal pure returns (uint8) { uint256 t; uint8 r; if ((t = (x >> 128)) > 0) { x = t; r += 128; } if ((t = (x >> 64)) > 0) { x = t; r += 64; } if ((t = (x >> 32)) > 0) { x = t; r += 32; } if ((t = (x >> 16)) > 0) { x = t; r += 16; } if ((t = (x >> 8)) > 0) { x = t; r += 8; } if ((t = (x >> 4)) > 0) { x = t; r += 4; } if ((t = (x >> 2)) > 0) { x = t; r += 2; } if ((t = (x >> 1)) > 0) { x = t; r += 1; } return r; } // https://en.wikipedia.org/wiki/Integer_square_root /** * @dev Get the square root of the number * @param x The number, usually 10^36 * @return int256 The square root of the number, usually 10^18 */ function sqrt(int256 x) internal pure returns (int256) { require(x >= 0, "negative sqrt"); if (x < 3) { return (x + 1) / 2; } // binary estimate // inspired by https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Binary_estimates uint8 n = mostSignificantBit(uint256(x)); // make sure initial estimate > sqrt(x) // 2^ceil((n + 1) / 2) as initial estimate // 2^(n + 1) > x // => 2^ceil((n + 1) / 2) > 2^((n + 1) / 2) > sqrt(x) n = (n + 1) / 2 + 1; // modified babylonian method int256 next = int256(1 << n); int256 y; do { y = next; next = (next + x / next) >> 1; } while (next < y); return y; } }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.7.4; import "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/math/SignedSafeMathUpgradeable.sol"; import "./Constant.sol"; import "./Utils.sol"; enum Round { CEIL, FLOOR } library SafeMathExt { using SafeMathUpgradeable for uint256; using SignedSafeMathUpgradeable for int256; /* * @dev Always half up for uint256 */ function wmul(uint256 x, uint256 y) internal pure returns (uint256 z) { z = x.mul(y).add(Constant.UNSIGNED_ONE / 2) / Constant.UNSIGNED_ONE; } /* * @dev Always half up for uint256 */ function wdiv(uint256 x, uint256 y) internal pure returns (uint256 z) { z = x.mul(Constant.UNSIGNED_ONE).add(y / 2).div(y); } /* * @dev Always half up for uint256 */ function wfrac( uint256 x, uint256 y, uint256 z ) internal pure returns (uint256 r) { r = x.mul(y).add(z / 2).div(z); } /* * @dev Always half up if no rounding parameter */ function wmul(int256 x, int256 y) internal pure returns (int256 z) { z = roundHalfUp(x.mul(y), Constant.SIGNED_ONE) / Constant.SIGNED_ONE; } /* * @dev Always half up if no rounding parameter */ function wdiv(int256 x, int256 y) internal pure returns (int256 z) { if (y < 0) { y = neg(y); x = neg(x); } z = roundHalfUp(x.mul(Constant.SIGNED_ONE), y).div(y); } /* * @dev Always half up if no rounding parameter */ function wfrac( int256 x, int256 y, int256 z ) internal pure returns (int256 r) { int256 t = x.mul(y); if (z < 0) { z = neg(z); t = neg(t); } r = roundHalfUp(t, z).div(z); } function wmul( int256 x, int256 y, Round round ) internal pure returns (int256 z) { z = div(x.mul(y), Constant.SIGNED_ONE, round); } function wdiv( int256 x, int256 y, Round round ) internal pure returns (int256 z) { z = div(x.mul(Constant.SIGNED_ONE), y, round); } function wfrac( int256 x, int256 y, int256 z, Round round ) internal pure returns (int256 r) { int256 t = x.mul(y); r = div(t, z, round); } function abs(int256 x) internal pure returns (int256) { return x >= 0 ? x : neg(x); } function neg(int256 a) internal pure returns (int256) { return SignedSafeMathUpgradeable.sub(int256(0), a); } /* * @dev ROUND_HALF_UP rule helper. * You have to call roundHalfUp(x, y) / y to finish the rounding operation. * 0.5 ≈ 1, 0.4 ≈ 0, -0.5 ≈ -1, -0.4 ≈ 0 */ function roundHalfUp(int256 x, int256 y) internal pure returns (int256) { require(y > 0, "roundHalfUp only supports y > 0"); if (x >= 0) { return x.add(y / 2); } return x.sub(y / 2); } /* * @dev Division, rounding ceil or rounding floor */ function div( int256 x, int256 y, Round round ) internal pure returns (int256 divResult) { require(y != 0, "division by zero"); divResult = x.div(y); if (x % y == 0) { return divResult; } bool isSameSign = Utils.hasTheSameSign(x, y); if (round == Round.CEIL && isSameSign) { divResult = divResult.add(1); } if (round == Round.FLOOR && !isSameSign) { divResult = divResult.sub(1); } } function max(int256 a, int256 b) internal pure returns (int256) { return a >= b ? a : b; } function min(int256 a, int256 b) internal pure returns (int256) { return a < b ? a : b; } function max(uint256 a, uint256 b) internal pure returns (uint256) { return a >= b ? a : b; } function min(uint256 a, uint256 b) internal pure returns (uint256) { return a < b ? a : b; } }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.7.4; import "@openzeppelin/contracts/utils/EnumerableSet.sol"; import "@openzeppelin/contracts-upgradeable/utils/EnumerableSetUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/math/SignedSafeMathUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; import "./SafeMathExt.sol"; library Utils { using SafeMathExt for int256; using SafeMathExt for uint256; using SafeMathUpgradeable for uint256; using SignedSafeMathUpgradeable for int256; using EnumerableSet for EnumerableSet.AddressSet; using EnumerableSetUpgradeable for EnumerableSetUpgradeable.AddressSet; using EnumerableSetUpgradeable for EnumerableSetUpgradeable.Bytes32Set; /* * @dev Check if two numbers have the same sign. Zero has the same sign with any number */ function hasTheSameSign(int256 x, int256 y) internal pure returns (bool) { if (x == 0 || y == 0) { return true; } return (x ^ y) >> 255 == 0; } /** * @dev Check if the trader has opened position in the trade. * Example: 2, 1 => true; 2, -1 => false; -2, -3 => true * @param amount The position of the trader after the trade * @param delta The update position amount of the trader after the trade * @return True if the trader has opened position in the trade */ function hasOpenedPosition(int256 amount, int256 delta) internal pure returns (bool) { if (amount == 0) { return false; } return Utils.hasTheSameSign(amount, delta); } /* * @dev Split the delta to two numbers. * Use for splitting the trading amount to the amount to close position and the amount to open position. * Examples: 2, 1 => 0, 1; 2, -1 => -1, 0; 2, -3 => -2, -1 */ function splitAmount(int256 amount, int256 delta) internal pure returns (int256, int256) { if (Utils.hasTheSameSign(amount, delta)) { return (0, delta); } else if (amount.abs() >= delta.abs()) { return (delta, 0); } else { return (amount.neg(), amount.add(delta)); } } /* * @dev Check if amount will be away from zero or cross zero if added the delta. * Use for checking if trading amount will make trader open position. * Example: 2, 1 => true; 2, -1 => false; 2, -3 => true */ function isOpen(int256 amount, int256 delta) internal pure returns (bool) { return Utils.hasTheSameSign(amount, delta) || amount.abs() < delta.abs(); } /* * @dev Get the id of the current chain */ function chainID() internal pure returns (uint256 id) { assembly { id := chainid() } } // function toArray( // EnumerableSet.AddressSet storage set, // uint256 begin, // uint256 end // ) internal view returns (address[] memory result) { // require(end > begin, "begin should be lower than end"); // uint256 length = set.length(); // if (begin >= length) { // return result; // } // uint256 safeEnd = end.min(length); // result = new address[](safeEnd.sub(begin)); // for (uint256 i = begin; i < safeEnd; i++) { // result[i.sub(begin)] = set.at(i); // } // return result; // } function toArray( EnumerableSetUpgradeable.AddressSet storage set, uint256 begin, uint256 end ) internal view returns (address[] memory result) { require(end > begin, "begin should be lower than end"); uint256 length = set.length(); if (begin >= length) { return result; } uint256 safeEnd = end.min(length); result = new address[](safeEnd.sub(begin)); for (uint256 i = begin; i < safeEnd; i++) { result[i.sub(begin)] = set.at(i); } return result; } function toArray( EnumerableSetUpgradeable.Bytes32Set storage set, uint256 begin, uint256 end ) internal view returns (bytes32[] memory result) { require(end > begin, "begin should be lower than end"); uint256 length = set.length(); if (begin >= length) { return result; } uint256 safeEnd = end.min(length); result = new bytes32[](safeEnd.sub(begin)); for (uint256 i = begin; i < safeEnd; i++) { result[i.sub(begin)] = set.at(i); } return result; } }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.7.4; import "@openzeppelin/contracts-upgradeable/math/SignedSafeMathUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/SafeCastUpgradeable.sol"; import "../libraries/SafeMathExt.sol"; import "../libraries/Utils.sol"; import "../libraries/OrderData.sol"; import "../Type.sol"; library MarginAccountModule { using SafeMathExt for int256; using SafeCastUpgradeable for uint256; using SignedSafeMathUpgradeable for int256; using OrderData for uint32; /** * @dev Get the initial margin of the trader in the perpetual. * Initial margin = price * abs(position) * initial margin rate * @param perpetual The perpetual object * @param trader The address of the trader * @param price The price to calculate the initial margin * @return initialMargin The initial margin of the trader in the perpetual */ function getInitialMargin( PerpetualStorage storage perpetual, address trader, int256 price ) internal view returns (int256 initialMargin) { initialMargin = perpetual .marginAccounts[trader] .position .wmul(price) .wmul(perpetual.initialMarginRate) .abs(); } /** * @dev Get the maintenance margin of the trader in the perpetual. * Maintenance margin = price * abs(position) * maintenance margin rate * @param perpetual The perpetual object * @param trader The address of the trader * @param price The price to calculate the maintenance margin * @return maintenanceMargin The maintenance margin of the trader in the perpetual */ function getMaintenanceMargin( PerpetualStorage storage perpetual, address trader, int256 price ) internal view returns (int256 maintenanceMargin) { maintenanceMargin = perpetual .marginAccounts[trader] .position .wmul(price) .wmul(perpetual.maintenanceMarginRate) .abs(); } /** * @dev Get the available cash of the trader in the perpetual. * Available cash = cash - position * unit accumulative funding * @param perpetual The perpetual object * @param trader The address of the trader * @return availableCash The available cash of the trader in the perpetual */ function getAvailableCash(PerpetualStorage storage perpetual, address trader) internal view returns (int256 availableCash) { MarginAccount storage account = perpetual.marginAccounts[trader]; availableCash = account.cash.sub(account.position.wmul(perpetual.unitAccumulativeFunding)); } /** * @dev Get the position of the trader in the perpetual * @param perpetual The perpetual object * @param trader The address of the trader * @return position The position of the trader in the perpetual */ function getPosition(PerpetualStorage storage perpetual, address trader) internal view returns (int256 position) { position = perpetual.marginAccounts[trader].position; } /** * @dev Get the margin of the trader in the perpetual. * Margin = available cash + position * price * @param perpetual The perpetual object * @param trader The address of the trader * @param price The price to calculate the margin * @return margin The margin of the trader in the perpetual */ function getMargin( PerpetualStorage storage perpetual, address trader, int256 price ) internal view returns (int256 margin) { margin = perpetual.marginAccounts[trader].position.wmul(price).add( getAvailableCash(perpetual, trader) ); } /** * @dev Get the settleable margin of the trader in the perpetual. * This is the margin trader can withdraw when the state of the perpetual is "CLEARED". * If the state of the perpetual is not "CLEARED", the settleable margin is always zero * @param perpetual The perpetual object * @param trader The address of the trader * @param price The price to calculate the settleable margin * @return margin The settleable margin of the trader in the perpetual */ function getSettleableMargin( PerpetualStorage storage perpetual, address trader, int256 price ) internal view returns (int256 margin) { margin = getMargin(perpetual, trader, price); if (margin > 0) { int256 rate = (getPosition(perpetual, trader) == 0) ? perpetual.redemptionRateWithoutPosition : perpetual.redemptionRateWithPosition; // make sure total redemption margin < total collateral of perpetual margin = margin.wmul(rate, Round.FLOOR); } else { margin = 0; } } /** * @dev Get the available margin of the trader in the perpetual. * Available margin = margin - (initial margin + keeper gas reward), keeper gas reward = 0 if position = 0 * @param perpetual The perpetual object * @param trader The address of the trader * @param price The price to calculate available margin * @return availableMargin The available margin of the trader in the perpetual */ function getAvailableMargin( PerpetualStorage storage perpetual, address trader, int256 price ) internal view returns (int256 availableMargin) { int256 threshold = getPosition(perpetual, trader) == 0 ? 0 : getInitialMargin(perpetual, trader, price).add(perpetual.keeperGasReward); availableMargin = getMargin(perpetual, trader, price).sub(threshold); } /** * @dev Check if the trader is initial margin safe in the perpetual, which means available margin >= 0 * @param perpetual The perpetual object * @param trader The address of the trader * @param price The price to calculate the available margin * @return isSafe True if the trader is initial margin safe in the perpetual */ function isInitialMarginSafe( PerpetualStorage storage perpetual, address trader, int256 price ) internal view returns (bool isSafe) { isSafe = (getAvailableMargin(perpetual, trader, price) >= 0); } /** * @dev Check if the trader is maintenance margin safe in the perpetual, which means * margin >= maintenance margin + keeper gas reward. Keeper gas reward = 0 if position = 0 * @param perpetual The perpetual object * @param trader The address of the trader * @param price The price to calculate the maintenance margin * @return isSafe True if the trader is maintenance margin safe in the perpetual */ function isMaintenanceMarginSafe( PerpetualStorage storage perpetual, address trader, int256 price ) internal view returns (bool isSafe) { int256 threshold = getPosition(perpetual, trader) == 0 ? 0 : getMaintenanceMargin(perpetual, trader, price).add(perpetual.keeperGasReward); isSafe = getMargin(perpetual, trader, price) >= threshold; } /** * @dev Check if the trader is margin safe in the perpetual, which means margin >= keeper gas reward. * Keeper gas reward = 0 if position = 0 * @param perpetual The perpetual object * @param trader The address of the trader * @param price The price to calculate the margin * @return isSafe True if the trader is margin safe in the perpetual */ function isMarginSafe( PerpetualStorage storage perpetual, address trader, int256 price ) internal view returns (bool isSafe) { int256 threshold = getPosition(perpetual, trader) == 0 ? 0 : perpetual.keeperGasReward; isSafe = getMargin(perpetual, trader, price) >= threshold; } /** * @dev Check if the account of the trader is empty in the perpetual, which means cash = 0 and position = 0 * @param perpetual The perpetual object * @param trader The address of the trader * @return isEmpty True if the account of the trader is empty in the perpetual */ function isEmptyAccount(PerpetualStorage storage perpetual, address trader) internal view returns (bool isEmpty) { MarginAccount storage account = perpetual.marginAccounts[trader]; isEmpty = (account.cash == 0 && account.position == 0); } /** * @dev Update the trader's cash in the perpetual * @param perpetual The perpetual object * @param trader The address of the trader * @param deltaCash The update cash(collateral) of the trader's account in the perpetual */ function updateCash( PerpetualStorage storage perpetual, address trader, int256 deltaCash ) internal { if (deltaCash == 0) { return; } MarginAccount storage account = perpetual.marginAccounts[trader]; account.cash = account.cash.add(deltaCash); } /** * @dev Update the trader's account in the perpetual * @param perpetual The perpetual object * @param trader The address of the trader * @param deltaPosition The update position of the trader's account in the perpetual * @param deltaCash The update cash(collateral) of the trader's account in the perpetual */ function updateMargin( PerpetualStorage storage perpetual, address trader, int256 deltaPosition, int256 deltaCash ) internal returns (int256 deltaOpenInterest) { MarginAccount storage account = perpetual.marginAccounts[trader]; int256 oldPosition = account.position; account.position = account.position.add(deltaPosition); account.cash = account.cash.add(deltaCash).add( perpetual.unitAccumulativeFunding.wmul(deltaPosition) ); if (oldPosition > 0) { deltaOpenInterest = oldPosition.neg(); } if (account.position > 0) { deltaOpenInterest = deltaOpenInterest.add(account.position); } perpetual.openInterest = perpetual.openInterest.add(deltaOpenInterest); } /** * @dev Reset the trader's account in the perpetual to empty, which means position = 0 and cash = 0 * @param perpetual The perpetual object * @param trader The address of the trader */ function resetAccount(PerpetualStorage storage perpetual, address trader) internal { MarginAccount storage account = perpetual.marginAccounts[trader]; account.cash = 0; account.position = 0; } // deprecated function setTargetLeverage( PerpetualStorage storage perpetual, address trader, int256 targetLeverage ) internal { perpetual.marginAccounts[trader].targetLeverage = targetLeverage; } function getTargetLeverage(PerpetualStorage storage perpetual, address trader) internal view returns (int256) { require(perpetual.initialMarginRate != 0, "initialMarginRate is not set"); int256 maxLeverage = Constant.SIGNED_ONE.wdiv(perpetual.initialMarginRate); int256 targetLeverage = perpetual.marginAccounts[trader].targetLeverage; targetLeverage = targetLeverage == 0 ? perpetual.defaultTargetLeverage.value : targetLeverage; return targetLeverage.min(maxLeverage); } function getTargetLeverageWithFlags( PerpetualStorage storage perpetual, address trader, uint32 flags ) internal view returns (int256 targetLeverage) { require(perpetual.initialMarginRate != 0, "initialMarginRate is not set"); int256 maxLeverage = Constant.SIGNED_ONE.wdiv(perpetual.initialMarginRate); bool _oldUseTargetLeverage = flags.oldUseTargetLeverage(); bool _newUseTargetLeverage = flags.newUseTargetLeverage(); require(!(_oldUseTargetLeverage && _newUseTargetLeverage), "invalid flags"); if (_oldUseTargetLeverage) { targetLeverage = perpetual.marginAccounts[trader].targetLeverage; } else { targetLeverage = flags.getTargetLeverageByFlags(); } targetLeverage = targetLeverage == 0 ? perpetual.defaultTargetLeverage.value : targetLeverage; targetLeverage = targetLeverage.min(maxLeverage); } }
// SPDX-License-Identifier: MIT pragma solidity >=0.6.0 <0.8.0; /** * @dev Wrappers over Solidity's arithmetic operations with added overflow * checks. * * Arithmetic operations in Solidity wrap on overflow. This can easily result * in bugs, because programmers usually assume that an overflow raises an * error, which is the standard behavior in high level programming languages. * `SafeMath` restores this intuition by reverting the transaction when an * operation overflows. * * Using this library instead of the unchecked operations eliminates an entire * class of bugs, so it's recommended to use it always. */ library SafeMathUpgradeable { /** * @dev Returns the addition of two unsigned integers, with an overflow flag. * * _Available since v3.4._ */ function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { uint256 c = a + b; if (c < a) return (false, 0); return (true, c); } /** * @dev Returns the substraction of two unsigned integers, with an overflow flag. * * _Available since v3.4._ */ function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { if (b > a) return (false, 0); return (true, a - b); } /** * @dev Returns the multiplication of two unsigned integers, with an overflow flag. * * _Available since v3.4._ */ function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 if (a == 0) return (true, 0); uint256 c = a * b; if (c / a != b) return (false, 0); return (true, c); } /** * @dev Returns the division of two unsigned integers, with a division by zero flag. * * _Available since v3.4._ */ function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { if (b == 0) return (false, 0); return (true, a / b); } /** * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. * * _Available since v3.4._ */ function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { if (b == 0) return (false, 0); return (true, a % b); } /** * @dev Returns the addition of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `+` operator. * * Requirements: * * - Addition cannot overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a, "SafeMath: addition overflow"); return c; } /** * @dev Returns the subtraction of two unsigned integers, reverting on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { require(b <= a, "SafeMath: subtraction overflow"); return a - b; } /** * @dev Returns the multiplication of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `*` operator. * * Requirements: * * - Multiplication cannot overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { if (a == 0) return 0; uint256 c = a * b; require(c / a == b, "SafeMath: multiplication overflow"); return c; } /** * @dev Returns the integer division of two unsigned integers, reverting on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. Note: this function uses a * `revert` opcode (which leaves remaining gas untouched) while Solidity * uses an invalid opcode to revert (consuming all remaining gas). * * Requirements: * * - The divisor cannot be zero. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { require(b > 0, "SafeMath: division by zero"); return a / b; } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * reverting when dividing by zero. * * Counterpart to Solidity's `%` operator. This function uses a `revert` * opcode (which leaves remaining gas untouched) while Solidity uses an * invalid opcode to revert (consuming all remaining gas). * * Requirements: * * - The divisor cannot be zero. */ function mod(uint256 a, uint256 b) internal pure returns (uint256) { require(b > 0, "SafeMath: modulo by zero"); return a % b; } /** * @dev Returns the subtraction of two unsigned integers, reverting with custom message on * overflow (when the result is negative). * * CAUTION: This function is deprecated because it requires allocating memory for the error * message unnecessarily. For custom revert reasons use {trySub}. * * Counterpart to Solidity's `-` operator. * * Requirements: * * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b <= a, errorMessage); return a - b; } /** * @dev Returns the integer division of two unsigned integers, reverting with custom message on * division by zero. The result is rounded towards zero. * * CAUTION: This function is deprecated because it requires allocating memory for the error * message unnecessarily. For custom revert reasons use {tryDiv}. * * Counterpart to Solidity's `/` operator. Note: this function uses a * `revert` opcode (which leaves remaining gas untouched) while Solidity * uses an invalid opcode to revert (consuming all remaining gas). * * Requirements: * * - The divisor cannot be zero. */ function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b > 0, errorMessage); return a / b; } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * reverting with custom message when dividing by zero. * * CAUTION: This function is deprecated because it requires allocating memory for the error * message unnecessarily. For custom revert reasons use {tryMod}. * * Counterpart to Solidity's `%` operator. This function uses a `revert` * opcode (which leaves remaining gas untouched) while Solidity uses an * invalid opcode to revert (consuming all remaining gas). * * Requirements: * * - The divisor cannot be zero. */ function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b > 0, errorMessage); return a % b; } }
// SPDX-License-Identifier: MIT pragma solidity >=0.6.0 <0.8.0; /** * @dev Library for managing * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive * types. * * Sets have the following properties: * * - Elements are added, removed, and checked for existence in constant time * (O(1)). * - Elements are enumerated in O(n). No guarantees are made on the ordering. * * ``` * contract Example { * // Add the library methods * using EnumerableSet for EnumerableSet.AddressSet; * * // Declare a set state variable * EnumerableSet.AddressSet private mySet; * } * ``` * * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`) * and `uint256` (`UintSet`) are supported. */ library EnumerableSet { // To implement this library for multiple types with as little code // repetition as possible, we write it in terms of a generic Set type with // bytes32 values. // The Set implementation uses private functions, and user-facing // implementations (such as AddressSet) are just wrappers around the // underlying Set. // This means that we can only create new EnumerableSets for types that fit // in bytes32. struct Set { // Storage of set values bytes32[] _values; // Position of the value in the `values` array, plus 1 because index 0 // means a value is not in the set. mapping (bytes32 => uint256) _indexes; } /** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function _add(Set storage set, bytes32 value) private returns (bool) { if (!_contains(set, value)) { set._values.push(value); // The value is stored at length-1, but we add 1 to all indexes // and use 0 as a sentinel value set._indexes[value] = set._values.length; return true; } else { return false; } } /** * @dev Removes a value from a set. O(1). * * Returns true if the value was removed from the set, that is if it was * present. */ function _remove(Set storage set, bytes32 value) private returns (bool) { // We read and store the value's index to prevent multiple reads from the same storage slot uint256 valueIndex = set._indexes[value]; if (valueIndex != 0) { // Equivalent to contains(set, value) // To delete an element from the _values array in O(1), we swap the element to delete with the last one in // the array, and then remove the last element (sometimes called as 'swap and pop'). // This modifies the order of the array, as noted in {at}. uint256 toDeleteIndex = valueIndex - 1; uint256 lastIndex = set._values.length - 1; // When the value to delete is the last one, the swap operation is unnecessary. However, since this occurs // so rarely, we still do the swap anyway to avoid the gas cost of adding an 'if' statement. bytes32 lastvalue = set._values[lastIndex]; // Move the last value to the index where the value to delete is set._values[toDeleteIndex] = lastvalue; // Update the index for the moved value set._indexes[lastvalue] = toDeleteIndex + 1; // All indexes are 1-based // Delete the slot where the moved value was stored set._values.pop(); // Delete the index for the deleted slot delete set._indexes[value]; return true; } else { return false; } } /** * @dev Returns true if the value is in the set. O(1). */ function _contains(Set storage set, bytes32 value) private view returns (bool) { return set._indexes[value] != 0; } /** * @dev Returns the number of values on the set. O(1). */ function _length(Set storage set) private view returns (uint256) { return set._values.length; } /** * @dev Returns the value stored at position `index` in the set. O(1). * * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function _at(Set storage set, uint256 index) private view returns (bytes32) { require(set._values.length > index, "EnumerableSet: index out of bounds"); return set._values[index]; } // Bytes32Set struct Bytes32Set { Set _inner; } /** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function add(Bytes32Set storage set, bytes32 value) internal returns (bool) { return _add(set._inner, value); } /** * @dev Removes a value from a set. O(1). * * Returns true if the value was removed from the set, that is if it was * present. */ function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) { return _remove(set._inner, value); } /** * @dev Returns true if the value is in the set. O(1). */ function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) { return _contains(set._inner, value); } /** * @dev Returns the number of values in the set. O(1). */ function length(Bytes32Set storage set) internal view returns (uint256) { return _length(set._inner); } /** * @dev Returns the value stored at position `index` in the set. O(1). * * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) { return _at(set._inner, index); } // AddressSet struct AddressSet { Set _inner; } /** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function add(AddressSet storage set, address value) internal returns (bool) { return _add(set._inner, bytes32(uint256(uint160(value)))); } /** * @dev Removes a value from a set. O(1). * * Returns true if the value was removed from the set, that is if it was * present. */ function remove(AddressSet storage set, address value) internal returns (bool) { return _remove(set._inner, bytes32(uint256(uint160(value)))); } /** * @dev Returns true if the value is in the set. O(1). */ function contains(AddressSet storage set, address value) internal view returns (bool) { return _contains(set._inner, bytes32(uint256(uint160(value)))); } /** * @dev Returns the number of values in the set. O(1). */ function length(AddressSet storage set) internal view returns (uint256) { return _length(set._inner); } /** * @dev Returns the value stored at position `index` in the set. O(1). * * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function at(AddressSet storage set, uint256 index) internal view returns (address) { return address(uint160(uint256(_at(set._inner, index)))); } // UintSet struct UintSet { Set _inner; } /** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function add(UintSet storage set, uint256 value) internal returns (bool) { return _add(set._inner, bytes32(value)); } /** * @dev Removes a value from a set. O(1). * * Returns true if the value was removed from the set, that is if it was * present. */ function remove(UintSet storage set, uint256 value) internal returns (bool) { return _remove(set._inner, bytes32(value)); } /** * @dev Returns true if the value is in the set. O(1). */ function contains(UintSet storage set, uint256 value) internal view returns (bool) { return _contains(set._inner, bytes32(value)); } /** * @dev Returns the number of values on the set. O(1). */ function length(UintSet storage set) internal view returns (uint256) { return _length(set._inner); } /** * @dev Returns the value stored at position `index` in the set. O(1). * * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function at(UintSet storage set, uint256 index) internal view returns (uint256) { return uint256(_at(set._inner, index)); } }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.7.4; pragma experimental ABIEncoderV2; import "../libraries/Utils.sol"; import "../Type.sol"; library OrderData { uint32 internal constant MASK_CLOSE_ONLY = 0x80000000; uint32 internal constant MASK_MARKET_ORDER = 0x40000000; uint32 internal constant MASK_STOP_LOSS_ORDER = 0x20000000; uint32 internal constant MASK_TAKE_PROFIT_ORDER = 0x10000000; uint32 internal constant MASK_USE_TARGET_LEVERAGE = 0x08000000; // old domain, will be removed in future string internal constant DOMAIN_NAME = "Mai Protocol v3"; bytes32 internal constant EIP712_DOMAIN_TYPEHASH = keccak256(abi.encodePacked("EIP712Domain(string name)")); bytes32 internal constant DOMAIN_SEPARATOR = keccak256(abi.encodePacked(EIP712_DOMAIN_TYPEHASH, keccak256(bytes(DOMAIN_NAME)))); bytes32 internal constant EIP712_ORDER_TYPE = keccak256( abi.encodePacked( "Order(address trader,address broker,address relayer,address referrer,address liquidityPool,", "int256 minTradeAmount,int256 amount,int256 limitPrice,int256 triggerPrice,uint256 chainID,", "uint64 expiredAt,uint32 perpetualIndex,uint32 brokerFeeLimit,uint32 flags,uint32 salt)" ) ); /* * @dev Check if the order is close-only order. Close-only order means the order can only close position * of the trader * @param order The order object * @return bool True if the order is close-only order */ function isCloseOnly(Order memory order) internal pure returns (bool) { return (order.flags & MASK_CLOSE_ONLY) > 0; } /* * @dev Check if the order is market order. Market order means the order which has no limit price, should be * executed immediately * @param order The order object * @return bool True if the order is market order */ function isMarketOrder(Order memory order) internal pure returns (bool) { return (order.flags & MASK_MARKET_ORDER) > 0; } /* * @dev Check if the order is stop-loss order. Stop-loss order means the order will trigger when the * price is worst than the trigger price * @param order The order object * @return bool True if the order is stop-loss order */ function isStopLossOrder(Order memory order) internal pure returns (bool) { return (order.flags & MASK_STOP_LOSS_ORDER) > 0; } /* * @dev Check if the order is take-profit order. Take-profit order means the order will trigger when * the price is better than the trigger price * @param order The order object * @return bool True if the order is take-profit order */ function isTakeProfitOrder(Order memory order) internal pure returns (bool) { return (order.flags & MASK_TAKE_PROFIT_ORDER) > 0; } /* * @dev Check if the flags contain close-only flag * @param flags The flags * @return bool True if the flags contain close-only flag */ function isCloseOnly(uint32 flags) internal pure returns (bool) { return (flags & MASK_CLOSE_ONLY) > 0; } /* * @dev Check if the flags contain market flag * @param flags The flags * @return bool True if the flags contain market flag */ function isMarketOrder(uint32 flags) internal pure returns (bool) { return (flags & MASK_MARKET_ORDER) > 0; } /* * @dev Check if the flags contain stop-loss flag * @param flags The flags * @return bool True if the flags contain stop-loss flag */ function isStopLossOrder(uint32 flags) internal pure returns (bool) { return (flags & MASK_STOP_LOSS_ORDER) > 0; } /* * @dev Check if the flags contain take-profit flag * @param flags The flags * @return bool True if the flags contain take-profit flag */ function isTakeProfitOrder(uint32 flags) internal pure returns (bool) { return (flags & MASK_TAKE_PROFIT_ORDER) > 0; } function oldUseTargetLeverage(uint32 flags) internal pure returns (bool) { return (flags & MASK_USE_TARGET_LEVERAGE) > 0; } function newUseTargetLeverage(uint32 flags) internal pure returns (bool) { return getTargetLeverageByFlags(flags) > 0; } function getTargetLeverageByFlags(uint32 flags) internal pure returns (int256) { return int256((flags >> 7) & 0xfffff) * 10**16; } function useTargetLeverage(uint32 flags) internal pure returns (bool) { bool _oldUseTargetLeverage = oldUseTargetLeverage(flags); bool _newUseTargetLeverage = newUseTargetLeverage(flags); require(!(_oldUseTargetLeverage && _newUseTargetLeverage), "invalid flags"); return _oldUseTargetLeverage || _newUseTargetLeverage; } /* * @dev Get the hash of the order * @param order The order object * @return bytes32 The hash of the order */ function getOrderHash(Order memory order) internal pure returns (bytes32) { bytes32 result = keccak256(abi.encode(EIP712_ORDER_TYPE, order)); return keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, result)); } /* * @dev Decode the signature from the data * @param data The data object to decode * @return signature The signature */ function decodeSignature(bytes memory data) internal pure returns (bytes memory signature) { require(data.length >= 350, "broken data"); bytes32 r; bytes32 s; uint8 v; uint8 signType; assembly { r := mload(add(data, 318)) s := mload(add(data, 350)) v := byte(24, mload(add(data, 292))) signType := byte(25, mload(add(data, 292))) } signature = abi.encodePacked(r, s, v, signType); } /* * @dev Decode the order from the data * @param data The data object to decode * @return order The order */ function decodeOrderData(bytes memory data) internal pure returns (Order memory order) { require(data.length >= 256, "broken data"); bytes32 tmp; assembly { // trader / 20 mstore(add(order, 0), mload(add(data, 20))) // broker / 20 mstore(add(order, 32), mload(add(data, 40))) // relayer / 20 mstore(add(order, 64), mload(add(data, 60))) // referrer / 20 mstore(add(order, 96), mload(add(data, 80))) // liquidityPool / 20 mstore(add(order, 128), mload(add(data, 100))) // minTradeAmount / 32 mstore(add(order, 160), mload(add(data, 132))) // amount / 32 mstore(add(order, 192), mload(add(data, 164))) // limitPrice / 32 mstore(add(order, 224), mload(add(data, 196))) // triggerPrice / 32 mstore(add(order, 256), mload(add(data, 228))) // chainID / 32 mstore(add(order, 288), mload(add(data, 260))) // expiredAt + perpetualIndex + brokerFeeLimit + flags + salt + v + signType / 26 tmp := mload(add(data, 292)) } order.expiredAt = uint64(bytes8(tmp)); order.perpetualIndex = uint32(bytes4(tmp << 64)); order.brokerFeeLimit = uint32(bytes4(tmp << 96)); order.flags = uint32(bytes4(tmp << 128)); order.salt = uint32(bytes4(tmp << 160)); } }
// SPDX-License-Identifier: MIT pragma solidity >=0.6.2 <0.8.0; /** * @dev Collection of functions related to the address type */ library AddressUpgradeable { /** * @dev Returns true if `account` is a contract. * * [IMPORTANT] * ==== * It is unsafe to assume that an address for which this function returns * false is an externally-owned account (EOA) and not a contract. * * Among others, `isContract` will return false for the following * types of addresses: * * - an externally-owned account * - a contract in construction * - an address where a contract will be created * - an address where a contract lived, but was destroyed * ==== */ function isContract(address account) internal view returns (bool) { // This method relies on extcodesize, which returns 0 for contracts in // construction, since the code is only stored at the end of the // constructor execution. uint256 size; // solhint-disable-next-line no-inline-assembly assembly { size := extcodesize(account) } return size > 0; } /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost * of certain opcodes, possibly making contracts go over the 2300 gas limit * imposed by `transfer`, making them unable to receive funds via * `transfer`. {sendValue} removes this limitation. * * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. * * IMPORTANT: because control is transferred to `recipient`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ function sendValue(address payable recipient, uint256 amount) internal { require(address(this).balance >= amount, "Address: insufficient balance"); // solhint-disable-next-line avoid-low-level-calls, avoid-call-value (bool success, ) = recipient.call{ value: amount }(""); require(success, "Address: unable to send value, recipient may have reverted"); } /** * @dev Performs a Solidity function call using a low level `call`. A * plain`call` is an unsafe replacement for a function call: use this * function instead. * * If `target` reverts with a revert reason, it is bubbled up by this * function (like regular Solidity function calls). * * Returns the raw returned data. To convert to the expected return value, * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. * * Requirements: * * - `target` must be a contract. * - calling `target` with `data` must not revert. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCall(target, data, "Address: low-level call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with * `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but also transferring `value` wei to `target`. * * Requirements: * * - the calling contract must have an ETH balance of at least `value`. * - the called Solidity function must be `payable`. * * _Available since v3.1._ */ function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); } /** * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but * with `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) { require(address(this).balance >= value, "Address: insufficient balance for call"); require(isContract(target), "Address: call to non-contract"); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = target.call{ value: value }(data); return _verifyCallResult(success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { return functionStaticCall(target, data, "Address: low-level static call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) { require(isContract(target), "Address: static call to non-contract"); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = target.staticcall(data); return _verifyCallResult(success, returndata, errorMessage); } function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) { if (success) { return returndata; } else { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly // solhint-disable-next-line no-inline-assembly assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } } }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.7.4; interface IOracle { /** * @dev The market is closed if the market is not in its regular trading period. */ function isMarketClosed() external returns (bool); /** * @dev The oracle service was shutdown and never online again. */ function isTerminated() external returns (bool); /** * @dev Get collateral symbol. */ function collateral() external view returns (string memory); /** * @dev Get underlying asset symbol. */ function underlyingAsset() external view returns (string memory); /** * @dev Mark price. */ function priceTWAPLong() external returns (int256 newPrice, uint256 newTimestamp); /** * @dev Index price. */ function priceTWAPShort() external returns (int256 newPrice, uint256 newTimestamp); }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.7.4; interface IAccessControl { function grantPrivilege(address trader, uint256 privilege) external; function revokePrivilege(address trader, uint256 privilege) external; function isGranted( address owner, address trader, uint256 privilege ) external view returns (bool); }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.7.4; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IGovernor { function initialize( string memory name, string memory symbol, address minter, address target_, address rewardToken, address poolCreator ) external; function totalSupply() external view returns (uint256); function getTarget() external view returns (address); function mint(address account, uint256 amount) external; function burn(address account, uint256 amount) external; function balanceOf(address account) external view returns (uint256); }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.7.4; import "./IAccessControl.sol"; import "./IPoolCreator.sol"; import "./ITracer.sol"; import "./IVersionControl.sol"; import "./IVariables.sol"; import "./IKeeperWhitelist.sol"; interface IPoolCreatorFull is IPoolCreator, IVersionControl, ITracer, IVariables, IAccessControl, IKeeperWhitelist { /** * @notice Owner of version control. */ function owner() external view override(IVersionControl, IVariables) returns (address); }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.7.4; interface ISymbolService { function isWhitelistedFactory(address factory) external view returns (bool); function addWhitelistedFactory(address factory) external; function removeWhitelistedFactory(address factory) external; function getPerpetualUID(uint256 symbol) external view returns (address liquidityPool, uint256 perpetualIndex); function getSymbols(address liquidityPool, uint256 perpetualIndex) external view returns (uint256[] memory symbols); function allocateSymbol(address liquidityPool, uint256 perpetualIndex) external; function assignReservedSymbol( address liquidityPool, uint256 perpetualIndex, uint256 symbol ) external; }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.7.4; pragma experimental ABIEncoderV2; import "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/math/SignedSafeMathUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/SafeERC20Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/SafeCastUpgradeable.sol"; import "../interface/IDecimals.sol"; import "../libraries/Constant.sol"; import "../Type.sol"; /** * @title Collateral Module * @dev Handle underlying collaterals. * In this file, parameter named with: * - [amount] means internal amount * - [rawAmount] means amount in decimals of underlying collateral */ library CollateralModule { using SafeMathUpgradeable for uint256; using SafeCastUpgradeable for int256; using SafeCastUpgradeable for uint256; using SignedSafeMathUpgradeable for int256; using SafeERC20Upgradeable for IERC20Upgradeable; uint256 internal constant SYSTEM_DECIMALS = 18; /** * @notice Initialize the collateral of the liquidity pool. Set up address, scaler and decimals of collateral * @param liquidityPool The liquidity pool object * @param collateral The address of the collateral * @param collateralDecimals The decimals of the collateral, must less than SYSTEM_DECIMALS, * must equal to decimals() if the function exists */ function initializeCollateral( LiquidityPoolStorage storage liquidityPool, address collateral, uint256 collateralDecimals ) public { require(collateralDecimals <= SYSTEM_DECIMALS, "collateral decimals is out of range"); try IDecimals(collateral).decimals() returns (uint8 decimals) { require(decimals == collateralDecimals, "decimals not match"); } catch {} uint256 factor = 10**(SYSTEM_DECIMALS.sub(collateralDecimals)); liquidityPool.scaler = (factor == 0 ? 1 : factor); liquidityPool.collateralToken = collateral; liquidityPool.collateralDecimals = collateralDecimals; } /** * @notice Transfer collateral from the account to the liquidity pool. * @param liquidityPool The liquidity pool object * @param account The address of the account * @param amount The amount of erc20 token to transfer. Always use decimals 18. */ function transferFromUser( LiquidityPoolStorage storage liquidityPool, address account, int256 amount ) public { if (amount <= 0) { return; } uint256 rawAmount = _toRawAmountRoundUp(liquidityPool, amount); IERC20Upgradeable collateralToken = IERC20Upgradeable(liquidityPool.collateralToken); uint256 previousBalance = collateralToken.balanceOf(address(this)); collateralToken.safeTransferFrom(account, address(this), rawAmount); uint256 postBalance = collateralToken.balanceOf(address(this)); require(postBalance.sub(previousBalance) == rawAmount, "incorrect transferred in amount"); } /** * @notice Transfer collateral from the liquidity pool to the account. * @param liquidityPool The liquidity pool object * @param account The address of the account * @param amount The amount of collateral to transfer. always use decimals 18. */ function transferToUser( LiquidityPoolStorage storage liquidityPool, address account, int256 amount ) public { if (amount <= 0) { return; } uint256 rawAmount = _toRawAmount(liquidityPool, amount); IERC20Upgradeable collateralToken = IERC20Upgradeable(liquidityPool.collateralToken); uint256 previousBalance = collateralToken.balanceOf(address(this)); collateralToken.safeTransfer(account, rawAmount); uint256 postBalance = collateralToken.balanceOf(address(this)); require(previousBalance.sub(postBalance) == rawAmount, "incorrect transferred out amount"); } function _toRawAmount(LiquidityPoolStorage storage liquidityPool, int256 amount) private view returns (uint256 rawAmount) { rawAmount = amount.toUint256().div(liquidityPool.scaler); } function _toRawAmountRoundUp(LiquidityPoolStorage storage liquidityPool, int256 amount) private view returns (uint256 rawAmount) { rawAmount = amount.toUint256(); rawAmount = rawAmount.div(liquidityPool.scaler).add( rawAmount % liquidityPool.scaler > 0 ? 1 : 0 ); } }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.7.4; import "./IProxyAdmin.sol"; interface IPoolCreator { function upgradeAdmin() external view returns (IProxyAdmin proxyAdmin); /** * @notice Create a liquidity pool with the latest version. * The sender will be the operator of pool. * * @param collateral he collateral address of the liquidity pool. * @param collateralDecimals The collateral's decimals of the liquidity pool. * @param nonce A random nonce to calculate the address of deployed contracts. * @param initData A bytes array contains data to initialize new created liquidity pool. * @return liquidityPool The address of the created liquidity pool. */ function createLiquidityPool( address collateral, uint256 collateralDecimals, int256 nonce, bytes calldata initData ) external returns (address liquidityPool, address governor); /** * @notice Upgrade a liquidity pool and governor pair then call a patch function on the upgraded contract (optional). * This method checks the sender and forwards the request to ProxyAdmin to do upgrading. * * @param targetVersionKey The key of version to be upgrade up. The target version must be compatible with * current version. * @param dataForLiquidityPool The patch calldata for upgraded liquidity pool. * @param dataForGovernor The patch calldata of upgraded governor. */ function upgradeToAndCall( bytes32 targetVersionKey, bytes memory dataForLiquidityPool, bytes memory dataForGovernor ) external; /** * @notice Indicates the universe settle state. * If the flag set to true: * - all the pereptual created by this poolCreator can be settled immediately; * - all the trading method will be unavailable. */ function isUniverseSettled() external view returns (bool); }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.7.4; import "./IProxyAdmin.sol"; interface ITracer { /** * @notice Activate the perpetual for the trader. Active means the trader's account is not empty in * the perpetual. Empty means cash and position are zero. Can only called by a liquidity pool. * * @param trader The address of the trader. * @param perpetualIndex The index of the perpetual in the liquidity pool. * @return True if the activation is successful. */ function activatePerpetualFor(address trader, uint256 perpetualIndex) external returns (bool); /** * @notice Deactivate the perpetual for the trader. Active means the trader's account is not empty in * the perpetual. Empty means cash and position are zero. Can only called by a liquidity pool. * * @param trader The address of the trader. * @param perpetualIndex The index of the perpetual in the liquidity pool. * @return True if the deactivation is successful. */ function deactivatePerpetualFor(address trader, uint256 perpetualIndex) external returns (bool); /** * @notice Liquidity pool must call this method when changing its ownership to the new operator. * Can only be called by a liquidity pool. This method does not affect 'ownership' or privileges * of operator but only make a record for further query. * * @param liquidityPool The address of the liquidity pool. * @param operator The address of the new operator, must be different from the old operator. */ function registerOperatorOfLiquidityPool(address liquidityPool, address operator) external; }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.7.4; import "./IProxyAdmin.sol"; interface IVersionControl { function owner() external view returns (address); function getLatestVersion() external view returns (bytes32 latestVersionKey); /** * @notice Get the details of the version. * * @param versionKey The key of the version to get. * @return liquidityPoolTemplate The address of the liquidity pool template. * @return governorTemplate The address of the governor template. * @return compatibility The compatibility of the specified version. */ function getVersion(bytes32 versionKey) external view returns ( address liquidityPoolTemplate, address governorTemplate, uint256 compatibility ); /** * @notice Get the description of the implementation of liquidity pool. * Description contains creator, create time, compatibility and note * * @param liquidityPool The address of the liquidity pool. * @param governor The address of the governor. * @return appliedVersionKey The version key of given liquidity pool and governor. */ function getAppliedVersionKey(address liquidityPool, address governor) external view returns (bytes32 appliedVersionKey); /** * @notice Check if a key is valid (exists). * * @param versionKey The key of the version to test. * @return isValid Return true if the version of given key is valid. */ function isVersionKeyValid(bytes32 versionKey) external view returns (bool isValid); /** * @notice Check if the implementation of liquidity pool target is compatible with the implementation base. * Being compatible means having larger compatibility. * * @param targetVersionKey The key of the version to be upgraded to. * @param baseVersionKey The key of the version to be upgraded from. * @return isCompatible True if the target version is compatible with the base version. */ function isVersionCompatible(bytes32 targetVersionKey, bytes32 baseVersionKey) external view returns (bool isCompatible); /** * @dev Get a certain number of implementations of liquidity pool within range [begin, end). * * @param begin The index of first element to retrieve. * @param end The end index of element, exclusive. * @return versionKeys An array contains current version keys. */ function listAvailableVersions(uint256 begin, uint256 end) external view returns (bytes32[] memory versionKeys); }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.7.4; import "./IProxyAdmin.sol"; interface IVariables { function owner() external view returns (address); /** * @notice Get the address of the vault * @return address The address of the vault */ function getVault() external view returns (address); /** * @notice Get the vault fee rate * @return int256 The vault fee rate */ function getVaultFeeRate() external view returns (int256); /** * @notice Get the address of the access controller. It's always its own address. * * @return address The address of the access controller. */ function getAccessController() external view returns (address); /** * @notice Get the address of the symbol service. * * @return Address The address of the symbol service. */ function getSymbolService() external view returns (address); /** * @notice Set the vault address. Can only called by owner. * * @param newVault The new value of the vault fee rate */ function setVault(address newVault) external; /** * @notice Get the address of the mcb token. * @dev [ConfirmBeforeDeployment] * * @return Address The address of the mcb token. */ function getMCBToken() external pure returns (address); /** * @notice Set the vault fee rate. Can only called by owner. * * @param newVaultFeeRate The new value of the vault fee rate */ function setVaultFeeRate(int256 newVaultFeeRate) external; }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.7.4; interface IKeeperWhitelist { /** * @notice Add an address to keeper whitelist. */ function addKeeper(address keeper) external; /** * @notice Remove an address from keeper whitelist. */ function removeKeeper(address keeper) external; /** * @notice Check if an address is in keeper whitelist. */ function isKeeper(address keeper) external view returns (bool); }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.7.4; interface IProxyAdmin { function getProxyImplementation(address proxy) external view returns (address); /** * @dev Upgrades `proxy` to `implementation`. See {TransparentUpgradeableProxy-upgradeTo}. * * Requirements: * * - This contract must be the admin of `proxy`. */ function upgrade(address proxy, address implementation) external; /** * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. See * {TransparentUpgradeableProxy-upgradeToAndCall}. * * Requirements: * * - This contract must be the admin of `proxy`. */ function upgradeAndCall( address proxy, address implementation, bytes memory data ) external payable; }
// SPDX-License-Identifier: MIT pragma solidity >=0.6.0 <0.8.0; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20Upgradeable { /** * @dev Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `recipient`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address recipient, uint256 amount) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * 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 * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @dev Moves `amount` tokens from `sender` to `recipient` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); }
// SPDX-License-Identifier: MIT pragma solidity >=0.6.0 <0.8.0; import "./IERC20Upgradeable.sol"; import "../../math/SafeMathUpgradeable.sol"; import "../../utils/AddressUpgradeable.sol"; /** * @title SafeERC20 * @dev Wrappers around ERC20 operations that throw on failure (when the token * 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 * successful. * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. */ library SafeERC20Upgradeable { using SafeMathUpgradeable for uint256; using AddressUpgradeable for address; function safeTransfer(IERC20Upgradeable token, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); } function safeTransferFrom(IERC20Upgradeable token, address from, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); } /** * @dev Deprecated. This function has issues similar to the ones found in * {IERC20-approve}, and its usage is discouraged. * * Whenever possible, use {safeIncreaseAllowance} and * {safeDecreaseAllowance} instead. */ function safeApprove(IERC20Upgradeable token, address spender, uint256 value) internal { // safeApprove should only be called when setting an initial allowance, // or when resetting it to zero. To increase and decrease it, use // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' // solhint-disable-next-line max-line-length require((value == 0) || (token.allowance(address(this), spender) == 0), "SafeERC20: approve from non-zero to non-zero allowance" ); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); } function safeIncreaseAllowance(IERC20Upgradeable token, address spender, uint256 value) internal { uint256 newAllowance = token.allowance(address(this), spender).add(value); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); } function safeDecreaseAllowance(IERC20Upgradeable token, address spender, uint256 value) internal { uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero"); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); } /** * @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). * @param token The token targeted by the call. * @param data The call data (encoded using abi.encode or one of its variants). */ function _callOptionalReturn(IERC20Upgradeable 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're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that // the target address contains contract code and also asserts for success in the low-level call. bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); if (returndata.length > 0) { // Return data is optional // solhint-disable-next-line max-line-length require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); } } }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.7.4; interface IDecimals { function decimals() external view returns (uint8); }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.7.4; pragma experimental ABIEncoderV2; import "../Type.sol"; interface ILiquidityPoolGetter { /** * @notice Get the info of the liquidity pool * @return isRunning True if the liquidity pool is running * @return isFastCreationEnabled True if the operator of the liquidity pool is allowed to create new perpetual * when the liquidity pool is running * @return addresses The related addresses of the liquidity pool * @return intNums Int type properties, see below for details. * @return uintNums Uint type properties, see below for details. */ function getLiquidityPoolInfo() external view returns ( bool isRunning, bool isFastCreationEnabled, // [0] creator, // [1] operator, // [2] transferringOperator, // [3] governor, // [4] shareToken, // [5] collateralToken, // [6] vault, address[7] memory addresses, // [0] vaultFeeRate, // [1] poolCash, // [2] insuranceFundCap, // [3] insuranceFund, // [4] donatedInsuranceFund, int256[5] memory intNums, // [0] collateralDecimals, // [1] perpetualCount, // [2] fundingTime, // [3] operatorExpiration, // [4] liquidityCap, // [5] shareTransferDelay, uint256[6] memory uintNums ); /** * @notice Get the info of the perpetual. Need to update the funding state and the oracle price * of each perpetual before and update the funding rate of each perpetual after * @param perpetualIndex The index of the perpetual in the liquidity pool * @return state The state of the perpetual * @return oracle The oracle's address of the perpetual * @return nums The related numbers of the perpetual */ function getPerpetualInfo(uint256 perpetualIndex) external view returns ( PerpetualState state, address oracle, // [0] totalCollateral // [1] markPrice, (return settlementPrice if it is in EMERGENCY state) // [2] indexPrice, // [3] fundingRate, // [4] unitAccumulativeFunding, // [5] initialMarginRate, // [6] maintenanceMarginRate, // [7] operatorFeeRate, // [8] lpFeeRate, // [9] referralRebateRate, // [10] liquidationPenaltyRate, // [11] keeperGasReward, // [12] insuranceFundRate, // [13-15] halfSpread value, min, max, // [16-18] openSlippageFactor value, min, max, // [19-21] closeSlippageFactor value, min, max, // [22-24] fundingRateLimit value, min, max, // [25-27] ammMaxLeverage value, min, max, // [28-30] maxClosePriceDiscount value, min, max, // [31] openInterest, // [32] maxOpenInterestRate, // [33-35] fundingRateFactor value, min, max, // [36-38] defaultTargetLeverage value, min, max, // [39-41] baseFundingRate value, min, max, int256[42] memory nums ); /** * @notice Get the account info of the trader. Need to update the funding state and the oracle price * of each perpetual before and update the funding rate of each perpetual after * @param perpetualIndex The index of the perpetual in the liquidity pool * @param trader The address of the trader * @return cash The cash(collateral) of the account * @return position The position of the account * @return availableMargin The available margin of the account * @return margin The margin of the account * @return settleableMargin The settleable margin of the account * @return isInitialMarginSafe True if the account is initial margin safe * @return isMaintenanceMarginSafe True if the account is maintenance margin safe * @return isMarginSafe True if the total value of margin account is beyond 0 * @return targetLeverage The target leverage for openning position. */ function getMarginAccount(uint256 perpetualIndex, address trader) external view returns ( int256 cash, int256 position, int256 availableMargin, int256 margin, int256 settleableMargin, bool isInitialMarginSafe, bool isMaintenanceMarginSafe, bool isMarginSafe, // bankrupt int256 targetLeverage ); /** * @notice Get the number of active accounts in the perpetual. * Active means the trader's account is not empty in the perpetual. * Empty means cash and position are zero * @param perpetualIndex The index of the perpetual in the liquidity pool * @return activeAccountCount The number of active accounts in the perpetual */ function getActiveAccountCount(uint256 perpetualIndex) external view returns (uint256); /** * @notice Get the active accounts in the perpetual whose index between begin and end. * Active means the trader's account is not empty in the perpetual. * Empty means cash and position are zero * @param perpetualIndex The index of the perpetual in the liquidity pool * @param begin The begin index * @param end The end index * @return result The active accounts in the perpetual whose index between begin and end */ function listActiveAccounts( uint256 perpetualIndex, uint256 begin, uint256 end ) external view returns (address[] memory result); /** * @notice Get the progress of clearing active accounts. * Return the number of total active accounts and the number of active accounts not cleared * @param perpetualIndex The index of the perpetual in the liquidity pool * @return left The left active accounts * @return total The total active accounts */ function getClearProgress(uint256 perpetualIndex) external view returns (uint256 left, uint256 total); /** * @notice Get the pool margin of the liquidity pool. * Pool margin is how much collateral of the pool considering the AMM's positions of perpetuals * @return poolMargin The pool margin of the liquidity pool */ function getPoolMargin() external view returns (int256 poolMargin, bool isSafe); /** * @notice Query the price, fees and cost when trade agaist amm. * The trading price is determined by the AMM based on the index price of the perpetual. * This method should returns the same result as a 'read-only' trade. * WARN: the result of this function is base on current storage of liquidityPool, not the latest. * To get the latest status, call `syncState` first. * * Flags is a 32 bit uint value which indicates: (from highest bit) * - close only only close position during trading; * - market order do not check limit price during trading; * - stop loss only available in brokerTrade mode; * - take profit only available in brokerTrade mode; * For stop loss and take profit, see `validateTriggerPrice` in OrderModule.sol for details. * * @param perpetualIndex The index of the perpetual in liquidity pool. * @param trader The address of trader. * @param amount The amount of position to trader, positive for buying and negative for selling. The amount always use decimals 18. * @param referrer The address of referrer who will get rebate from the deal. * @param flags The flags of the trade. * @return tradePrice The average fill price. * @return totalFee The total fee collected from the trader after the trade. * @return cost Deposit or withdraw to let effective leverage == targetLeverage if flags contain USE_TARGET_LEVERAGE. > 0 if deposit, < 0 if withdraw. */ function queryTrade( uint256 perpetualIndex, address trader, int256 amount, address referrer, uint32 flags ) external returns ( int256 tradePrice, int256 totalFee, int256 cost ); /** * @notice Query cash to add / share to mint when adding liquidity to the liquidity pool. * Only one of cashToAdd or shareToMint may be non-zero. * * @param cashToAdd The amount of cash to add, always use decimals 18. * @param shareToMint The amount of share token to mint, always use decimals 18. * @return cashToAddResult The amount of cash to add, always use decimals 18. Equal to cashToAdd if cashToAdd is non-zero. * @return shareToMintResult The amount of cash to add, always use decimals 18. Equal to shareToMint if shareToMint is non-zero. */ function queryAddLiquidity(int256 cashToAdd, int256 shareToMint) external view returns (int256 cashToAddResult, int256 shareToMintResult); /** * @notice Query cash to return / share to redeem when removing liquidity from the liquidity pool. * Only one of shareToRemove or cashToReturn may be non-zero. * Can only called when the pool is running. * * @param shareToRemove The amount of share token to redeem, always use decimals 18. * @param cashToReturn The amount of cash to return, always use decimals 18. * @return shareToRemoveResult The amount of share token to redeem, always use decimals 18. Equal to shareToRemove if shareToRemove is non-zero. * @return cashToReturnResult The amount of cash to return, always use decimals 18. Equal to cashToReturn if cashToReturn is non-zero. */ function queryRemoveLiquidity(int256 shareToRemove, int256 cashToReturn) external view returns (int256 shareToRemoveResult, int256 cashToReturnResult); }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.7.4; pragma experimental ABIEncoderV2; import "@openzeppelin/contracts-upgradeable/math/SignedSafeMathUpgradeable.sol"; import "../interface/IOracle.sol"; import "../libraries/OrderData.sol"; import "../libraries/SafeMathExt.sol"; import "../libraries/Utils.sol"; import "./AMMModule.sol"; import "./LiquidityPoolModule.sol"; import "./MarginAccountModule.sol"; import "./PerpetualModule.sol"; import "../Type.sol"; library TradeModule { using SafeMathExt for int256; using SignedSafeMathUpgradeable for int256; using OrderData for uint32; using AMMModule for LiquidityPoolStorage; using LiquidityPoolModule for LiquidityPoolStorage; using MarginAccountModule for PerpetualStorage; using PerpetualModule for PerpetualStorage; using MarginAccountModule for MarginAccount; event Trade( uint256 perpetualIndex, address indexed trader, int256 position, int256 price, int256 fee, int256 lpFee ); event Liquidate( uint256 perpetualIndex, address indexed liquidator, address indexed trader, int256 amount, int256 price, int256 penalty, int256 penaltyToLP ); event TransferFeeToVault( uint256 perpetualIndex, address indexed trader, address indexed vault, int256 vaultFee ); event TransferFeeToOperator( uint256 perpetualIndex, address indexed trader, address indexed operator, int256 operatorFee ); event TransferFeeToReferrer( uint256 perpetualIndex, address indexed trader, address indexed referrer, int256 referralRebate ); /** * @dev See `trade` in Perpetual.sol for details. * * @param liquidityPool The reference of liquidity pool storage. * @param perpetualIndex The index of the perpetual in liquidity pool. * @param trader The address of trader. * @param amount The amount of position to trader, positive for buying and negative for selling. * @param limitPrice The worst price the trader accepts. * @param referrer The address of referrer who will get rebate in the deal. * @param flags The flags of the trade, contains extra config for trading. * @return tradeAmount The amount of positions actually traded in the transaction. */ function trade( LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex, address trader, int256 amount, int256 limitPrice, address referrer, uint32 flags ) public returns (int256 tradeAmount) { (int256 deltaCash, int256 deltaPosition) = preTrade( liquidityPool, perpetualIndex, trader, amount, limitPrice, flags ); doTrade(liquidityPool, perpetualIndex, trader, deltaCash, deltaPosition); (int256 lpFee, int256 totalFee) = postTrade( liquidityPool, perpetualIndex, trader, referrer, deltaCash, deltaPosition, flags ); emit Trade( perpetualIndex, trader, deltaPosition.neg(), deltaCash.wdiv(deltaPosition).abs(), totalFee, lpFee ); tradeAmount = deltaPosition.neg(); require( liquidityPool.isTraderMarginSafe(perpetualIndex, trader, tradeAmount), "trader margin unsafe" ); } function preTrade( LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex, address trader, int256 amount, int256 limitPrice, uint32 flags ) internal returns (int256 deltaCash, int256 deltaPosition) { PerpetualStorage storage perpetual = liquidityPool.perpetuals[perpetualIndex]; require(!IOracle(perpetual.oracle).isMarketClosed(), "market is closed now"); // handle close only flag if (flags.isCloseOnly()) { amount = getMaxPositionToClose(perpetual.getPosition(trader), amount); require(amount != 0, "no amount to close"); } // query price from AMM (deltaCash, deltaPosition) = liquidityPool.queryTradeWithAMM( perpetualIndex, amount.neg(), false ); // check price if (!flags.isMarketOrder()) { int256 tradePrice = deltaCash.wdiv(deltaPosition).abs(); validatePrice(amount >= 0, tradePrice, limitPrice); } } function doTrade( LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex, address trader, int256 deltaCash, int256 deltaPosition ) internal { PerpetualStorage storage perpetual = liquidityPool.perpetuals[perpetualIndex]; int256 deltaOpenInterest1 = perpetual.updateMargin(address(this), deltaPosition, deltaCash); int256 deltaOpenInterest2 = perpetual.updateMargin( trader, deltaPosition.neg(), deltaCash.neg() ); require(perpetual.openInterest >= 0, "negative open interest"); if (deltaOpenInterest1.add(deltaOpenInterest2) > 0) { // open interest will increase, check limit (int256 poolMargin, ) = liquidityPool.getPoolMargin(); require( perpetual.openInterest <= perpetual.maxOpenInterestRate.wfrac(poolMargin, perpetual.getIndexPrice()), "open interest exceeds limit" ); } } /** * @dev Execute the trade. If the trader has opened position in the trade, his account should be * initial margin safe after the trade. If not, his account should be margin safe * * @param liquidityPool The reference of liquidity pool storage. * @param perpetualIndex The index of pereptual storage. * @param trader The address of trader. * @param referrer The address of referrer who will get rebate from the deal. * @param deltaCash The amount of cash changes in a trade. * @param deltaPosition The amount of position changes in a trade. * @return lpFee The amount of fee for lp provider. * @return totalFee The total fee collected from the trader after the trade. */ function postTrade( LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex, address trader, address referrer, int256 deltaCash, int256 deltaPosition, uint32 flags ) internal returns (int256 lpFee, int256 totalFee) { PerpetualStorage storage perpetual = liquidityPool.perpetuals[perpetualIndex]; // fees int256 operatorFee; int256 vaultFee; int256 referralRebate; { bool hasOpened = Utils.hasOpenedPosition( perpetual.getPosition(trader), deltaPosition.neg() ); (lpFee, operatorFee, vaultFee, referralRebate) = getFees( liquidityPool, perpetual, trader, referrer, deltaCash.abs(), hasOpened ); } totalFee = lpFee.add(operatorFee).add(vaultFee).add(referralRebate); perpetual.updateCash(trader, totalFee.neg()); // trader deposit/withdraw if (flags.useTargetLeverage()) { liquidityPool.adjustMarginLeverage( perpetualIndex, trader, deltaPosition.neg(), deltaCash.neg(), totalFee, flags ); } // send fee transferFee( liquidityPool, perpetualIndex, trader, referrer, lpFee, operatorFee, vaultFee, referralRebate ); } /** * @dev Get the fees of the trade. If the margin of the trader is not enough for fee: * 1. If trader open position, the trade will be reverted. * 2. If trader close position, the fee will be decreasing in proportion according to * the margin left in the trader's account * The rebate of referral will only calculate the lpFee and operatorFee. * The vault fee will not be counted in. * * @param liquidityPool The reference of liquidity pool storage. * @param perpetual The reference of pereptual storage. * @param trader The address of trader. * @param referrer The address of referrer who will get rebate from the deal. * @param tradeValue The amount of trading value, measured by collateral, abs of deltaCash. * @return lpFee The amount of fee to the Liquidity provider. * @return operatorFee The amount of fee to the operator. * @return vaultFee The amount of fee to the vault. * @return referralRebate The amount of rebate of the refferral. */ function getFees( LiquidityPoolStorage storage liquidityPool, PerpetualStorage storage perpetual, address trader, address referrer, int256 tradeValue, bool hasOpened ) public view returns ( int256 lpFee, int256 operatorFee, int256 vaultFee, int256 referralRebate ) { require(tradeValue >= 0, "trade value is negative"); vaultFee = tradeValue.wmul(liquidityPool.getVaultFeeRate()); lpFee = tradeValue.wmul(perpetual.lpFeeRate); if (liquidityPool.getOperator() != address(0)) { operatorFee = tradeValue.wmul(perpetual.operatorFeeRate); } int256 totalFee = lpFee.add(operatorFee).add(vaultFee); int256 availableMargin = perpetual.getAvailableMargin(trader, perpetual.getMarkPrice()); if (!hasOpened) { if (availableMargin <= 0) { lpFee = 0; operatorFee = 0; vaultFee = 0; referralRebate = 0; } else if (totalFee > availableMargin) { // make sure the sum of fees < available margin int256 rate = availableMargin.wdiv(totalFee, Round.FLOOR); operatorFee = operatorFee.wmul(rate, Round.FLOOR); vaultFee = vaultFee.wmul(rate, Round.FLOOR); lpFee = availableMargin.sub(operatorFee).sub(vaultFee); } } if ( referrer != address(0) && perpetual.referralRebateRate > 0 && lpFee.add(operatorFee) > 0 ) { int256 lpFeeRebate = lpFee.wmul(perpetual.referralRebateRate); int256 operatorFeeRabate = operatorFee.wmul(perpetual.referralRebateRate); referralRebate = lpFeeRebate.add(operatorFeeRabate); lpFee = lpFee.sub(lpFeeRebate); operatorFee = operatorFee.sub(operatorFeeRabate); } } function transferFee( LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex, address trader, address referrer, int256 lpFee, int256 operatorFee, int256 vaultFee, int256 referralRebate ) internal { PerpetualStorage storage perpetual = liquidityPool.perpetuals[perpetualIndex]; perpetual.updateCash(address(this), lpFee); address vault = liquidityPool.getVault(); liquidityPool.transferFromPerpetualToUser(perpetual.id, vault, vaultFee); emit TransferFeeToVault(perpetual.id, trader, vault, vaultFee); address operator = liquidityPool.getOperator(); liquidityPool.transferFromPerpetualToUser(perpetual.id, operator, operatorFee); emit TransferFeeToOperator(perpetual.id, trader, operator, operatorFee); liquidityPool.transferFromPerpetualToUser(perpetual.id, referrer, referralRebate); emit TransferFeeToReferrer(perpetual.id, trader, referrer, referralRebate); } /** * @dev See `liquidateByAMM` in Perpetual.sol for details. * * @param liquidityPool The reference of liquidity pool storage. * @param perpetualIndex The index of the perpetual in liquidity pool. * @param liquidator The address of the account calling the liquidation method. * @param trader The address of the liquidated account. * @return liquidatedAmount The amount of positions actually liquidated in the transaction. */ function liquidateByAMM( LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex, address liquidator, address trader ) public returns (int256 liquidatedAmount) { PerpetualStorage storage perpetual = liquidityPool.perpetuals[perpetualIndex]; require( !perpetual.isMaintenanceMarginSafe(trader, perpetual.getMarkPrice()), "trader is safe" ); int256 position = perpetual.getPosition(trader); // 0. price / amount (int256 deltaCash, int256 deltaPosition) = liquidityPool.queryTradeWithAMM( perpetualIndex, position, true ); require(deltaPosition != 0, "insufficient liquidity"); // 2. trade int256 liquidatePrice = deltaCash.wdiv(deltaPosition).abs(); perpetual.updateMargin(address(this), deltaPosition, deltaCash); perpetual.updateMargin( trader, deltaPosition.neg(), deltaCash.add(perpetual.keeperGasReward).neg() ); require(perpetual.openInterest >= 0, "negative open interest"); liquidityPool.transferFromPerpetualToUser( perpetual.id, liquidator, perpetual.keeperGasReward ); // 3. penalty min(markPrice * liquidationPenaltyRate, margin / position) * deltaPosition (int256 penalty, int256 penaltyToLiquidator) = postLiquidate( liquidityPool, perpetual, address(this), trader, position, deltaPosition.neg() ); emit Liquidate( perpetualIndex, address(this), trader, deltaPosition.neg(), liquidatePrice, penalty, penaltyToLiquidator ); liquidatedAmount = deltaPosition.neg(); } /** * @dev See `liquidateByTrader` in Perpetual.sol for details. * * @param liquidityPool The reference of liquidity pool storage. * @param perpetualIndex The index of the perpetual in liquidity pool. * @param liquidator The address of the account calling the liquidation method. * @param trader The address of the liquidated account. * @param amount The amount of position to be taken from liquidated trader. * @param limitPrice The worst price liquidator accepts. * @return liquidatedAmount The amount of positions actually liquidated in the transaction. */ function liquidateByTrader( LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex, address liquidator, address trader, int256 amount, int256 limitPrice ) public returns (int256 liquidatedAmount) { PerpetualStorage storage perpetual = liquidityPool.perpetuals[perpetualIndex]; int256 markPrice = perpetual.getMarkPrice(); require(!perpetual.isMaintenanceMarginSafe(trader, markPrice), "trader is safe"); // 0. price / amount validatePrice(amount >= 0, markPrice, limitPrice); int256 position = perpetual.getPosition(trader); int256 deltaPosition = getMaxPositionToClose(position, amount.neg()).neg(); int256 deltaCash = markPrice.wmul(deltaPosition).neg(); // 1. execute perpetual.updateMargin(liquidator, deltaPosition, deltaCash); perpetual.updateMargin(trader, deltaPosition.neg(), deltaCash.neg()); require(perpetual.openInterest >= 0, "negative open interest"); // 2. penalty min(markPrice * liquidationPenaltyRate, margin / position) * deltaPosition (int256 penalty, ) = postLiquidate( liquidityPool, perpetual, liquidator, trader, position, deltaPosition.neg() ); liquidatedAmount = deltaPosition.neg(); require( liquidityPool.isTraderMarginSafe(perpetualIndex, liquidator, liquidatedAmount), "liquidator margin unsafe" ); emit Liquidate(perpetualIndex, liquidator, trader, liquidatedAmount, markPrice, penalty, 0); } /** * @dev Handle liquidate penalty / fee. * * @param liquidityPool The reference of liquidity pool storage. * @param perpetual The reference of perpetual storage. * @param liquidator The address of the account calling the liquidation method. * @param trader The address of the liquidated account. * @param position The amount of position owned by trader before liquidation. * @param deltaPosition The amount of position to be taken from liquidated trader. * @return penalty The amount of positions actually liquidated in the transaction. * @return penaltyToLiquidator The amount of positions actually liquidated in the transaction. */ function postLiquidate( LiquidityPoolStorage storage liquidityPool, PerpetualStorage storage perpetual, address liquidator, address trader, int256 position, int256 deltaPosition ) public returns (int256 penalty, int256 penaltyToLiquidator) { int256 vaultFee = 0; { int256 markPrice = perpetual.getMarkPrice(); int256 remainingMargin = perpetual.getMargin(trader, markPrice); int256 liquidationValue = markPrice.wmul(deltaPosition).abs(); penalty = liquidationValue.wmul(perpetual.liquidationPenaltyRate).min( remainingMargin.wfrac(deltaPosition.abs(), position.abs()) ); remainingMargin = remainingMargin.sub(penalty); if (remainingMargin > 0) { vaultFee = liquidationValue.wmul(liquidityPool.getVaultFeeRate()).min( remainingMargin ); liquidityPool.transferFromPerpetualToUser( perpetual.id, liquidityPool.getVault(), vaultFee ); } } int256 penaltyToFund; bool isEmergency; if (penalty > 0) { penaltyToFund = penalty.wmul(perpetual.insuranceFundRate); penaltyToLiquidator = penalty.sub(penaltyToFund); } else { int256 totalInsuranceFund = liquidityPool.insuranceFund.add( liquidityPool.donatedInsuranceFund ); if (totalInsuranceFund.add(penalty) < 0) { // ensure donatedInsuranceFund >= 0 penalty = totalInsuranceFund.neg(); isEmergency = true; } penaltyToFund = penalty; penaltyToLiquidator = 0; } int256 penaltyToLP = liquidityPool.updateInsuranceFund(penaltyToFund); perpetual.updateCash(address(this), penaltyToLP); perpetual.updateCash(liquidator, penaltyToLiquidator); perpetual.updateCash(trader, penalty.add(vaultFee).neg()); if (penaltyToFund >= 0) { perpetual.decreaseTotalCollateral(penaltyToFund.sub(penaltyToLP)); } else { // penaltyToLP = 0 when penaltyToFund < 0 perpetual.increaseTotalCollateral(penaltyToFund.neg()); } if (isEmergency) { liquidityPool.setEmergencyState(perpetual.id); } } /** * @dev Get the max position amount of trader will be closed in the trade. * @param position Current position of trader. * @param amount The trading amount of position. * @return maxPositionToClose The max position amount of trader will be closed in the trade. */ function getMaxPositionToClose(int256 position, int256 amount) internal pure returns (int256 maxPositionToClose) { require(position != 0, "trader has no position to close"); require(!Utils.hasTheSameSign(position, amount), "trader must be close only"); maxPositionToClose = amount.abs() > position.abs() ? position.neg() : amount; } /** * @dev Check if the price is better than the limit price. * @param isLong True if the side is long. * @param price The price to be validate. * @param priceLimit The limit price. */ function validatePrice( bool isLong, int256 price, int256 priceLimit ) internal pure { require(price > 0, "price must be positive"); bool isPriceSatisfied = isLong ? price <= priceLimit : price >= priceLimit; require(isPriceSatisfied, "price exceeds limit"); } /** * @dev A readonly version of trade * * This function was written post-audit. So there's a lot of repeated logic here. * NOTE: max openInterest is NOT exact the same as trade(). In this function, poolMargin * will be smaller, so that the openInterst limit is also smaller (more strict). * @param liquidityPool The reference of liquidity pool storage. * @param perpetualIndex The index of the perpetual in liquidity pool. * @param trader The address of trader. * @param amount The amount of position to trader, positive for buying and negative for selling. * @param flags The flags of the trade, contains extra config for trading. * @return tradePrice The average fill price. * @return totalFee The total fee collected from the trader after the trade. * @return cost Deposit or withdraw to let effective leverage == targetLeverage if flags contain USE_TARGET_LEVERAGE. > 0 if deposit, < 0 if withdraw. */ function queryTrade( LiquidityPoolStorage storage liquidityPool, uint256 perpetualIndex, address trader, int256 amount, address referrer, uint32 flags ) public returns ( int256 tradePrice, int256 totalFee, int256 cost ) { PerpetualStorage storage perpetual = liquidityPool.perpetuals[perpetualIndex]; MarginAccount memory account = perpetual.marginAccounts[trader]; // clone (int256 deltaCash, int256 deltaPosition) = preTrade( liquidityPool, perpetualIndex, trader, amount, amount > 0 ? type(int256).max : 0, flags ); tradePrice = deltaCash.wdiv(deltaPosition).abs(); readonlyDoTrade(liquidityPool, perpetual, account, deltaCash, deltaPosition); (totalFee, cost) = readonlyPostTrade( liquidityPool, perpetual, account, referrer, deltaCash, deltaPosition, flags ); } // A readonly version of doTrade. This function was written post-audit. So there's a lot of repeated logic here. // NOTE: max openInterest is NOT exact the same as trade(). In this function, poolMargin // will be smaller, so that the openInterst limit is also smaller (more strict). function readonlyDoTrade( LiquidityPoolStorage storage liquidityPool, PerpetualStorage storage perpetual, MarginAccount memory account, int256 deltaCash, int256 deltaPosition ) internal view { int256 deltaOpenInterest1; int256 deltaOpenInterest2; (, , deltaOpenInterest1) = readonlyUpdateMargin( perpetual, perpetual.marginAccounts[address(this)].cash, perpetual.marginAccounts[address(this)].position, deltaPosition, deltaCash ); (account.cash, account.position, deltaOpenInterest2) = readonlyUpdateMargin( perpetual, account.cash, account.position, deltaPosition.neg(), deltaCash.neg() ); int256 perpetualOpenInterest = perpetual.openInterest.add(deltaOpenInterest1).add( deltaOpenInterest2 ); require(perpetualOpenInterest >= 0, "negative open interest"); if (deltaOpenInterest1.add(deltaOpenInterest2) > 0) { // open interest will increase, check limit (int256 poolMargin, ) = liquidityPool.getPoolMargin(); // NOTE: this is a slight different from trade() require( perpetualOpenInterest <= perpetual.maxOpenInterestRate.wfrac(poolMargin, perpetual.getIndexPrice()), "open interest exceeds limit" ); } } // A readonly version of postTrade. This function was written post-audit. So there's a lot of repeated logic here. function readonlyPostTrade( LiquidityPoolStorage storage liquidityPool, PerpetualStorage storage perpetual, MarginAccount memory account, address referrer, int256 deltaCash, int256 deltaPosition, uint32 flags ) internal view returns (int256 totalFee, int256 adjustCollateral) { // fees int256 lpFee; int256 operatorFee; int256 vaultFee; int256 referralRebate; { bool hasOpened = Utils.hasOpenedPosition(account.position, deltaPosition.neg()); (lpFee, operatorFee, vaultFee, referralRebate) = readonlyGetFees( liquidityPool, perpetual, account, referrer, deltaCash.abs(), hasOpened ); } totalFee = lpFee.add(operatorFee).add(vaultFee).add(referralRebate); // was updateCash account.cash = account.cash.add(totalFee.neg()); // trader deposit/withdraw if (flags.useTargetLeverage()) { adjustCollateral = LiquidityPoolModule.readonlyAdjustMarginLeverage( perpetual, account, deltaPosition.neg(), deltaCash.neg(), totalFee, flags ); } account.cash = account.cash.add(adjustCollateral); } // A readonly version of MarginAccountModule.updateMargin. This function was written post-audit. So there's a lot of repeated logic here. function readonlyUpdateMargin( PerpetualStorage storage perpetual, int256 oldCash, int256 oldPosition, int256 deltaPosition, int256 deltaCash ) internal view returns ( int256 newCash, int256 newPosition, int256 deltaOpenInterest ) { newPosition = oldPosition.add(deltaPosition); newCash = oldCash.add(deltaCash).add(perpetual.unitAccumulativeFunding.wmul(deltaPosition)); if (oldPosition > 0) { deltaOpenInterest = oldPosition.neg(); } if (newPosition > 0) { deltaOpenInterest = deltaOpenInterest.add(newPosition); } } // A readonly version of getFees. This function was written post-audit. So there's a lot of repeated logic here. function readonlyGetFees( LiquidityPoolStorage storage liquidityPool, PerpetualStorage storage perpetual, MarginAccount memory trader, address referrer, int256 tradeValue, bool hasOpened ) public view returns ( int256 lpFee, int256 operatorFee, int256 vaultFee, int256 referralRebate ) { require(tradeValue >= 0, "trade value is negative"); vaultFee = tradeValue.wmul(liquidityPool.getVaultFeeRate()); lpFee = tradeValue.wmul(perpetual.lpFeeRate); if (liquidityPool.getOperator() != address(0)) { operatorFee = tradeValue.wmul(perpetual.operatorFeeRate); } int256 totalFee = lpFee.add(operatorFee).add(vaultFee); int256 availableMargin = LiquidityPoolModule.readonlyGetAvailableMargin( perpetual, trader, perpetual.getMarkPrice() ); if (!hasOpened) { if (availableMargin <= 0) { lpFee = 0; operatorFee = 0; vaultFee = 0; referralRebate = 0; } else if (totalFee > availableMargin) { // make sure the sum of fees < available margin int256 rate = availableMargin.wdiv(totalFee, Round.FLOOR); operatorFee = operatorFee.wmul(rate, Round.FLOOR); vaultFee = vaultFee.wmul(rate, Round.FLOOR); lpFee = availableMargin.sub(operatorFee).sub(vaultFee); } } if ( referrer != address(0) && perpetual.referralRebateRate > 0 && lpFee.add(operatorFee) > 0 ) { int256 lpFeeRebate = lpFee.wmul(perpetual.referralRebateRate); int256 operatorFeeRabate = operatorFee.wmul(perpetual.referralRebateRate); referralRebate = lpFeeRebate.add(operatorFeeRabate); lpFee = lpFee.sub(lpFeeRebate); operatorFee = operatorFee.sub(operatorFeeRabate); } } }
// SPDX-License-Identifier: MIT pragma solidity >=0.6.0 <0.8.0; import "../utils/ContextUpgradeable.sol";
// SPDX-License-Identifier: MIT pragma solidity >=0.6.0 <0.8.0; import "../proxy/Initializable.sol"; /* * @dev Provides information about the current execution context, including the * sender of the transaction and its data. While these are generally available * via msg.sender and msg.data, they should not be accessed in such a direct * manner, since when dealing with GSN meta-transactions the account sending and * paying for execution may not be the actual sender (as far as an application * is concerned). * * This contract is only required for intermediate, library-like contracts. */ abstract contract ContextUpgradeable is Initializable { function __Context_init() internal initializer { __Context_init_unchained(); } function __Context_init_unchained() internal initializer { } function _msgSender() internal view virtual returns (address payable) { return msg.sender; } function _msgData() internal view virtual returns (bytes memory) { this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 return msg.data; } uint256[50] private __gap; }
// SPDX-License-Identifier: MIT // solhint-disable-next-line compiler-version pragma solidity >=0.4.24 <0.8.0; import "../utils/AddressUpgradeable.sol"; /** * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed * behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. * * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as * possible by providing the encoded function call as the `_data` argument to {UpgradeableProxy-constructor}. * * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. */ abstract contract Initializable { /** * @dev Indicates that the contract has been initialized. */ bool private _initialized; /** * @dev Indicates that the contract is in the process of being initialized. */ bool private _initializing; /** * @dev Modifier to protect an initializer function from being invoked twice. */ modifier initializer() { require(_initializing || _isConstructor() || !_initialized, "Initializable: contract is already initialized"); bool isTopLevelCall = !_initializing; if (isTopLevelCall) { _initializing = true; _initialized = true; } _; if (isTopLevelCall) { _initializing = false; } } /// @dev Returns true if and only if the function is running in the constructor function _isConstructor() private view returns (bool) { return !AddressUpgradeable.isContract(address(this)); } }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.7.4; interface ILiquidityPoolGovernance { function setEmergencyState(uint256 perpetualIndex) external; }
// SPDX-License-Identifier: MIT pragma solidity >=0.6.0 <0.8.0; import "../proxy/Initializable.sol"; /** * @dev Contract module that helps prevent reentrant calls to a function. * * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier * available, which can be applied to functions to make sure there are no nested * (reentrant) calls to them. * * Note that because there is a single `nonReentrant` guard, functions marked as * `nonReentrant` may not call one another. This can be worked around by making * those functions `private`, and then adding `external` `nonReentrant` entry * points to them. * * TIP: If you would like to learn more about reentrancy and alternative ways * to protect against it, check out our blog post * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. */ abstract contract ReentrancyGuardUpgradeable is Initializable { // Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled. // The values being non-zero value makes deployment a bit more expensive, // but in exchange the refund on every call to nonReentrant will be lower in // amount. Since refunds are capped to a percentage of the total // transaction's gas, it is best to keep them low in cases like this one, to // increase the likelihood of the full refund coming into effect. uint256 private constant _NOT_ENTERED = 1; uint256 private constant _ENTERED = 2; uint256 private _status; function __ReentrancyGuard_init() internal initializer { __ReentrancyGuard_init_unchained(); } function __ReentrancyGuard_init_unchained() internal initializer { _status = _NOT_ENTERED; } /** * @dev Prevents a contract from calling itself, directly or indirectly. * Calling a `nonReentrant` function from another `nonReentrant` * function is not supported. It is possible to prevent this from happening * by making the `nonReentrant` function external, and make it call a * `private` function that does the actual work. */ modifier nonReentrant() { // On the first call to nonReentrant, _notEntered will be true require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); // Any calls to nonReentrant after this point will fail _status = _ENTERED; _; // By storing the original value once again, a refund is triggered (see // https://eips.ethereum.org/EIPS/eip-2200) _status = _NOT_ENTERED; } uint256[49] private __gap; }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.7.4; import "../Type.sol"; interface IPerpetual { /** * @notice Deposit collateral to the perpetual. * Can only called when the perpetual's state is "NORMAL". * This method will always increase `cash` amount in trader's margin account. * * @param perpetualIndex The index of the perpetual in the liquidity pool. * @param trader The address of the trader. * @param amount The amount of collateral to deposit. The amount always use decimals 18. */ function deposit( uint256 perpetualIndex, address trader, int256 amount ) external; /** * @notice Withdraw collateral from the trader's account of the perpetual. * After withdrawn, trader shall at least has maintenance margin left in account. * Can only called when the perpetual's state is "NORMAL". * Margin account must at least keep * The trader's cash will decrease in the perpetual. * Need to update the funding state and the oracle price of each perpetual before * and update the funding rate of each perpetual after * * @param perpetualIndex The index of the perpetual in the liquidity pool. * @param trader The address of the trader. * @param amount The amount of collateral to withdraw. The amount always use decimals 18. */ function withdraw( uint256 perpetualIndex, address trader, int256 amount ) external; /** * @notice If the state of the perpetual is "CLEARED", anyone authorized withdraw privilege by trader can settle * trader's account in the perpetual. Which means to calculate how much the collateral should be returned * to the trader, return it to trader's wallet and clear the trader's cash and position in the perpetual. * * @param perpetualIndex The index of the perpetual in the liquidity pool * @param trader The address of the trader. */ function settle(uint256 perpetualIndex, address trader) external; /** * @notice Clear the next active account of the perpetual which state is "EMERGENCY" and send gas reward of collateral * to sender. If all active accounts are cleared, the clear progress is done and the perpetual's state will * change to "CLEARED". Active means the trader's account is not empty in the perpetual. * Empty means cash and position are zero * * @param perpetualIndex The index of the perpetual in the liquidity pool. */ function clear(uint256 perpetualIndex) external; /** * @notice Trade with AMM in the perpetual, require sender is granted the trade privilege by the trader. * The trading price is determined by the AMM based on the index price of the perpetual. * Trader must be initial margin safe if opening position and margin safe if closing position * @param perpetualIndex The index of the perpetual in the liquidity pool * @param trader The address of trader * @param amount The position amount of the trade * @param limitPrice The worst price the trader accepts * @param deadline The deadline of the trade * @param referrer The referrer's address of the trade * @param flags The flags of the trade * @return int256 The update position amount of the trader after the trade */ function trade( uint256 perpetualIndex, address trader, int256 amount, int256 limitPrice, uint256 deadline, address referrer, uint32 flags ) external returns (int256); /** * @notice Trade with AMM by the order, initiated by the broker. * The trading price is determined by the AMM based on the index price of the perpetual. * Trader must be initial margin safe if opening position and margin safe if closing position * @param orderData The order data object * @param amount The position amount of the trade * @return int256 The update position amount of the trader after the trade */ function brokerTrade(bytes memory orderData, int256 amount) external returns (int256); /** * @notice Liquidate the trader if the trader's margin balance is lower than maintenance margin (unsafe). * Liquidate can be considered as a forced trading between AMM and unsafe margin account; * Based on current liquidity of AMM, it may take positions up to an amount equal to all the position * of the unsafe account. Besides the position, trader need to pay an extra penalty to AMM * for taking the unsafe assets. See TradeModule.sol for ehe strategy of penalty. * * The liquidate price will be determined by AMM. * Caller of this method can be anyone, then get a reward to make up for transaction gas fee. * * If a trader's margin balance is lower than 0 (bankrupt), insurance fund will be use to fill the loss * to make the total profit and loss balanced. (first the `insuranceFund` then the `donatedInsuranceFund`) * * If insurance funds are drained, the state of perpetual will turn to enter "EMERGENCY" than shutdown. * Can only liquidate when the perpetual's state is "NORMAL". * * @param perpetualIndex The index of the perpetual in liquidity pool * @param trader The address of trader to be liquidated. * @return liquidationAmount The amount of positions actually liquidated in the transaction. The amount always use decimals 18. */ function liquidateByAMM(uint256 perpetualIndex, address trader) external returns (int256 liquidationAmount); /** * @notice This method is generally consistent with `liquidateByAMM` function, but there some difference: * - The liquidation price is no longer determined by AMM, but the mark price; * - The penalty is taken by trader who takes position but AMM; * * @param perpetualIndex The index of the perpetual in liquidity pool. * @param liquidator The address of liquidator to receive the liquidated position. * @param trader The address of trader to be liquidated. * @param amount The amount of position to be taken from liquidated trader. The amount always use decimals 18. * @param limitPrice The worst price liquidator accepts. * @param deadline The deadline of transaction. * @return liquidationAmount The amount of positions actually liquidated in the transaction. */ function liquidateByTrader( uint256 perpetualIndex, address liquidator, address trader, int256 amount, int256 limitPrice, uint256 deadline ) external returns (int256 liquidationAmount); }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.7.4; pragma experimental ABIEncoderV2; import "@openzeppelin/contracts-upgradeable/math/SignedSafeMathUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/SafeCastUpgradeable.sol"; import "../interface/IAccessControl.sol"; import "../libraries/Utils.sol"; import "../libraries/OrderData.sol"; import "../libraries/SafeMathExt.sol"; import "../libraries/Signature.sol"; import "./MarginAccountModule.sol"; import "./PerpetualModule.sol"; import "../Type.sol"; library OrderModule { using SignedSafeMathUpgradeable for int256; using SafeCastUpgradeable for int256; using SafeMathExt for int256; using OrderData for Order; using OrderData for uint32; using MarginAccountModule for PerpetualStorage; using PerpetualModule for PerpetualStorage; /** * @notice Validate that order's signer is granted the trade privilege by order's trader * @param liquidityPool The liquidity pool object * @param order The order object * @param signature The signature */ function validateSignature( LiquidityPoolStorage storage liquidityPool, Order memory order, bytes memory signature ) public view { bytes32 orderHash = order.getOrderHash(); address signer = Signature.getSigner(orderHash, signature); if (signer != order.trader) { bool isAuthorized = IAccessControl(liquidityPool.accessController).isGranted( order.trader, signer, order.flags.useTargetLeverage() ? Constant.PRIVILEGE_TRADE | Constant.PRIVILEGE_DEPOSIT | Constant.PRIVILEGE_WITHDRAW : Constant.PRIVILEGE_TRADE ); require(isAuthorized, "signer is unauthorized"); } } /** * @notice Validate the order: * 1. broker of order = msg.sender * 2. relayer of order = tx.origin * 3. liquidity pool of order = address(this) * 4. perpetual index of order < count of perpetuals * 5. trading amount != 0 and has the same sign with amount of order * 6. amount of order != 0 * 7. minimum trading amount of order <= abs(trading amount) <= abs(amount of order) * 8. order is not expire * 9. chain id of order is correct * 10. order is stop loss order and taker profit order at the same time * @param liquidityPool The liquidity pool * @param order The order * @param amount The trading amount of position */ function validateOrder( LiquidityPoolStorage storage liquidityPool, Order memory order, int256 amount ) public view { // broker / relayer require(order.broker == msg.sender, "broker mismatch"); require(order.relayer == tx.origin, "relayer mismatch"); // pool / perpetual require(order.liquidityPool == address(this), "liquidity pool mismatch"); require( order.perpetualIndex < liquidityPool.perpetualCount, "perpetual index out of range" ); // amount require(amount != 0 && Utils.hasTheSameSign(amount, order.amount), "invalid amount"); require(order.amount != 0, "order amount is 0"); require(amount.abs() >= order.minTradeAmount, "amount is less than min trade amount"); require(amount.abs() <= order.amount.abs(), "amount exceeds order amount"); // expire require(order.expiredAt >= block.timestamp, "order is expired"); // chain id require(order.chainID == Utils.chainID(), "chainid mismatch"); // close only require( !(order.isStopLossOrder() && order.isTakeProfitOrder()), "stop-loss order cannot be take-profit" ); } /** * @notice Validate the trigger price of the order * When position > 0, if stop loss order: index price must <= trigger price, * if take profit order: index price must >= trigger price. * When position < 0, if stop loss order: index price must >= trigger price, * if take profit order: index price must <= trigger price * @param liquidityPool The liquidity pool * @param order The order */ function validateTriggerPrice(LiquidityPoolStorage storage liquidityPool, Order memory order) public view { int256 indexPrice = liquidityPool.perpetuals[order.perpetualIndex].getIndexPrice(); if ( (order.isStopLossOrder() && order.amount > 0) || (order.isTakeProfitOrder() && order.amount < 0) ) { // stop-loss + long / take-profit + short require(indexPrice >= order.triggerPrice, "trigger price is not reached"); } else if ( (order.isStopLossOrder() && order.amount < 0) || (order.isTakeProfitOrder() && order.amount > 0) ) { // stop-loss + long / take-profit + short require(indexPrice <= order.triggerPrice, "trigger price is not reached"); } } }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.7.4; pragma experimental ABIEncoderV2; import "@openzeppelin/contracts-upgradeable/cryptography/ECDSAUpgradeable.sol"; import "../Type.sol"; library Signature { uint8 internal constant SIGN_TYPE_ETH = 0x0; uint8 internal constant SIGN_TYPE_EIP712 = 0x1; /* * @dev Get the signer of the transaction * @param signedHash The hash of the transaction * @param signature The signature of the transaction * @return signer The signer of the transaction */ function getSigner(bytes32 digest, bytes memory signature) internal pure returns (address signer) { bytes32 r; bytes32 s; uint8 v; uint8 signType; assembly { r := mload(add(signature, 0x20)) s := mload(add(signature, 0x40)) v := byte(0, mload(add(signature, 0x60))) signType := byte(1, mload(add(signature, 0x60))) } if (signType == SIGN_TYPE_ETH) { digest = ECDSAUpgradeable.toEthSignedMessageHash(digest); } else if (signType != SIGN_TYPE_EIP712) { revert("unsupported sign type"); } signer = ECDSAUpgradeable.recover(digest, v, r, s); } }
// SPDX-License-Identifier: MIT pragma solidity >=0.6.0 <0.8.0; /** * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. * * These functions can be used to verify that a message was signed by the holder * of the private keys of a given address. */ library ECDSAUpgradeable { /** * @dev Returns the address that signed a hashed message (`hash`) with * `signature`. This address can then be used for verification purposes. * * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: * this function rejects them by requiring the `s` value to be in the lower * half order, and the `v` value to be either 27 or 28. * * IMPORTANT: `hash` _must_ be the result of a hash operation for the * verification to be secure: it is possible to craft signatures that * recover to arbitrary addresses for non-hashed data. A safe way to ensure * this is by receiving a hash of the original message (which may otherwise * be too long), and then calling {toEthSignedMessageHash} on it. */ function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { // Check the signature length if (signature.length != 65) { revert("ECDSA: invalid signature length"); } // Divide the signature in r, s and v variables bytes32 r; bytes32 s; uint8 v; // ecrecover takes the signature parameters, and the only way to get them // currently is to use assembly. // solhint-disable-next-line no-inline-assembly assembly { r := mload(add(signature, 0x20)) s := mload(add(signature, 0x40)) v := byte(0, mload(add(signature, 0x60))) } return recover(hash, v, r, s); } /** * @dev Overload of {ECDSA-recover-bytes32-bytes-} that receives the `v`, * `r` and `s` signature fields separately. */ function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) { // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most // signatures from current libraries generate a unique signature with an s-value in the lower half order. // // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept // these malleable signatures as well. require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, "ECDSA: invalid signature 's' value"); require(v == 27 || v == 28, "ECDSA: invalid signature 'v' value"); // If the signature is valid (and not malleable), return the signer address address signer = ecrecover(hash, v, r, s); require(signer != address(0), "ECDSA: invalid signature"); return signer; } /** * @dev Returns an Ethereum Signed Message, created from a `hash`. This * replicates the behavior of the * https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`eth_sign`] * JSON-RPC method. * * See {recover}. */ function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) { // 32 is the length in bytes of hash, // enforced by the type signature above return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); } }
{ "optimizer": { "enabled": true, "runs": 1000 }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "abi" ] } }, "libraries": { "contracts/module/AMMModule.sol": { "AMMModule": "0xe594234baed1230cd1ab29544793ff0631b2fba7" }, "contracts/module/LiquidityPoolModule.sol": { "LiquidityPoolModule": "0xd72345330e2da53e215daa3ffbad8f779cd4a7b4" }, "contracts/module/OrderModule.sol": { "OrderModule": "0x0e535aa06e9e937f4837f43f1060545f0a4ed97c" }, "contracts/module/TradeModule.sol": { "TradeModule": "0x5661ebd2dca2a5f57c9bae32faa53e439478fac2" } } }
[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"indexed":true,"internalType":"address","name":"keeper","type":"address"}],"name":"AddAMMKeeper","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"trader","type":"address"},{"indexed":false,"internalType":"int256","name":"addedCash","type":"int256"},{"indexed":false,"internalType":"int256","name":"mintedShare","type":"int256"},{"indexed":false,"internalType":"int256","name":"addedPoolMargin","type":"int256"}],"name":"AddLiquidity","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"indexed":true,"internalType":"address","name":"keeper","type":"address"}],"name":"AddTraderKeeper","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newOperator","type":"address"}],"name":"ClaimOperator","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"indexed":true,"internalType":"address","name":"trader","type":"address"}],"name":"Clear","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"indexed":false,"internalType":"address","name":"governor","type":"address"},{"indexed":false,"internalType":"address","name":"shareToken","type":"address"},{"indexed":false,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"address","name":"oracle","type":"address"},{"indexed":false,"internalType":"address","name":"collateral","type":"address"},{"indexed":false,"internalType":"int256[9]","name":"baseParams","type":"int256[9]"},{"indexed":false,"internalType":"int256[9]","name":"riskParams","type":"int256[9]"}],"name":"CreatePerpetual","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"indexed":true,"internalType":"address","name":"trader","type":"address"},{"indexed":false,"internalType":"int256","name":"amount","type":"int256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"int256","name":"amount","type":"int256"}],"name":"DonateInsuranceFund","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"indexed":true,"internalType":"address","name":"liquidator","type":"address"},{"indexed":true,"internalType":"address","name":"trader","type":"address"},{"indexed":false,"internalType":"int256","name":"amount","type":"int256"},{"indexed":false,"internalType":"int256","name":"price","type":"int256"},{"indexed":false,"internalType":"int256","name":"penalty","type":"int256"},{"indexed":false,"internalType":"int256","name":"penaltyToLP","type":"int256"}],"name":"Liquidate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"}],"name":"OperatorCheckIn","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"indexed":true,"internalType":"address","name":"keeper","type":"address"}],"name":"RemoveAMMKeeper","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"trader","type":"address"},{"indexed":false,"internalType":"int256","name":"returnedCash","type":"int256"},{"indexed":false,"internalType":"int256","name":"burnedShare","type":"int256"},{"indexed":false,"internalType":"int256","name":"removedPoolMargin","type":"int256"}],"name":"RemoveLiquidity","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"indexed":true,"internalType":"address","name":"keeper","type":"address"}],"name":"RemoveTraderKeeper","type":"event"},{"anonymous":false,"inputs":[],"name":"RevokeOperator","type":"event"},{"anonymous":false,"inputs":[],"name":"RunLiquidityPool","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"perpetualIndex","type":"uint256"}],"name":"SetClearedState","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"indexed":false,"internalType":"int256","name":"settlementPrice","type":"int256"},{"indexed":false,"internalType":"uint256","name":"settlementTime","type":"uint256"}],"name":"SetEmergencyState","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"int256[4]","name":"value","type":"int256[4]"}],"name":"SetLiquidityPoolParameter","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"perpetualIndex","type":"uint256"}],"name":"SetNormalState","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"indexed":true,"internalType":"address","name":"oldOracle","type":"address"},{"indexed":true,"internalType":"address","name":"newOracle","type":"address"}],"name":"SetOracle","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"indexed":false,"internalType":"int256[9]","name":"baseParams","type":"int256[9]"}],"name":"SetPerpetualBaseParameter","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"indexed":false,"internalType":"int256[9]","name":"riskParams","type":"int256[9]"},{"indexed":false,"internalType":"int256[9]","name":"minRiskParamValues","type":"int256[9]"},{"indexed":false,"internalType":"int256[9]","name":"maxRiskParamValues","type":"int256[9]"}],"name":"SetPerpetualRiskParameter","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"indexed":true,"internalType":"address","name":"trader","type":"address"},{"indexed":false,"internalType":"int256","name":"targetLeverage","type":"int256"}],"name":"SetTargetLeverage","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"indexed":true,"internalType":"address","name":"trader","type":"address"},{"indexed":false,"internalType":"int256","name":"amount","type":"int256"}],"name":"Settle","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"indexed":true,"internalType":"address","name":"trader","type":"address"},{"indexed":false,"internalType":"int256","name":"position","type":"int256"},{"indexed":false,"internalType":"int256","name":"price","type":"int256"},{"indexed":false,"internalType":"int256","name":"fee","type":"int256"},{"indexed":false,"internalType":"int256","name":"lpFee","type":"int256"}],"name":"Trade","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"int256","name":"amount","type":"int256"}],"name":"TransferExcessInsuranceFundToLP","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"indexed":true,"internalType":"address","name":"trader","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"int256","name":"operatorFee","type":"int256"}],"name":"TransferFeeToOperator","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"indexed":true,"internalType":"address","name":"trader","type":"address"},{"indexed":true,"internalType":"address","name":"referrer","type":"address"},{"indexed":false,"internalType":"int256","name":"referralRebate","type":"int256"}],"name":"TransferFeeToReferrer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"indexed":true,"internalType":"address","name":"trader","type":"address"},{"indexed":true,"internalType":"address","name":"vault","type":"address"},{"indexed":false,"internalType":"int256","name":"vaultFee","type":"int256"}],"name":"TransferFeeToVault","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newOperator","type":"address"}],"name":"TransferOperatorTo","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"indexed":false,"internalType":"int256","name":"fundingRate","type":"int256"}],"name":"UpdateFundingRate","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"indexed":false,"internalType":"int256[9]","name":"riskParams","type":"int256[9]"}],"name":"UpdatePerpetualRiskParameter","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"int256","name":"poolMargin","type":"int256"}],"name":"UpdatePoolMargin","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"indexed":true,"internalType":"address","name":"oracle","type":"address"},{"indexed":false,"internalType":"int256","name":"markPrice","type":"int256"},{"indexed":false,"internalType":"uint256","name":"markPriceUpdateTime","type":"uint256"},{"indexed":false,"internalType":"int256","name":"indexPrice","type":"int256"},{"indexed":false,"internalType":"uint256","name":"indexPriceUpdateTime","type":"uint256"}],"name":"UpdatePrice","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"indexed":false,"internalType":"int256","name":"unitAccumulativeFunding","type":"int256"}],"name":"UpdateUnitAccumulativeFunding","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"indexed":true,"internalType":"address","name":"trader","type":"address"},{"indexed":false,"internalType":"int256","name":"amount","type":"int256"}],"name":"Withdraw","type":"event"},{"inputs":[{"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"internalType":"address","name":"keeper","type":"address"}],"name":"addAMMKeeper","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"int256","name":"cashToAdd","type":"int256"}],"name":"addLiquidity","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"orderData","type":"bytes"},{"internalType":"int256","name":"amount","type":"int256"}],"name":"brokerTrade","outputs":[{"internalType":"int256","name":"tradeAmount","type":"int256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"checkIn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claimOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"perpetualIndex","type":"uint256"}],"name":"clear","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"oracle","type":"address"},{"internalType":"int256[9]","name":"baseParams","type":"int256[9]"},{"internalType":"int256[9]","name":"riskParams","type":"int256[9]"},{"internalType":"int256[9]","name":"minRiskParamValues","type":"int256[9]"},{"internalType":"int256[9]","name":"maxRiskParamValues","type":"int256[9]"}],"name":"createPerpetual","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"internalType":"address","name":"trader","type":"address"},{"internalType":"int256","name":"amount","type":"int256"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"int256","name":"amount","type":"int256"}],"name":"donateInsuranceFund","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"int256","name":"cashToAdd","type":"int256"}],"name":"donateLiquidity","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"internalType":"int256","name":"settlementPrice","type":"int256"}],"name":"forceToSetEmergencyState","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"forceToSyncState","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"perpetualIndex","type":"uint256"}],"name":"getActiveAccountCount","outputs":[{"internalType":"uint256","name":"activeAccountCount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"perpetualIndex","type":"uint256"}],"name":"getClearProgress","outputs":[{"internalType":"uint256","name":"left","type":"uint256"},{"internalType":"uint256","name":"total","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLiquidityPoolInfo","outputs":[{"internalType":"bool","name":"isRunning","type":"bool"},{"internalType":"bool","name":"isFastCreationEnabled","type":"bool"},{"internalType":"address[7]","name":"addresses","type":"address[7]"},{"internalType":"int256[5]","name":"intNums","type":"int256[5]"},{"internalType":"uint256[6]","name":"uintNums","type":"uint256[6]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"internalType":"address","name":"trader","type":"address"}],"name":"getMarginAccount","outputs":[{"internalType":"int256","name":"cash","type":"int256"},{"internalType":"int256","name":"position","type":"int256"},{"internalType":"int256","name":"availableMargin","type":"int256"},{"internalType":"int256","name":"margin","type":"int256"},{"internalType":"int256","name":"settleableMargin","type":"int256"},{"internalType":"bool","name":"isInitialMarginSafe","type":"bool"},{"internalType":"bool","name":"isMaintenanceMarginSafe","type":"bool"},{"internalType":"bool","name":"isMarginSafe","type":"bool"},{"internalType":"int256","name":"targetLeverage","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"perpetualIndex","type":"uint256"}],"name":"getPerpetualInfo","outputs":[{"internalType":"enum PerpetualState","name":"state","type":"uint8"},{"internalType":"address","name":"oracle","type":"address"},{"internalType":"int256[42]","name":"nums","type":"int256[42]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPoolMargin","outputs":[{"internalType":"int256","name":"poolMargin","type":"int256"},{"internalType":"bool","name":"isAMMSafe","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"address","name":"collateral","type":"address"},{"internalType":"uint256","name":"collateralDecimals","type":"uint256"},{"internalType":"address","name":"governor","type":"address"},{"internalType":"bytes","name":"initData","type":"bytes"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"internalType":"address","name":"trader","type":"address"}],"name":"liquidateByAMM","outputs":[{"internalType":"int256","name":"liquidationAmount","type":"int256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"internalType":"address","name":"liquidator","type":"address"},{"internalType":"address","name":"trader","type":"address"},{"internalType":"int256","name":"amount","type":"int256"},{"internalType":"int256","name":"limitPrice","type":"int256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"liquidateByTrader","outputs":[{"internalType":"int256","name":"liquidationAmount","type":"int256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"internalType":"uint256","name":"begin","type":"uint256"},{"internalType":"uint256","name":"end","type":"uint256"}],"name":"listActiveAccounts","outputs":[{"internalType":"address[]","name":"result","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"internalType":"uint256","name":"begin","type":"uint256"},{"internalType":"uint256","name":"end","type":"uint256"}],"name":"listByAMMKeepers","outputs":[{"internalType":"address[]","name":"result","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int256","name":"cashToAdd","type":"int256"},{"internalType":"int256","name":"shareToMint","type":"int256"}],"name":"queryAddLiquidity","outputs":[{"internalType":"int256","name":"cashToAddResult","type":"int256"},{"internalType":"int256","name":"shareToMintResult","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int256","name":"shareToRemove","type":"int256"},{"internalType":"int256","name":"cashToReturn","type":"int256"}],"name":"queryRemoveLiquidity","outputs":[{"internalType":"int256","name":"shareToRemoveResult","type":"int256"},{"internalType":"int256","name":"cashToReturnResult","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"internalType":"address","name":"trader","type":"address"},{"internalType":"int256","name":"amount","type":"int256"},{"internalType":"address","name":"referrer","type":"address"},{"internalType":"uint32","name":"flags","type":"uint32"}],"name":"queryTrade","outputs":[{"internalType":"int256","name":"tradePrice","type":"int256"},{"internalType":"int256","name":"totalFee","type":"int256"},{"internalType":"int256","name":"cost","type":"int256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"internalType":"address","name":"keeper","type":"address"}],"name":"removeAMMKeeper","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"int256","name":"shareToRemove","type":"int256"},{"internalType":"int256","name":"cashToReturn","type":"int256"}],"name":"removeLiquidity","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"revokeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"runLiquidityPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"perpetualIndex","type":"uint256"}],"name":"setEmergencyState","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"int256[4]","name":"params","type":"int256[4]"}],"name":"setLiquidityPoolParameter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"internalType":"address","name":"oracle","type":"address"}],"name":"setOracle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"internalType":"int256[9]","name":"baseParams","type":"int256[9]"}],"name":"setPerpetualBaseParameter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"internalType":"int256[9]","name":"riskParams","type":"int256[9]"},{"internalType":"int256[9]","name":"minRiskParamValues","type":"int256[9]"},{"internalType":"int256[9]","name":"maxRiskParamValues","type":"int256[9]"}],"name":"setPerpetualRiskParameter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"internalType":"address","name":"trader","type":"address"},{"internalType":"int256","name":"targetLeverage","type":"int256"}],"name":"setTargetLeverage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"internalType":"address","name":"trader","type":"address"}],"name":"settle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"internalType":"address","name":"trader","type":"address"},{"internalType":"int256","name":"amount","type":"int256"},{"internalType":"int256","name":"limitPrice","type":"int256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"address","name":"referrer","type":"address"},{"internalType":"uint32","name":"flags","type":"uint32"}],"name":"trade","outputs":[{"internalType":"int256","name":"tradeAmount","type":"int256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOperator","type":"address"}],"name":"transferOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"internalType":"int256[9]","name":"riskParams","type":"int256[9]"}],"name":"updatePerpetualRiskParameter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"perpetualIndex","type":"uint256"},{"internalType":"address","name":"trader","type":"address"},{"internalType":"int256","name":"amount","type":"int256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]
Contract Creation Code
608060405234801561001057600080fd5b506173c280620000216000396000f3fe6080604052600436106102c85760003560e01c80636ef05a401161017957806390bbd3e4116100d6578063bd5b0a091161008a578063d73b5e4211610064578063d73b5e42146107e8578063dbabbf7714610808578063ed3574571461082b576102ee565b8063bd5b0a0914610793578063c0fe1af8146107b3578063d54e65fb146107d3576102ee565b80639db316b8116100bb5780639db316b814610749578063b080445614610769578063b674759c1461077e576102ee565b806390bbd3e414610709578063962d193814610729576102ee565b806378f140ea1161012d57806385dcd60a1161011257806385dcd60a1461069a57806389664990146106c95780638ceef981146106e9576102ee565b806378f140ea146106455780637d3ba80f14610665576102ee565b80637074020e1161015e5780637074020e146105f05780637086fd3214610610578063709240c414610625576102ee565b80636ef05a40146105b05780636fca8b99146105d0576102ee565b806329605e7711610227578063580818e3116101db5780635d0f1248116101c05780635d0f1248146105505780636282a56814610570578063675a0aa514610590576102ee565b8063580818e3146105105780635cfb7f8f14610530576102ee565b8063385975301161020c57806338597530146104b057806349a60713146104d05780634fababb3146104f0576102ee565b806329605e77146104705780632d47ba5714610490576102ee565b806313f07f451161027e57806317c675691161026357806317c675691461041b578063183ff0851461043b5780631bc5aa2014610450576102ee565b806313f07f45146103c057806316b3dd0e146103ed576102ee565b80630cdc105a116102af5780630cdc105a1461034d5780630f28c8b11461037357806310ec4beb146103a0576102ee565b8062092cce146102f35780629f22ae1461032b576102ee565b366102ee5760405162461bcd60e51b81526004016102e590616dae565b60405180910390fd5b600080fd5b3480156102ff57600080fd5b5061031361030e366004616545565b61084b565b60405161032293929190616a30565b60405180910390f35b34801561033757600080fd5b5061034b610346366004616596565b610ab5565b005b34801561035957600080fd5b50610362610c25565b6040516103229594939291906169b4565b34801561037f57600080fd5b5061039361038e366004616545565b610f02565b6040516103229190616a83565b3480156103ac57600080fd5b5061034b6103bb366004616545565b610f75565b3480156103cc57600080fd5b506103e06103db366004616775565b61106f565b6040516103229190616967565b3480156103f957600080fd5b5061040d610408366004616575565b6110e7565b604051610322929190616a9c565b34801561042757600080fd5b5061034b610436366004616596565b6113a1565b34801561044757600080fd5b5061034b6114ad565b34801561045c57600080fd5b506103e061046b366004616775565b6115a8565b34801561047c57600080fd5b5061034b61048b366004616335565b61161f565b34801561049c57600080fd5b5061034b6104ab366004616618565b6117c6565b3480156104bc57600080fd5b5061034b6104cb366004616545565b6119b9565b3480156104dc57600080fd5b506103936104eb3660046165c1565b611cea565b3480156104fc57600080fd5b5061039361050b3660046164a9565b61220b565b34801561051c57600080fd5b5061034b61052b366004616596565b612543565b34801561053c57600080fd5b5061034b61054b366004616545565b612696565b34801561055c57600080fd5b5061034b61056b3660046163f7565b612989565b34801561057c57600080fd5b5061034b61058b3660046166ff565b612b44565b34801561059c57600080fd5b5061034b6105ab3660046166ff565b612c51565b3480156105bc57600080fd5b5061034b6105cb366004616618565b612d57565b3480156105dc57600080fd5b5061034b6105eb366004616545565b6131de565b3480156105fc57600080fd5b5061034b61060b36600461634f565b613281565b34801561061c57600080fd5b5061034b6133a5565b34801561063157600080fd5b50610393610640366004616696565b6134df565b34801561065157600080fd5b5061034b610660366004616618565b6137c7565b34801561067157600080fd5b50610685610680366004616596565b613ad4565b60405161032299989796959493929190616ac0565b3480156106a657600080fd5b506106ba6106b536600461664c565b613bdc565b60405161032293929190616aaa565b3480156106d557600080fd5b5061034b6106e4366004616575565b613dcf565b3480156106f557600080fd5b5061040d610704366004616575565b6140d8565b34801561071557600080fd5b5061040d610724366004616545565b6142f8565b34801561073557600080fd5b5061034b610744366004616596565b6143a0565b34801561075557600080fd5b5061034b610764366004616463565b6145c6565b34801561077557600080fd5b5061034b6146db565b34801561078a57600080fd5b5061034b6147dd565b34801561079f57600080fd5b506103936107ae366004616596565b6148be565b3480156107bf57600080fd5b5061034b6107ce366004616545565b614c94565b3480156107df57600080fd5b5061034b614d73565b3480156107f457600080fd5b5061034b610803366004616575565b614ddb565b34801561081457600080fd5b5061081d6150b9565b604051610322929190616a8c565b34801561083757600080fd5b5061034b610846366004616723565b615149565b6000806108566161e9565b604254849081106108a4576040805162461bcd60e51b81526020600482015260136024820152721c195c9c195d1d585b081b9bdd08195e1a5cdd606a1b604482015290519081900360640190fd5b6000858152604360209081526040918290206001810154835161054081019094526002820154845260ff8116975061010090046001600160a01b03169550919081016108ef8361526d565b81526020016108fd8361529f565b8152600a830154602080830191909152600b8401546040830152600c8401546060830152600d8401546080830152600e84015460a0830152600f84015460c0830152601084015460e08301526011840154610100830152601284015461012083015260138401546101408301526016840154610160830152601784015461018083015260188401546101a083015260198401546101c0830152601a8401546101e0830152601b840154610200830152601c840154610220830152601d840154610240830152601e840154610260830152601f8401546102808301528301546102a082015260218301546102c082015260258301546102e08201526026830154610300820152602783015461032082015260288301546103408201526029830154610360820152602a83015461038082015260038301546103a082015260158301546103c082015260228301546103e082015260238301546104008201526024830154610420820152603583015461044082015260368301546104608201526037830154610480820152603d8301546104a0820152603e8301546104c0820152603f909201546104e09092019190915293959294505050565b6000610ac160336152cd565b90506001600160a01b03811615610b2e57806001600160a01b0316610ae46152f7565b6001600160a01b031614610b295760405162461bcd60e51b81526004018080602001828103825260218152602001806172236021913960400191505060405180910390fd5b610b87565b6036546001600160a01b0316610b426152f7565b6001600160a01b031614610b875760405162461bcd60e51b81526004018080602001828103825260218152602001806172aa6021913960400191505060405180910390fd5b604080517f48ea454c00000000000000000000000000000000000000000000000000000000815260336004820152602481018590526001600160a01b0384166044820152905173d72345330e2da53e215daa3ffbad8f779cd4a7b4916348ea454c916064808301926000929190829003018186803b158015610c0857600080fd5b505af4158015610c1c573d6000803e3d6000fd5b50505050505050565b600080610c30616208565b610c38616226565b610c40616244565b603380546040805160e081019091526001600160a01b0362010000830416815260ff80831698506101009092049091169550906020820190610c81906152cd565b6001600160a01b03168152602001610c9960336152fb565b6001600160a01b03908116825260365481166020808401919091526037548216604080850191909152603b54909216606084015281517fe1858ce900000000000000000000000000000000000000000000000000000000815260336004820152915160809093019273d72345330e2da53e215daa3ffbad8f779cd4a7b49263e1858ce9926024808301939192829003018186803b158015610d3957600080fd5b505af4158015610d4d573d6000803e3d6000fd5b505050506040513d6020811015610d6357600080fd5b50516001600160a01b03169052604080517fb1a4182a00000000000000000000000000000000000000000000000000000000815260336004820152905191945073d72345330e2da53e215daa3ffbad8f779cd4a7b49163b1a4182a91602480820192602092909190829003018186803b158015610ddf57600080fd5b505af4158015610df3573d6000803e3d6000fd5b505050506040513d6020811015610e0957600080fd5b50518252603c54602080840191909152604454604080850191909152604554606080860191909152604654608080870191909152603a54855260425485850152603d5485840152603f54918501919091526048549084015280517f374331ff00000000000000000000000000000000000000000000000000000000815260336004820152905173d72345330e2da53e215daa3ffbad8f779cd4a7b49263374331ff9260248082019391829003018186803b158015610ec657600080fd5b505af4158015610eda573d6000803e3d6000fd5b505050506040513d6020811015610ef057600080fd5b505160a0820152939492939192909190565b6000816033600f01548110610f54576040805162461bcd60e51b81526020600482015260136024820152721c195c9c195d1d585b081b9bdd08195e1a5cdd606a1b604482015290519081900360640190fd5b6000838152604360205260409020610f6e90603001615321565b9392505050565b60026074541415610fcd576040805162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604482015290519081900360640190fd5b600260745560335460ff16610ff45760405162461bcd60e51b81526004016102e590616de5565b73d72345330e2da53e215daa3ffbad8f779cd4a7b46383a1eaa860336110186152f7565b846040518463ffffffff1660e01b81526004016110379392919061700b565b60006040518083038186803b15801561104f57600080fd5b505af4158015611063573d6000803e3d6000fd5b50506001607455505050565b6060836033600f015481106110c1576040805162461bcd60e51b81526020600482015260136024820152721c195c9c195d1d585b081b9bdd08195e1a5cdd606a1b604482015290519081900360640190fd5b60008581526043602052604090206110dd60308201868661532c565b9695505050505050565b603354600090819060ff16611143576040805162461bcd60e51b815260206004820152601360248201527f706f6f6c206973206e6f742072756e6e696e6700000000000000000000000000604482015290519081900360640190fd5b60006111ca603360040160009054906101000a90046001600160a01b03166001600160a01b03166318160ddd6040518163ffffffff1660e01b815260040160206040518083038186803b15801561119957600080fd5b505afa1580156111ad573d6000803e3d6000fd5b505050506040513d60208110156111c357600080fd5b505161544a565b90506000851380156111da575083155b1561128a57604080517ff72ee9c8000000000000000000000000000000000000000000000000000000008152603360048201526024810183905260448101879052815173e594234baed1230cd1ab29544793ff0631b2fba79263f72ee9c89260648082019391829003018186803b15801561125457600080fd5b505af4158015611268573d6000803e3d6000fd5b505050506040513d604081101561127e57600080fd5b50518593509150611399565b841580156112985750600084135b1561134c57604080517f89127d52000000000000000000000000000000000000000000000000000000008152603360048201526024810183905260448101869052905173e594234baed1230cd1ab29544793ff0631b2fba7916389127d52916064808301926020929190829003018186803b15801561131657600080fd5b505af415801561132a573d6000803e3d6000fd5b505050506040513d602081101561134057600080fd5b50519250839150611399565b6040805162461bcd60e51b815260206004820152601160248201527f696e76616c696420706172616d65746572000000000000000000000000000000604482015290519081900360640190fd5b509250929050565b6036546001600160a01b03166113b56152f7565b6001600160a01b031614611410576040805162461bcd60e51b815260206004820152601860248201527f6f6e6c7920676f7665726e6f7220697320616c6c6f7765640000000000000000604482015290519081900360640190fd5b604080517f38dbaf7100000000000000000000000000000000000000000000000000000000815260336004820152602481018490526001600160a01b0383166044820152905173d72345330e2da53e215daa3ffbad8f779cd4a7b4916338dbaf71916064808301926000929190829003018186803b15801561149157600080fd5b505af41580156114a5573d6000803e3d6000fd5b505050505050565b6114b760336152cd565b6001600160a01b03166114c86152f7565b6001600160a01b031614611523576040805162461bcd60e51b815260206004820152601860248201527f6f6e6c79206f70657261746f7220697320616c6c6f7765640000000000000000604482015290519081900360640190fd5b604080517f5980576b00000000000000000000000000000000000000000000000000000000815260336004820152905173d72345330e2da53e215daa3ffbad8f779cd4a7b491635980576b916024808301926000929190829003018186803b15801561158e57600080fd5b505af41580156115a2573d6000803e3d6000fd5b50505050565b6060836033600f015481106115fa576040805162461bcd60e51b81526020600482015260136024820152721c195c9c195d1d585b081b9bdd08195e1a5cdd606a1b604482015290519081900360640190fd5b600085815260436020526040902061161690603901858561532c565b95945050505050565b600061162b60336152cd565b90506001600160a01b0381161561169857806001600160a01b031661164e6152f7565b6001600160a01b0316146116935760405162461bcd60e51b81526004018080602001828103825260218152602001806172236021913960400191505060405180910390fd5b6116f1565b6036546001600160a01b03166116ac6152f7565b6001600160a01b0316146116f15760405162461bcd60e51b81526004018080602001828103825260218152602001806172aa6021913960400191505060405180910390fd5b6001600160a01b03821661174c576040805162461bcd60e51b815260206004820152601c60248201527f6e6577206f70657261746f72206973207a65726f206164647265737300000000604482015290519081900360640190fd5b604080517f4f28feeb000000000000000000000000000000000000000000000000000000008152603360048201526001600160a01b0384166024820152905173d72345330e2da53e215daa3ffbad8f779cd4a7b491634f28feeb916044808301926000929190829003018186803b15801561149157600080fd5b81600773d72345330e2da53e215daa3ffbad8f779cd4a7b463e57d06a86033846117ee6152f7565b856040518563ffffffff1660e01b815260040180858152602001846001600160a01b03168152602001836001600160a01b0316815260200182815260200194505050505060206040518083038186803b15801561184a57600080fd5b505af415801561185e573d6000803e3d6000fd5b505050506040513d602081101561187457600080fd5b50516118bd576040805162461bcd60e51b81526020600482015260136024820152723ab730baba3437b934bd32b21031b0b63632b960691b604482015290519081900360640190fd5b6001600160a01b0384166118e35760405162461bcd60e51b81526004016102e590616f0e565b670de0b6b3a764000083071561190b5760405162461bcd60e51b81526004016102e590616b73565b6000831361192b5760405162461bcd60e51b81526004016102e590616cac565b6040517fdaad7f6900000000000000000000000000000000000000000000000000000000815273d72345330e2da53e215daa3ffbad8f779cd4a7b49063daad7f6990611982906033908990899089906004016171bf565b60006040518083038186803b15801561199a57600080fd5b505af41580156119ae573d6000803e3d6000fd5b505050505050505050565b60408051634b071c5760e11b815260336004820152426024820181905291516001929173d72345330e2da53e215daa3ffbad8f779cd4a7b49163960e38ae91604480820192600092909190829003018186803b158015611a1857600080fd5b505af4158015611a2c573d6000803e3d6000fd5b5050604080516304c43a9960e31b8152603360048201528515156024820152905173d72345330e2da53e215daa3ffbad8f779cd4a7b49350632621d4c892506044808301926000929190829003018186803b158015611a8a57600080fd5b505af4158015611a9e573d6000803e3d6000fd5b50505050600019831415611b3457604080517fa99d455a00000000000000000000000000000000000000000000000000000000815260336004820152905173d72345330e2da53e215daa3ffbad8f779cd4a7b49163a99d455a916024808301926000929190829003018186803b158015611b1757600080fd5b505af4158015611b2b573d6000803e3d6000fd5b50505050611c98565b6000838152604360209081526040808320600181015482517fd1cc9976000000000000000000000000000000000000000000000000000000008152925191946101009091046001600160a01b03169363d1cc9976936004808201949293918390030190829087803b158015611ba857600080fd5b505af1158015611bbc573d6000803e3d6000fd5b505050506040513d6020811015611bd257600080fd5b5051611c25576040805162461bcd60e51b815260206004820152601460248201527f707265726571756973697465206e6f74206d6574000000000000000000000000604482015290519081900360640190fd5b604080516301ec95cd60e51b81526033600482015260248101869052905173d72345330e2da53e215daa3ffbad8f779cd4a7b491633d92b9a0916044808301926000929190829003018186803b158015611c7e57600080fd5b505af4158015611c92573d6000803e3d6000fd5b50505050505b60408051634c86902960e11b815260336004820152905173d72345330e2da53e215daa3ffbad8f779cd4a7b49163990d2052916024808301926000929190829003018186803b158015610c0857600080fd5b600060026074541415611d44576040805162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604482015290519081900360640190fd5b6002607481905550603360000160029054906101000a90046001600160a01b03166001600160a01b0316634831d32f6040518163ffffffff1660e01b815260040160206040518083038186803b158015611d9d57600080fd5b505afa158015611db1573d6000803e3d6000fd5b505050506040513d6020811015611dc757600080fd5b505115611e0e576040805162461bcd60e51b815260206004820152601060248201526f1d5b9a5d995c9cd9481cd95d1d1b195960821b604482015290519081900360640190fd5b85600873d72345330e2da53e215daa3ffbad8f779cd4a7b463e57d06a8603384611e366152f7565b856040518563ffffffff1660e01b815260040180858152602001846001600160a01b03168152602001836001600160a01b0316815260200182815260200194505050505060206040518083038186803b158015611e9257600080fd5b505af4158015611ea6573d6000803e3d6000fd5b505050506040513d6020811015611ebc57600080fd5b5051611f05576040805162461bcd60e51b81526020600482015260136024820152723ab730baba3437b934bd32b21031b0b63632b960691b604482015290519081900360640190fd5b60408051634b071c5760e11b815260336004820152426024820181905291516000929173d72345330e2da53e215daa3ffbad8f779cd4a7b49163960e38ae916044808201928792909190829003018186803b158015611f6357600080fd5b505af4158015611f77573d6000803e3d6000fd5b5050604080516304c43a9960e31b8152603360048201528515156024820152905173d72345330e2da53e215daa3ffbad8f779cd4a7b49350632621d4c892506044808301926000929190829003018186803b158015611fd557600080fd5b505af4158015611fe9573d6000803e3d6000fd5b5060029250611ff6915050565b60008c81526043602052604090206001015460ff16600481111561201657fe5b146120335760405162461bcd60e51b81526004016102e590616c18565b6001600160a01b0389166120595760405162461bcd60e51b81526004016102e590616f0e565b6001600160a01b0389163014156120825760405162461bcd60e51b81526004016102e590616b3c565b8761209f5760405162461bcd60e51b81526004016102e590616be1565b60008712156120c05760405162461bcd60e51b81526004016102e590616b05565b428610156120e05760405162461bcd60e51b81526004016102e590616d77565b6040517f15e47827000000000000000000000000000000000000000000000000000000008152735661ebd2dca2a5f57c9bae32faa53e439478fac2906315e478279061213b906033908f908f908f908f908f9060040161718d565b60206040518083038186803b15801561215357600080fd5b505af4158015612167573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061218b919061655d565b9450603373d72345330e2da53e215daa3ffbad8f779cd4a7b463990d205290916040518263ffffffff1660e01b81526004018082815260200191505060006040518083038186803b1580156121df57600080fd5b505af41580156121f3573d6000803e3d6000fd5b5050600160745550949b9a5050505050505050505050565b60408051634b071c5760e11b815260336004820152426024820181905291516000928392909173d72345330e2da53e215daa3ffbad8f779cd4a7b49163960e38ae9160448083019287929190829003018186803b15801561226b57600080fd5b505af415801561227f573d6000803e3d6000fd5b5050604080516304c43a9960e31b8152603360048201528515156024820152905173d72345330e2da53e215daa3ffbad8f779cd4a7b49350632621d4c892506044808301926000929190829003018186803b1580156122dd57600080fd5b505af41580156122f1573d6000803e3d6000fd5b505050506122fd616262565b61230686615492565b905060606123138761557e565b6040517f8120e3d1000000000000000000000000000000000000000000000000000000008152909150730e535aa06e9e937f4837f43f1060545f0a4ed97c90638120e3d19061236b90603390869086906004016170b1565b60006040518083038186803b15801561238357600080fd5b505af4158015612397573d6000803e3d6000fd5b50506040517ff2ab44c2000000000000000000000000000000000000000000000000000000008152730e535aa06e9e937f4837f43f1060545f0a4ed97c925063f2ab44c291506123f09060339086908b90600401617123565b60006040518083038186803b15801561240857600080fd5b505af415801561241c573d6000803e3d6000fd5b50506040517f4af2e327000000000000000000000000000000000000000000000000000000008152730e535aa06e9e937f4837f43f1060545f0a4ed97c9250634af2e327915061247390603390869060040161709c565b60006040518083038186803b15801561248b57600080fd5b505af415801561249f573d6000803e3d6000fd5b505050506124cd82610160015163ffffffff168360000151888560e001518660600151876101a001516155f0565b94505050603373d72345330e2da53e215daa3ffbad8f779cd4a7b463990d205290916040518263ffffffff1660e01b81526004018082815260200191505060006040518083038186803b15801561252357600080fd5b505af4158015612537573d6000803e3d6000fd5b50505050505092915050565b600061254f60336152cd565b90506001600160a01b038116156125bc57806001600160a01b03166125726152f7565b6001600160a01b0316146125b75760405162461bcd60e51b81526004018080602001828103825260218152602001806172236021913960400191505060405180910390fd5b612615565b6036546001600160a01b03166125d06152f7565b6001600160a01b0316146126155760405162461bcd60e51b81526004018080602001828103825260218152602001806172aa6021913960400191505060405180910390fd5b604080517fc01fa80800000000000000000000000000000000000000000000000000000000815260336004820152602481018590526001600160a01b0384166044820152905173d72345330e2da53e215daa3ffbad8f779cd4a7b49163c01fa808916064808301926000929190829003018186803b158015610c0857600080fd5b603360000160029054906101000a90046001600160a01b03166001600160a01b0316634831d32f6040518163ffffffff1660e01b815260040160206040518083038186803b1580156126e757600080fd5b505afa1580156126fb573d6000803e3d6000fd5b505050506040513d602081101561271157600080fd5b505115612758576040805162461bcd60e51b815260206004820152601060248201526f1d5b9a5d995c9cd9481cd95d1d1b195960821b604482015290519081900360640190fd5b60408051634b071c5760e11b815260336004820152426024820181905291516000929173d72345330e2da53e215daa3ffbad8f779cd4a7b49163960e38ae916044808201928792909190829003018186803b1580156127b657600080fd5b505af41580156127ca573d6000803e3d6000fd5b5050604080516304c43a9960e31b8152603360048201528515156024820152905173d72345330e2da53e215daa3ffbad8f779cd4a7b49350632621d4c892506044808301926000929190829003018186803b15801561282857600080fd5b505af415801561283c573d6000803e3d6000fd5b5050505060026074541415612898576040805162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604482015290519081900360640190fd5b600260745560335460ff166128bf5760405162461bcd60e51b81526004016102e590616de5565b73d72345330e2da53e215daa3ffbad8f779cd4a7b463dcaf674960336128e36152f7565b866040518463ffffffff1660e01b81526004016129029392919061700b565b60006040518083038186803b15801561291a57600080fd5b505af415801561292e573d6000803e3d6000fd5b50506001607455505060408051634c86902960e11b815260336004820152905173d72345330e2da53e215daa3ffbad8f779cd4a7b49163990d2052916024808301926000929190829003018186803b158015610c0857600080fd5b603360000160029054906101000a90046001600160a01b03166001600160a01b0316634831d32f6040518163ffffffff1660e01b815260040160206040518083038186803b1580156129da57600080fd5b505afa1580156129ee573d6000803e3d6000fd5b505050506040513d6020811015612a0457600080fd5b505115612a4b576040805162461bcd60e51b815260206004820152601060248201526f1d5b9a5d995c9cd9481cd95d1d1b195960821b604482015290519081900360640190fd5b60335460ff161580612a645750603354610100900460ff165b15612aaf57612a7360336152cd565b6001600160a01b0316612a846152f7565b6001600160a01b031614612aaa5760405162461bcd60e51b81526004016102e590616e95565b612ae9565b6036546001600160a01b0316612ac36152f7565b6001600160a01b031614612ae95760405162461bcd60e51b81526004016102e590616e53565b6040517fbb89ac2400000000000000000000000000000000000000000000000000000000815273d72345330e2da53e215daa3ffbad8f779cd4a7b49063bb89ac2490611982906033908990899089908990899060040161704e565b612b4e60336152cd565b6001600160a01b0316612b5f6152f7565b6001600160a01b031614612bba576040805162461bcd60e51b815260206004820152601860248201527f6f6e6c79206f70657261746f7220697320616c6c6f7765640000000000000000604482015290519081900360640190fd5b6040517febf1f2680000000000000000000000000000000000000000000000000000000081526033600482018181526024830185905273d72345330e2da53e215daa3ffbad8f779cd4a7b49263ebf1f268929186918691906044018261012080828437600081840152601f19601f820116905080830192505050935050505060006040518083038186803b15801561149157600080fd5b6036546001600160a01b0316612c656152f7565b6001600160a01b031614612cc0576040805162461bcd60e51b815260206004820152601860248201527f6f6e6c7920676f7665726e6f7220697320616c6c6f7765640000000000000000604482015290519081900360640190fd5b6040517ff20d68960000000000000000000000000000000000000000000000000000000081526033600482018181526024830185905273d72345330e2da53e215daa3ffbad8f779cd4a7b49263f20d6896929186918691906044018261012080828437600081840152601f19601f820116905080830192505050935050505060006040518083038186803b15801561149157600080fd5b60026074541415612daf576040805162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604482015290519081900360640190fd5b6002607481905550603360000160029054906101000a90046001600160a01b03166001600160a01b0316634831d32f6040518163ffffffff1660e01b815260040160206040518083038186803b158015612e0857600080fd5b505afa158015612e1c573d6000803e3d6000fd5b505050506040513d6020811015612e3257600080fd5b505115612e79576040805162461bcd60e51b815260206004820152601060248201526f1d5b9a5d995c9cd9481cd95d1d1b195960821b604482015290519081900360640190fd5b60408051634b071c5760e11b815260336004820152426024820181905291516000929173d72345330e2da53e215daa3ffbad8f779cd4a7b49163960e38ae916044808201928792909190829003018186803b158015612ed757600080fd5b505af4158015612eeb573d6000803e3d6000fd5b5050604080516304c43a9960e31b8152603360048201528515156024820152905173d72345330e2da53e215daa3ffbad8f779cd4a7b49350632621d4c892506044808301926000929190829003018186803b158015612f4957600080fd5b505af4158015612f5d573d6000803e3d6000fd5b50505050836002603373d72345330e2da53e215daa3ffbad8f779cd4a7b463e57d06a8909184612f8b6152f7565b856040518563ffffffff1660e01b815260040180858152602001846001600160a01b03168152602001836001600160a01b0316815260200182815260200194505050505060206040518083038186803b158015612fe757600080fd5b505af4158015612ffb573d6000803e3d6000fd5b505050506040513d602081101561301157600080fd5b505161305a576040805162461bcd60e51b81526020600482015260136024820152723ab730baba3437b934bd32b21031b0b63632b960691b604482015290519081900360640190fd5b600260008881526043602052604090206001015460ff16600481111561307c57fe5b146130995760405162461bcd60e51b81526004016102e590616c18565b6001600160a01b0386166130bf5760405162461bcd60e51b81526004016102e590616f0e565b600085136130df5760405162461bcd60e51b81526004016102e590616be1565b6040517fa54b4d7800000000000000000000000000000000000000000000000000000000815273d72345330e2da53e215daa3ffbad8f779cd4a7b49063a54b4d7890613136906033908b908b908b906004016171bf565b60006040518083038186803b15801561314e57600080fd5b505af4158015613162573d6000803e3d6000fd5b505060408051634c86902960e11b815260336004820152905173d72345330e2da53e215daa3ffbad8f779cd4a7b4955063990d2052945060248083019450600093509091829003018186803b1580156131ba57600080fd5b505af41580156131ce573d6000803e3d6000fd5b5050600160745550505050505050565b60026074541415613236576040805162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604482015290519081900360640190fd5b600260745560335460ff1661325d5760405162461bcd60e51b81526004016102e590616de5565b73d72345330e2da53e215daa3ffbad8f779cd4a7b463d767897e60336110186152f7565b600054610100900460ff168061329a575061329a6157ab565b806132a8575060005460ff16155b6132e35760405162461bcd60e51b815260040180806020018281038252602e8152602001806172cb602e913960400191505060405180910390fd5b600054610100900460ff1615801561330e576000805460ff1961ff0019909116610100171660011790555b73d72345330e2da53e215daa3ffbad8f779cd4a7b463c9ab757160336133326152f7565b89898c8a8a8a6040518963ffffffff1660e01b815260040161335b989796959493929190616fa2565b60006040518083038186803b15801561337357600080fd5b505af4158015613387573d6000803e3d6000fd5b505050508015610c1c576000805461ff001916905550505050505050565b60408051634b071c5760e11b815260336004820152426024820181905291516000929173d72345330e2da53e215daa3ffbad8f779cd4a7b49163960e38ae916044808201928792909190829003018186803b15801561340357600080fd5b505af4158015613417573d6000803e3d6000fd5b5050604080516304c43a9960e31b8152603360048201528515156024820152905173d72345330e2da53e215daa3ffbad8f779cd4a7b49350632621d4c892506044808301926000929190829003018186803b15801561347557600080fd5b505af4158015613489573d6000803e3d6000fd5b505060408051634c86902960e11b815260336004820152905173d72345330e2da53e215daa3ffbad8f779cd4a7b4935063990d205292506024808301926000929190829003018186803b15801561149157600080fd5b6000866134f18363ffffffff166157bc565b6134fc5760046134ff565b60075b73d72345330e2da53e215daa3ffbad8f779cd4a7b463e57d06a86033846135246152f7565b856040518563ffffffff1660e01b815260040180858152602001846001600160a01b03168152602001836001600160a01b0316815260200182815260200194505050505060206040518083038186803b15801561358057600080fd5b505af4158015613594573d6000803e3d6000fd5b505050506040513d60208110156135aa57600080fd5b50516135f3576040805162461bcd60e51b81526020600482015260136024820152723ab730baba3437b934bd32b21031b0b63632b960691b604482015290519081900360640190fd5b60408051634b071c5760e11b815260336004820152426024820181905291516000929173d72345330e2da53e215daa3ffbad8f779cd4a7b49163960e38ae916044808201928792909190829003018186803b15801561365157600080fd5b505af4158015613665573d6000803e3d6000fd5b5050604080516304c43a9960e31b8152603360048201528515156024820152905173d72345330e2da53e215daa3ffbad8f779cd4a7b49350632621d4c892506044808301926000929190829003018186803b1580156136c357600080fd5b505af41580156136d7573d6000803e3d6000fd5b505050506001600160a01b038b166137015760405162461bcd60e51b81526004016102e590616f0e565b8961371e5760405162461bcd60e51b81526004016102e590616be1565b4288101561373e5760405162461bcd60e51b81526004016102e590616d77565b61374c8c8c8c8c8b8b6155f0565b9450603373d72345330e2da53e215daa3ffbad8f779cd4a7b463990d205290916040518263ffffffff1660e01b81526004018082815260200191505060006040518083038186803b1580156137a057600080fd5b505af41580156137b4573d6000803e3d6000fd5b5050505050505050979650505050505050565b6002607454141561381f576040805162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604482015290519081900360640190fd5b6002607481905550603360000160029054906101000a90046001600160a01b03166001600160a01b0316634831d32f6040518163ffffffff1660e01b815260040160206040518083038186803b15801561387857600080fd5b505afa15801561388c573d6000803e3d6000fd5b505050506040513d60208110156138a257600080fd5b5051156138e9576040805162461bcd60e51b815260206004820152601060248201526f1d5b9a5d995c9cd9481cd95d1d1b195960821b604482015290519081900360640190fd5b81600173d72345330e2da53e215daa3ffbad8f779cd4a7b463e57d06a86033846139116152f7565b856040518563ffffffff1660e01b815260040180858152602001846001600160a01b03168152602001836001600160a01b0316815260200182815260200194505050505060206040518083038186803b15801561396d57600080fd5b505af4158015613981573d6000803e3d6000fd5b505050506040513d602081101561399757600080fd5b50516139e0576040805162461bcd60e51b81526020600482015260136024820152723ab730baba3437b934bd32b21031b0b63632b960691b604482015290519081900360640190fd5b600260008681526043602052604090206001015460ff166004811115613a0257fe5b14613a1f5760405162461bcd60e51b81526004016102e590616c18565b6001600160a01b038416613a455760405162461bcd60e51b81526004016102e590616f0e565b60008313613a655760405162461bcd60e51b81526004016102e590616be1565b6040517f028c4ff600000000000000000000000000000000000000000000000000000000815273d72345330e2da53e215daa3ffbad8f779cd4a7b49063028c4ff690613abc906033908990899089906004016171bf565b60006040518083038186803b1580156131ba57600080fd5b60008060008060008060008060008a6033600f01548110613b32576040805162461bcd60e51b81526020600482015260136024820152721c195c9c195d1d585b081b9bdd08195e1a5cdd606a1b604482015290519081900360640190fd5b60008c81526043602090815260408083206001600160a01b038f1684526034810190925282209091613b638361526d565b82546001840154909e509c509050613b7c838f8361580f565b9a50613b89838f83615859565b9950613b96838f83615890565b9850613ba3838f836158e9565b9750613bb0838f83615901565b9650613bbd838f83615947565b9550613bc9838f615963565b9450505050509295985092959850929598565b600080806001600160a01b038716613c3b576040805162461bcd60e51b815260206004820152600e60248201527f696e76616c696420747261646572000000000000000000000000000000000000604482015290519081900360640190fd5b85613c8d576040805162461bcd60e51b815260206004820152600e60248201527f696e76616c696420616d6f756e74000000000000000000000000000000000000604482015290519081900360640190fd5b600260008981526043602052604090206001015460ff166004811115613caf57fe5b14613ceb5760405162461bcd60e51b81526004018080602001828103825260238152602001806172876023913960400191505060405180910390fd5b604080517f1c137ad100000000000000000000000000000000000000000000000000000000815260336004820152602481018a90526001600160a01b03808a166044830152606482018990528716608482015263ffffffff861660a48201529051735661ebd2dca2a5f57c9bae32faa53e439478fac291631c137ad19160c4808301926060929190829003018186803b158015613d8757600080fd5b505af4158015613d9b573d6000803e3d6000fd5b505050506040513d6060811015613db157600080fd5b5080516020820151604090920151909a919950975095505050505050565b60026074541415613e27576040805162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604482015290519081900360640190fd5b600260745560408051634b071c5760e11b815260336004820152426024820181905291516000929173d72345330e2da53e215daa3ffbad8f779cd4a7b49163960e38ae916044808201928792909190829003018186803b158015613e8a57600080fd5b505af4158015613e9e573d6000803e3d6000fd5b5050604080516304c43a9960e31b8152603360048201528515156024820152905173d72345330e2da53e215daa3ffbad8f779cd4a7b49350632621d4c892506044808301926000929190829003018186803b158015613efc57600080fd5b505af4158015613f10573d6000803e3d6000fd5b505060335460ff169150613f3890505760405162461bcd60e51b81526004016102e590616de5565b603360000160029054906101000a90046001600160a01b03166001600160a01b0316634831d32f6040518163ffffffff1660e01b815260040160206040518083038186803b158015613f8957600080fd5b505afa158015613f9d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613fc19190616489565b15613fee57613fd260336004615a1f565b613fee5760405162461bcd60e51b81526004016102e590616e1c565b73d72345330e2da53e215daa3ffbad8f779cd4a7b4632d7966d860336140126152f7565b87876040518563ffffffff1660e01b8152600401614033949392919061702a565b60006040518083038186803b15801561404b57600080fd5b505af415801561405f573d6000803e3d6000fd5b505060408051634c86902960e11b815260336004820152905173d72345330e2da53e215daa3ffbad8f779cd4a7b4935063990d205292506024808301926000929190829003018186803b1580156140b557600080fd5b505af41580156140c9573d6000803e3d6000fd5b50506001607455505050505050565b603354600090819060ff16614134576040805162461bcd60e51b815260206004820152601360248201527f706f6f6c206973206e6f742072756e6e696e6700000000000000000000000000604482015290519081900360640190fd5b600061418a603360040160009054906101000a90046001600160a01b03166001600160a01b03166318160ddd6040518163ffffffff1660e01b815260040160206040518083038186803b15801561119957600080fd5b905060008513801561419a575083155b1561424257604080517feeffc15d000000000000000000000000000000000000000000000000000000008152603360048201526024810183905260448101879052905173e594234baed1230cd1ab29544793ff0631b2fba79163eeffc15d916064808301926080929190829003018186803b15801561421857600080fd5b505af415801561422c573d6000803e3d6000fd5b505050506040513d608081101561127e57600080fd5b841580156142505750600084135b1561134c57604080517f68dddaa6000000000000000000000000000000000000000000000000000000008152603360048201526024810183905260448101869052905173e594234baed1230cd1ab29544793ff0631b2fba7916368dddaa6916064808301926080929190829003018186803b1580156142ce57600080fd5b505af41580156142e2573d6000803e3d6000fd5b505050506040513d608081101561134057600080fd5b600080826033600f0154811061434b576040805162461bcd60e51b81526020600482015260136024820152721c195c9c195d1d585b081b9bdd08195e1a5cdd606a1b604482015290519081900360640190fd5b600084815260436020526040902061436560308201615321565b93506002600182015460ff16600481111561437c57fe5b1461438b5780602b0154614397565b61439781603001615321565b92505050915091565b80600273d72345330e2da53e215daa3ffbad8f779cd4a7b463e57d06a86033846143c86152f7565b856040518563ffffffff1660e01b815260040180858152602001846001600160a01b03168152602001836001600160a01b0316815260200182815260200194505050505060206040518083038186803b15801561442457600080fd5b505af4158015614438573d6000803e3d6000fd5b505050506040513d602081101561444e57600080fd5b5051614497576040805162461bcd60e51b81526020600482015260136024820152723ab730baba3437b934bd32b21031b0b63632b960691b604482015290519081900360640190fd5b600260745414156144ef576040805162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604482015290519081900360640190fd5b60026074556001600160a01b03831661451a5760405162461bcd60e51b81526004016102e590616f0e565b600460008581526043602052604090206001015460ff16600481111561453c57fe5b146145595760405162461bcd60e51b81526004016102e590616f45565b6040517fb2f6decc00000000000000000000000000000000000000000000000000000000815273d72345330e2da53e215daa3ffbad8f779cd4a7b49063b2f6decc906145ae9060339088908890600401617147565b60006040518083038186803b1580156140b557600080fd5b6036546001600160a01b03166145da6152f7565b6001600160a01b031614614635576040805162461bcd60e51b815260206004820152601860248201527f6f6e6c7920676f7665726e6f7220697320616c6c6f7765640000000000000000604482015290519081900360640190fd5b6040517fe3e2898600000000000000000000000000000000000000000000000000000000815260336004820181815273d72345330e2da53e215daa3ffbad8f779cd4a7b49263e3e289869291859160240182608080828437600081840152601f19601f8201169050808301925050509250505060006040518083038186803b1580156146c057600080fd5b505af41580156146d4573d6000803e3d6000fd5b5050505050565b6146e560336152cd565b6001600160a01b03166146f66152f7565b6001600160a01b031614614751576040805162461bcd60e51b815260206004820152601860248201527f6f6e6c79206f70657261746f7220697320616c6c6f7765640000000000000000604482015290519081900360640190fd5b60335460ff16156147745760405162461bcd60e51b81526004016102e590616c75565b6040517f4e74d93000000000000000000000000000000000000000000000000000000000815273d72345330e2da53e215daa3ffbad8f779cd4a7b490634e74d930906147c590603390600401616a83565b60006040518083038186803b15801561158e57600080fd5b6147e760336152cd565b6001600160a01b03166147f86152f7565b6001600160a01b031614614853576040805162461bcd60e51b815260206004820152601860248201527f6f6e6c79206f70657261746f7220697320616c6c6f7765640000000000000000604482015290519081900360640190fd5b604080517fbdc5e84900000000000000000000000000000000000000000000000000000000815260336004820152905173d72345330e2da53e215daa3ffbad8f779cd4a7b49163bdc5e849916024808301926000929190829003018186803b15801561158e57600080fd5b600060026074541415614918576040805162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604482015290519081900360640190fd5b6002607481905550603360000160029054906101000a90046001600160a01b03166001600160a01b0316634831d32f6040518163ffffffff1660e01b815260040160206040518083038186803b15801561497157600080fd5b505afa158015614985573d6000803e3d6000fd5b505050506040513d602081101561499b57600080fd5b5051156149e2576040805162461bcd60e51b815260206004820152601060248201526f1d5b9a5d995c9cd9481cd95d1d1b195960821b604482015290519081900360640190fd5b60408051634b071c5760e11b815260336004820152426024820181905291516000929173d72345330e2da53e215daa3ffbad8f779cd4a7b49163960e38ae916044808201928792909190829003018186803b158015614a4057600080fd5b505af4158015614a54573d6000803e3d6000fd5b5050604080516304c43a9960e31b8152603360048201528515156024820152905173d72345330e2da53e215daa3ffbad8f779cd4a7b49350632621d4c892506044808301926000929190829003018186803b158015614ab257600080fd5b505af4158015614ac6573d6000803e3d6000fd5b50505050614adb85614ad66152f7565b615a82565b614af75760405162461bcd60e51b81526004016102e590616ed7565b600260008681526043602052604090206001015460ff166004811115614b1957fe5b14614b365760405162461bcd60e51b81526004016102e590616c18565b6001600160a01b038416614b5c5760405162461bcd60e51b81526004016102e590616f0e565b6001600160a01b038416301415614b855760405162461bcd60e51b81526004016102e590616b3c565b735661ebd2dca2a5f57c9bae32faa53e439478fac263e810d619603387614baa6152f7565b886040518563ffffffff1660e01b8152600401614bca9493929190617166565b60206040518083038186803b158015614be257600080fd5b505af4158015614bf6573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190614c1a919061655d565b9250603373d72345330e2da53e215daa3ffbad8f779cd4a7b463990d205290916040518263ffffffff1660e01b81526004018082815260200191505060006040518083038186803b158015614c6e57600080fd5b505af4158015614c82573d6000803e3d6000fd5b50506001607455509295945050505050565b60026074541415614cec576040805162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604482015290519081900360640190fd5b6002607455600360008281526043602052604090206001015460ff166004811115614d1357fe5b14614d305760405162461bcd60e51b81526004016102e590616d1a565b73d72345330e2da53e215daa3ffbad8f779cd4a7b4632ef9dab9603383614d556152f7565b6040518463ffffffff1660e01b815260040161103793929190617147565b73d72345330e2da53e215daa3ffbad8f779cd4a7b463299cbfda6033614d976152f7565b6040518363ffffffff1660e01b815260040180838152602001826001600160a01b031681526020019250505060006040518083038186803b15801561158e57600080fd5b60408051634b071c5760e11b815260336004820152426024820181905291516001929173d72345330e2da53e215daa3ffbad8f779cd4a7b49163960e38ae91604480820192600092909190829003018186803b158015614e3a57600080fd5b505af4158015614e4e573d6000803e3d6000fd5b5050604080516304c43a9960e31b8152603360048201528515156024820152905173d72345330e2da53e215daa3ffbad8f779cd4a7b49350632621d4c892506044808301926000929190829003018186803b158015614eac57600080fd5b505af4158015614ec0573d6000803e3d6000fd5b50506036546001600160a01b03169150614eda90506152f7565b6001600160a01b031614614f35576040805162461bcd60e51b815260206004820152601860248201527f6f6e6c7920676f7665726e6f7220697320616c6c6f7765640000000000000000604482015290519081900360640190fd5b6000831215614f8b576040805162461bcd60e51b815260206004820152601960248201527f6e6567617469766520736574746c656d656e7420707269636500000000000000604482015290519081900360640190fd5b614f936162de565b5060408051808201825284815242602080830191825260008881526043909152838120835160068201819055925160078201819055600480830194909455600582015584516301ec95cd60e51b815260339381019390935260248301899052935192939273d72345330e2da53e215daa3ffbad8f779cd4a7b492633d92b9a0926044808301939192829003018186803b15801561502f57600080fd5b505af4158015615043573d6000803e3d6000fd5b505060408051634c86902960e11b815260336004820152905173d72345330e2da53e215daa3ffbad8f779cd4a7b4955063990d2052945060248083019450600093509091829003018186803b15801561509b57600080fd5b505af41580156150af573d6000803e3d6000fd5b5050505050505050565b600080603373e594234baed1230cd1ab29544793ff0631b2fba763c12b64d590916040518263ffffffff1660e01b815260040180828152602001915050604080518083038186803b15801561510d57600080fd5b505af4158015615121573d6000803e3d6000fd5b505050506040513d604081101561513757600080fd5b50805160209091015190939092509050565b6036546001600160a01b031661515d6152f7565b6001600160a01b0316146151b8576040805162461bcd60e51b815260206004820152601860248201527f6f6e6c7920676f7665726e6f7220697320616c6c6f7765640000000000000000604482015290519081900360640190fd5b603373d72345330e2da53e215daa3ffbad8f779cd4a7b4633d792b6e9091868686866040518663ffffffff1660e01b81526004018086815260200185815260200184600960200280828437600083820152601f01601f191690910190508361012080828437600083820152601f01601f191690910190508261012080828437600081840152601f19601f8201169050808301925050509550505050505060006040518083038186803b15801561509b57600080fd5b60006002600183015460ff16600481111561528457fe5b14615293576008820154615299565b60068201545b92915050565b60006002600183015460ff1660048111156152b657fe5b146152c5576008820154615299565b506004015490565b600081600c01544211156152e2576000615299565b5060018101546001600160a01b03165b919050565b3390565b600081600c0154421115615310576000615299565b50600201546001600160a01b031690565b600061529982615b50565b6060828211615382576040805162461bcd60e51b815260206004820152601e60248201527f626567696e2073686f756c64206265206c6f776572207468616e20656e640000604482015290519081900360640190fd5b600061538d85615321565b905080841061539c5750610f6e565b60006153a88483615b54565b90506153b48186615b6a565b67ffffffffffffffff811180156153ca57600080fd5b506040519080825280602002602001820160405280156153f4578160200160208202803683370190505b509250845b818110156154405761540b8782615bc7565b846154168389615b6a565b8151811061542057fe5b6001600160a01b03909216602092830291909101909101526001016153f9565b5050509392505050565b6000600160ff1b821061548e5760405162461bcd60e51b81526004018080602001828103825260288152602001806173416028913960400191505060405180910390fd5b5090565b61549a616262565b610100825110156154bd5760405162461bcd60e51b81526004016102e590616baa565b6014820151815260288201516020820152603c82015160408083019190915260508301516060808401919091526064840151608080850191909152608485015160a08086019190915260a486015160c08087019190915260c487015160e087015260e4870151610100870152610104870151610120870152610124909601519586901c61014086015263ffffffff9086901c81166101608601529085901c81166101808501529084901c81166101a084015292901c9091166101c082015290565b606061015e825110156155a35760405162461bcd60e51b81526004016102e590616baa565b61013e82015161015e830151610124840151604051601882901a9160191a906155d690859085908590859060200161690c565b604051602081830303815290604052945050505050919050565b6000603360000160029054906101000a90046001600160a01b03166001600160a01b0316634831d32f6040518163ffffffff1660e01b815260040160206040518083038186803b15801561564357600080fd5b505afa158015615657573d6000803e3d6000fd5b505050506040513d602081101561566d57600080fd5b5051156156b4576040805162461bcd60e51b815260206004820152601060248201526f1d5b9a5d995c9cd9481cd95d1d1b195960821b604482015290519081900360640190fd5b600260008881526043602052604090206001015460ff1660048111156156d657fe5b146156f35760405162461bcd60e51b81526004016102e590616c18565b6040517f443d6cae000000000000000000000000000000000000000000000000000000008152735661ebd2dca2a5f57c9bae32faa53e439478fac29063443d6cae90615750906033908b908b908b908b908b908b906004016171e3565b60206040518083038186803b15801561576857600080fd5b505af415801561577c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906157a0919061655d565b979650505050505050565b60006157b630615bd3565b15905090565b6000806157c883615bd9565b905060006157d584615be4565b90508180156157e15750805b156157fe5760405162461bcd60e51b81526004016102e590616ce3565b81806158075750805b949350505050565b60008061581c8585615bf7565b1561583f5761583a8560120154615834878787615c18565b90615c5b565b615842565b60005b905061161681615853878787615859565b90615cc0565b60006158076158688585615d25565b6001600160a01b03851660009081526034870160205260409020600101546158349085615d5f565b600061589d848484615859565b905060008113156158df5760006158b48585615bf7565b156158c35784602f01546158c9565b84602e01545b90506158d782826001615d94565b915050610f6e565b5060009392505050565b6000806158f785858561580f565b1215949350505050565b60008061590e8585615bf7565b1561592b576159268560120154615834878787615db2565b61592e565b60005b90508061593c868686615859565b121595945050505050565b6000806159548585615bf7565b1561592b57846012015461592e565b600082600c0154600014156159bf576040805162461bcd60e51b815260206004820152601c60248201527f696e697469616c4d617267696e52617465206973206e6f742073657400000000604482015290519081900360640190fd5b60006159e084600c0154670de0b6b3a7640000615dea90919063ffffffff16565b6001600160a01b03841660009081526034860160205260409020600201549091508015615a0d5780615a13565b60358501545b90506116168183615e30565b600f820154600090815b81811015615a7757836004811115615a3d57fe5b600082815260108701602052604090206001015460ff166004811115615a5f57fe5b14615a6f57600092505050615299565b600101615a29565b506001949350505050565b6000828152604360205260408120603901615a9c81615321565b615b46576033546040517f6ba42aaa000000000000000000000000000000000000000000000000000000008152620100009091046001600160a01b031690636ba42aaa90615aee908690600401616953565b60206040518083038186803b158015615b0657600080fd5b505afa158015615b1a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190615b3e9190616489565b915050615299565b615b3e8184615e3f565b5490565b6000818310615b635781610f6e565b5090919050565b600082821115615bc1576040805162461bcd60e51b815260206004820152601e60248201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604482015290519081900360640190fd5b50900390565b6000610f6e8383615e54565b3b151590565b630800000016151590565b600080615bf083615eb8565b1392915050565b6001600160a01b031660009081526034909101602052604090206001015490565b600c8301546001600160a01b0383166000908152603485016020526040812060010154909161580791615c569190615c509086615d5f565b90615d5f565b615ecc565b6000828201818312801590615c705750838112155b80615c855750600083128015615c8557508381125b610f6e5760405162461bcd60e51b81526004018080602001828103825260218152602001806172666021913960400191505060405180910390fd5b6000818303818312801590615cd55750838113155b80615cea5750600083128015615cea57508381135b610f6e5760405162461bcd60e51b81526004018080602001828103825260248152602001806173696024913960400191505060405180910390fd5b6001600160a01b03811660009081526034830160205260408120600b840154600182015461580791615d579190615d5f565b825490615cc0565b6000670de0b6b3a7640000615d85615d778585615ee4565b670de0b6b3a7640000615f8d565b81615d8c57fe5b059392505050565b6000615807615da38585615ee4565b670de0b6b3a76400008461600c565b600d8301546001600160a01b0383166000908152603485016020526040812060010154909161580791615c569190615c509086615d5f565b600080821215615e0b57615dfd826160e9565b9150615e08836160e9565b92505b610f6e82615e2a615e2486670de0b6b3a7640000615ee4565b85615f8d565b906160f6565b6000818312615b635781610f6e565b6000610f6e836001600160a01b0384166161ae565b81546000908210615e965760405162461bcd60e51b81526004018080602001828103825260228152602001806172446022913960400191505060405180910390fd5b826000018281548110615ea557fe5b9060005260206000200154905092915050565b60071c620fffff16662386f26fc100000290565b60008082121561548e57615edf826160e9565b615299565b600082615ef357506000615299565b82600019148015615f075750600160ff1b82145b15615f435760405162461bcd60e51b815260040180806020018281038252602781526020018061731a6027913960400191505060405180910390fd5b82820282848281615f5057fe5b0514610f6e5760405162461bcd60e51b815260040180806020018281038252602781526020018061731a6027913960400191505060405180910390fd5b6000808213615fe3576040805162461bcd60e51b815260206004820152601f60248201527f726f756e6448616c665570206f6e6c7920737570706f7274732079203e203000604482015290519081900360640190fd5b60008312615fff57615ff88360028405615c5b565b9050615299565b610f6e8360028405615cc0565b600082616060576040805162461bcd60e51b815260206004820152601060248201527f6469766973696f6e206279207a65726f00000000000000000000000000000000604482015290519081900360640190fd5b61606a84846160f6565b905082848161607557fe5b0761607f57610f6e565b600061608b85856161c6565b9050600083600181111561609b57fe5b1480156160a55750805b156160b8576160b5826001615c5b565b91505b60018360018111156160c657fe5b1480156160d1575080155b156160e157611616826001615cc0565b509392505050565b6000615299600083615cc0565b60008161614a576040805162461bcd60e51b815260206004820181905260248201527f5369676e6564536166654d6174683a206469766973696f6e206279207a65726f604482015290519081900360640190fd5b8160001914801561615e5750600160ff1b83145b1561619a5760405162461bcd60e51b81526004018080602001828103825260218152602001806172f96021913960400191505060405180910390fd5b60008284816161a557fe5b05949350505050565b60009081526001919091016020526040902054151590565b60008215806161d3575081155b156161e057506001615299565b501860ff1d1590565b604051806105400160405280602a906020820280368337509192915050565b6040518060e001604052806007906020820280368337509192915050565b6040518060a001604052806005906020820280368337509192915050565b6040518060c001604052806006906020820280368337509192915050565b604080516101e081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e08101829052610100810182905261012081018290526101408101829052610160810182905261018081018290526101a081018290526101c081019190915290565b604051806040016040528060008152602001600081525090565b80356001600160a01b03811681146152f257600080fd5b80610120810183101561529957600080fd5b803563ffffffff811681146152f257600080fd5b600060208284031215616346578081fd5b610f6e826162f8565b60008060008060008060a08789031215616367578182fd5b616370876162f8565b955061637e602088016162f8565b945060408701359350616393606088016162f8565b9250608087013567ffffffffffffffff808211156163af578384fd5b818901915089601f8301126163c2578384fd5b8135818111156163d0578485fd5b8a60208285010111156163e1578485fd5b6020830194508093505050509295509295509295565b60008060008060006104a0868803121561640f578081fd5b616418866162f8565b9450616427876020880161630f565b935061643787610140880161630f565b925061644787610260880161630f565b915061645787610380880161630f565b90509295509295909350565b600060808284031215616474578081fd5b82608083011115616483578081fd5b50919050565b60006020828403121561649a578081fd5b81518015158114610f6e578182fd5b600080604083850312156164bb578182fd5b823567ffffffffffffffff808211156164d2578384fd5b818501915085601f8301126164e5578384fd5b8135818111156164f157fe5b6040516020601f8301601f191682018101848111838210171561651057fe5b6040528282528483018101891015616526578687fd5b8281860182840137918101820195909552939694909301359450505050565b600060208284031215616556578081fd5b5035919050565b60006020828403121561656e578081fd5b5051919050565b60008060408385031215616587578182fd5b50508035926020909101359150565b600080604083850312156165a8578182fd5b823591506165b8602084016162f8565b90509250929050565b60008060008060008060c087890312156165d9578182fd5b863595506165e9602088016162f8565b94506165f7604088016162f8565b9350606087013592506080870135915060a087013590509295509295509295565b60008060006060848603121561662c578081fd5b8335925061663c602085016162f8565b9150604084013590509250925092565b600080600080600060a08688031215616663578283fd5b85359450616673602087016162f8565b935060408601359250616688606087016162f8565b915061645760808701616321565b600080600080600080600060e0888a0312156166b0578485fd5b873596506166c0602089016162f8565b95506040880135945060608801359350608088013592506166e360a089016162f8565b91506166f160c08901616321565b905092959891949750929550565b6000806101408385031215616712578182fd5b823591506165b8846020850161630f565b6000806000806103808587031215616739578182fd5b8435935061674a866020870161630f565b925061675a86610140870161630f565b915061676a86610260870161630f565b905092959194509250565b600080600060608486031215616789578081fd5b505081359360208301359350604090920135919050565b6001600160a01b0316815260200190565b6001600160a01b03169052565b8060005b60098110156115a25781358452602093840193909101906001016167c2565b8060005b60068110156115a25781518452602093840193909101906001016167e5565b61680f8282516167b1565b602081015161682160208401826167b1565b50604081015161683460408401826167b1565b50606081015161684760608401826167b1565b50608081015161685a60808401826167b1565b5060a081015160a083015260c081015160c083015260e081015160e0830152610100808201518184015250610120808201518184015250610140808201516168a4828501826168fe565b5050610160808201516168b9828501826168f4565b5050610180808201516168ce828501826168f4565b50506101a0808201516168e3828501826168f4565b50506101c0808201516115a2828501825b63ffffffff169052565b67ffffffffffffffff169052565b93845260208401929092527fff0000000000000000000000000000000000000000000000000000000000000060f891821b8116604085015291901b16604182015260420190565b6001600160a01b0391909116815260200190565b6020808252825182820181905260009190848201906040850190845b818110156169a85783516001600160a01b031683529284019291840191600101616983565b50909695505050505050565b6000610280820190508615158252602086151581840152604083018660005b60078110156169f5576169e78383516167a0565b9250908301906001016169d3565b50505061012083018560005b6005811015616a1e57815183529183019190830190600101616a01565b505050506110dd6101c08301846167e1565b610580810160058510616a3f57fe5b84825260206001600160a01b03851681840152604083018460005b602a811015616a7757815183529183019190830190600101616a5a565b50505050949350505050565b90815260200190565b9182521515602082015260400190565b918252602082015260400190565b9283526020830191909152604082015260600190565b9889526020890197909752604088019590955260608701939093526080860191909152151560a0850152151560c0840152151560e08301526101008201526101200190565b60208082526013908201527f696e76616c6964206c696d697420707269636500000000000000000000000000604082015260600190565b60208082526014908201527f63616e6e6f74206c697175696461746520414d4d000000000000000000000000604082015260600190565b6020808252601e908201527f7461726765744c65766572616765206d75737420626520696e74656765720000604082015260600190565b6020808252600b908201527f62726f6b656e2064617461000000000000000000000000000000000000000000604082015260600190565b6020808252600e908201527f696e76616c696420616d6f756e74000000000000000000000000000000000000604082015260600190565b60208082526023908201527f70657270657475616c2073686f756c6420626520696e204e4f524d414c20737460408201527f6174650000000000000000000000000000000000000000000000000000000000606082015260800190565b6020808252600f908201527f616c72656164792072756e6e696e670000000000000000000000000000000000604082015260600190565b6020808252601a908201527f7461726765744c65766572616765206973206e65676174697665000000000000604082015260600190565b6020808252600d908201527f696e76616c696420666c61677300000000000000000000000000000000000000604082015260600190565b60208082526026908201527f70657270657475616c2073686f756c6420626520696e20454d455247454e435960408201527f2073746174650000000000000000000000000000000000000000000000000000606082015260800190565b60208082526011908201527f646561646c696e65206578636565646564000000000000000000000000000000604082015260600190565b6020808252601e908201527f636f6e747261637420646f6573206e6f74206163636570742065746865720000604082015260600190565b60208082526013908201527f706f6f6c206973206e6f742072756e6e696e6700000000000000000000000000604082015260600190565b6020808252601d908201527f616c6c2070657270657475616c206d75737420626520636c6561726564000000604082015260600190565b60208082526022908201527f6f6e6c7920676f7665726e6f722063616e206372656174652070657270657475604082015261185b60f21b606082015260800190565b60208082526022908201527f6f6e6c79206f70657261746f722063616e206372656174652070657270657475604082015261185b60f21b606082015260800190565b60208082526015908201527f63616c6c6572206d757374206265206b65657065720000000000000000000000604082015260600190565b6020808252600e908201527f696e76616c696420747261646572000000000000000000000000000000000000604082015260600190565b60208082526024908201527f70657270657475616c2073686f756c6420626520696e20434c4541524544207360408201527f7461746500000000000000000000000000000000000000000000000000000000606082015260800190565b60008982526001600160a01b03808a1660208401528089166040840152876060840152808716608084015280861660a08401525060e060c08301528260e0830152610100838582850137828401810191909152601f909201601f19160101979650505050505050565b9283526001600160a01b03919091166020830152604082015260600190565b9384526001600160a01b039290921660208401526040830152606082015260800190565b8681526001600160a01b03861660208201526104c0810161707260408301876167be565b6170806101608301866167be565b61708e6102808301856167be565b6157a06103a08301846167be565b8281526102008101610f6e6020830184616804565b600061022085835260206170c781850187616804565b81610200850152845180838601528392505b808310156170f8578583018201518584016102400152918101916170d9565b8083111561710a578361024082870101525b601f01601f191693909301610240019695505050505050565b83815261022081016171386020830185616804565b82610200830152949350505050565b92835260208301919091526001600160a01b0316604082015260600190565b93845260208401929092526001600160a01b03908116604084015216606082015260800190565b95865260208601949094526001600160a01b03928316604086015291166060840152608083015260a082015260c00190565b93845260208401929092526001600160a01b03166040830152606082015260800190565b96875260208701959095526001600160a01b039384166040870152606086019290925260808501521660a083015263ffffffff1660c082015260e0019056fe63616e206f6e6c7920626520696e69746961746564206279206f70657261746f72456e756d657261626c655365743a20696e646578206f7574206f6620626f756e64735369676e6564536166654d6174683a206164646974696f6e206f766572666c6f7770657270657475616c2073686f756c6420626520696e204e4f524d414c20737461746563616e206f6e6c7920626520696e6974696174656420627920676f7665726e6f72496e697469616c697a61626c653a20636f6e747261637420697320616c726561647920696e697469616c697a65645369676e6564536166654d6174683a206469766973696f6e206f766572666c6f775369676e6564536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f7753616665436173743a2076616c756520646f65736e27742066697420696e20616e20696e743235365369676e6564536166654d6174683a207375627472616374696f6e206f766572666c6f77a2646970667358221220a6f3f86a018a8e08362ff80974f6b184d56ee1dcb5a1c82ec38cf113c8cdf1cf64736f6c63430007040033
Age | Block | Fee Address | BC Fee Address | Voting Power | Jailed | Incoming |
---|
Make sure to use the "Vote Down" button for any spammy posts, and the "Vote Up" for interesting conversations.