编译器
0.8.16+commit.07a7930e
文件 1 的 17:Address.sol
pragma solidity ^0.8.1;
library Address {
function isContract(address account) internal view returns (bool) {
return account.code.length > 0;
}
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
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");
}
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");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResult(success, returndata, errorMessage);
}
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResult(success, returndata, errorMessage);
}
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
require(isContract(target), "Address: delegate call to non-contract");
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResult(success, returndata, errorMessage);
}
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
if (returndata.length > 0) {
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
文件 2 的 17:BokkyPooBahsDateTimeLibrary.sol
pragma solidity ^0.8.16;
library BokkyPooBahsDateTimeLibrary {
uint256 constant SECONDS_PER_DAY = 24 * 60 * 60;
uint256 constant SECONDS_PER_HOUR = 60 * 60;
uint256 constant SECONDS_PER_MINUTE = 60;
int256 constant OFFSET19700101 = 2440588;
uint256 constant DOW_MON = 1;
uint256 constant DOW_TUE = 2;
uint256 constant DOW_WED = 3;
uint256 constant DOW_THU = 4;
uint256 constant DOW_FRI = 5;
uint256 constant DOW_SAT = 6;
uint256 constant DOW_SUN = 7;
function _daysFromDate(
uint256 year,
uint256 month,
uint256 day
) internal pure returns (uint256 _days) {
require(year >= 1970);
int256 _year = int256(year);
int256 _month = int256(month);
int256 _day = int256(day);
int256 __days = _day -
32075 +
(1461 * (_year + 4800 + (_month - 14) / 12)) /
4 +
(367 * (_month - 2 - ((_month - 14) / 12) * 12)) /
12 -
(3 * ((_year + 4900 + (_month - 14) / 12) / 100)) /
4 -
OFFSET19700101;
_days = uint256(__days);
}
function _daysToDate(uint256 _days)
internal
pure
returns (
uint256 year,
uint256 month,
uint256 day
)
{
int256 __days = int256(_days);
int256 L = __days + 68569 + OFFSET19700101;
int256 N = (4 * L) / 146097;
L = L - (146097 * N + 3) / 4;
int256 _year = (4000 * (L + 1)) / 1461001;
L = L - (1461 * _year) / 4 + 31;
int256 _month = (80 * L) / 2447;
int256 _day = L - (2447 * _month) / 80;
L = _month / 11;
_month = _month + 2 - 12 * L;
_year = 100 * (N - 49) + _year + L;
year = uint256(_year);
month = uint256(_month);
day = uint256(_day);
}
function timestampFromDate(
uint256 year,
uint256 month,
uint256 day
) internal pure returns (uint256 timestamp) {
timestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY;
}
function timestampFromDateTime(
uint256 year,
uint256 month,
uint256 day,
uint256 hour,
uint256 minute,
uint256 second
) internal pure returns (uint256 timestamp) {
timestamp =
_daysFromDate(year, month, day) *
SECONDS_PER_DAY +
hour *
SECONDS_PER_HOUR +
minute *
SECONDS_PER_MINUTE +
second;
}
function timestampToDate(uint256 timestamp)
internal
pure
returns (
uint256 year,
uint256 month,
uint256 day
)
{
(year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY);
}
function timestampToDateTime(uint256 timestamp)
internal
pure
returns (
uint256 year,
uint256 month,
uint256 day,
uint256 hour,
uint256 minute,
uint256 second
)
{
(year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY);
uint256 secs = timestamp % SECONDS_PER_DAY;
hour = secs / SECONDS_PER_HOUR;
secs = secs % SECONDS_PER_HOUR;
minute = secs / SECONDS_PER_MINUTE;
second = secs % SECONDS_PER_MINUTE;
}
function isValidDate(
uint256 year,
uint256 month,
uint256 day
) internal pure returns (bool valid) {
if (year >= 1970 && month > 0 && month <= 12) {
uint256 daysInMonth = _getDaysInMonth(year, month);
if (day > 0 && day <= daysInMonth) {
valid = true;
}
}
}
function isValidDateTime(
uint256 year,
uint256 month,
uint256 day,
uint256 hour,
uint256 minute,
uint256 second
) internal pure returns (bool valid) {
if (isValidDate(year, month, day)) {
if (hour < 24 && minute < 60 && second < 60) {
valid = true;
}
}
}
function isLeapYear(uint256 timestamp) internal pure returns (bool leapYear) {
uint256 year;
uint256 month;
uint256 day;
(year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY);
leapYear = _isLeapYear(year);
}
function _isLeapYear(uint256 year) internal pure returns (bool leapYear) {
leapYear = ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
}
function isWeekDay(uint256 timestamp) internal pure returns (bool weekDay) {
weekDay = getDayOfWeek(timestamp) <= DOW_FRI;
}
function isWeekEnd(uint256 timestamp) internal pure returns (bool weekEnd) {
weekEnd = getDayOfWeek(timestamp) >= DOW_SAT;
}
function getDaysInMonth(uint256 timestamp)
internal
pure
returns (uint256 daysInMonth)
{
uint256 year;
uint256 month;
uint256 day;
(year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY);
daysInMonth = _getDaysInMonth(year, month);
}
function _getDaysInMonth(uint256 year, uint256 month)
internal
pure
returns (uint256 daysInMonth)
{
if (
month == 1 ||
month == 3 ||
month == 5 ||
month == 7 ||
month == 8 ||
month == 10 ||
month == 12
) {
daysInMonth = 31;
} else if (month != 2) {
daysInMonth = 30;
} else {
daysInMonth = _isLeapYear(year) ? 29 : 28;
}
}
function getDayOfWeek(uint256 timestamp)
internal
pure
returns (uint256 dayOfWeek)
{
uint256 _days = timestamp / SECONDS_PER_DAY;
dayOfWeek = ((_days + 3) % 7) + 1;
}
function getYear(uint256 timestamp) internal pure returns (uint256 year) {
uint256 month;
uint256 day;
(year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY);
}
function getMonth(uint256 timestamp) internal pure returns (uint256 month) {
uint256 year;
uint256 day;
(year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY);
}
function getDay(uint256 timestamp) internal pure returns (uint256 day) {
uint256 year;
uint256 month;
(year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY);
}
function getHour(uint256 timestamp) internal pure returns (uint256 hour) {
uint256 secs = timestamp % SECONDS_PER_DAY;
hour = secs / SECONDS_PER_HOUR;
}
function getMinute(uint256 timestamp) internal pure returns (uint256 minute) {
uint256 secs = timestamp % SECONDS_PER_HOUR;
minute = secs / SECONDS_PER_MINUTE;
}
function getSecond(uint256 timestamp) internal pure returns (uint256 second) {
second = timestamp % SECONDS_PER_MINUTE;
}
function addYears(uint256 timestamp, uint256 _years)
internal
pure
returns (uint256 newTimestamp)
{
uint256 year;
uint256 month;
uint256 day;
(year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY);
year += _years;
uint256 daysInMonth = _getDaysInMonth(year, month);
if (day > daysInMonth) {
day = daysInMonth;
}
newTimestamp =
_daysFromDate(year, month, day) *
SECONDS_PER_DAY +
(timestamp % SECONDS_PER_DAY);
require(newTimestamp >= timestamp);
}
function addMonths(uint256 timestamp, uint256 _months)
internal
pure
returns (uint256 newTimestamp)
{
uint256 year;
uint256 month;
uint256 day;
(year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY);
month += _months;
year += (month - 1) / 12;
month = ((month - 1) % 12) + 1;
uint256 daysInMonth = _getDaysInMonth(year, month);
if (day > daysInMonth) {
day = daysInMonth;
}
newTimestamp =
_daysFromDate(year, month, day) *
SECONDS_PER_DAY +
(timestamp % SECONDS_PER_DAY);
require(newTimestamp >= timestamp);
}
function addDays(uint256 timestamp, uint256 _days)
internal
pure
returns (uint256 newTimestamp)
{
newTimestamp = timestamp + _days * SECONDS_PER_DAY;
require(newTimestamp >= timestamp);
}
function addHours(uint256 timestamp, uint256 _hours)
internal
pure
returns (uint256 newTimestamp)
{
newTimestamp = timestamp + _hours * SECONDS_PER_HOUR;
require(newTimestamp >= timestamp);
}
function addMinutes(uint256 timestamp, uint256 _minutes)
internal
pure
returns (uint256 newTimestamp)
{
newTimestamp = timestamp + _minutes * SECONDS_PER_MINUTE;
require(newTimestamp >= timestamp);
}
function addSeconds(uint256 timestamp, uint256 _seconds)
internal
pure
returns (uint256 newTimestamp)
{
newTimestamp = timestamp + _seconds;
require(newTimestamp >= timestamp);
}
function subYears(uint256 timestamp, uint256 _years)
internal
pure
returns (uint256 newTimestamp)
{
uint256 year;
uint256 month;
uint256 day;
(year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY);
year -= _years;
uint256 daysInMonth = _getDaysInMonth(year, month);
if (day > daysInMonth) {
day = daysInMonth;
}
newTimestamp =
_daysFromDate(year, month, day) *
SECONDS_PER_DAY +
(timestamp % SECONDS_PER_DAY);
require(newTimestamp <= timestamp);
}
function subMonths(uint256 timestamp, uint256 _months)
internal
pure
returns (uint256 newTimestamp)
{
uint256 year;
uint256 month;
uint256 day;
(year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY);
uint256 yearMonth = year * 12 + (month - 1) - _months;
year = yearMonth / 12;
month = (yearMonth % 12) + 1;
uint256 daysInMonth = _getDaysInMonth(year, month);
if (day > daysInMonth) {
day = daysInMonth;
}
newTimestamp =
_daysFromDate(year, month, day) *
SECONDS_PER_DAY +
(timestamp % SECONDS_PER_DAY);
require(newTimestamp <= timestamp);
}
function subDays(uint256 timestamp, uint256 _days)
internal
pure
returns (uint256 newTimestamp)
{
newTimestamp = timestamp - _days * SECONDS_PER_DAY;
require(newTimestamp <= timestamp);
}
function subHours(uint256 timestamp, uint256 _hours)
internal
pure
returns (uint256 newTimestamp)
{
newTimestamp = timestamp - _hours * SECONDS_PER_HOUR;
require(newTimestamp <= timestamp);
}
function subMinutes(uint256 timestamp, uint256 _minutes)
internal
pure
returns (uint256 newTimestamp)
{
newTimestamp = timestamp - _minutes * SECONDS_PER_MINUTE;
require(newTimestamp <= timestamp);
}
function subSeconds(uint256 timestamp, uint256 _seconds)
internal
pure
returns (uint256 newTimestamp)
{
newTimestamp = timestamp - _seconds;
require(newTimestamp <= timestamp);
}
function diffYears(uint256 fromTimestamp, uint256 toTimestamp)
internal
pure
returns (uint256 _years)
{
require(fromTimestamp <= toTimestamp);
uint256 fromYear;
uint256 fromMonth;
uint256 fromDay;
uint256 toYear;
uint256 toMonth;
uint256 toDay;
(fromYear, fromMonth, fromDay) = _daysToDate(
fromTimestamp / SECONDS_PER_DAY
);
(toYear, toMonth, toDay) = _daysToDate(toTimestamp / SECONDS_PER_DAY);
_years = toYear - fromYear;
}
function diffMonths(uint256 fromTimestamp, uint256 toTimestamp)
internal
pure
returns (uint256 _months)
{
require(fromTimestamp <= toTimestamp);
uint256 fromYear;
uint256 fromMonth;
uint256 fromDay;
uint256 toYear;
uint256 toMonth;
uint256 toDay;
(fromYear, fromMonth, fromDay) = _daysToDate(
fromTimestamp / SECONDS_PER_DAY
);
(toYear, toMonth, toDay) = _daysToDate(toTimestamp / SECONDS_PER_DAY);
_months = toYear * 12 + toMonth - fromYear * 12 - fromMonth;
}
function diffDays(uint256 fromTimestamp, uint256 toTimestamp)
internal
pure
returns (uint256 _days)
{
require(fromTimestamp <= toTimestamp);
_days = (toTimestamp - fromTimestamp) / SECONDS_PER_DAY;
}
function diffHours(uint256 fromTimestamp, uint256 toTimestamp)
internal
pure
returns (uint256 _hours)
{
require(fromTimestamp <= toTimestamp);
_hours = (toTimestamp - fromTimestamp) / SECONDS_PER_HOUR;
}
function diffMinutes(uint256 fromTimestamp, uint256 toTimestamp)
internal
pure
returns (uint256 _minutes)
{
require(fromTimestamp <= toTimestamp);
_minutes = (toTimestamp - fromTimestamp) / SECONDS_PER_MINUTE;
}
function diffSeconds(uint256 fromTimestamp, uint256 toTimestamp)
internal
pure
returns (uint256 _seconds)
{
require(fromTimestamp <= toTimestamp);
_seconds = toTimestamp - fromTimestamp;
}
}
文件 3 的 17:Context.sol
pragma solidity ^0.8.0;
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
文件 4 的 17:Counters.sol
pragma solidity ^0.8.0;
library Counters {
struct Counter {
uint256 _value;
}
function current(Counter storage counter) internal view returns (uint256) {
return counter._value;
}
function increment(Counter storage counter) internal {
unchecked {
counter._value += 1;
}
}
function decrement(Counter storage counter) internal {
uint256 value = counter._value;
require(value > 0, "Counter: decrement overflow");
unchecked {
counter._value = value - 1;
}
}
function reset(Counter storage counter) internal {
counter._value = 0;
}
}
文件 5 的 17:IERC165.sol
pragma solidity ^0.8.0;
interface IERC165 {
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
文件 6 的 17:IERC20.sol
pragma solidity ^0.8.0;
interface IERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
}
文件 7 的 17:IERC20Metadata.sol
pragma solidity ^0.8.0;
import "../IERC20.sol";
interface IERC20Metadata is IERC20 {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
}
文件 8 的 17:IERC721.sol
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
interface IERC721 is IERC165 {
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
function balanceOf(address owner) external view returns (uint256 balance);
function ownerOf(uint256 tokenId) external view returns (address owner);
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes calldata data
) external;
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) external;
function transferFrom(
address from,
address to,
uint256 tokenId
) external;
function approve(address to, uint256 tokenId) external;
function setApprovalForAll(address operator, bool _approved) external;
function getApproved(uint256 tokenId) external view returns (address operator);
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
文件 9 的 17:IFeeReducer.sol
pragma solidity ^0.8.16;
interface IFeeReducer {
function percentDiscount(
address wallet,
address collateralToken,
uint256 collateralAmount,
uint16 leverage
) external view returns (uint256, uint256);
}
文件 10 的 17:IPerpetualFutures.sol
import '@openzeppelin/contracts/token/ERC721/IERC721.sol';
pragma solidity ^0.8.16;
interface IPerpetualFutures {
struct Index {
string name;
uint256 dowOpenMin;
uint256 dowOpenMax;
uint256 hourOpenMin;
uint256 hourOpenMax;
bool isActive;
}
struct PositionLifecycle {
uint256 openTime;
uint256 openFees;
uint256 closeTime;
uint256 closeFees;
uint256 settleCollPriceUSD;
uint256 settleMainPriceUSD;
}
struct Position {
PositionLifecycle lifecycle;
uint256 indexIdx;
address collateralToken;
uint256 collateralAmount;
bool isLong;
uint16 leverage;
uint256 indexPriceStart;
uint256 indexPriceSettle;
uint256 amountWon;
uint256 amountLost;
bool isSettled;
uint256 mainCollateralSettledAmount;
}
struct ActionRequest {
uint256 timestamp;
address requester;
uint256 indexIdx;
uint256 tokenId;
address owner;
address collateralToken;
uint256 collateralAmount;
bool isLong;
uint16 leverage;
uint256 openSlippage;
uint256 desiredIdxPriceStart;
}
function openFeeETH() external view returns (uint256);
function mainCollateralToken() external view returns (address);
function relays(address wallet) external view returns (bool);
function perpsNft() external view returns (IERC721);
function positions(uint256 tokenId) external view returns (Position memory);
function openPositionRequest(
address collateralToken,
uint256 indexInd,
uint256 desiredPrice,
uint256 slippage,
uint256 collateral,
uint16 leverage,
bool isLong,
uint256 tokenId,
address owner
) external payable;
function executeSettlement(
uint256 tokenId,
address to,
uint256 amount
) external;
}
文件 11 的 17:Ownable.sol
pragma solidity ^0.8.0;
import "../utils/Context.sol";
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor() {
_transferOwnership(_msgSender());
}
modifier onlyOwner() {
_checkOwner();
_;
}
function owner() public view virtual returns (address) {
return _owner;
}
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
文件 12 的 17:PerpetualFutures.sol
pragma solidity ^0.8.16;
import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol';
import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
import './interfaces/IFeeReducer.sol';
import './interfaces/IPerpetualFutures.sol';
import './libraries/BokkyPooBahsDateTimeLibrary.sol';
import './pfYDF.sol';
import './PerpsTriggerOrders.sol';
import './PerpetualFuturesUnsettledHandler.sol';
contract PerpetualFutures is Ownable, PerpsTriggerOrders {
using SafeERC20 for IERC20Metadata;
uint256 constant FACTOR = 10**18;
uint256 constant PERC_DEN = 100000;
pfYDF public perpsNft;
IFeeReducer public feeReducer;
bool public tradingEnabled;
mapping(address => bool) public relays;
address public mainCollateralToken =
0x30dcBa0405004cF124045793E1933C798Af9E66a;
mapping(address => bool) _validColl;
address[] _allCollTokens;
mapping(address => uint256) _allCollTokensInd;
uint16 public maxLeverage = 1500;
mapping(uint256 => uint16) public maxLevIdxOverride;
uint256 public maxProfitPerc = PERC_DEN * 10;
uint256 public openFeeETH;
uint256 public openFeePositionSize = (PERC_DEN * 1) / 1000;
uint256 public closeFeePositionSize = (PERC_DEN * 1) / 1000;
uint256 public closeFeePerDurationUnit = 1 hours;
uint256 public closeFeePerDuration = (PERC_DEN * 5) / 100000;
mapping(address => uint256) public amtOpenLong;
mapping(address => uint256) public amtOpenShort;
mapping(address => uint256) public maxCollateralOpenDiff;
mapping(address => uint256) public minCollateralAmount;
IPerpetualFutures.Index[] public indexes;
uint256 public pendingPositionExp = 10 minutes;
IPerpetualFutures.ActionRequest[] public pendingOpenPositions;
IPerpetualFutures.ActionRequest[] public pendingClosePositions;
mapping(uint256 => bool) _hasPendingCloseRequest;
mapping(uint256 => IPerpetualFutures.Position) public positions;
uint256[] public allOpenPositions;
mapping(uint256 => uint256) internal _openPositionsIdx;
PerpetualFuturesUnsettledHandler public unsettledHandler;
event CloseUnsettledPosition(uint256 indexed tokenId);
event OpenPositionRequest(
address indexed user,
uint256 requestIdx,
uint256 indexPriceStartDesired,
uint256 positionCollateral,
bool isLong,
uint256 leverage
);
event OpenPosition(
uint256 indexed tokenId,
address indexed user,
uint256 indexPriceStart,
uint256 positionCollateral,
bool isLong,
uint256 leverage
);
event ClosePositionRequest(
uint256 indexed tokenId,
address indexed user,
uint256 requestIdx,
uint256 collateralReduction
);
event ClosePosition(
uint256 indexed tokenId,
address indexed user,
uint256 indexPriceStart,
uint256 indexPriceSettle,
uint256 amountWon,
uint256 amountLost
);
event LiquidatePosition(uint256 indexed tokenId);
event ClosePositionFromTriggerOrder(uint256 indexed tokenId);
event SettlePosition(address indexed to, uint256 amountSettled);
modifier onlyRelay() {
require(relays[_msgSender()], 'RELAY');
_;
}
modifier onlyUnsettled() {
require(_msgSender() == address(unsettledHandler), 'UNSETTLED');
_;
}
constructor() {
perpsNft = new pfYDF();
perpsNft.transferOwnership(_msgSender());
_setPfydf(address(perpsNft));
unsettledHandler = new PerpetualFuturesUnsettledHandler();
}
function getAllIndexes()
external
view
returns (IPerpetualFutures.Index[] memory)
{
return indexes;
}
function getAllValidCollateralTokens()
external
view
returns (address[] memory)
{
return _allCollTokens;
}
function getAllOpenPositions() external view returns (uint256[] memory) {
return allOpenPositions;
}
function getOpenPositionRequests()
external
view
returns (IPerpetualFutures.ActionRequest[] memory)
{
return pendingOpenPositions;
}
function getClosePositionRequests()
external
view
returns (IPerpetualFutures.ActionRequest[] memory)
{
return pendingClosePositions;
}
function openPositionRequest(
address _collToken,
uint256 _indexInd,
uint256 _desiredPrice,
uint256 _slippage,
uint256 _collateral,
uint16 _leverage,
bool _isLong,
uint256 _tokenId,
address _owner
) external payable {
require(tradingEnabled);
require(indexes[_indexInd].isActive, 'INVIDX');
require(_leverage >= 10);
require(_collateral >= minCollateralAmount[_collToken], 'MINCOLL');
require(_canOpenAgainstIndex(_indexInd, 0), 'INDOOB1');
require(
_collToken == mainCollateralToken || _validColl[_collToken],
'POSTOKEN1'
);
if (maxLevIdxOverride[_indexInd] > 0) {
require(_leverage <= maxLevIdxOverride[_indexInd], 'LEV1');
} else {
require(_leverage <= maxLeverage, 'LEV2');
}
if (openFeeETH > 0) {
require(msg.value == openFeeETH, 'OPENFEE');
(bool _s, ) = payable(owner()).call{ value: openFeeETH }('');
require(_s, 'FEESEND');
}
if (_tokenId > 0) {
require(perpsNft.ownerOf(_tokenId) == _msgSender(), 'OWNER');
}
pendingOpenPositions.push(
IPerpetualFutures.ActionRequest({
timestamp: block.timestamp,
requester: _msgSender(),
tokenId: _tokenId,
indexIdx: _indexInd,
owner: _owner == address(0) ? _msgSender() : _owner,
collateralToken: _collToken,
collateralAmount: _collateral,
isLong: _isLong,
leverage: _leverage,
openSlippage: _slippage,
desiredIdxPriceStart: _desiredPrice
})
);
emit OpenPositionRequest(
_msgSender(),
pendingOpenPositions.length - 1,
_desiredPrice,
_collateral,
_isLong,
_leverage
);
}
function openPositionRequestCancel(uint256 _openReqIdx) external {
require(
_msgSender() == pendingOpenPositions[_openReqIdx].requester ||
block.timestamp >
pendingOpenPositions[_openReqIdx].timestamp + pendingPositionExp
);
pendingOpenPositions[_openReqIdx] = pendingOpenPositions[
pendingOpenPositions.length - 1
];
pendingOpenPositions.pop();
}
function openPosition(uint256 _openPrice, uint256 _pendingIdx)
external
onlyRelay
{
IPerpetualFutures.ActionRequest memory _ar = pendingOpenPositions[
_pendingIdx
];
pendingOpenPositions[_pendingIdx] = pendingOpenPositions[
pendingOpenPositions.length - 1
];
pendingOpenPositions.pop();
(uint256 _openFee, uint256 _finalColl) = _processCollateral(
_ar.requester,
_ar.tokenId == 0
? _ar.collateralToken
: positions[_ar.tokenId].collateralToken,
_ar.collateralAmount,
_ar.leverage
);
_slippageValidation(
_ar.desiredIdxPriceStart,
_openPrice,
_ar.openSlippage,
_ar.isLong
);
uint256 _newTokenId = _ar.tokenId == 0
? perpsNft.mint(_ar.owner)
: _ar.tokenId;
uint256 _currentCollateral = positions[_newTokenId].collateralAmount;
uint16 _currentLeverage = positions[_newTokenId].leverage;
IPerpetualFutures.Position storage _pos = _setOpenPosition(
_newTokenId,
_openFee,
_openPrice,
_finalColl,
_ar
);
_validateAndUpdateOpenAmounts(
_newTokenId,
_getPositionAmount(_currentCollateral, _currentLeverage),
_getPositionAmount(_pos.collateralAmount, _pos.leverage)
);
emit OpenPosition(
_newTokenId,
_ar.owner,
_openPrice,
_finalColl,
_pos.isLong,
_pos.leverage
);
}
function closePositionRequest(uint256 _tokenId, uint256 _collateralReduction)
external
{
address _user = perpsNft.ownerOf(_tokenId);
require(_msgSender() == _user);
require(!_hasPendingCloseRequest[_tokenId]);
_hasPendingCloseRequest[_tokenId] = true;
pendingClosePositions.push(
IPerpetualFutures.ActionRequest({
timestamp: block.timestamp,
requester: _msgSender(),
tokenId: _tokenId,
collateralAmount: _collateralReduction,
indexIdx: positions[_tokenId].indexIdx,
owner: _msgSender(),
collateralToken: address(0),
isLong: false,
leverage: 0,
openSlippage: 0,
desiredIdxPriceStart: 0
})
);
emit ClosePositionRequest(
_tokenId,
_msgSender(),
pendingClosePositions.length - 1,
_collateralReduction
);
}
function closePositionRequestCancel(uint256 _closeReqIdx) external {
uint256 _tokenId = pendingClosePositions[_closeReqIdx].tokenId;
if (
block.timestamp <=
pendingClosePositions[_closeReqIdx].timestamp + pendingPositionExp
) {
address _user = perpsNft.ownerOf(_tokenId);
require(_msgSender() == _user);
}
delete _hasPendingCloseRequest[_tokenId];
pendingClosePositions[_closeReqIdx] = pendingClosePositions[
pendingClosePositions.length - 1
];
pendingClosePositions.pop();
}
function closePosition(uint256 _closePrice, uint256 _pendingCloseIdx)
external
onlyRelay
{
uint256 _tokenId = pendingClosePositions[_pendingCloseIdx].tokenId;
require(_tokenId > 0);
uint256 _collateralReduction = pendingClosePositions[_pendingCloseIdx]
.collateralAmount;
delete _hasPendingCloseRequest[_tokenId];
pendingClosePositions[_pendingCloseIdx] = pendingClosePositions[
pendingClosePositions.length - 1
];
pendingClosePositions.pop();
_closePosition(_tokenId, _closePrice, _collateralReduction);
}
function _closePosition(
uint256 _tokenId,
uint256 _currentPrice,
uint256 _collateralReduction
) internal {
address _user = perpsNft.ownerOf(_tokenId);
require(perpsNft.doesTokenExist(_tokenId));
uint256 _prevCollateral = positions[_tokenId].collateralAmount;
uint16 _prevLeverage = positions[_tokenId].leverage;
bool _isClosed = _getAndClosePositionPLInfo(
_tokenId,
_user,
_currentPrice,
_collateralReduction
);
if (_isClosed) {
_updateCloseAmounts(
_tokenId,
_getPositionAmount(_prevCollateral, _prevLeverage),
0
);
_removeOpenPosition(_tokenId);
perpsNft.burn(_tokenId);
emit ClosePosition(
_tokenId,
_user,
positions[_tokenId].indexPriceStart,
positions[_tokenId].indexPriceSettle,
positions[_tokenId].amountWon,
positions[_tokenId].amountLost
);
} else {
_updateCloseAmounts(
_tokenId,
_getPositionAmount(_prevCollateral, _prevLeverage),
_getPositionAmount(
positions[_tokenId].collateralAmount,
positions[_tokenId].leverage
)
);
}
}
function executeSettlement(
uint256 tokenId,
address _to,
uint256 _amount
) external onlyUnsettled {
positions[tokenId].mainCollateralSettledAmount = _amount;
IERC20Metadata(mainCollateralToken).safeTransfer(_to, _amount);
emit SettlePosition(_to, _amount);
}
function getIndexAndPLInfo(uint256 _tokenId, uint256 _currentIndexPrice)
external
view
returns (
uint256,
uint256,
bool,
bool
)
{
IPerpetualFutures.Position memory _position = positions[_tokenId];
return
_getIdxAndPLInfo(
_position.indexPriceStart,
_currentIndexPrice,
_position.collateralAmount,
_position.leverage,
_position.isLong
);
}
function _getIdxAndPLInfo(
uint256 _openIndexPrice,
uint256 _currentIndexPrice,
uint256 _collateralAmount,
uint16 _leverage,
bool _isLong
)
internal
view
returns (
uint256,
uint256,
bool,
bool
)
{
bool _settlePriceIsHigher = _currentIndexPrice > _openIndexPrice;
uint256 _indexAbsDiffFromOpen = _settlePriceIsHigher
? _currentIndexPrice - _openIndexPrice
: _openIndexPrice - _currentIndexPrice;
uint256 _absolutePL = (_getPositionAmount(_collateralAmount, _leverage) *
_indexAbsDiffFromOpen) / _openIndexPrice;
bool _isProfit = _isLong ? _settlePriceIsHigher : !_settlePriceIsHigher;
bool _isMax;
if (_isProfit) {
uint256 _maxProfit = (_collateralAmount * maxProfitPerc) / PERC_DEN;
if (_absolutePL > _maxProfit) {
_absolutePL = _maxProfit;
_isMax = true;
}
}
uint256 _amountReturnToUser = _collateralAmount;
if (_isProfit) {
_amountReturnToUser += _absolutePL;
} else {
if (_absolutePL > _amountReturnToUser) {
_amountReturnToUser = 0;
} else {
_amountReturnToUser -= _absolutePL;
}
}
return (_amountReturnToUser, _absolutePL, _isProfit, _isMax);
}
function getLiquidationPriceChange(uint256 _tokenId)
public
view
returns (uint256)
{
return
(positions[_tokenId].indexPriceStart * 85) /
10 /
positions[_tokenId].leverage;
}
function getPositionCloseFees(uint256 _tokenId)
external
view
returns (uint256, uint256)
{
return
_getCloseFees(
_tokenId,
positions[_tokenId].collateralToken,
positions[_tokenId].collateralAmount,
positions[_tokenId].leverage,
positions[_tokenId].lifecycle.openTime
);
}
function _getCloseFees(
uint256 _tokenId,
address _collateral,
uint256 _amount,
uint16 _leverage,
uint256 _openTime
) internal view returns (uint256, uint256) {
address _owner = perpsNft.ownerOf(_tokenId);
(uint256 _percentOff, uint256 _percOffDenomenator) = getFeeDiscount(
_owner,
_collateral,
_amount,
_leverage
);
uint256 _positionAmount = _getPositionAmount(_amount, _leverage);
uint256 _closingFeePosition = (_positionAmount * closeFeePositionSize) /
PERC_DEN;
uint256 _closingFeeDurationPerUnit = (_positionAmount *
closeFeePerDuration) / PERC_DEN;
uint256 _closingFeeDurationTotal = (_closingFeeDurationPerUnit *
(block.timestamp - _openTime)) / closeFeePerDurationUnit;
if (_percentOff > 0) {
_closingFeePosition -=
(_closingFeePosition * _percentOff) /
_percOffDenomenator;
_closingFeeDurationTotal -=
(_closingFeeDurationTotal * _percentOff) /
_percOffDenomenator;
}
return (_closingFeePosition, _closingFeeDurationTotal);
}
function setValidCollateralToken(address _token, bool _isValid)
external
onlyOwner
{
require(_validColl[_token] != _isValid);
_validColl[_token] = _isValid;
if (_isValid) {
_allCollTokensInd[_token] = _allCollTokens.length;
_allCollTokens.push(_token);
} else {
uint256 _ind = _allCollTokensInd[_token];
delete _allCollTokensInd[_token];
_allCollTokens[_ind] = _allCollTokens[_allCollTokens.length - 1];
_allCollTokens.pop();
}
}
function setMaxLeverage(uint16 _max) external onlyOwner {
require(_max <= 2500);
maxLeverage = _max;
}
function setMaxLevIdxOverride(uint256 _idx, uint16 _max) external onlyOwner {
require(_max <= 2500);
maxLevIdxOverride[_idx] = _max;
}
function setMaxProfitPerc(uint256 _max) external onlyOwner {
require(_max >= PERC_DEN);
maxProfitPerc = _max;
}
function setMaxTriggerOrders(uint8 _max) external onlyOwner {
maxTriggerOrders = _max;
}
function setOpenFeePositionSize(uint256 _percentage) external onlyOwner {
require(_percentage < (PERC_DEN * 10) / 100);
openFeePositionSize = _percentage;
}
function setOpenFeeETH(uint256 _wei) external onlyOwner {
openFeeETH = _wei;
}
function setCloseFeePositionSize(uint256 _percentage) external onlyOwner {
require(_percentage < (PERC_DEN * 10) / 100);
closeFeePositionSize = _percentage;
}
function setPendingPositionExp(uint256 _expiration) external onlyOwner {
require(_expiration <= 1 hours);
pendingPositionExp = _expiration;
}
function setCloseFeePositionPerDurationUnit(uint256 _seconds)
external
onlyOwner
{
require(_seconds >= 10 minutes);
closeFeePerDurationUnit = _seconds;
}
function setClosePositionFeePerDuration(uint256 _percentage)
external
onlyOwner
{
require(_percentage < (PERC_DEN * 1) / 100);
closeFeePerDuration = _percentage;
}
function setRelay(address _wallet, bool _isRelay) external onlyOwner {
require(relays[_wallet] != _isRelay);
relays[_wallet] = _isRelay;
}
function setMaxCollateralOpenDiff(address _collateral, uint256 _amount)
external
onlyOwner
{
maxCollateralOpenDiff[_collateral] = _amount;
}
function setMinCollateralAmount(address _collateral, uint256 _amount)
external
onlyOwner
{
minCollateralAmount[_collateral] = _amount;
}
function addIndex(string memory _name) external onlyOwner {
IPerpetualFutures.Index storage _newIndex = indexes.push();
_newIndex.name = _name;
_newIndex.isActive = true;
}
function activateIndex(uint256 _idx) external onlyOwner {
require(_idx < indexes.length);
indexes[_idx].isActive = true;
}
function removeIndex(uint256 _idx) external onlyOwner {
indexes[_idx].isActive = false;
}
function updateIndexOpenTimeBounds(
uint256 _indexInd,
uint256 _dowOpenMin,
uint256 _dowOpenMax,
uint256 _hourOpenMin,
uint256 _hourOpenMax
) external onlyOwner {
IPerpetualFutures.Index storage _index = indexes[_indexInd];
_index.dowOpenMin = _dowOpenMin;
_index.dowOpenMax = _dowOpenMax;
_index.hourOpenMin = _hourOpenMin;
_index.hourOpenMax = _hourOpenMax;
}
function setTradingEnabled(bool _tradingEnabled) external onlyOwner {
tradingEnabled = _tradingEnabled;
}
function setFeeReducer(IFeeReducer _reducer) external onlyOwner {
feeReducer = _reducer;
}
function processFees(uint256 _amount) external onlyOwner {
IERC20Metadata(mainCollateralToken).safeTransfer(
mainCollateralToken,
_amount
);
}
function checkUpkeep(uint256 _tokenId, uint256 _currentPrice)
external
view
returns (bool upkeepNeeded)
{
(bool _shouldTrigger, ) = shouldPositionCloseFromTrigger(
_tokenId,
_currentPrice
);
return shouldPositionLiquidate(_tokenId, _currentPrice) || _shouldTrigger;
}
function performUpkeep(uint256 _tokenId, uint256 _currentPrice)
external
onlyRelay
returns (bool wasLiquidated)
{
return _checkAndLiquidatePosition(_tokenId, _currentPrice);
}
function _checkAndLiquidatePosition(uint256 _tokenId, uint256 _currentPrice)
internal
returns (bool)
{
bool _shouldLiquidate = shouldPositionLiquidate(_tokenId, _currentPrice);
(
bool _triggerClose,
uint256 _collateralChange
) = shouldPositionCloseFromTrigger(_tokenId, _currentPrice);
if (_shouldLiquidate || _triggerClose) {
_closePosition(_tokenId, _currentPrice, _collateralChange);
if (_shouldLiquidate) {
emit LiquidatePosition(_tokenId);
} else if (_triggerClose) {
emit ClosePositionFromTriggerOrder(_tokenId);
}
return true;
}
return false;
}
function getFeeDiscount(
address _wallet,
address _token,
uint256 _amount,
uint16 _leverage
) public view returns (uint256, uint256) {
return
address(feeReducer) != address(0)
? feeReducer.percentDiscount(_wallet, _token, _amount, _leverage)
: (0, 0);
}
function _getPositionOpenFee(
address _user,
address _collateralToken,
uint256 _collateral,
uint16 _leverage
) internal view returns (uint256) {
uint256 _positionPreFee = (_collateral * _leverage) / 10;
uint256 _openFee = (_positionPreFee * openFeePositionSize) / PERC_DEN;
(uint256 _percentOff, uint256 _percOffDenomenator) = getFeeDiscount(
_user,
_collateralToken,
_collateral,
_leverage
);
if (_percentOff > 0) {
_openFee -= (_openFee * _percentOff) / _percOffDenomenator;
}
return _openFee;
}
function _setOpenPosition(
uint256 _tokenId,
uint256 _openFee,
uint256 _openPrice,
uint256 _newCollateral,
IPerpetualFutures.ActionRequest memory _ar
) internal returns (IPerpetualFutures.Position storage) {
IPerpetualFutures.Position storage _pos = positions[_tokenId];
if (_tokenId == _ar.tokenId) {
uint256 _prevSize = _getPositionAmount(
_pos.collateralAmount,
_pos.leverage
);
uint256 _addedSize = _getPositionAmount(_newCollateral, _ar.leverage);
uint256 _newSize = _prevSize + _addedSize;
(, uint256 _closingFeeDurationTotal) = _getCloseFees(
_tokenId,
_pos.collateralToken,
_pos.collateralAmount,
_pos.leverage,
_pos.lifecycle.openTime
);
_pos.leverage = uint16(
(_newSize * 10) / (_pos.collateralAmount + _newCollateral)
);
_pos.indexPriceStart =
((_pos.indexPriceStart * _prevSize) + (_openPrice * _addedSize)) /
_newSize;
_pos.lifecycle.closeFees += _closingFeeDurationTotal;
} else {
_pos.indexIdx = _ar.indexIdx;
_pos.collateralToken = _ar.collateralToken;
_pos.isLong = _ar.isLong;
_pos.leverage = _ar.leverage;
_pos.indexPriceStart = _openPrice;
_openPositionsIdx[_tokenId] = allOpenPositions.length;
allOpenPositions.push(_tokenId);
}
_pos.collateralAmount += _newCollateral;
_pos.lifecycle.openFees += _openFee;
_pos.lifecycle.openTime = block.timestamp;
return _pos;
}
function _removeOpenPosition(uint256 _tokenId) internal {
uint256 _allPositionsIdx = _openPositionsIdx[_tokenId];
uint256 _tokenIdMoving = allOpenPositions[allOpenPositions.length - 1];
delete _openPositionsIdx[_tokenId];
_openPositionsIdx[_tokenIdMoving] = _allPositionsIdx;
allOpenPositions[_allPositionsIdx] = _tokenIdMoving;
allOpenPositions.pop();
}
function _checkAndSettlePosition(
uint256 _tokenId,
address _collateralToken,
uint256 _collateralAmount,
address _closingUser,
uint256 _returnAmount,
bool _isClosing
) internal {
if (_returnAmount > 0) {
if (_collateralToken == mainCollateralToken) {
positions[_tokenId].isSettled = _isClosing;
IERC20Metadata(_collateralToken).safeTransfer(
_closingUser,
_returnAmount
);
} else {
if (_returnAmount > _collateralAmount) {
if (_collateralToken == address(0)) {
uint256 _before = address(this).balance;
payable(_closingUser).call{ value: _collateralAmount }('');
require(address(this).balance >= _before - _collateralAmount);
} else {
IERC20Metadata(_collateralToken).safeTransfer(
_closingUser,
_collateralAmount
);
}
unsettledHandler.addUnsettledPosition(
_tokenId,
_closingUser,
_collateralToken,
_returnAmount - _collateralAmount
);
emit CloseUnsettledPosition(_tokenId);
} else {
positions[_tokenId].isSettled = _isClosing;
if (_collateralToken == address(0)) {
uint256 _before = address(this).balance;
payable(_closingUser).call{ value: _returnAmount }('');
require(address(this).balance >= _before - _returnAmount);
} else {
IERC20Metadata(_collateralToken).safeTransfer(
_closingUser,
_returnAmount
);
}
}
}
} else {
positions[_tokenId].isSettled = _isClosing;
}
}
function _getPositionAmount(uint256 _collateralAmount, uint16 _leverage)
internal
pure
returns (uint256)
{
return (_collateralAmount * _leverage) / 10;
}
function _getAndClosePositionPLInfo(
uint256 _tokenId,
address _closingUser,
uint256 _currentPrice,
uint256 _collateralReduction
) internal returns (bool) {
IPerpetualFutures.Position storage _position = positions[_tokenId];
bool _isClosing = _collateralReduction == 0 ||
_collateralReduction >= _position.collateralAmount;
(
uint256 _closingFeePosition,
uint256 _closingFeeDurationTotal
) = _getCloseFees(
_tokenId,
_position.collateralToken,
_isClosing ? _position.collateralAmount : _collateralReduction,
_position.leverage,
_position.lifecycle.openTime
);
uint256 _totalCloseFees = _position.lifecycle.closeFees +
_closingFeePosition +
_closingFeeDurationTotal;
_position.lifecycle.closeFees += _totalCloseFees;
(
uint256 _amountReturnToUser,
uint256 _absolutePL,
bool _isProfit,
) = _getIdxAndPLInfo(
_position.indexPriceStart,
_currentPrice,
_isClosing ? _position.collateralAmount : _collateralReduction,
_position.leverage,
_position.isLong
);
_position.amountWon += _isProfit ? _absolutePL : 0;
_position.amountLost += _isProfit
? 0
: _absolutePL > _position.collateralAmount
? _position.collateralAmount
: _absolutePL;
if (_isClosing) {
_position.lifecycle.closeTime = block.timestamp;
_position.indexPriceSettle = _currentPrice;
_amountReturnToUser = _totalCloseFees > _amountReturnToUser
? 0
: _amountReturnToUser - _totalCloseFees;
} else {
_position.collateralAmount -= _collateralReduction;
}
_checkAndSettlePosition(
_tokenId,
_position.collateralToken,
_isClosing ? _position.collateralAmount : _collateralReduction,
_closingUser,
_amountReturnToUser,
_isClosing
);
return _isClosing;
}
function _validateAndUpdateOpenAmounts(
uint256 _tokenId,
uint256 _oldPositionSize,
uint256 _newPositionSize
) internal {
if (positions[_tokenId].isLong) {
amtOpenLong[positions[_tokenId].collateralToken] -= _oldPositionSize;
amtOpenLong[positions[_tokenId].collateralToken] += _newPositionSize;
} else {
amtOpenShort[positions[_tokenId].collateralToken] -= _oldPositionSize;
amtOpenShort[positions[_tokenId].collateralToken] += _newPositionSize;
}
if (maxCollateralOpenDiff[positions[_tokenId].collateralToken] > 0) {
uint256 _openDiff = amtOpenLong[positions[_tokenId].collateralToken] >
amtOpenShort[positions[_tokenId].collateralToken]
? amtOpenLong[positions[_tokenId].collateralToken] -
amtOpenShort[positions[_tokenId].collateralToken]
: amtOpenShort[positions[_tokenId].collateralToken] -
amtOpenLong[positions[_tokenId].collateralToken];
require(
_openDiff <= maxCollateralOpenDiff[positions[_tokenId].collateralToken]
);
}
}
function _updateCloseAmounts(
uint256 _tokenId,
uint256 _previousPositionSize,
uint256 _currentPositionSize
) internal {
if (positions[_tokenId].isLong) {
amtOpenLong[positions[_tokenId].collateralToken] -= _previousPositionSize;
amtOpenLong[positions[_tokenId].collateralToken] += _currentPositionSize;
} else {
amtOpenShort[
positions[_tokenId].collateralToken
] -= _previousPositionSize;
amtOpenShort[positions[_tokenId].collateralToken] += _currentPositionSize;
}
}
function _processCollateral(
address _user,
address _collToken,
uint256 _collateral,
uint16 _leverage
) internal returns (uint256, uint256) {
uint256 _openFee;
uint256 _finalCollateral;
if (_collToken == address(0)) {
require(msg.value > 0, 'COLL3');
_collateral = msg.value;
_openFee = _getPositionOpenFee(_user, _collToken, _collateral, _leverage);
_finalCollateral = _collateral - _openFee;
} else {
IERC20Metadata _collCont = IERC20Metadata(_collToken);
require(_collCont.balanceOf(_user) >= _collateral, 'BAL1');
uint256 _before = _collCont.balanceOf(address(this));
_collCont.safeTransferFrom(_user, address(this), _collateral);
_collateral = _collCont.balanceOf(address(this)) - _before;
_openFee = _getPositionOpenFee(_user, _collToken, _collateral, _leverage);
_finalCollateral = _collateral - _openFee;
}
return (_openFee, _finalCollateral);
}
function _slippageValidation(
uint256 _desiredPrice,
uint256 _currentPrice,
uint256 _slippage,
bool _isLong
) internal pure {
uint256 _idxSlipDiff;
if (_isLong && _currentPrice > _desiredPrice) {
_idxSlipDiff = _currentPrice - _desiredPrice;
} else if (!_isLong && _desiredPrice > _currentPrice) {
_idxSlipDiff = _desiredPrice - _currentPrice;
}
if (_idxSlipDiff > 0) {
require(
(_idxSlipDiff * FACTOR) / _desiredPrice <= (_slippage * FACTOR) / 1000
);
}
}
function _canOpenAgainstIndex(uint256 _ind, uint256 _timestamp)
internal
view
returns (bool)
{
return
_doTimeBoundsPass(
_timestamp,
indexes[_ind].dowOpenMin,
indexes[_ind].dowOpenMax,
indexes[_ind].hourOpenMin,
indexes[_ind].hourOpenMax
);
}
function _doTimeBoundsPass(
uint256 _timestamp,
uint256 _dowOpenMin,
uint256 _dowOpenMax,
uint256 _hourOpenMin,
uint256 _hourOpenMax
) internal view returns (bool) {
_timestamp = _timestamp == 0 ? block.timestamp : _timestamp;
if (_dowOpenMin >= 1 && _dowOpenMax >= 1) {
uint256 _dow = BokkyPooBahsDateTimeLibrary.getDayOfWeek(_timestamp);
if (_dow < _dowOpenMin || _dow > _dowOpenMax) {
return false;
}
}
if (_hourOpenMin >= 1 || _hourOpenMax >= 1) {
uint256 _hour = BokkyPooBahsDateTimeLibrary.getHour(_timestamp);
if (_hour < _hourOpenMin || _hour > _hourOpenMax) {
return false;
}
}
return true;
}
function shouldPositionLiquidate(uint256 _tokenId, uint256 _currentPrice)
public
view
returns (bool)
{
IPerpetualFutures.Position memory _pos = positions[_tokenId];
uint256 _priceChangeForLiquidation = getLiquidationPriceChange(_tokenId);
(uint256 _closingFeeMain, uint256 _closingFeeTime) = _getCloseFees(
_tokenId,
_pos.collateralToken,
_pos.collateralAmount,
_pos.leverage,
_pos.lifecycle.openTime
);
(uint256 _amountReturnToUser, , , bool _isMax) = _getIdxAndPLInfo(
_pos.indexPriceStart,
_currentPrice,
_pos.collateralAmount,
_pos.leverage,
_pos.isLong
);
uint256 _indexPriceDelinquencyPrice = _pos.isLong
? _pos.indexPriceStart - _priceChangeForLiquidation
: _pos.indexPriceStart + _priceChangeForLiquidation;
bool _priceInLiquidation = _pos.isLong
? _currentPrice <= _indexPriceDelinquencyPrice
: _currentPrice >= _indexPriceDelinquencyPrice;
bool _feesExceedReturn = _closingFeeMain + _closingFeeTime >=
_amountReturnToUser;
return _priceInLiquidation || _feesExceedReturn || _isMax;
}
function shouldPositionCloseFromTrigger(
uint256 _tokenId,
uint256 _currIdxPrice
) public view returns (bool, uint256) {
for (uint256 _i = 0; _i < triggerOrders[_tokenId].length; _i++) {
uint256 _collateralChange = triggerOrders[_tokenId][_i]
.amountCollateralChange;
uint256 _target = triggerOrders[_tokenId][_i].idxPriceTarget;
bool _lessThanEQ = _target < triggerOrders[_tokenId][_i].idxPriceCurrent;
if (_lessThanEQ) {
if (_currIdxPrice <= _target) {
return (true, _collateralChange);
}
} else {
if (_currIdxPrice >= _target) {
return (true, _collateralChange);
}
}
}
return (false, 0);
}
function withdrawERC20(address _token, uint256 _amount) external onlyOwner {
IERC20Metadata _contract = IERC20Metadata(_token);
_amount = _amount == 0 ? _contract.balanceOf(address(this)) : _amount;
require(_amount > 0);
_contract.safeTransfer(owner(), _amount);
}
function withdrawETH(uint256 _amount) external onlyOwner {
_amount = _amount == 0 ? address(this).balance : _amount;
(bool _s, ) = payable(owner()).call{ value: _amount }('');
require(_s);
}
}
文件 13 的 17:PerpetualFuturesUnsettledHandler.sol
pragma solidity ^0.8.16;
import '@openzeppelin/contracts/utils/Context.sol';
import '@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol';
import './interfaces/IPerpetualFutures.sol';
contract PerpetualFuturesUnsettledHandler is Context {
IPerpetualFutures public perpetualFutures;
struct UnsettledPositions {
uint256 tokenId;
address owner;
address collateralToken;
uint256 unsettledAmount;
}
UnsettledPositions[] public unsettled;
event AddUnsettledPosition(
uint256 indexed tokenId,
address indexed token,
uint256 amount,
uint256 idx
);
event SettlePosition(
uint256 indexed tokenId,
uint256 collateralAmount,
uint256 mainAmount,
uint256 collateralPriceUSD,
uint256 mainPriceUSD
);
modifier onlyPerps() {
require(_msgSender() == address(perpetualFutures), 'UNAUTHORIZED');
_;
}
modifier onlyRelay() {
require(perpetualFutures.relays(_msgSender()), 'UNAUTHORIZED');
_;
}
constructor() {
perpetualFutures = IPerpetualFutures(_msgSender());
}
function getAllUnsettled()
external
view
returns (UnsettledPositions[] memory)
{
return unsettled;
}
function getUnsettledLength() external view returns (uint256) {
return unsettled.length;
}
function addUnsettledPosition(
uint256 _tokenId,
address _ownerAtAddTime,
address _token,
uint256 _amount
) external onlyPerps {
unsettled.push(
UnsettledPositions({
tokenId: _tokenId,
owner: _ownerAtAddTime,
collateralToken: _token,
unsettledAmount: _amount
})
);
emit AddUnsettledPosition(_tokenId, _token, _amount, unsettled.length - 1);
}
function settleUnsettledPosition(
uint256 _idx,
uint256 _collPriceUSD,
uint256 _mainPriceUSD
) external onlyRelay {
UnsettledPositions memory _info = unsettled[_idx];
uint256 _mainSettleAmt = (_info.unsettledAmount *
10**IERC20Metadata(perpetualFutures.mainCollateralToken()).decimals() *
_collPriceUSD) /
_mainPriceUSD /
10**IERC20Metadata(_info.collateralToken).decimals();
perpetualFutures.executeSettlement(
_info.tokenId,
_info.owner,
_mainSettleAmt
);
unsettled[_idx] = unsettled[unsettled.length - 1];
unsettled.pop();
emit SettlePosition(
_info.tokenId,
_info.unsettledAmount,
_mainSettleAmt,
_collPriceUSD,
_mainPriceUSD
);
}
}
文件 14 的 17:PerpsTriggerOrders.sol
pragma solidity ^0.8.16;
import '@openzeppelin/contracts/utils/Context.sol';
import '@openzeppelin/contracts/token/ERC721/IERC721.sol';
contract PerpsTriggerOrders is Context {
IERC721 internal _pfydf;
uint8 public maxTriggerOrders = 2;
struct TriggerOrder {
uint256 idxPriceCurrent;
uint256 idxPriceTarget;
uint256 amountCollateralChange;
}
mapping(uint256 => TriggerOrder[]) public triggerOrders;
modifier onlyPositionOwner(uint256 _tokenId) {
require(msg.sender == _pfydf.ownerOf(_tokenId), 'UNAUTHORIZED');
_;
}
function getAllPositionTriggerOrders(uint256 _tokenId)
external
view
returns (TriggerOrder[] memory)
{
return triggerOrders[_tokenId];
}
function addTriggerOrder(
uint256 _tokenId,
uint256 _idxPriceTarget,
uint256 _currentPrice,
uint256 _collateralChange
) external onlyPositionOwner(_tokenId) {
_addTriggerOrder(
_tokenId,
_idxPriceTarget,
_currentPrice,
_collateralChange
);
}
function updateTriggerOrder(
uint256 _tokenId,
uint256 _idx,
uint256 _idxPriceTarget,
uint256 _newCollateralChange
) external onlyPositionOwner(_tokenId) {
_updateTriggerOrder(_tokenId, _idx, _idxPriceTarget, _newCollateralChange);
}
function removeTriggerOrder(uint256 _tokenId, uint256 _idx)
external
onlyPositionOwner(_tokenId)
{
_removeTriggerOrder(_tokenId, _idx);
}
function _addTriggerOrder(
uint256 _tokenId,
uint256 _idxPriceTarget,
uint256 _idxCurrentPrice,
uint256 _collateralChange
) internal {
require(_idxPriceTarget > 0, 'TO0');
require(triggerOrders[_tokenId].length < maxTriggerOrders, 'TO1');
require(_idxCurrentPrice != _idxPriceTarget, 'TO2');
triggerOrders[_tokenId].push(
TriggerOrder({
idxPriceCurrent: _idxCurrentPrice,
idxPriceTarget: _idxPriceTarget,
amountCollateralChange: _collateralChange
})
);
}
function _updateTriggerOrder(
uint256 _tokenId,
uint256 _idx,
uint256 _idxTargetPrice,
uint256 _newCollateralChange
) internal {
require(_idxTargetPrice > 0, 'TO0');
TriggerOrder storage _order = triggerOrders[_tokenId][_idx];
bool _isTargetLess = _order.idxPriceTarget < _order.idxPriceCurrent;
require(
_isTargetLess
? _idxTargetPrice < _order.idxPriceCurrent
: _idxTargetPrice > _order.idxPriceCurrent,
'TO3'
);
_order.idxPriceTarget = _idxTargetPrice;
_order.amountCollateralChange = _newCollateralChange;
}
function _removeTriggerOrder(uint256 _tokenId, uint256 _idx) internal {
triggerOrders[_tokenId][_idx] = triggerOrders[_tokenId][
triggerOrders[_tokenId].length - 1
];
triggerOrders[_tokenId].pop();
}
function _setPfydf(address _nft) internal {
_pfydf = IERC721(_nft);
}
}
文件 15 的 17:SafeERC20.sol
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/draft-IERC20Permit.sol";
import "../../../utils/Address.sol";
library SafeERC20 {
using Address for address;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
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(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
}
function safePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
function _callOptionalReturn(IERC20 token, bytes memory data) private {
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
文件 16 的 17:draft-IERC20Permit.sol
pragma solidity ^0.8.0;
interface IERC20Permit {
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
function nonces(address owner) external view returns (uint256);
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
文件 17 的 17:pfYDF.sol
pragma solidity ^0.8.16;
import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/utils/Context.sol';
import '@openzeppelin/contracts/utils/Counters.sol';
contract pfYDF is Context, Ownable {
using Counters for Counters.Counter;
address public perpetualFutures;
mapping(uint256 => address) _owners;
Counters.Counter _ids;
mapping(address => uint256[]) public allUserOwned;
mapping(uint256 => uint256) public ownedIndex;
mapping(uint256 => uint256) public tokenMintedAt;
event Burn(uint256 indexed tokenId, address indexed owner);
event Mint(uint256 indexed tokenId, address indexed owner);
modifier onlyPerps() {
require(_msgSender() == perpetualFutures, 'only perps');
_;
}
constructor() {
perpetualFutures = _msgSender();
}
function mint(address owner) external onlyPerps returns (uint256) {
_ids.increment();
_mint(owner, _ids.current());
tokenMintedAt[_ids.current()] = block.timestamp;
emit Mint(_ids.current(), owner);
return _ids.current();
}
function burn(uint256 _tokenId) external onlyPerps {
address _user = ownerOf(_tokenId);
require(_exists(_tokenId));
_burn(_tokenId);
emit Burn(_tokenId, _user);
}
function getLastMintedTokenId() external view returns (uint256) {
return _ids.current();
}
function doesTokenExist(uint256 _tokenId) external view returns (bool) {
return _exists(_tokenId);
}
function setPerpetualFutures(address _perps) external onlyOwner {
perpetualFutures = _perps;
}
function getAllUserOwned(address _user)
external
view
returns (uint256[] memory)
{
return allUserOwned[_user];
}
function ownerOf(uint256 _tokenId) public view returns (address) {
require(_owners[_tokenId] != address(0));
return _owners[_tokenId];
}
function _mint(address _to, uint256 _tokenId) internal {
require(_to != address(0) && _owners[_tokenId] == address(0));
_owners[_tokenId] = _to;
_afterTokenTransfer(address(0), _to, _tokenId);
}
function _burn(uint256 _tokenId) internal {
address _user = _owners[_tokenId];
require(_owners[_tokenId] != address(0));
delete _owners[_tokenId];
_afterTokenTransfer(_user, address(0), _tokenId);
}
function _exists(uint256 _tokenId) internal view returns (bool) {
return _owners[_tokenId] != address(0);
}
function _afterTokenTransfer(
address _from,
address _to,
uint256 _tokenId
) internal {
if (_from != address(0)) {
uint256 _currIndex = ownedIndex[_tokenId];
uint256 _tokenIdMovingIndices = allUserOwned[_from][
allUserOwned[_from].length - 1
];
allUserOwned[_from][_currIndex] = allUserOwned[_from][
allUserOwned[_from].length - 1
];
allUserOwned[_from].pop();
ownedIndex[_tokenIdMovingIndices] = _currIndex;
}
if (_to != address(0)) {
ownedIndex[_tokenId] = allUserOwned[_to].length;
allUserOwned[_to].push(_tokenId);
}
}
}
{
"compilationTarget": {
"contracts/PerpetualFutures.sol": "PerpetualFutures"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "none"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"indexPriceStart","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"indexPriceSettle","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountWon","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountLost","type":"uint256"}],"name":"ClosePosition","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ClosePositionFromTriggerOrder","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"requestIdx","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"collateralReduction","type":"uint256"}],"name":"ClosePositionRequest","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"CloseUnsettledPosition","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"LiquidatePosition","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"indexPriceStart","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"positionCollateral","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isLong","type":"bool"},{"indexed":false,"internalType":"uint256","name":"leverage","type":"uint256"}],"name":"OpenPosition","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"requestIdx","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"indexPriceStartDesired","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"positionCollateral","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isLong","type":"bool"},{"indexed":false,"internalType":"uint256","name":"leverage","type":"uint256"}],"name":"OpenPositionRequest","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountSettled","type":"uint256"}],"name":"SettlePosition","type":"event"},{"inputs":[{"internalType":"uint256","name":"_idx","type":"uint256"}],"name":"activateIndex","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_name","type":"string"}],"name":"addIndex","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_idxPriceTarget","type":"uint256"},{"internalType":"uint256","name":"_currentPrice","type":"uint256"},{"internalType":"uint256","name":"_collateralChange","type":"uint256"}],"name":"addTriggerOrder","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"allOpenPositions","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"amtOpenLong","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"amtOpenShort","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_currentPrice","type":"uint256"}],"name":"checkUpkeep","outputs":[{"internalType":"bool","name":"upkeepNeeded","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"closeFeePerDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"closeFeePerDurationUnit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"closeFeePositionSize","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_closePrice","type":"uint256"},{"internalType":"uint256","name":"_pendingCloseIdx","type":"uint256"}],"name":"closePosition","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_collateralReduction","type":"uint256"}],"name":"closePositionRequest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_closeReqIdx","type":"uint256"}],"name":"closePositionRequestCancel","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"executeSettlement","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"feeReducer","outputs":[{"internalType":"contract IFeeReducer","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllIndexes","outputs":[{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"dowOpenMin","type":"uint256"},{"internalType":"uint256","name":"dowOpenMax","type":"uint256"},{"internalType":"uint256","name":"hourOpenMin","type":"uint256"},{"internalType":"uint256","name":"hourOpenMax","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"}],"internalType":"struct IPerpetualFutures.Index[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllOpenPositions","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"getAllPositionTriggerOrders","outputs":[{"components":[{"internalType":"uint256","name":"idxPriceCurrent","type":"uint256"},{"internalType":"uint256","name":"idxPriceTarget","type":"uint256"},{"internalType":"uint256","name":"amountCollateralChange","type":"uint256"}],"internalType":"struct PerpsTriggerOrders.TriggerOrder[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllValidCollateralTokens","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getClosePositionRequests","outputs":[{"components":[{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"address","name":"requester","type":"address"},{"internalType":"uint256","name":"indexIdx","type":"uint256"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"collateralToken","type":"address"},{"internalType":"uint256","name":"collateralAmount","type":"uint256"},{"internalType":"bool","name":"isLong","type":"bool"},{"internalType":"uint16","name":"leverage","type":"uint16"},{"internalType":"uint256","name":"openSlippage","type":"uint256"},{"internalType":"uint256","name":"desiredIdxPriceStart","type":"uint256"}],"internalType":"struct IPerpetualFutures.ActionRequest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_wallet","type":"address"},{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint16","name":"_leverage","type":"uint16"}],"name":"getFeeDiscount","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_currentIndexPrice","type":"uint256"}],"name":"getIndexAndPLInfo","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"getLiquidationPriceChange","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getOpenPositionRequests","outputs":[{"components":[{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"address","name":"requester","type":"address"},{"internalType":"uint256","name":"indexIdx","type":"uint256"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"collateralToken","type":"address"},{"internalType":"uint256","name":"collateralAmount","type":"uint256"},{"internalType":"bool","name":"isLong","type":"bool"},{"internalType":"uint16","name":"leverage","type":"uint16"},{"internalType":"uint256","name":"openSlippage","type":"uint256"},{"internalType":"uint256","name":"desiredIdxPriceStart","type":"uint256"}],"internalType":"struct IPerpetualFutures.ActionRequest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"getPositionCloseFees","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"indexes","outputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"dowOpenMin","type":"uint256"},{"internalType":"uint256","name":"dowOpenMax","type":"uint256"},{"internalType":"uint256","name":"hourOpenMin","type":"uint256"},{"internalType":"uint256","name":"hourOpenMax","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"mainCollateralToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"maxCollateralOpenDiff","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"maxLevIdxOverride","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxLeverage","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxProfitPerc","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxTriggerOrders","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"minCollateralAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"openFeeETH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"openFeePositionSize","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_openPrice","type":"uint256"},{"internalType":"uint256","name":"_pendingIdx","type":"uint256"}],"name":"openPosition","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_collToken","type":"address"},{"internalType":"uint256","name":"_indexInd","type":"uint256"},{"internalType":"uint256","name":"_desiredPrice","type":"uint256"},{"internalType":"uint256","name":"_slippage","type":"uint256"},{"internalType":"uint256","name":"_collateral","type":"uint256"},{"internalType":"uint16","name":"_leverage","type":"uint16"},{"internalType":"bool","name":"_isLong","type":"bool"},{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"address","name":"_owner","type":"address"}],"name":"openPositionRequest","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_openReqIdx","type":"uint256"}],"name":"openPositionRequestCancel","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"pendingClosePositions","outputs":[{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"address","name":"requester","type":"address"},{"internalType":"uint256","name":"indexIdx","type":"uint256"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"collateralToken","type":"address"},{"internalType":"uint256","name":"collateralAmount","type":"uint256"},{"internalType":"bool","name":"isLong","type":"bool"},{"internalType":"uint16","name":"leverage","type":"uint16"},{"internalType":"uint256","name":"openSlippage","type":"uint256"},{"internalType":"uint256","name":"desiredIdxPriceStart","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"pendingOpenPositions","outputs":[{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"address","name":"requester","type":"address"},{"internalType":"uint256","name":"indexIdx","type":"uint256"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"collateralToken","type":"address"},{"internalType":"uint256","name":"collateralAmount","type":"uint256"},{"internalType":"bool","name":"isLong","type":"bool"},{"internalType":"uint16","name":"leverage","type":"uint16"},{"internalType":"uint256","name":"openSlippage","type":"uint256"},{"internalType":"uint256","name":"desiredIdxPriceStart","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingPositionExp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_currentPrice","type":"uint256"}],"name":"performUpkeep","outputs":[{"internalType":"bool","name":"wasLiquidated","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"perpsNft","outputs":[{"internalType":"contract pfYDF","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"positions","outputs":[{"components":[{"internalType":"uint256","name":"openTime","type":"uint256"},{"internalType":"uint256","name":"openFees","type":"uint256"},{"internalType":"uint256","name":"closeTime","type":"uint256"},{"internalType":"uint256","name":"closeFees","type":"uint256"},{"internalType":"uint256","name":"settleCollPriceUSD","type":"uint256"},{"internalType":"uint256","name":"settleMainPriceUSD","type":"uint256"}],"internalType":"struct IPerpetualFutures.PositionLifecycle","name":"lifecycle","type":"tuple"},{"internalType":"uint256","name":"indexIdx","type":"uint256"},{"internalType":"address","name":"collateralToken","type":"address"},{"internalType":"uint256","name":"collateralAmount","type":"uint256"},{"internalType":"bool","name":"isLong","type":"bool"},{"internalType":"uint16","name":"leverage","type":"uint16"},{"internalType":"uint256","name":"indexPriceStart","type":"uint256"},{"internalType":"uint256","name":"indexPriceSettle","type":"uint256"},{"internalType":"uint256","name":"amountWon","type":"uint256"},{"internalType":"uint256","name":"amountLost","type":"uint256"},{"internalType":"bool","name":"isSettled","type":"bool"},{"internalType":"uint256","name":"mainCollateralSettledAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"processFees","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"relays","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_idx","type":"uint256"}],"name":"removeIndex","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_idx","type":"uint256"}],"name":"removeTriggerOrder","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_seconds","type":"uint256"}],"name":"setCloseFeePositionPerDurationUnit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_percentage","type":"uint256"}],"name":"setCloseFeePositionSize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_percentage","type":"uint256"}],"name":"setClosePositionFeePerDuration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IFeeReducer","name":"_reducer","type":"address"}],"name":"setFeeReducer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_collateral","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"setMaxCollateralOpenDiff","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_idx","type":"uint256"},{"internalType":"uint16","name":"_max","type":"uint16"}],"name":"setMaxLevIdxOverride","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"_max","type":"uint16"}],"name":"setMaxLeverage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_max","type":"uint256"}],"name":"setMaxProfitPerc","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"_max","type":"uint8"}],"name":"setMaxTriggerOrders","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_collateral","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"setMinCollateralAmount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_wei","type":"uint256"}],"name":"setOpenFeeETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_percentage","type":"uint256"}],"name":"setOpenFeePositionSize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_expiration","type":"uint256"}],"name":"setPendingPositionExp","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_wallet","type":"address"},{"internalType":"bool","name":"_isRelay","type":"bool"}],"name":"setRelay","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"_tradingEnabled","type":"bool"}],"name":"setTradingEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"bool","name":"_isValid","type":"bool"}],"name":"setValidCollateralToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_currIdxPrice","type":"uint256"}],"name":"shouldPositionCloseFromTrigger","outputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_currentPrice","type":"uint256"}],"name":"shouldPositionLiquidate","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tradingEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"triggerOrders","outputs":[{"internalType":"uint256","name":"idxPriceCurrent","type":"uint256"},{"internalType":"uint256","name":"idxPriceTarget","type":"uint256"},{"internalType":"uint256","name":"amountCollateralChange","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unsettledHandler","outputs":[{"internalType":"contract PerpetualFuturesUnsettledHandler","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_indexInd","type":"uint256"},{"internalType":"uint256","name":"_dowOpenMin","type":"uint256"},{"internalType":"uint256","name":"_dowOpenMax","type":"uint256"},{"internalType":"uint256","name":"_hourOpenMin","type":"uint256"},{"internalType":"uint256","name":"_hourOpenMax","type":"uint256"}],"name":"updateIndexOpenTimeBounds","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_idx","type":"uint256"},{"internalType":"uint256","name":"_idxPriceTarget","type":"uint256"},{"internalType":"uint256","name":"_newCollateralChange","type":"uint256"}],"name":"updateTriggerOrder","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"withdrawERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"withdrawETH","outputs":[],"stateMutability":"nonpayable","type":"function"}]