// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)pragmasolidity ^0.8.1;/**
* @dev Collection of functions related to the address type
*/libraryAddress{
/**
* @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
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/functionisContract(address account) internalviewreturns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0// for contracts in construction, since the code is only stored at the end// of the constructor execution.return account.code.length>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://consensys.net/diligence/blog/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.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/functionsendValue(addresspayable 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");
}
/**
* @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._
*/functionfunctionCall(address target, bytesmemory data) internalreturns (bytesmemory) {
return functionCallWithValue(target, data, 0, "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._
*/functionfunctionCall(address target,
bytesmemory data,
stringmemory errorMessage
) internalreturns (bytesmemory) {
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._
*/functionfunctionCallWithValue(address target, bytesmemory data, uint256 value) internalreturns (bytesmemory) {
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._
*/functionfunctionCallWithValue(address target,
bytesmemory data,
uint256 value,
stringmemory errorMessage
) internalreturns (bytesmemory) {
require(address(this).balance>= value, "Address: insufficient balance for call");
(bool success, bytesmemory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/functionfunctionStaticCall(address target, bytesmemory data) internalviewreturns (bytesmemory) {
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._
*/functionfunctionStaticCall(address target,
bytesmemory data,
stringmemory errorMessage
) internalviewreturns (bytesmemory) {
(bool success, bytesmemory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/functionfunctionDelegateCall(address target, bytesmemory data) internalreturns (bytesmemory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/functionfunctionDelegateCall(address target,
bytesmemory data,
stringmemory errorMessage
) internalreturns (bytesmemory) {
(bool success, bytesmemory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/functionverifyCallResultFromTarget(address target,
bool success,
bytesmemory returndata,
stringmemory errorMessage
) internalviewreturns (bytesmemory) {
if (success) {
if (returndata.length==0) {
// only check isContract if the call was successful and the return data is empty// otherwise we already know that it was a contractrequire(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/functionverifyCallResult(bool success,
bytesmemory returndata,
stringmemory errorMessage
) internalpurereturns (bytesmemory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function_revert(bytesmemory returndata, stringmemory errorMessage) privatepure{
// Look for revert reason and bubble it up if presentif (returndata.length>0) {
// The easiest way to bubble the revert reason is using memory via assembly/// @solidity memory-safe-assemblyassembly {
let returndata_size :=mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;// @title ArbSys// @dev Globally available variables for Arbitrum may have both an L1 and an L2// value, the ArbSys interface is used to retrieve the L2 valueinterfaceArbSys{
functionarbBlockNumber() externalviewreturns (uint256);
functionarbBlockHash(uint256 blockNumber) externalviewreturns (bytes32);
}
Contract Source Code
File 7 of 103: Array.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"@openzeppelin/contracts/utils/math/SafeCast.sol";
import"../error/Errors.sol";
/**
* @title Array
* @dev Library for array functions
*/libraryArray{
usingSafeCastforint256;
/**
* @dev Gets the value of the element at the specified index in the given array. If the index is out of bounds, returns 0.
*
* @param arr the array to get the value from
* @param index the index of the element in the array
* @return the value of the element at the specified index in the array
*/functionget(bytes32[] memory arr, uint256 index) internalpurereturns (bytes32) {
if (index < arr.length) {
return arr[index];
}
returnbytes32(0);
}
/**
* @dev Determines whether all of the elements in the given array are equal to the specified value.
*
* @param arr the array to check the elements of
* @param value the value to compare the elements of the array to
* @return true if all of the elements in the array are equal to the specified value, false otherwise
*/functionareEqualTo(uint256[] memory arr, uint256 value) internalpurereturns (bool) {
for (uint256 i; i < arr.length; i++) {
if (arr[i] != value) {
returnfalse;
}
}
returntrue;
}
/**
* @dev Determines whether all of the elements in the given array are greater than the specified value.
*
* @param arr the array to check the elements of
* @param value the value to compare the elements of the array to
* @return true if all of the elements in the array are greater than the specified value, false otherwise
*/functionareGreaterThan(uint256[] memory arr, uint256 value) internalpurereturns (bool) {
for (uint256 i; i < arr.length; i++) {
if (arr[i] <= value) {
returnfalse;
}
}
returntrue;
}
/**
* @dev Determines whether all of the elements in the given array are greater than or equal to the specified value.
*
* @param arr the array to check the elements of
* @param value the value to compare the elements of the array to
* @return true if all of the elements in the array are greater than or equal to the specified value, false otherwise
*/functionareGreaterThanOrEqualTo(uint256[] memory arr, uint256 value) internalpurereturns (bool) {
for (uint256 i; i < arr.length; i++) {
if (arr[i] < value) {
returnfalse;
}
}
returntrue;
}
/**
* @dev Determines whether all of the elements in the given array are less than the specified value.
*
* @param arr the array to check the elements of
* @param value the value to compare the elements of the array to
* @return true if all of the elements in the array are less than the specified value, false otherwise
*/functionareLessThan(uint256[] memory arr, uint256 value) internalpurereturns (bool) {
for (uint256 i; i < arr.length; i++) {
if (arr[i] >= value) {
returnfalse;
}
}
returntrue;
}
/**
* @dev Determines whether all of the elements in the given array are less than or equal to the specified value.
*
* @param arr the array to check the elements of
* @param value the value to compare the elements of the array to
* @return true if all of the elements in the array are less than or equal to the specified value, false otherwise
*/functionareLessThanOrEqualTo(uint256[] memory arr, uint256 value) internalpurereturns (bool) {
for (uint256 i; i < arr.length; i++) {
if (arr[i] > value) {
returnfalse;
}
}
returntrue;
}
/**
* @dev Gets the median value of the elements in the given array. For arrays with an odd number of elements, returns the element at the middle index. For arrays with an even number of elements, returns the average of the two middle elements.
*
* @param arr the array to get the median value from
* @return the median value of the elements in the given array
*/functiongetMedian(uint256[] memory arr) internalpurereturns (uint256) {
if (arr.length%2==1) {
return arr[arr.length/2];
}
return (arr[arr.length/2] + arr[arr.length/2-1]) /2;
}
/**
* @dev Gets the uncompacted value at the specified index in the given array of compacted values.
*
* @param compactedValues the array of compacted values to get the uncompacted value from
* @param index the index of the uncompacted value in the array
* @param compactedValueBitLength the length of each compacted value, in bits
* @param bitmask the bitmask to use to extract the uncompacted value from the compacted value
* @return the uncompacted value at the specified index in the array of compacted values
*/functiongetUncompactedValue(uint256[] memory compactedValues,
uint256 index,
uint256 compactedValueBitLength,
uint256 bitmask,
stringmemory label
) internalpurereturns (uint256) {
uint256 compactedValuesPerSlot =256/ compactedValueBitLength;
uint256 slotIndex = index / compactedValuesPerSlot;
if (slotIndex >= compactedValues.length) {
revert Errors.CompactedArrayOutOfBounds(compactedValues, index, slotIndex, label);
}
uint256 slotBits = compactedValues[slotIndex];
uint256 offset = (index - slotIndex * compactedValuesPerSlot) * compactedValueBitLength;
uint256 value = (slotBits >> offset) & bitmask;
return value;
}
}
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"@openzeppelin/contracts/token/ERC20/IERC20.sol";
import"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import"../token/TokenUtils.sol";
import"../role/RoleModule.sol";
// @title Bank// @dev Contract to handle storing and transferring of tokenscontractBankisRoleModule{
usingSafeERC20forIERC20;
DataStore publicimmutable dataStore;
constructor(RoleStore _roleStore, DataStore _dataStore) RoleModule(_roleStore) {
dataStore = _dataStore;
}
receive() externalpayable{
address wnt = TokenUtils.wnt(dataStore);
if (msg.sender!= wnt) {
revert Errors.InvalidNativeTokenSender(msg.sender);
}
}
// @dev transfer tokens from this contract to a receiver//// @param token the token to transfer// @param amount the amount to transfer// @param receiver the address to transfer tofunctiontransferOut(address token,
address receiver,
uint256 amount
) externalonlyController{
_transferOut(token, receiver, amount);
}
// @dev transfer tokens from this contract to a receiver// handles native token transfers as well//// @param token the token to transfer// @param amount the amount to transfer// @param receiver the address to transfer to// @param shouldUnwrapNativeToken whether to unwrap the wrapped native token// before transferringfunctiontransferOut(address token,
address receiver,
uint256 amount,
bool shouldUnwrapNativeToken
) externalonlyController{
address wnt = TokenUtils.wnt(dataStore);
if (token == wnt && shouldUnwrapNativeToken) {
_transferOutNativeToken(token, receiver, amount);
} else {
_transferOut(token, receiver, amount);
}
}
// @dev transfer native tokens from this contract to a receiver//// @param token the token to transfer// @param amount the amount to transfer// @param receiver the address to transfer to// @param shouldUnwrapNativeToken whether to unwrap the wrapped native token// before transferringfunctiontransferOutNativeToken(address receiver,
uint256 amount
) externalonlyController{
address wnt = TokenUtils.wnt(dataStore);
_transferOutNativeToken(wnt, receiver, amount);
}
// @dev transfer tokens from this contract to a receiver//// @param token the token to transfer// @param amount the amount to transfer// @param receiver the address to transfer tofunction_transferOut(address token,
address receiver,
uint256 amount
) internal{
if (receiver ==address(this)) {
revert Errors.SelfTransferNotSupported(receiver);
}
TokenUtils.transfer(dataStore, token, receiver, amount);
_afterTransferOut(token);
}
// @dev unwrap wrapped native tokens and transfer the native tokens from// this contract to a receiver//// @param token the token to transfer// @param amount the amount to transfer// @param receiver the address to transfer tofunction_transferOutNativeToken(address token,
address receiver,
uint256 amount
) internal{
if (receiver ==address(this)) {
revert Errors.SelfTransferNotSupported(receiver);
}
TokenUtils.withdrawAndSendNativeToken(
dataStore,
token,
receiver,
amount
);
_afterTransferOut(token);
}
function_afterTransferOut(address/* token */) internalvirtual{}
}
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"./Order.sol";
import"../market/Market.sol";
import"../data/DataStore.sol";
import"../event/EventEmitter.sol";
import"../referral/IReferralStorage.sol";
import"../order/OrderVault.sol";
import"../oracle/Oracle.sol";
import"../swap/SwapHandler.sol";
// @title Order// @dev Library for common order functions used in OrderUtils, IncreaseOrderUtils// DecreaseOrderUtils, SwapOrderUtilslibraryBaseOrderUtils{
usingSafeCastforint256;
usingSafeCastforuint256;
usingOrderforOrder.Props;
usingPriceforPrice.Props;
// @dev ExecuteOrderParams struct used in executeOrder to avoid stack// too deep errors//// @param contracts ExecuteOrderParamsContracts// @param key the key of the order to execute// @param order the order to execute// @param swapPathMarkets the market values of the markets in the swapPath// @param minOracleTimestamp the min oracle timestamp// @param maxOracleTimestamp the max oracle timestamp// @param market market values of the trading market// @param keeper the keeper sending the transaction// @param startingGas the starting gas// @param secondaryOrderType the secondary order typestructExecuteOrderParams {
ExecuteOrderParamsContracts contracts;
bytes32 key;
Order.Props order;
Market.Props[] swapPathMarkets;
uint256 minOracleTimestamp;
uint256 maxOracleTimestamp;
Market.Props market;
address keeper;
uint256 startingGas;
Order.SecondaryOrderType secondaryOrderType;
}
// @param dataStore DataStore// @param eventEmitter EventEmitter// @param orderVault OrderVault// @param oracle Oracle// @param swapHandler SwapHandler// @param referralStorage IReferralStoragestructExecuteOrderParamsContracts {
DataStore dataStore;
EventEmitter eventEmitter;
OrderVault orderVault;
Oracle oracle;
SwapHandler swapHandler;
IReferralStorage referralStorage;
}
structGetExecutionPriceCache {
uint256 price;
uint256 executionPrice;
int256 adjustedPriceImpactUsd;
}
functionisSupportedOrder(Order.OrderType orderType) internalpurereturns (bool) {
return orderType == Order.OrderType.MarketSwap ||
orderType == Order.OrderType.LimitSwap ||
orderType == Order.OrderType.MarketIncrease ||
orderType == Order.OrderType.MarketDecrease ||
orderType == Order.OrderType.LimitIncrease ||
orderType == Order.OrderType.LimitDecrease ||
orderType == Order.OrderType.StopIncrease ||
orderType == Order.OrderType.StopLossDecrease ||
orderType == Order.OrderType.Liquidation;
}
// @dev check if an orderType is a market order// @param orderType the order type// @return whether an orderType is a market orderfunctionisMarketOrder(Order.OrderType orderType) internalpurereturns (bool) {
// a liquidation order is not considered as a market orderreturn orderType == Order.OrderType.MarketSwap ||
orderType == Order.OrderType.MarketIncrease ||
orderType == Order.OrderType.MarketDecrease;
}
// @dev check if an orderType is a swap order// @param orderType the order type// @return whether an orderType is a swap orderfunctionisSwapOrder(Order.OrderType orderType) internalpurereturns (bool) {
return orderType == Order.OrderType.MarketSwap ||
orderType == Order.OrderType.LimitSwap;
}
// @dev check if an orderType is a position order// @param orderType the order type// @return whether an orderType is a position orderfunctionisPositionOrder(Order.OrderType orderType) internalpurereturns (bool) {
return isIncreaseOrder(orderType) || isDecreaseOrder(orderType);
}
// @dev check if an orderType is an increase order// @param orderType the order type// @return whether an orderType is an increase orderfunctionisIncreaseOrder(Order.OrderType orderType) internalpurereturns (bool) {
return orderType == Order.OrderType.MarketIncrease ||
orderType == Order.OrderType.LimitIncrease ||
orderType == Order.OrderType.StopIncrease;
}
// @dev check if an orderType is a decrease order// @param orderType the order type// @return whether an orderType is a decrease orderfunctionisDecreaseOrder(Order.OrderType orderType) internalpurereturns (bool) {
return orderType == Order.OrderType.MarketDecrease ||
orderType == Order.OrderType.LimitDecrease ||
orderType == Order.OrderType.StopLossDecrease ||
orderType == Order.OrderType.Liquidation;
}
// @dev check if an orderType is a liquidation order// @param orderType the order type// @return whether an orderType is a liquidation orderfunctionisLiquidationOrder(Order.OrderType orderType) internalpurereturns (bool) {
return orderType == Order.OrderType.Liquidation;
}
// @dev validate the price for increase / decrease orders based on the triggerPrice// the acceptablePrice for increase / decrease orders is validated in getExecutionPrice//// it is possible to update the oracle to support a primaryPrice and a secondaryPrice// which would allow for stop-loss orders to be executed at exactly the triggerPrice//// however, this may lead to gaming issues, an example:// - the current price is $2020// - a user has a long position and creates a stop-loss decrease order for < $2010// - if the order has a swap from ETH to USDC and the user is able to cause the order// to be frozen / unexecutable by manipulating state or otherwise// - then if price decreases to $2000, and the user is able to manipulate state such that// the order becomes executable with $2010 being used as the price instead// - then the user would be able to perform the swap at a higher price than should possible//// additionally, using the exact order's triggerPrice could lead to gaming issues during times// of volatility due to users setting tight stop-losses to minimize loss while betting on a// directional price movement, fees and price impact should help a bit with this, but there// still may be some probability of success//// the order keepers can use the closest oracle price to the triggerPrice for execution, which// should lead to similar order execution prices with reduced gaming risks//// if an order is frozen, the frozen order keepers should use the most recent price for order// execution instead//// @param oracle Oracle// @param indexToken the index token// @param orderType the order type// @param triggerPrice the order's triggerPrice// @param isLong whether the order is for a long or shortfunctionvalidateOrderTriggerPrice(
Oracle oracle,
address indexToken,
Order.OrderType orderType,
uint256 triggerPrice,
bool isLong
) internalview{
if (
isSwapOrder(orderType) ||
isMarketOrder(orderType) ||
isLiquidationOrder(orderType)
) {
return;
}
Price.Props memory primaryPrice = oracle.getPrimaryPrice(indexToken);
// for limit increase long positions:// - the order should be executed when the oracle price is <= triggerPrice// - primaryPrice.max should be used for the oracle price// for limit increase short positions:// - the order should be executed when the oracle price is >= triggerPrice// - primaryPrice.min should be used for the oracle priceif (orderType == Order.OrderType.LimitIncrease) {
bool ok = isLong ? primaryPrice.max<= triggerPrice : primaryPrice.min>= triggerPrice;
if (!ok) {
revert Errors.InvalidOrderPrices(primaryPrice.min, primaryPrice.max, triggerPrice, uint256(orderType));
}
return;
}
// for stop increase long positions:// - the order should be executed when the oracle price is >= triggerPrice// - primaryPrice.max should be used for the oracle price// for stop increase short positions:// - the order should be executed when the oracle price is <= triggerPrice// - primaryPrice.min should be used for the oracle priceif (orderType == Order.OrderType.StopIncrease) {
bool ok = isLong ? primaryPrice.max>= triggerPrice : primaryPrice.min<= triggerPrice;
if (!ok) {
revert Errors.InvalidOrderPrices(primaryPrice.min, primaryPrice.max, triggerPrice, uint256(orderType));
}
return;
}
// for limit decrease long positions:// - the order should be executed when the oracle price is >= triggerPrice// - primaryPrice.min should be used for the oracle price// for limit decrease short positions:// - the order should be executed when the oracle price is <= triggerPrice// - primaryPrice.max should be used for the oracle priceif (orderType == Order.OrderType.LimitDecrease) {
bool ok = isLong ? primaryPrice.min>= triggerPrice : primaryPrice.max<= triggerPrice;
if (!ok) {
revert Errors.InvalidOrderPrices(primaryPrice.min, primaryPrice.max, triggerPrice, uint256(orderType));
}
return;
}
// for stop-loss decrease long positions:// - the order should be executed when the oracle price is <= triggerPrice// - primaryPrice.min should be used for the oracle price// for stop-loss decrease short positions:// - the order should be executed when the oracle price is >= triggerPrice// - primaryPrice.max should be used for the oracle priceif (orderType == Order.OrderType.StopLossDecrease) {
bool ok = isLong ? primaryPrice.min<= triggerPrice : primaryPrice.max>= triggerPrice;
if (!ok) {
revert Errors.InvalidOrderPrices(primaryPrice.min, primaryPrice.max, triggerPrice, uint256(orderType));
}
return;
}
revert Errors.UnsupportedOrderType(uint256(orderType));
}
functionvalidateOrderValidFromTime(
Order.OrderType orderType,
uint256 validFromTime
) internalview{
if (isMarketOrder(orderType)) {
return;
}
uint256 currentTimestamp = Chain.currentTimestamp();
if (validFromTime > currentTimestamp) {
revert Errors.OrderValidFromTimeNotReached(validFromTime, currentTimestamp);
}
}
functiongetExecutionPriceForIncrease(uint256 sizeDeltaUsd,
uint256 sizeDeltaInTokens,
uint256 acceptablePrice,
bool isLong
) internalpurereturns (uint256) {
if (sizeDeltaInTokens ==0) {
revert Errors.EmptySizeDeltaInTokens();
}
uint256 executionPrice = sizeDeltaUsd / sizeDeltaInTokens;
// increase order:// - long: executionPrice should be smaller than acceptablePrice// - short: executionPrice should be larger than acceptablePriceif (
(isLong && executionPrice <= acceptablePrice) ||
(!isLong && executionPrice >= acceptablePrice)
) {
return executionPrice;
}
// the validateOrderTriggerPrice function should have validated if the price fulfills// the order's trigger price//// for increase orders, the negative price impact is not capped//// for both increase and decrease orders, if it is due to price impact that the// order cannot be fulfilled then the order should be frozen//// this is to prevent gaming by manipulation of the price impact value//// usually it should be costly to game the price impact value// however, for certain cases, e.g. a user already has a large position opened// the user may create limit orders that would only trigger after they close// their position, this gives the user the option to cancel the pending order if// prices do not move in their favour or to close their position and let the order// execute if prices move in their favour//// it may also be possible for users to prevent the execution of orders from other users// by manipulating the price impact, though this should be costlyrevert Errors.OrderNotFulfillableAtAcceptablePrice(executionPrice, acceptablePrice);
}
functiongetExecutionPriceForDecrease(
Price.Props memory indexTokenPrice,
uint256 positionSizeInUsd,
uint256 positionSizeInTokens,
uint256 sizeDeltaUsd,
int256 priceImpactUsd,
uint256 acceptablePrice,
bool isLong
) internalpurereturns (uint256) {
GetExecutionPriceCache memory cache;
// decrease order:// - long: use the smaller price// - short: use the larger price
cache.price = indexTokenPrice.pickPrice(!isLong);
cache.executionPrice = cache.price;
// using closing of long positions as an example// realized pnl is calculated as totalPositionPnl * sizeDeltaInTokens / position.sizeInTokens// totalPositionPnl: position.sizeInTokens * executionPrice - position.sizeInUsd// sizeDeltaInTokens: position.sizeInTokens * sizeDeltaUsd / position.sizeInUsd// realized pnl: (position.sizeInTokens * executionPrice - position.sizeInUsd) * (position.sizeInTokens * sizeDeltaUsd / position.sizeInUsd) / position.sizeInTokens// realized pnl: (position.sizeInTokens * executionPrice - position.sizeInUsd) * (sizeDeltaUsd / position.sizeInUsd)// priceImpactUsd should adjust the execution price such that:// [(position.sizeInTokens * executionPrice - position.sizeInUsd) * (sizeDeltaUsd / position.sizeInUsd)] -// [(position.sizeInTokens * price - position.sizeInUsd) * (sizeDeltaUsd / position.sizeInUsd)] = priceImpactUsd//// (position.sizeInTokens * executionPrice - position.sizeInUsd) - (position.sizeInTokens * price - position.sizeInUsd)// = priceImpactUsd / (sizeDeltaUsd / position.sizeInUsd)// = priceImpactUsd * position.sizeInUsd / sizeDeltaUsd//// position.sizeInTokens * executionPrice - position.sizeInTokens * price = priceImpactUsd * position.sizeInUsd / sizeDeltaUsd// position.sizeInTokens * (executionPrice - price) = priceImpactUsd * position.sizeInUsd / sizeDeltaUsd// executionPrice - price = (priceImpactUsd * position.sizeInUsd) / (sizeDeltaUsd * position.sizeInTokens)// executionPrice = price + (priceImpactUsd * position.sizeInUsd) / (sizeDeltaUsd * position.sizeInTokens)// executionPrice = price + (priceImpactUsd / sizeDeltaUsd) * (position.sizeInUsd / position.sizeInTokens)// executionPrice = price + (priceImpactUsd * position.sizeInUsd / position.sizeInTokens) / sizeDeltaUsd//// e.g. if price is $2000, sizeDeltaUsd is $5000, priceImpactUsd is -$1000, position.sizeInUsd is $10,000, position.sizeInTokens is 5// executionPrice = 2000 + (-1000 * 10,000 / 5) / 5000 = 1600// realizedPnl based on price, without price impact: 0// realizedPnl based on executionPrice, with price impact: (5 * 1600 - 10,000) * (5 * 5000 / 10,000) / 5 => -1000// a positive adjustedPriceImpactUsd would decrease the executionPrice// a negative adjustedPriceImpactUsd would increase the executionPrice// for increase orders, the adjustedPriceImpactUsd is added to the divisor// a positive adjustedPriceImpactUsd would increase the divisor and decrease the executionPrice// increase long order:// - if price impact is positive, adjustedPriceImpactUsd should be positive, to decrease the executionPrice// - if price impact is negative, adjustedPriceImpactUsd should be negative, to increase the executionPrice// increase short order:// - if price impact is positive, adjustedPriceImpactUsd should be negative, to increase the executionPrice// - if price impact is negative, adjustedPriceImpactUsd should be positive, to decrease the executionPrice// for decrease orders, the adjustedPriceImpactUsd adjusts the numerator// a positive adjustedPriceImpactUsd would increase the divisor and increase the executionPrice// decrease long order:// - if price impact is positive, adjustedPriceImpactUsd should be positive, to increase the executionPrice// - if price impact is negative, adjustedPriceImpactUsd should be negative, to decrease the executionPrice// decrease short order:// - if price impact is positive, adjustedPriceImpactUsd should be negative, to decrease the executionPrice// - if price impact is negative, adjustedPriceImpactUsd should be positive, to increase the executionPrice// adjust price by price impactif (sizeDeltaUsd >0&& positionSizeInTokens >0) {
cache.adjustedPriceImpactUsd = isLong ? priceImpactUsd : -priceImpactUsd;
if (cache.adjustedPriceImpactUsd <0&& (-cache.adjustedPriceImpactUsd).toUint256() > sizeDeltaUsd) {
revert Errors.PriceImpactLargerThanOrderSize(cache.adjustedPriceImpactUsd, sizeDeltaUsd);
}
int256 adjustment = Precision.mulDiv(positionSizeInUsd, cache.adjustedPriceImpactUsd, positionSizeInTokens) / sizeDeltaUsd.toInt256();
int256 _executionPrice = cache.price.toInt256() + adjustment;
if (_executionPrice <0) {
revert Errors.NegativeExecutionPrice(_executionPrice, cache.price, positionSizeInUsd, cache.adjustedPriceImpactUsd, sizeDeltaUsd);
}
cache.executionPrice = _executionPrice.toUint256();
}
// decrease order:// - long: executionPrice should be larger than acceptablePrice// - short: executionPrice should be smaller than acceptablePriceif (
(isLong && cache.executionPrice >= acceptablePrice) ||
(!isLong && cache.executionPrice <= acceptablePrice)
) {
return cache.executionPrice;
}
// the validateOrderTriggerPrice function should have validated if the price fulfills// the order's trigger price//// for decrease orders, the price impact should already be capped, so if the user// had set an acceptable price within the range of the capped price impact, then// the order should be fulfillable at the acceptable price//// for increase orders, the negative price impact is not capped//// for both increase and decrease orders, if it is due to price impact that the// order cannot be fulfilled then the order should be frozen//// this is to prevent gaming by manipulation of the price impact value//// usually it should be costly to game the price impact value// however, for certain cases, e.g. a user already has a large position opened// the user may create limit orders that would only trigger after they close// their position, this gives the user the option to cancel the pending order if// prices do not move in their favour or to close their position and let the order// execute if prices move in their favour//// it may also be possible for users to prevent the execution of orders from other users// by manipulating the price impact, though this should be costlyrevert Errors.OrderNotFulfillableAtAcceptablePrice(cache.executionPrice, acceptablePrice);
}
// @dev validate that an order exists// @param order the order to checkfunctionvalidateNonEmptyOrder(Order.Props memory order) internalpure{
if (order.account() ==address(0)) {
revert Errors.EmptyOrder();
}
if (order.sizeDeltaUsd() ==0&& order.initialCollateralDeltaAmount() ==0) {
revert Errors.EmptyOrder();
}
}
functiongetPositionKey(Order.Props memory order) internalpurereturns (bytes32) {
if (isDecreaseOrder(order.orderType())) {
return Position.getPositionKey(
order.account(),
order.market(),
order.initialCollateralToken(),
order.isLong()
);
}
revert Errors.UnsupportedOrderType(uint256(order.orderType()));
}
}
Contract Source Code
File 13 of 103: Calc.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"@openzeppelin/contracts/utils/math/SignedMath.sol";
import"@openzeppelin/contracts/utils/math/SafeCast.sol";
/**
* @title Calc
* @dev Library for math functions
*/libraryCalc{
usingSignedMathforint256;
usingSafeCastforuint256;
// this method assumes that min is less than maxfunctionboundMagnitude(int256 value, uint256 min, uint256 max) internalpurereturns (int256) {
uint256 magnitude = value.abs();
if (magnitude < min) {
magnitude = min;
}
if (magnitude > max) {
magnitude = max;
}
int256 sign = value ==0 ? int256(1) : value / value.abs().toInt256();
return magnitude.toInt256() * sign;
}
/**
* @dev Calculates the result of dividing the first number by the second number,
* rounded up to the nearest integer.
*
* @param a the dividend
* @param b the divisor
* @return the result of dividing the first number by the second number, rounded up to the nearest integer
*/functionroundUpDivision(uint256 a, uint256 b) internalpurereturns (uint256) {
return (a + b -1) / b;
}
/**
* Calculates the result of dividing the first number by the second number,
* rounded up to the nearest integer.
* The rounding is purely on the magnitude of a, if a is negative the result
* is a larger magnitude negative
*
* @param a the dividend
* @param b the divisor
* @return the result of dividing the first number by the second number, rounded up to the nearest integer
*/functionroundUpMagnitudeDivision(int256 a, uint256 b) internalpurereturns (int256) {
if (a <0) {
return (a - b.toInt256() +1) / b.toInt256();
}
return (a + b.toInt256() -1) / b.toInt256();
}
/**
* Adds two numbers together and return a uint256 value, treating the second number as a signed integer.
*
* @param a the first number
* @param b the second number
* @return the result of adding the two numbers together
*/functionsumReturnUint256(uint256 a, int256 b) internalpurereturns (uint256) {
if (b >0) {
return a + b.abs();
}
return a - b.abs();
}
/**
* Adds two numbers together and return an int256 value, treating the second number as a signed integer.
*
* @param a the first number
* @param b the second number
* @return the result of adding the two numbers together
*/functionsumReturnInt256(uint256 a, int256 b) internalpurereturns (int256) {
return a.toInt256() + b;
}
/**
* @dev Calculates the absolute difference between two numbers.
*
* @param a the first number
* @param b the second number
* @return the absolute difference between the two numbers
*/functiondiff(uint256 a, uint256 b) internalpurereturns (uint256) {
return a > b ? a - b : b - a;
}
/**
* Adds two numbers together, the result is bounded to prevent overflows.
*
* @param a the first number
* @param b the second number
* @return the result of adding the two numbers together
*/functionboundedAdd(int256 a, int256 b) internalpurereturns (int256) {
// if either a or b is zero or if the signs are different there should not be any overflowsif (a ==0|| b ==0|| (a <0&& b >0) || (a >0&& b <0)) {
return a + b;
}
// if adding `b` to `a` would result in a value less than the min int256 value// then return the min int256 valueif (a <0&& b <=type(int256).min- a) {
returntype(int256).min;
}
// if adding `b` to `a` would result in a value more than the max int256 value// then return the max int256 valueif (a >0&& b >=type(int256).max- a) {
returntype(int256).max;
}
return a + b;
}
/**
* Returns a - b, the result is bounded to prevent overflows.
* Note that this will revert if b is type(int256).min because of the usage of "-b".
*
* @param a the first number
* @param b the second number
* @return the bounded result of a - b
*/functionboundedSub(int256 a, int256 b) internalpurereturns (int256) {
// if either a or b is zero or the signs are the same there should not be any overflowif (a ==0|| b ==0|| (a >0&& b >0) || (a <0&& b <0)) {
return a - b;
}
// if adding `-b` to `a` would result in a value greater than the max int256 value// then return the max int256 valueif (a >0&&-b >=type(int256).max- a) {
returntype(int256).max;
}
// if subtracting `b` from `a` would result in a value less than the min int256 value// then return the min int256 valueif (a <0&&-b <=type(int256).min- a) {
returntype(int256).min;
}
return a - b;
}
/**
* Converts the given unsigned integer to a signed integer, using the given
* flag to determine whether the result should be positive or negative.
*
* @param a the unsigned integer to convert
* @param isPositive whether the result should be positive (if true) or negative (if false)
* @return the signed integer representation of the given unsigned integer
*/functiontoSigned(uint256 a, bool isPositive) internalpurereturns (int256) {
if (isPositive) {
return a.toInt256();
} else {
return-a.toInt256();
}
}
}
Contract Source Code
File 14 of 103: CallbackUtils.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"@openzeppelin/contracts/utils/Address.sol";
import"../data/DataStore.sol";
import"../data/Keys.sol";
import"./IOrderCallbackReceiver.sol";
import"./IDepositCallbackReceiver.sol";
import"./IWithdrawalCallbackReceiver.sol";
import"./IShiftCallbackReceiver.sol";
import"./IGasFeeCallbackReceiver.sol";
import"./IGlvDepositCallbackReceiver.sol";
import"./IGlvWithdrawalCallbackReceiver.sol";
// @title CallbackUtils// @dev most features require a two step process to complete// the user first sends a request transaction, then a second transaction is sent// by a keeper to execute the request//// to allow for better composability with other contracts, a callback contract// can be specified to be called after request executions or cancellations//// in case it is necessary to add "before" callbacks, extra care should be taken// to ensure that important state cannot be changed during the before callback// for example, if an order can be cancelled in the "before" callback during// order execution, it may lead to an order being executed even though the user// was already refunded for its cancellation//// the details from callback errors are not processed to avoid cases where a malicious// callback contract returns a very large value to cause transactions to run out of gaslibraryCallbackUtils{
usingAddressforaddress;
usingDepositforDeposit.Props;
usingWithdrawalforWithdrawal.Props;
usingShiftforShift.Props;
usingOrderforOrder.Props;
usingGlvDepositforGlvDeposit.Props;
usingGlvWithdrawalforGlvWithdrawal.Props;
eventAfterDepositExecutionError(bytes32 key, Deposit.Props deposit);
eventAfterDepositCancellationError(bytes32 key, Deposit.Props deposit);
eventAfterWithdrawalExecutionError(bytes32 key, Withdrawal.Props withdrawal);
eventAfterWithdrawalCancellationError(bytes32 key, Withdrawal.Props withdrawal);
eventAfterShiftExecutionError(bytes32 key, Shift.Props shift);
eventAfterShiftCancellationError(bytes32 key, Shift.Props shift);
eventAfterOrderExecutionError(bytes32 key, Order.Props order);
eventAfterOrderCancellationError(bytes32 key, Order.Props order);
eventAfterOrderFrozenError(bytes32 key, Order.Props order);
eventAfterGlvDepositExecutionError(bytes32 key, GlvDeposit.Props glvDeposit);
eventAfterGlvDepositCancellationError(bytes32 key, GlvDeposit.Props glvDeposit);
eventAfterGlvWithdrawalExecutionError(bytes32 key, GlvWithdrawal.Props glvWithdrawal);
eventAfterGlvWithdrawalCancellationError(bytes32 key, GlvWithdrawal.Props glvWithdrawal);
// @dev validate that the callbackGasLimit is less than the max specified value// this is to prevent callback gas limits which are larger than the max gas limits per block// as this would allow for callback contracts that can consume all gas and conditionally cause// executions to fail// @param dataStore DataStore// @param callbackGasLimit the callback gas limitfunctionvalidateCallbackGasLimit(DataStore dataStore, uint256 callbackGasLimit) internalview{
uint256 maxCallbackGasLimit = dataStore.getUint(Keys.MAX_CALLBACK_GAS_LIMIT);
if (callbackGasLimit > maxCallbackGasLimit) {
revert Errors.MaxCallbackGasLimitExceeded(callbackGasLimit, maxCallbackGasLimit);
}
}
functionvalidateGasLeftForCallback(uint256 callbackGasLimit) internalview{
uint256 gasToBeForwarded =gasleft() /64*63;
if (gasToBeForwarded < callbackGasLimit) {
revert Errors.InsufficientGasLeftForCallback(gasToBeForwarded, callbackGasLimit);
}
}
functionsetSavedCallbackContract(DataStore dataStore, address account, address market, address callbackContract) external{
dataStore.setAddress(Keys.savedCallbackContract(account, market), callbackContract);
}
functiongetSavedCallbackContract(DataStore dataStore, address account, address market) internalviewreturns (address) {
return dataStore.getAddress(Keys.savedCallbackContract(account, market));
}
functionrefundExecutionFee(
DataStore dataStore,
bytes32 key,
address callbackContract,
uint256 refundFeeAmount,
EventUtils.EventLogData memory eventData
) internalreturns (bool) {
if (!isValidCallbackContract(callbackContract)) { returnfalse; }
uint256 gasLimit = dataStore.getUint(Keys.REFUND_EXECUTION_FEE_GAS_LIMIT);
validateGasLeftForCallback(gasLimit);
try IGasFeeCallbackReceiver(callbackContract).refundExecutionFee{ gas: gasLimit, value: refundFeeAmount }(
key,
eventData
) {
returntrue;
} catch {
returnfalse;
}
}
// @dev called after a deposit execution// @param key the key of the deposit// @param deposit the deposit that was executedfunctionafterDepositExecution(bytes32 key,
Deposit.Props memory deposit,
EventUtils.EventLogData memory eventData
) internal{
if (!isValidCallbackContract(deposit.callbackContract())) { return; }
validateGasLeftForCallback(deposit.callbackGasLimit());
try IDepositCallbackReceiver(deposit.callbackContract()).afterDepositExecution{ gas: deposit.callbackGasLimit() }(
key,
deposit,
eventData
) {
} catch {
emit AfterDepositExecutionError(key, deposit);
}
}
// @dev called after a deposit cancellation// @param key the key of the deposit// @param deposit the deposit that was cancelledfunctionafterDepositCancellation(bytes32 key,
Deposit.Props memory deposit,
EventUtils.EventLogData memory eventData
) internal{
if (!isValidCallbackContract(deposit.callbackContract())) { return; }
validateGasLeftForCallback(deposit.callbackGasLimit());
try IDepositCallbackReceiver(deposit.callbackContract()).afterDepositCancellation{ gas: deposit.callbackGasLimit() }(
key,
deposit,
eventData
) {
} catch {
emit AfterDepositCancellationError(key, deposit);
}
}
// @dev called after a withdrawal execution// @param key the key of the withdrawal// @param withdrawal the withdrawal that was executedfunctionafterWithdrawalExecution(bytes32 key,
Withdrawal.Props memory withdrawal,
EventUtils.EventLogData memory eventData
) internal{
if (!isValidCallbackContract(withdrawal.callbackContract())) { return; }
validateGasLeftForCallback(withdrawal.callbackGasLimit());
try IWithdrawalCallbackReceiver(withdrawal.callbackContract()).afterWithdrawalExecution{ gas: withdrawal.callbackGasLimit() }(
key,
withdrawal,
eventData
) {
} catch {
emit AfterWithdrawalExecutionError(key, withdrawal);
}
}
// @dev called after a withdrawal cancellation// @param key the key of the withdrawal// @param withdrawal the withdrawal that was cancelledfunctionafterWithdrawalCancellation(bytes32 key,
Withdrawal.Props memory withdrawal,
EventUtils.EventLogData memory eventData
) internal{
if (!isValidCallbackContract(withdrawal.callbackContract())) { return; }
validateGasLeftForCallback(withdrawal.callbackGasLimit());
try IWithdrawalCallbackReceiver(withdrawal.callbackContract()).afterWithdrawalCancellation{ gas: withdrawal.callbackGasLimit() }(
key,
withdrawal,
eventData
) {
} catch {
emit AfterWithdrawalCancellationError(key, withdrawal);
}
}
functionafterShiftExecution(bytes32 key,
Shift.Props memory shift,
EventUtils.EventLogData memory eventData
) internal{
if (!isValidCallbackContract(shift.callbackContract())) { return; }
validateGasLeftForCallback(shift.callbackGasLimit());
try IShiftCallbackReceiver(shift.callbackContract()).afterShiftExecution{ gas: shift.callbackGasLimit() }(
key,
shift,
eventData
) {
} catch {
emit AfterShiftExecutionError(key, shift);
}
}
functionafterShiftCancellation(bytes32 key,
Shift.Props memory shift,
EventUtils.EventLogData memory eventData
) internal{
if (!isValidCallbackContract(shift.callbackContract())) { return; }
validateGasLeftForCallback(shift.callbackGasLimit());
try IShiftCallbackReceiver(shift.callbackContract()).afterShiftCancellation{ gas: shift.callbackGasLimit() }(
key,
shift,
eventData
) {
} catch {
emit AfterShiftCancellationError(key, shift);
}
}
// @dev called after an order execution// note that the order.size, order.initialCollateralDeltaAmount and other// properties may be updated during execution, the new values may not be// updated in the order object for the callback// @param key the key of the order// @param order the order that was executedfunctionafterOrderExecution(bytes32 key,
Order.Props memory order,
EventUtils.EventLogData memory eventData
) internal{
if (!isValidCallbackContract(order.callbackContract())) { return; }
validateGasLeftForCallback(order.callbackGasLimit());
try IOrderCallbackReceiver(order.callbackContract()).afterOrderExecution{ gas: order.callbackGasLimit() }(
key,
order,
eventData
) {
} catch {
emit AfterOrderExecutionError(key, order);
}
}
// @dev called after an order cancellation// @param key the key of the order// @param order the order that was cancelledfunctionafterOrderCancellation(bytes32 key,
Order.Props memory order,
EventUtils.EventLogData memory eventData
) internal{
if (!isValidCallbackContract(order.callbackContract())) { return; }
validateGasLeftForCallback(order.callbackGasLimit());
try IOrderCallbackReceiver(order.callbackContract()).afterOrderCancellation{ gas: order.callbackGasLimit() }(
key,
order,
eventData
) {
} catch {
emit AfterOrderCancellationError(key, order);
}
}
// @dev called after an order has been frozen, see OrderUtils.freezeOrder in OrderHandler for more info// @param key the key of the order// @param order the order that was frozenfunctionafterOrderFrozen(bytes32 key,
Order.Props memory order,
EventUtils.EventLogData memory eventData
) internal{
if (!isValidCallbackContract(order.callbackContract())) { return; }
validateGasLeftForCallback(order.callbackGasLimit());
try IOrderCallbackReceiver(order.callbackContract()).afterOrderFrozen{ gas: order.callbackGasLimit() }(
key,
order,
eventData
) {
} catch {
emit AfterOrderFrozenError(key, order);
}
}
// @dev called after a glvDeposit execution// @param key the key of the glvDeposit// @param glvDeposit the glvDeposit that was executedfunctionafterGlvDepositExecution(bytes32 key,
GlvDeposit.Props memory glvDeposit,
EventUtils.EventLogData memory eventData
) internal{
if (!isValidCallbackContract(glvDeposit.callbackContract())) {
return;
}
validateGasLeftForCallback(glvDeposit.callbackGasLimit());
try IGlvDepositCallbackReceiver(glvDeposit.callbackContract()).afterGlvDepositExecution{ gas: glvDeposit.callbackGasLimit() }(
key,
glvDeposit,
eventData
) {
} catch {
emit AfterGlvDepositExecutionError(key, glvDeposit);
}
}
// @dev called after a glvDeposit cancellation// @param key the key of the glvDeposit// @param glvDeposit the glvDeposit that was cancelledfunctionafterGlvDepositCancellation(bytes32 key,
GlvDeposit.Props memory glvDeposit,
EventUtils.EventLogData memory eventData
) internal{
if (!isValidCallbackContract(glvDeposit.callbackContract())) { return; }
validateGasLeftForCallback(glvDeposit.callbackGasLimit());
try IGlvDepositCallbackReceiver(glvDeposit.callbackContract()).afterGlvDepositCancellation{ gas: glvDeposit.callbackGasLimit() }(
key,
glvDeposit,
eventData
) {
} catch {
emit AfterGlvDepositCancellationError(key, glvDeposit);
}
}
// @dev called after a glvWithdrawal execution// @param key the key of the glvWithdrawal// @param glvWithdrawal the glvWithdrawal that was executedfunctionafterGlvWithdrawalExecution(bytes32 key,
GlvWithdrawal.Props memory glvWithdrawal,
EventUtils.EventLogData memory eventData
) internal{
if (!isValidCallbackContract(glvWithdrawal.callbackContract())) { return; }
validateGasLeftForCallback(glvWithdrawal.callbackGasLimit());
try IGlvWithdrawalCallbackReceiver(glvWithdrawal.callbackContract()).afterGlvWithdrawalExecution{ gas: glvWithdrawal.callbackGasLimit() }(
key,
glvWithdrawal,
eventData
) {
} catch {
emit AfterGlvWithdrawalExecutionError(key, glvWithdrawal);
}
}
// @dev called after a glvWithdrawal cancellation// @param key the key of the glvWithdrawal// @param glvWithdrawal the glvWithdrawal that was cancelledfunctionafterGlvWithdrawalCancellation(bytes32 key,
GlvWithdrawal.Props memory glvWithdrawal,
EventUtils.EventLogData memory eventData
) internal{
if (!isValidCallbackContract(glvWithdrawal.callbackContract())) { return; }
validateGasLeftForCallback(glvWithdrawal.callbackGasLimit());
try IGlvWithdrawalCallbackReceiver(glvWithdrawal.callbackContract()).afterGlvWithdrawalCancellation{ gas: glvWithdrawal.callbackGasLimit() }(
key,
glvWithdrawal,
eventData
) {
} catch {
emit AfterGlvWithdrawalCancellationError(key, glvWithdrawal);
}
}
// @dev validates that the given address is a contract// @param callbackContract the contract to callfunctionisValidCallbackContract(address callbackContract) internalviewreturns (bool) {
if (callbackContract ==address(0)) { returnfalse; }
if (!callbackContract.isContract()) { returnfalse; }
returntrue;
}
}
Contract Source Code
File 15 of 103: Cast.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"../error/Errors.sol";
/**
* @title Cast
* @dev Library for casting functions
*/libraryCast{
functiontoBytes32(address value) internalpurereturns (bytes32) {
returnbytes32(uint256(uint160(value)));
}
/**
* @dev Converts a bytes array to a uint256.
* Handles cases where the uint256 stored in bytes is stored with or without padding.
* @param uint256AsBytes The bytes array representing the uint256 value.
* @return value The uint256 value obtained from the bytes array.
*/functionbytesToUint256(bytesmemory uint256AsBytes) internalpurereturns (uint256) {
uint256 length = uint256AsBytes.length;
if(length >32) {
revert Errors.Uint256AsBytesLengthExceeds32Bytes(length);
}
if (length ==0) {
return0;
}
uint256 value;
assembly {
value :=mload(add(uint256AsBytes, 32))
}
return value = value >> (8* (32- length));
}
}
Contract Source Code
File 16 of 103: Chain.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"./ArbSys.sol";
// @title Chain// @dev Wrap the calls to retrieve chain variables to handle differences// between chain implementationslibraryChain{
// if the ARBITRUM_CHAIN_ID changes, a new version of this library// and contracts depending on it would need to be deployeduint256publicconstant ARBITRUM_CHAIN_ID =42161;
uint256publicconstant ARBITRUM_SEPOLIA_CHAIN_ID =421614;
ArbSys publicconstant arbSys = ArbSys(address(100));
// @dev return the current block's timestamp// @return the current block's timestampfunctioncurrentTimestamp() internalviewreturns (uint256) {
returnblock.timestamp;
}
// @dev return the current block's number// @return the current block's numberfunctioncurrentBlockNumber() internalviewreturns (uint256) {
if (shouldUseArbSysValues()) {
return arbSys.arbBlockNumber();
}
returnblock.number;
}
// @dev return the current block's hash// @return the current block's hashfunctiongetBlockHash(uint256 blockNumber) internalviewreturns (bytes32) {
if (shouldUseArbSysValues()) {
return arbSys.arbBlockHash(blockNumber);
}
returnblockhash(blockNumber);
}
functionshouldUseArbSysValues() internalviewreturns (bool) {
returnblock.chainid== ARBITRUM_CHAIN_ID ||block.chainid== ARBITRUM_SEPOLIA_CHAIN_ID;
}
}
Contract Source Code
File 17 of 103: ChainlinkPriceFeedUtils.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"../chain/Chain.sol";
import"../data/DataStore.sol";
import"../data/Keys.sol";
import"../utils/Precision.sol";
import"./IPriceFeed.sol";
// @title ChainlinkPriceFeedProviderUtils// @dev Library for Chainlink price feedlibraryChainlinkPriceFeedUtils{
// there is a small risk of stale pricing due to latency in price updates or if the chain is down// this is meant to be for temporary use until low latency price feeds are supported for all tokensfunctiongetPriceFeedPrice(DataStore dataStore, address token) internalviewreturns (bool, uint256) {
address priceFeedAddress = dataStore.getAddress(Keys.priceFeedKey(token));
if (priceFeedAddress ==address(0)) {
return (false, 0);
}
IPriceFeed priceFeed = IPriceFeed(priceFeedAddress);
(
/* uint80 roundID */,
int256 _price,
/* uint256 startedAt */,
uint256 timestamp,
/* uint80 answeredInRound */
) = priceFeed.latestRoundData();
if (_price <=0) {
revert Errors.InvalidFeedPrice(token, _price);
}
uint256 heartbeatDuration = dataStore.getUint(Keys.priceFeedHeartbeatDurationKey(token));
if (Chain.currentTimestamp() > timestamp && Chain.currentTimestamp() - timestamp > heartbeatDuration) {
revert Errors.ChainlinkPriceFeedNotUpdated(token, timestamp, heartbeatDuration);
}
uint256 price = SafeCast.toUint256(_price);
uint256 precision = getPriceFeedMultiplier(dataStore, token);
uint256 adjustedPrice = Precision.mulDiv(price, precision, Precision.FLOAT_PRECISION);
return (true, adjustedPrice);
}
// @dev get the multiplier value to convert the external price feed price to the price of 1 unit of the token// represented with 30 decimals// for example, if USDC has 6 decimals and a price of 1 USD, one unit of USDC would have a price of// 1 / (10 ^ 6) * (10 ^ 30) => 1 * (10 ^ 24)// if the external price feed has 8 decimals, the price feed price would be 1 * (10 ^ 8)// in this case the priceFeedMultiplier should be 10 ^ 46// the conversion of the price feed price would be 1 * (10 ^ 8) * (10 ^ 46) / (10 ^ 30) => 1 * (10 ^ 24)// formula for decimals for price feed multiplier: 60 - (external price feed decimals) - (token decimals)//// @param dataStore DataStore// @param token the token to get the price feed multiplier for// @return the price feed multiplerfunctiongetPriceFeedMultiplier(DataStore dataStore, address token) internalviewreturns (uint256) {
uint256 multiplier = dataStore.getUint(Keys.priceFeedMultiplierKey(token));
if (multiplier ==0) {
revert Errors.EmptyChainlinkPriceFeedMultiplier(token);
}
return multiplier;
}
}
Contract Source Code
File 18 of 103: Context.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)pragmasolidity ^0.8.0;/**
* @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 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.
*/abstractcontractContext{
function_msgSender() internalviewvirtualreturns (address) {
returnmsg.sender;
}
function_msgData() internalviewvirtualreturns (bytescalldata) {
returnmsg.data;
}
}
Contract Source Code
File 19 of 103: DataStore.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"../role/RoleModule.sol";
import"../utils/Calc.sol";
// @title DataStore// @dev DataStore for all general state valuescontractDataStoreisRoleModule{
usingSafeCastforint256;
usingEnumerableSetforEnumerableSet.Bytes32Set;
usingEnumerableSetforEnumerableSet.AddressSet;
usingEnumerableSetforEnumerableSet.UintSet;
usingEnumerableValuesforEnumerableSet.Bytes32Set;
usingEnumerableValuesforEnumerableSet.AddressSet;
usingEnumerableValuesforEnumerableSet.UintSet;
// store for uint valuesmapping(bytes32=>uint256) public uintValues;
// store for int valuesmapping(bytes32=>int256) public intValues;
// store for address valuesmapping(bytes32=>address) public addressValues;
// store for bool valuesmapping(bytes32=>bool) public boolValues;
// store for string valuesmapping(bytes32=>string) public stringValues;
// store for bytes32 valuesmapping(bytes32=>bytes32) public bytes32Values;
// store for uint[] valuesmapping(bytes32=>uint256[]) public uintArrayValues;
// store for int[] valuesmapping(bytes32=>int256[]) public intArrayValues;
// store for address[] valuesmapping(bytes32=>address[]) public addressArrayValues;
// store for bool[] valuesmapping(bytes32=>bool[]) public boolArrayValues;
// store for string[] valuesmapping(bytes32=>string[]) public stringArrayValues;
// store for bytes32[] valuesmapping(bytes32=>bytes32[]) public bytes32ArrayValues;
// store for bytes32 setsmapping(bytes32=> EnumerableSet.Bytes32Set) internal bytes32Sets;
// store for address setsmapping(bytes32=> EnumerableSet.AddressSet) internal addressSets;
// store for uint256 setsmapping(bytes32=> EnumerableSet.UintSet) internal uintSets;
constructor(RoleStore _roleStore) RoleModule(_roleStore) {}
// @dev get the uint value for the given key// @param key the key of the value// @return the uint value for the keyfunctiongetUint(bytes32 key) externalviewreturns (uint256) {
return uintValues[key];
}
// @dev set the uint value for the given key// @param key the key of the value// @param value the value to set// @return the uint value for the keyfunctionsetUint(bytes32 key, uint256 value) externalonlyControllerreturns (uint256) {
uintValues[key] = value;
return value;
}
// @dev delete the uint value for the given key// @param key the key of the valuefunctionremoveUint(bytes32 key) externalonlyController{
delete uintValues[key];
}
// @dev add the input int value to the existing uint value// @param key the key of the value// @param value the input int value// @return the new uint valuefunctionapplyDeltaToUint(bytes32 key, int256 value, stringmemory errorMessage) externalonlyControllerreturns (uint256) {
uint256 currValue = uintValues[key];
if (value <0&& (-value).toUint256() > currValue) {
revert(errorMessage);
}
uint256 nextUint = Calc.sumReturnUint256(currValue, value);
uintValues[key] = nextUint;
return nextUint;
}
// @dev add the input uint value to the existing uint value// @param key the key of the value// @param value the input int value// @return the new uint valuefunctionapplyDeltaToUint(bytes32 key, uint256 value) externalonlyControllerreturns (uint256) {
uint256 currValue = uintValues[key];
uint256 nextUint = currValue + value;
uintValues[key] = nextUint;
return nextUint;
}
// @dev add the input int value to the existing uint value, prevent the uint// value from becoming negative// @param key the key of the value// @param value the input int value// @return the new uint valuefunctionapplyBoundedDeltaToUint(bytes32 key, int256 value) externalonlyControllerreturns (uint256) {
uint256 uintValue = uintValues[key];
if (value <0&& (-value).toUint256() > uintValue) {
uintValues[key] =0;
return0;
}
uint256 nextUint = Calc.sumReturnUint256(uintValue, value);
uintValues[key] = nextUint;
return nextUint;
}
// @dev add the input uint value to the existing uint value// @param key the key of the value// @param value the input uint value// @return the new uint valuefunctionincrementUint(bytes32 key, uint256 value) externalonlyControllerreturns (uint256) {
uint256 nextUint = uintValues[key] + value;
uintValues[key] = nextUint;
return nextUint;
}
// @dev subtract the input uint value from the existing uint value// @param key the key of the value// @param value the input uint value// @return the new uint valuefunctiondecrementUint(bytes32 key, uint256 value) externalonlyControllerreturns (uint256) {
uint256 nextUint = uintValues[key] - value;
uintValues[key] = nextUint;
return nextUint;
}
// @dev get the int value for the given key// @param key the key of the value// @return the int value for the keyfunctiongetInt(bytes32 key) externalviewreturns (int256) {
return intValues[key];
}
// @dev set the int value for the given key// @param key the key of the value// @param value the value to set// @return the int value for the keyfunctionsetInt(bytes32 key, int256 value) externalonlyControllerreturns (int256) {
intValues[key] = value;
return value;
}
functionremoveInt(bytes32 key) externalonlyController{
delete intValues[key];
}
// @dev add the input int value to the existing int value// @param key the key of the value// @param value the input int value// @return the new int valuefunctionapplyDeltaToInt(bytes32 key, int256 value) externalonlyControllerreturns (int256) {
int256 nextInt = intValues[key] + value;
intValues[key] = nextInt;
return nextInt;
}
// @dev add the input int value to the existing int value// @param key the key of the value// @param value the input int value// @return the new int valuefunctionincrementInt(bytes32 key, int256 value) externalonlyControllerreturns (int256) {
int256 nextInt = intValues[key] + value;
intValues[key] = nextInt;
return nextInt;
}
// @dev subtract the input int value from the existing int value// @param key the key of the value// @param value the input int value// @return the new int valuefunctiondecrementInt(bytes32 key, int256 value) externalonlyControllerreturns (int256) {
int256 nextInt = intValues[key] - value;
intValues[key] = nextInt;
return nextInt;
}
// @dev get the address value for the given key// @param key the key of the value// @return the address value for the keyfunctiongetAddress(bytes32 key) externalviewreturns (address) {
return addressValues[key];
}
// @dev set the address value for the given key// @param key the key of the value// @param value the value to set// @return the address value for the keyfunctionsetAddress(bytes32 key, address value) externalonlyControllerreturns (address) {
addressValues[key] = value;
return value;
}
// @dev delete the address value for the given key// @param key the key of the valuefunctionremoveAddress(bytes32 key) externalonlyController{
delete addressValues[key];
}
// @dev get the bool value for the given key// @param key the key of the value// @return the bool value for the keyfunctiongetBool(bytes32 key) externalviewreturns (bool) {
return boolValues[key];
}
// @dev set the bool value for the given key// @param key the key of the value// @param value the value to set// @return the bool value for the keyfunctionsetBool(bytes32 key, bool value) externalonlyControllerreturns (bool) {
boolValues[key] = value;
return value;
}
// @dev delete the bool value for the given key// @param key the key of the valuefunctionremoveBool(bytes32 key) externalonlyController{
delete boolValues[key];
}
// @dev get the string value for the given key// @param key the key of the value// @return the string value for the keyfunctiongetString(bytes32 key) externalviewreturns (stringmemory) {
return stringValues[key];
}
// @dev set the string value for the given key// @param key the key of the value// @param value the value to set// @return the string value for the keyfunctionsetString(bytes32 key, stringmemory value) externalonlyControllerreturns (stringmemory) {
stringValues[key] = value;
return value;
}
// @dev delete the string value for the given key// @param key the key of the valuefunctionremoveString(bytes32 key) externalonlyController{
delete stringValues[key];
}
// @dev get the bytes32 value for the given key// @param key the key of the value// @return the bytes32 value for the keyfunctiongetBytes32(bytes32 key) externalviewreturns (bytes32) {
return bytes32Values[key];
}
// @dev set the bytes32 value for the given key// @param key the key of the value// @param value the value to set// @return the bytes32 value for the keyfunctionsetBytes32(bytes32 key, bytes32 value) externalonlyControllerreturns (bytes32) {
bytes32Values[key] = value;
return value;
}
// @dev delete the bytes32 value for the given key// @param key the key of the valuefunctionremoveBytes32(bytes32 key) externalonlyController{
delete bytes32Values[key];
}
// @dev get the uint array for the given key// @param key the key of the uint array// @return the uint array for the keyfunctiongetUintArray(bytes32 key) externalviewreturns (uint256[] memory) {
return uintArrayValues[key];
}
// @dev set the uint array for the given key// @param key the key of the uint array// @param value the value of the uint arrayfunctionsetUintArray(bytes32 key, uint256[] memory value) externalonlyController{
uintArrayValues[key] = value;
}
// @dev delete the uint array for the given key// @param key the key of the uint array// @param value the value of the uint arrayfunctionremoveUintArray(bytes32 key) externalonlyController{
delete uintArrayValues[key];
}
// @dev get the int array for the given key// @param key the key of the int array// @return the int array for the keyfunctiongetIntArray(bytes32 key) externalviewreturns (int256[] memory) {
return intArrayValues[key];
}
// @dev set the int array for the given key// @param key the key of the int array// @param value the value of the int arrayfunctionsetIntArray(bytes32 key, int256[] memory value) externalonlyController{
intArrayValues[key] = value;
}
// @dev delete the int array for the given key// @param key the key of the int array// @param value the value of the int arrayfunctionremoveIntArray(bytes32 key) externalonlyController{
delete intArrayValues[key];
}
// @dev get the address array for the given key// @param key the key of the address array// @return the address array for the keyfunctiongetAddressArray(bytes32 key) externalviewreturns (address[] memory) {
return addressArrayValues[key];
}
// @dev set the address array for the given key// @param key the key of the address array// @param value the value of the address arrayfunctionsetAddressArray(bytes32 key, address[] memory value) externalonlyController{
addressArrayValues[key] = value;
}
// @dev delete the address array for the given key// @param key the key of the address array// @param value the value of the address arrayfunctionremoveAddressArray(bytes32 key) externalonlyController{
delete addressArrayValues[key];
}
// @dev get the bool array for the given key// @param key the key of the bool array// @return the bool array for the keyfunctiongetBoolArray(bytes32 key) externalviewreturns (bool[] memory) {
return boolArrayValues[key];
}
// @dev set the bool array for the given key// @param key the key of the bool array// @param value the value of the bool arrayfunctionsetBoolArray(bytes32 key, bool[] memory value) externalonlyController{
boolArrayValues[key] = value;
}
// @dev delete the bool array for the given key// @param key the key of the bool array// @param value the value of the bool arrayfunctionremoveBoolArray(bytes32 key) externalonlyController{
delete boolArrayValues[key];
}
// @dev get the string array for the given key// @param key the key of the string array// @return the string array for the keyfunctiongetStringArray(bytes32 key) externalviewreturns (string[] memory) {
return stringArrayValues[key];
}
// @dev set the string array for the given key// @param key the key of the string array// @param value the value of the string arrayfunctionsetStringArray(bytes32 key, string[] memory value) externalonlyController{
stringArrayValues[key] = value;
}
// @dev delete the string array for the given key// @param key the key of the string array// @param value the value of the string arrayfunctionremoveStringArray(bytes32 key) externalonlyController{
delete stringArrayValues[key];
}
// @dev get the bytes32 array for the given key// @param key the key of the bytes32 array// @return the bytes32 array for the keyfunctiongetBytes32Array(bytes32 key) externalviewreturns (bytes32[] memory) {
return bytes32ArrayValues[key];
}
// @dev set the bytes32 array for the given key// @param key the key of the bytes32 array// @param value the value of the bytes32 arrayfunctionsetBytes32Array(bytes32 key, bytes32[] memory value) externalonlyController{
bytes32ArrayValues[key] = value;
}
// @dev delete the bytes32 array for the given key// @param key the key of the bytes32 array// @param value the value of the bytes32 arrayfunctionremoveBytes32Array(bytes32 key) externalonlyController{
delete bytes32ArrayValues[key];
}
// @dev check whether the given value exists in the set// @param setKey the key of the set// @param value the value to checkfunctioncontainsBytes32(bytes32 setKey, bytes32 value) externalviewreturns (bool) {
return bytes32Sets[setKey].contains(value);
}
// @dev get the length of the set// @param setKey the key of the setfunctiongetBytes32Count(bytes32 setKey) externalviewreturns (uint256) {
return bytes32Sets[setKey].length();
}
// @dev get the values of the set in the given range// @param setKey the key of the set// @param the start of the range, values at the start index will be returned// in the result// @param the end of the range, values at the end index will not be returned// in the resultfunctiongetBytes32ValuesAt(bytes32 setKey, uint256 start, uint256 end) externalviewreturns (bytes32[] memory) {
return bytes32Sets[setKey].valuesAt(start, end);
}
// @dev add the given value to the set// @param setKey the key of the set// @param value the value to addfunctionaddBytes32(bytes32 setKey, bytes32 value) externalonlyController{
bytes32Sets[setKey].add(value);
}
// @dev remove the given value from the set// @param setKey the key of the set// @param value the value to removefunctionremoveBytes32(bytes32 setKey, bytes32 value) externalonlyController{
bytes32Sets[setKey].remove(value);
}
// @dev check whether the given value exists in the set// @param setKey the key of the set// @param value the value to checkfunctioncontainsAddress(bytes32 setKey, address value) externalviewreturns (bool) {
return addressSets[setKey].contains(value);
}
// @dev get the length of the set// @param setKey the key of the setfunctiongetAddressCount(bytes32 setKey) externalviewreturns (uint256) {
return addressSets[setKey].length();
}
// @dev get the values of the set in the given range// @param setKey the key of the set// @param the start of the range, values at the start index will be returned// in the result// @param the end of the range, values at the end index will not be returned// in the resultfunctiongetAddressValuesAt(bytes32 setKey, uint256 start, uint256 end) externalviewreturns (address[] memory) {
return addressSets[setKey].valuesAt(start, end);
}
// @dev add the given value to the set// @param setKey the key of the set// @param value the value to addfunctionaddAddress(bytes32 setKey, address value) externalonlyController{
addressSets[setKey].add(value);
}
// @dev remove the given value from the set// @param setKey the key of the set// @param value the value to removefunctionremoveAddress(bytes32 setKey, address value) externalonlyController{
addressSets[setKey].remove(value);
}
// @dev check whether the given value exists in the set// @param setKey the key of the set// @param value the value to checkfunctioncontainsUint(bytes32 setKey, uint256 value) externalviewreturns (bool) {
return uintSets[setKey].contains(value);
}
// @dev get the length of the set// @param setKey the key of the setfunctiongetUintCount(bytes32 setKey) externalviewreturns (uint256) {
return uintSets[setKey].length();
}
// @dev get the values of the set in the given range// @param setKey the key of the set// @param the start of the range, values at the start index will be returned// in the result// @param the end of the range, values at the end index will not be returned// in the resultfunctiongetUintValuesAt(bytes32 setKey, uint256 start, uint256 end) externalviewreturns (uint256[] memory) {
return uintSets[setKey].valuesAt(start, end);
}
// @dev add the given value to the set// @param setKey the key of the set// @param value the value to addfunctionaddUint(bytes32 setKey, uint256 value) externalonlyController{
uintSets[setKey].add(value);
}
// @dev remove the given value from the set// @param setKey the key of the set// @param value the value to removefunctionremoveUint(bytes32 setKey, uint256 value) externalonlyController{
uintSets[setKey].remove(value);
}
}
Contract Source Code
File 20 of 103: DecreaseOrderUtils.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"./BaseOrderUtils.sol";
import"../swap/SwapUtils.sol";
import"../position/DecreasePositionUtils.sol";
import"../error/ErrorUtils.sol";
// @title DecreaseOrderUtils// @dev Library for functions to help with processing a decrease order// note that any updates to the eventDatalibraryDecreaseOrderUtils{
usingPositionforPosition.Props;
usingOrderforOrder.Props;
usingArrayforuint256[];
usingEventUtilsforEventUtils.AddressItems;
usingEventUtilsforEventUtils.UintItems;
usingEventUtilsforEventUtils.IntItems;
usingEventUtilsforEventUtils.BoolItems;
usingEventUtilsforEventUtils.Bytes32Items;
usingEventUtilsforEventUtils.BytesItems;
usingEventUtilsforEventUtils.StringItems;
// @dev process a decrease order// @param params BaseOrderUtils.ExecuteOrderParamsfunctionprocessOrder(BaseOrderUtils.ExecuteOrderParams memory params) externalreturns (EventUtils.EventLogData memory) {
Order.Props memory order = params.order;
MarketUtils.validatePositionMarket(params.contracts.dataStore, params.market);
bytes32 positionKey = Position.getPositionKey(order.account(), order.market(), order.initialCollateralToken(), order.isLong());
Position.Props memory position = PositionStoreUtils.get(params.contracts.dataStore, positionKey);
PositionUtils.validateNonEmptyPosition(position);
validateOracleTimestamp(
params.contracts.dataStore,
order.orderType(),
order.updatedAtTime(),
order.validFromTime(),
position.increasedAtTime(),
position.decreasedAtTime(),
params.minOracleTimestamp,
params.maxOracleTimestamp
);
DecreasePositionUtils.DecreasePositionResult memory result = DecreasePositionUtils.decreasePosition(
PositionUtils.UpdatePositionParams(
params.contracts,
params.market,
order,
params.key,
position,
positionKey,
params.secondaryOrderType
)
);
// if the pnlToken and the collateralToken are different// and if a swap fails or no swap was requested// then it is possible to receive two separate tokens from decreasing// the position// transfer the two tokens to the user in this case and skip processing// the swapPathif (result.secondaryOutputAmount >0) {
_validateOutputAmount(
params.contracts.oracle,
result.outputToken,
result.outputAmount,
result.secondaryOutputToken,
result.secondaryOutputAmount,
order.minOutputAmount()
);
MarketToken(payable(order.market())).transferOut(
result.outputToken,
order.receiver(),
result.outputAmount,
order.shouldUnwrapNativeToken()
);
MarketToken(payable(order.market())).transferOut(
result.secondaryOutputToken,
order.receiver(),
result.secondaryOutputAmount,
order.shouldUnwrapNativeToken()
);
return getOutputEventData(
result.outputToken,
result.outputAmount,
result.secondaryOutputToken,
result.secondaryOutputAmount,
result.orderSizeDeltaUsd,
result.orderInitialCollateralDeltaAmount
);
}
try params.contracts.swapHandler.swap(
SwapUtils.SwapParams(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.contracts.oracle,
Bank(payable(order.market())),
params.key,
result.outputToken,
result.outputAmount,
params.swapPathMarkets,
0,
order.receiver(),
order.uiFeeReceiver(),
order.shouldUnwrapNativeToken()
)
) returns (address tokenOut, uint256 swapOutputAmount) {
_validateOutputAmount(
params.contracts.oracle,
tokenOut,
swapOutputAmount,
order.minOutputAmount()
);
return getOutputEventData(
tokenOut,
swapOutputAmount,
address(0),
0,
result.orderSizeDeltaUsd,
result.orderInitialCollateralDeltaAmount
);
} catch (bytesmemory reasonBytes) {
(stringmemory reason, /* bool hasRevertMessage */) = ErrorUtils.getRevertMessage(reasonBytes);
_handleSwapError(
params.contracts.oracle,
order,
result,
reason,
reasonBytes
);
return getOutputEventData(
result.outputToken,
result.outputAmount,
address(0),
0,
result.orderSizeDeltaUsd,
result.orderInitialCollateralDeltaAmount
);
}
}
functionvalidateOracleTimestamp(
DataStore dataStore,
Order.OrderType orderType,
uint256 orderUpdatedAtTime,
uint256 orderValidFromTime,
uint256 positionIncreasedAtTime,
uint256 positionDecreasedAtTime,
uint256 minOracleTimestamp,
uint256 maxOracleTimestamp
) internalview{
if (orderType == Order.OrderType.MarketDecrease) {
if (minOracleTimestamp < orderUpdatedAtTime) {
revert Errors.OracleTimestampsAreSmallerThanRequired(minOracleTimestamp, orderUpdatedAtTime);
}
uint256 requestExpirationTime = dataStore.getUint(Keys.REQUEST_EXPIRATION_TIME);
if (maxOracleTimestamp > orderUpdatedAtTime + requestExpirationTime) {
revert Errors.OracleTimestampsAreLargerThanRequestExpirationTime(
maxOracleTimestamp,
orderUpdatedAtTime,
requestExpirationTime
);
}
return;
}
if (
!BaseOrderUtils.isMarketOrder(orderType) &&
minOracleTimestamp < orderValidFromTime
) {
revert Errors.OracleTimestampsAreSmallerThanRequired(minOracleTimestamp, orderValidFromTime);
}
// a user could attempt to frontrun prices by creating a limit decrease// order without a position// when price moves in the user's favour, the user would create a// position then// e.g. price is $5000, a user creates a stop-loss order to// close a long position when price is below $5000// if price decreases to $4995, the user opens a long position at// price $4995// since slightly older prices may be used to execute a position// the user's stop-loss order could be executed at price $5000// for this reason, both the orderUpdatedAtTime and the// positionIncreasedAtTime need to be used as a reference//// if there are multiple decrease orders, an execution of one decrease// order would update the position, so the reference check here is only// with positionIncreasedAtTime instead of a positionUpdatedAtTime valueif (
orderType == Order.OrderType.LimitDecrease ||
orderType == Order.OrderType.StopLossDecrease
) {
uint256 latestUpdatedAtTime = orderUpdatedAtTime > positionIncreasedAtTime ? orderUpdatedAtTime : positionIncreasedAtTime;
if (minOracleTimestamp < latestUpdatedAtTime) {
revert Errors.OracleTimestampsAreSmallerThanRequired(minOracleTimestamp, latestUpdatedAtTime);
}
return;
}
if (orderType == Order.OrderType.Liquidation) {
uint256 latestUpdatedAtTime = positionIncreasedAtTime > positionDecreasedAtTime ? positionIncreasedAtTime : positionDecreasedAtTime;
if (minOracleTimestamp < latestUpdatedAtTime) {
revert Errors.OracleTimestampsAreSmallerThanRequired(minOracleTimestamp, latestUpdatedAtTime);
}
return;
}
revert Errors.UnsupportedOrderType(uint256(orderType));
}
// note that minOutputAmount is treated as a USD value for this validationfunction_validateOutputAmount(
Oracle oracle,
address outputToken,
uint256 outputAmount,
uint256 minOutputAmount
) internalview{
uint256 outputTokenPrice = oracle.getPrimaryPrice(outputToken).min;
uint256 outputUsd = outputAmount * outputTokenPrice;
if (outputUsd < minOutputAmount) {
revert Errors.InsufficientOutputAmount(outputUsd, minOutputAmount);
}
}
// note that minOutputAmount is treated as a USD value for this validationfunction_validateOutputAmount(
Oracle oracle,
address outputToken,
uint256 outputAmount,
address secondaryOutputToken,
uint256 secondaryOutputAmount,
uint256 minOutputAmount
) internalview{
uint256 outputTokenPrice = oracle.getPrimaryPrice(outputToken).min;
uint256 outputUsd = outputAmount * outputTokenPrice;
uint256 secondaryOutputTokenPrice = oracle.getPrimaryPrice(secondaryOutputToken).min;
uint256 secondaryOutputUsd = secondaryOutputAmount * secondaryOutputTokenPrice;
uint256 totalOutputUsd = outputUsd + secondaryOutputUsd;
if (totalOutputUsd < minOutputAmount) {
revert Errors.InsufficientOutputAmount(totalOutputUsd, minOutputAmount);
}
}
function_handleSwapError(
Oracle oracle,
Order.Props memory order,
DecreasePositionUtils.DecreasePositionResult memory result,
stringmemory reason,
bytesmemory reasonBytes
) internal{
emit SwapUtils.SwapReverted(reason, reasonBytes);
_validateOutputAmount(
oracle,
result.outputToken,
result.outputAmount,
order.minOutputAmount()
);
MarketToken(payable(order.market())).transferOut(
result.outputToken,
order.receiver(),
result.outputAmount,
order.shouldUnwrapNativeToken()
);
}
functiongetOutputEventData(address outputToken,
uint256 outputAmount,
address secondaryOutputToken,
uint256 secondaryOutputAmount,
uint256 orderSizeDeltaUsd,
uint256 orderInitialCollateralDeltaAmount
) internalpurereturns (EventUtils.EventLogData memory) {
EventUtils.EventLogData memory eventData;
eventData.addressItems.initItems(2);
eventData.addressItems.setItem(0, "outputToken", outputToken);
eventData.addressItems.setItem(1, "secondaryOutputToken", secondaryOutputToken);
eventData.uintItems.initItems(4);
eventData.uintItems.setItem(0, "outputAmount", outputAmount);
eventData.uintItems.setItem(1, "secondaryOutputAmount", secondaryOutputAmount);
eventData.uintItems.setItem(2, "orderSizeDeltaUsd", orderSizeDeltaUsd);
eventData.uintItems.setItem(3, "orderInitialCollateralDeltaAmount", orderInitialCollateralDeltaAmount);
return eventData;
}
}
Contract Source Code
File 21 of 103: DecreasePositionCollateralUtils.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"../data/DataStore.sol";
import"../event/EventEmitter.sol";
import"../oracle/Oracle.sol";
import"../pricing/PositionPricingUtils.sol";
import"./Position.sol";
import"./PositionEventUtils.sol";
import"./PositionUtils.sol";
import"../order/BaseOrderUtils.sol";
import"../order/OrderEventUtils.sol";
import"./DecreasePositionSwapUtils.sol";
// @title DecreasePositionCollateralUtils// @dev Library for functions to help with the calculations when decreasing a positionlibraryDecreasePositionCollateralUtils{
usingSafeCastforuint256;
usingSafeCastforint256;
usingPositionforPosition.Props;
usingOrderforOrder.Props;
usingPriceforPrice.Props;
usingEventUtilsforEventUtils.AddressItems;
usingEventUtilsforEventUtils.UintItems;
usingEventUtilsforEventUtils.IntItems;
usingEventUtilsforEventUtils.BoolItems;
usingEventUtilsforEventUtils.Bytes32Items;
usingEventUtilsforEventUtils.BytesItems;
usingEventUtilsforEventUtils.StringItems;
structProcessCollateralCache {
bool isInsolventCloseAllowed;
bool wasSwapped;
uint256 swapOutputAmount;
PayForCostResult result;
}
structPayForCostResult {
uint256 amountPaidInCollateralToken;
uint256 amountPaidInSecondaryOutputToken;
uint256 remainingCostUsd;
}
// @dev handle the collateral changes of the position// @param params PositionUtils.UpdatePositionParams// @param cache DecreasePositionCache// @return (PositionUtils.DecreasePositionCollateralValues, PositionPricingUtils.PositionFees)functionprocessCollateral(
PositionUtils.UpdatePositionParams memory params,
PositionUtils.DecreasePositionCache memory cache
) externalreturns (
PositionUtils.DecreasePositionCollateralValues memory,
PositionPricingUtils.PositionFees memory) {
ProcessCollateralCache memory collateralCache;
PositionUtils.DecreasePositionCollateralValues memory values;
values.output.outputToken = params.position.collateralToken();
values.output.secondaryOutputToken = cache.pnlToken;
// only allow insolvent closing if it is a liquidation or ADL order// isInsolventCloseAllowed is used in handleEarlyReturn to determine// whether the txn should revert if the remainingCostUsd is below zero//// for isInsolventCloseAllowed to be true, the sizeDeltaUsd must equal// the position size, otherwise there may be pending positive pnl that// could be used to pay for fees and the position would be undercharged// if the position is not fully closed//// for ADLs it may be possible that a position needs to be closed by a larger// size to fully pay for fees, but closing by that larger size could cause a PnlOvercorrected// error to be thrown in AdlHandler, this case should be rare
collateralCache.isInsolventCloseAllowed =
params.order.sizeDeltaUsd() == params.position.sizeInUsd() &&
(
BaseOrderUtils.isLiquidationOrder(params.order.orderType()) ||
params.secondaryOrderType == Order.SecondaryOrderType.Adl
);
// in case price impact is too high it is capped and the difference is made to be claimable// the execution price is based on the capped price impact so it may be a better price than what it should be// priceImpactDiffUsd is the difference between the maximum price impact and the originally calculated price impact// e.g. if the originally calculated price impact is -$100, but the capped price impact is -$80// then priceImpactDiffUsd would be $20
(values.priceImpactUsd, values.priceImpactDiffUsd, values.executionPrice) = PositionUtils.getExecutionPriceForDecrease(params, cache.prices.indexTokenPrice);
// the totalPositionPnl is calculated based on the current indexTokenPrice instead of the executionPrice// since the executionPrice factors in price impact which should be accounted for separately// the sizeDeltaInTokens is calculated as position.sizeInTokens() * sizeDeltaUsd / position.sizeInUsd()// the basePnlUsd is the pnl to be realized, and is calculated as:// totalPositionPnl * sizeDeltaInTokens / position.sizeInTokens()
(values.basePnlUsd, values.uncappedBasePnlUsd, values.sizeDeltaInTokens) = PositionUtils.getPositionPnlUsd(
params.contracts.dataStore,
params.market,
cache.prices,
params.position,
params.order.sizeDeltaUsd()
);
PositionPricingUtils.GetPositionFeesParams memory getPositionFeesParams = PositionPricingUtils.GetPositionFeesParams(
params.contracts.dataStore, // dataStore
params.contracts.referralStorage, // referralStorage
params.position, // position
cache.collateralTokenPrice, // collateralTokenPrice
values.priceImpactUsd >0, // forPositiveImpact
params.market.longToken, // longToken
params.market.shortToken, // shortToken
params.order.sizeDeltaUsd(), // sizeDeltaUsd
params.order.uiFeeReceiver(), // uiFeeReceiver
BaseOrderUtils.isLiquidationOrder(params.order.orderType()) // isLiquidation
);
// if the pnl is positive, deduct the pnl amount from the poolif (values.basePnlUsd >0) {
// use pnlTokenPrice.max to minimize the tokens paid outuint256 deductionAmountForPool = values.basePnlUsd.toUint256() / cache.pnlTokenPrice.max;
MarketUtils.applyDeltaToPoolAmount(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.market,
cache.pnlToken,
-deductionAmountForPool.toInt256()
);
if (values.output.outputToken == cache.pnlToken) {
values.output.outputAmount += deductionAmountForPool;
} else {
values.output.secondaryOutputAmount += deductionAmountForPool;
}
}
if (values.priceImpactUsd >0) {
// use indexTokenPrice.min to maximize the position impact pool reductionuint256 deductionAmountForImpactPool = Calc.roundUpDivision(values.priceImpactUsd.toUint256(), cache.prices.indexTokenPrice.min);
MarketUtils.applyDeltaToPositionImpactPool(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.market.marketToken,
-deductionAmountForImpactPool.toInt256()
);
// use pnlTokenPrice.max to minimize the payout from the pool// some impact pool value may be transferred to the market token pool if there is a// large spread between min and max prices// since if there is a positive priceImpactUsd, the impact pool would be reduced using indexTokenPrice.min to// maximize the deduction value, while the market token pool is reduced using the pnlTokenPrice.max to minimize// the deduction value// the pool value is calculated by subtracting the worth of the tokens in the position impact pool// so this transfer of value would increase the price of the market tokenuint256 deductionAmountForPool = values.priceImpactUsd.toUint256() / cache.pnlTokenPrice.max;
MarketUtils.applyDeltaToPoolAmount(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.market,
cache.pnlToken,
-deductionAmountForPool.toInt256()
);
if (values.output.outputToken == cache.pnlToken) {
values.output.outputAmount += deductionAmountForPool;
} else {
values.output.secondaryOutputAmount += deductionAmountForPool;
}
}
// swap profit to the collateral token// if the decreasePositionSwapType was set to NoSwap or if the swap fails due// to insufficient liquidity or other reasons then it is possible that// the profit remains in a different token from the collateral token
(collateralCache.wasSwapped, collateralCache.swapOutputAmount) = DecreasePositionSwapUtils.swapProfitToCollateralToken(
params,
cache.pnlToken,
values.output.secondaryOutputAmount
);
// if the swap was successful the profit should have been swapped// to the collateral tokenif (collateralCache.wasSwapped) {
values.output.outputAmount += collateralCache.swapOutputAmount;
values.output.secondaryOutputAmount =0;
}
values.remainingCollateralAmount = params.position.collateralAmount();
PositionPricingUtils.PositionFees memory fees = PositionPricingUtils.getPositionFees(
getPositionFeesParams
);
// pay for funding fees
(values, collateralCache.result) = payForCost(
params,
values,
cache.prices,
cache.collateralTokenPrice,
// use collateralTokenPrice.min because the payForCost// will divide the USD value by the price.min as well
fees.funding.fundingFeeAmount * cache.collateralTokenPrice.min
);
if (collateralCache.result.amountPaidInSecondaryOutputToken >0) {
address holdingAddress = params.contracts.dataStore.getAddress(Keys.HOLDING_ADDRESS);
if (holdingAddress ==address(0)) {
revert Errors.EmptyHoldingAddress();
}
// send the funding fee amount to the holding address// this funding fee amount should be swapped to the required token// and the resulting tokens should be deposited back into the pool
MarketUtils.incrementClaimableCollateralAmount(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.market.marketToken,
values.output.secondaryOutputToken,
holdingAddress,
collateralCache.result.amountPaidInSecondaryOutputToken
);
}
if (collateralCache.result.amountPaidInCollateralToken < fees.funding.fundingFeeAmount) {
// the case where this is insufficient collateral to pay funding fees// should be rare, and the difference should be small// in case it happens, the pool should be topped up with the required amount using// the claimable amount sent to the holding address, an insurance fund, or similar mechanism
PositionEventUtils.emitInsufficientFundingFeePayment(
params.contracts.eventEmitter,
params.market.marketToken,
params.position.collateralToken(),
fees.funding.fundingFeeAmount,
collateralCache.result.amountPaidInCollateralToken,
collateralCache.result.amountPaidInSecondaryOutputToken
);
}
if (collateralCache.result.remainingCostUsd >0) {
return handleEarlyReturn(
params,
values,
fees,
collateralCache,
"funding"
);
}
// pay for negative pnlif (values.basePnlUsd <0) {
(values, collateralCache.result) = payForCost(
params,
values,
cache.prices,
cache.collateralTokenPrice,
(-values.basePnlUsd).toUint256()
);
if (collateralCache.result.amountPaidInCollateralToken >0) {
MarketUtils.applyDeltaToPoolAmount(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.market,
params.position.collateralToken(),
collateralCache.result.amountPaidInCollateralToken.toInt256()
);
}
if (collateralCache.result.amountPaidInSecondaryOutputToken >0) {
MarketUtils.applyDeltaToPoolAmount(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.market,
values.output.secondaryOutputToken,
collateralCache.result.amountPaidInSecondaryOutputToken.toInt256()
);
}
if (collateralCache.result.remainingCostUsd >0) {
return handleEarlyReturn(
params,
values,
fees,
collateralCache,
"pnl"
);
}
}
// pay for fees
(values, collateralCache.result) = payForCost(
params,
values,
cache.prices,
cache.collateralTokenPrice,
// use collateralTokenPrice.min because the payForCost// will divide the USD value by the price.min as well
fees.totalCostAmountExcludingFunding * cache.collateralTokenPrice.min
);
// if fees were fully paid in the collateral token, update the pool and claimable fee amountsif (collateralCache.result.remainingCostUsd ==0&& collateralCache.result.amountPaidInSecondaryOutputToken ==0) {
// there may be a large amount of borrowing fees that could have been accumulated// these fees could cause the pool to become unbalanced, price impact is not paid for causing// this imbalance// the swap impact pool should be built up so that it can be used to pay for positive price impact// for re-balancing to help handle this case
MarketUtils.applyDeltaToPoolAmount(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.market,
params.position.collateralToken(),
fees.feeAmountForPool.toInt256()
);
FeeUtils.incrementClaimableFeeAmount(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.market.marketToken,
params.position.collateralToken(),
fees.feeReceiverAmount,
Keys.POSITION_FEE_TYPE
);
FeeUtils.incrementClaimableUiFeeAmount(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.order.uiFeeReceiver(),
params.market.marketToken,
params.position.collateralToken(),
fees.ui.uiFeeAmount,
Keys.UI_POSITION_FEE_TYPE
);
} else {
// the fees are expected to be paid in the collateral token// if there are insufficient funds to pay for fees entirely in the collateral token// then credit the fee amount entirely to the poolif (collateralCache.result.amountPaidInCollateralToken >0) {
MarketUtils.applyDeltaToPoolAmount(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.market,
params.position.collateralToken(),
collateralCache.result.amountPaidInCollateralToken.toInt256()
);
}
if (collateralCache.result.amountPaidInSecondaryOutputToken >0) {
MarketUtils.applyDeltaToPoolAmount(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.market,
values.output.secondaryOutputToken,
collateralCache.result.amountPaidInSecondaryOutputToken.toInt256()
);
}
// empty the fees since the amount was entirely paid to the pool instead of for fees// it is possible for the txn execution to still complete even in this case// as long as the remainingCostUsd is still zero
fees = getEmptyFees(fees);
}
if (collateralCache.result.remainingCostUsd >0) {
return handleEarlyReturn(
params,
values,
fees,
collateralCache,
"fees"
);
}
// pay for negative price impactif (values.priceImpactUsd <0) {
(values, collateralCache.result) = payForCost(
params,
values,
cache.prices,
cache.collateralTokenPrice,
(-values.priceImpactUsd).toUint256()
);
if (collateralCache.result.amountPaidInCollateralToken >0) {
MarketUtils.applyDeltaToPoolAmount(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.market,
params.position.collateralToken(),
collateralCache.result.amountPaidInCollateralToken.toInt256()
);
MarketUtils.applyDeltaToPositionImpactPool(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.market.marketToken,
(collateralCache.result.amountPaidInCollateralToken * cache.collateralTokenPrice.min/ cache.prices.indexTokenPrice.max).toInt256()
);
}
if (collateralCache.result.amountPaidInSecondaryOutputToken >0) {
MarketUtils.applyDeltaToPoolAmount(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.market,
values.output.secondaryOutputToken,
collateralCache.result.amountPaidInSecondaryOutputToken.toInt256()
);
MarketUtils.applyDeltaToPositionImpactPool(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.market.marketToken,
(collateralCache.result.amountPaidInSecondaryOutputToken * cache.pnlTokenPrice.min/ cache.prices.indexTokenPrice.max).toInt256()
);
}
if (collateralCache.result.remainingCostUsd >0) {
return handleEarlyReturn(
params,
values,
fees,
collateralCache,
"impact"
);
}
}
// pay for price impact diffif (values.priceImpactDiffUsd >0) {
(values, collateralCache.result) = payForCost(
params,
values,
cache.prices,
cache.collateralTokenPrice,
values.priceImpactDiffUsd
);
if (collateralCache.result.amountPaidInCollateralToken >0) {
MarketUtils.incrementClaimableCollateralAmount(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.market.marketToken,
params.position.collateralToken(),
params.order.account(),
collateralCache.result.amountPaidInCollateralToken
);
}
if (collateralCache.result.amountPaidInSecondaryOutputToken >0) {
MarketUtils.incrementClaimableCollateralAmount(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.market.marketToken,
values.output.secondaryOutputToken,
params.order.account(),
collateralCache.result.amountPaidInSecondaryOutputToken
);
}
if (collateralCache.result.remainingCostUsd >0) {
return handleEarlyReturn(
params,
values,
fees,
collateralCache,
"diff"
);
}
}
// the priceImpactDiffUsd has been deducted from the output amount or the position's collateral// to reduce the chance that the position's collateral is reduced by an unexpected amount, adjust the// initialCollateralDeltaAmount by the priceImpactDiffAmount// this would also help to prevent the position's leverage from being unexpectedly increased//// note that this calculation may not be entirely accurate since it is possible that the priceImpactDiffUsd// could have been paid with one of or a combination of collateral / outputAmount / secondaryOutputAmountif (params.order.initialCollateralDeltaAmount() >0&& values.priceImpactDiffUsd >0) {
uint256 initialCollateralDeltaAmount = params.order.initialCollateralDeltaAmount();
uint256 priceImpactDiffAmount = values.priceImpactDiffUsd / cache.collateralTokenPrice.min;
if (initialCollateralDeltaAmount > priceImpactDiffAmount) {
params.order.setInitialCollateralDeltaAmount(initialCollateralDeltaAmount - priceImpactDiffAmount);
} else {
params.order.setInitialCollateralDeltaAmount(0);
}
OrderEventUtils.emitOrderCollateralDeltaAmountAutoUpdated(
params.contracts.eventEmitter,
params.orderKey,
initialCollateralDeltaAmount, // collateralDeltaAmount
params.order.initialCollateralDeltaAmount() // nextCollateralDeltaAmount
);
}
// cap the withdrawable amount to the remainingCollateralAmountif (params.order.initialCollateralDeltaAmount() > values.remainingCollateralAmount) {
OrderEventUtils.emitOrderCollateralDeltaAmountAutoUpdated(
params.contracts.eventEmitter,
params.orderKey,
params.order.initialCollateralDeltaAmount(), // collateralDeltaAmount
values.remainingCollateralAmount // nextCollateralDeltaAmount
);
params.order.setInitialCollateralDeltaAmount(values.remainingCollateralAmount);
}
if (params.order.initialCollateralDeltaAmount() >0) {
values.remainingCollateralAmount -= params.order.initialCollateralDeltaAmount();
values.output.outputAmount += params.order.initialCollateralDeltaAmount();
}
return (values, fees);
}
functionpayForCost(
PositionUtils.UpdatePositionParams memory params,
PositionUtils.DecreasePositionCollateralValues memory values,
MarketUtils.MarketPrices memory prices,
Price.Props memory collateralTokenPrice,
uint256 costUsd
) internalpurereturns (PositionUtils.DecreasePositionCollateralValues memory, PayForCostResult memory) {
PayForCostResult memory result;
if (costUsd ==0) { return (values, result); }
uint256 remainingCostInOutputToken = Calc.roundUpDivision(costUsd, collateralTokenPrice.min);
if (values.output.outputAmount >0) {
if (values.output.outputAmount > remainingCostInOutputToken) {
result.amountPaidInCollateralToken += remainingCostInOutputToken;
values.output.outputAmount -= remainingCostInOutputToken;
remainingCostInOutputToken =0;
} else {
result.amountPaidInCollateralToken += values.output.outputAmount;
remainingCostInOutputToken -= values.output.outputAmount;
values.output.outputAmount =0;
}
}
if (remainingCostInOutputToken ==0) { return (values, result); }
if (values.remainingCollateralAmount >0) {
if (values.remainingCollateralAmount > remainingCostInOutputToken) {
result.amountPaidInCollateralToken += remainingCostInOutputToken;
values.remainingCollateralAmount -= remainingCostInOutputToken;
remainingCostInOutputToken =0;
} else {
result.amountPaidInCollateralToken += values.remainingCollateralAmount;
remainingCostInOutputToken -= values.remainingCollateralAmount;
values.remainingCollateralAmount =0;
}
}
if (remainingCostInOutputToken ==0) { return (values, result); }
Price.Props memory secondaryOutputTokenPrice = MarketUtils.getCachedTokenPrice(values.output.secondaryOutputToken, params.market, prices);
uint256 remainingCostInSecondaryOutputToken = remainingCostInOutputToken * collateralTokenPrice.min/ secondaryOutputTokenPrice.min;
if (values.output.secondaryOutputAmount >0) {
if (values.output.secondaryOutputAmount > remainingCostInSecondaryOutputToken) {
result.amountPaidInSecondaryOutputToken += remainingCostInSecondaryOutputToken;
values.output.secondaryOutputAmount -= remainingCostInSecondaryOutputToken;
remainingCostInSecondaryOutputToken =0;
} else {
result.amountPaidInSecondaryOutputToken += values.output.secondaryOutputAmount;
remainingCostInSecondaryOutputToken -= values.output.secondaryOutputAmount;
values.output.secondaryOutputAmount =0;
}
}
result.remainingCostUsd = remainingCostInSecondaryOutputToken * secondaryOutputTokenPrice.min;
return (values, result);
}
functionhandleEarlyReturn(
PositionUtils.UpdatePositionParams memory params,
PositionUtils.DecreasePositionCollateralValues memory values,
PositionPricingUtils.PositionFees memory fees,
ProcessCollateralCache memory collateralCache,
stringmemory step
) internalreturns (PositionUtils.DecreasePositionCollateralValues memory, PositionPricingUtils.PositionFees memory) {
if (!collateralCache.isInsolventCloseAllowed) {
revert Errors.InsufficientFundsToPayForCosts(collateralCache.result.remainingCostUsd, step);
}
PositionEventUtils.emitPositionFeesInfo(
params.contracts.eventEmitter,
params.orderKey,
params.positionKey,
params.market.marketToken,
params.position.collateralToken(),
params.order.sizeDeltaUsd(),
false, // isIncrease
fees
);
PositionEventUtils.emitInsolventClose(
params.contracts.eventEmitter,
params.orderKey,
params.position.collateralAmount(),
values.basePnlUsd,
collateralCache.result.remainingCostUsd,
step
);
return (values, getEmptyFees(fees));
}
functiongetEmptyFees(
PositionPricingUtils.PositionFees memory fees
) internalpurereturns (PositionPricingUtils.PositionFees memory) {
PositionPricingUtils.PositionReferralFees memory referral = PositionPricingUtils.PositionReferralFees({
referralCode: bytes32(0),
affiliate: address(0),
trader: address(0),
totalRebateFactor: 0,
affiliateRewardFactor: 0,
adjustedAffiliateRewardFactor: 0,
traderDiscountFactor: 0,
totalRebateAmount: 0,
traderDiscountAmount: 0,
affiliateRewardAmount: 0
});
PositionPricingUtils.PositionProFees memory pro = PositionPricingUtils.PositionProFees({
traderTier: 0,
traderDiscountFactor: 0,
traderDiscountAmount: 0
});
// allow the accumulated funding fees to still be claimable// return the latestFundingFeeAmountPerSize, latestLongTokenClaimableFundingAmountPerSize,// latestShortTokenClaimableFundingAmountPerSize values as these may be used to update the// position's values if the position will be partially closed
PositionPricingUtils.PositionFundingFees memory funding = PositionPricingUtils.PositionFundingFees({
fundingFeeAmount: 0,
claimableLongTokenAmount: fees.funding.claimableLongTokenAmount,
claimableShortTokenAmount: fees.funding.claimableShortTokenAmount,
latestFundingFeeAmountPerSize: fees.funding.latestFundingFeeAmountPerSize,
latestLongTokenClaimableFundingAmountPerSize: fees.funding.latestLongTokenClaimableFundingAmountPerSize,
latestShortTokenClaimableFundingAmountPerSize: fees.funding.latestShortTokenClaimableFundingAmountPerSize
});
PositionPricingUtils.PositionBorrowingFees memory borrowing = PositionPricingUtils.PositionBorrowingFees({
borrowingFeeUsd: 0,
borrowingFeeAmount: 0,
borrowingFeeReceiverFactor: 0,
borrowingFeeAmountForFeeReceiver: 0
});
PositionPricingUtils.PositionUiFees memory ui = PositionPricingUtils.PositionUiFees({
uiFeeReceiver: address(0),
uiFeeReceiverFactor: 0,
uiFeeAmount: 0
});
PositionPricingUtils.PositionLiquidationFees memory liquidation = PositionPricingUtils.PositionLiquidationFees({
liquidationFeeUsd: 0,
liquidationFeeAmount: 0,
liquidationFeeReceiverFactor: 0,
liquidationFeeAmountForFeeReceiver: 0
});
// all fees are zeroed even though funding may have been paid// the funding fee amount value may not be accurate in the events due to this
PositionPricingUtils.PositionFees memory _fees = PositionPricingUtils.PositionFees({
referral: referral,
pro: pro,
funding: funding,
borrowing: borrowing,
ui: ui,
liquidation: liquidation,
collateralTokenPrice: fees.collateralTokenPrice,
positionFeeFactor: 0,
protocolFeeAmount: 0,
positionFeeReceiverFactor: 0,
feeReceiverAmount: 0,
feeAmountForPool: 0,
positionFeeAmountForPool: 0,
positionFeeAmount: 0,
totalCostAmountExcludingFunding: 0,
totalCostAmount: 0,
totalDiscountAmount: 0
});
return _fees;
}
}
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"../utils/Precision.sol";
import"../data/DataStore.sol";
import"../event/EventEmitter.sol";
import"../oracle/Oracle.sol";
import"../pricing/PositionPricingUtils.sol";
import"./Position.sol";
import"./PositionStoreUtils.sol";
import"./PositionUtils.sol";
import"./PositionEventUtils.sol";
import"../order/BaseOrderUtils.sol";
import"../order/OrderEventUtils.sol";
import"./DecreasePositionCollateralUtils.sol";
// @title DecreasePositionUtils// @dev Library for functions to help with decreasing a positionlibraryDecreasePositionUtils{
usingSafeCastforuint256;
usingSafeCastforint256;
usingPositionforPosition.Props;
usingOrderforOrder.Props;
usingPriceforPrice.Props;
// @dev DecreasePositionResult struct for the results of decreasePosition// @param outputToken the output token// @param outputAmount the output amount// @param secondaryOutputToken the secondary output token// @param secondaryOutputAmount the secondary output amountstructDecreasePositionResult {
address outputToken;
uint256 outputAmount;
address secondaryOutputToken;
uint256 secondaryOutputAmount;
uint256 orderSizeDeltaUsd;
uint256 orderInitialCollateralDeltaAmount;
}
// @dev decreases a position// The decreasePosition function decreases the size of an existing position// in a market. It takes a PositionUtils.UpdatePositionParams object as an input, which// includes information about the position to be decreased, the market in// which the position exists, and the order that is being used to decrease the position.//// The function first calculates the prices of the tokens in the market, and then// checks whether the position is liquidatable based on the current market prices.// If the order is a liquidation order and the position is not liquidatable, the function reverts.//// If there is not enough collateral in the position to complete the decrease,// the function reverts. Otherwise, the function updates the position's size and// collateral amount, and increments the claimable funding amount for// the market if necessary.//// Finally, the function returns a DecreasePositionResult object containing// information about the outcome of the decrease operation, including the amount// of collateral removed from the position and any fees that were paid.// @param params PositionUtils.UpdatePositionParamsfunctiondecreasePosition(
PositionUtils.UpdatePositionParams memory params
) externalreturns (DecreasePositionResult memory) {
PositionUtils.DecreasePositionCache memory cache;
cache.prices = MarketUtils.getMarketPrices(
params.contracts.oracle,
params.market
);
cache.collateralTokenPrice = MarketUtils.getCachedTokenPrice(
params.order.initialCollateralToken(),
params.market,
cache.prices
);
// cap the order size to the position sizeif (params.order.sizeDeltaUsd() > params.position.sizeInUsd()) {
if (params.order.orderType() == Order.OrderType.LimitDecrease ||
params.order.orderType() == Order.OrderType.StopLossDecrease) {
OrderEventUtils.emitOrderSizeDeltaAutoUpdated(
params.contracts.eventEmitter,
params.orderKey,
params.order.sizeDeltaUsd(),
params.position.sizeInUsd()
);
params.order.setSizeDeltaUsd(params.position.sizeInUsd());
} else {
revert Errors.InvalidDecreaseOrderSize(params.order.sizeDeltaUsd(), params.position.sizeInUsd());
}
}
// cap the initialCollateralDeltaAmount to the position collateralAmountif (params.order.initialCollateralDeltaAmount() > params.position.collateralAmount()) {
OrderEventUtils.emitOrderCollateralDeltaAmountAutoUpdated(
params.contracts.eventEmitter,
params.orderKey,
params.order.initialCollateralDeltaAmount(),
params.position.collateralAmount()
);
params.order.setInitialCollateralDeltaAmount(params.position.collateralAmount());
}
// if the position will be partially decreased then do a check on the// remaining collateral amount and update the order attributes if neededif (params.order.sizeDeltaUsd() < params.position.sizeInUsd()) {
// estimate pnl based on indexTokenPrice
(cache.estimatedPositionPnlUsd, /* int256 uncappedBasePnlUsd */, /* uint256 sizeDeltaInTokens */) = PositionUtils.getPositionPnlUsd(
params.contracts.dataStore,
params.market,
cache.prices,
params.position,
params.position.sizeInUsd()
);
cache.estimatedRealizedPnlUsd = Precision.mulDiv(cache.estimatedPositionPnlUsd, params.order.sizeDeltaUsd(), params.position.sizeInUsd());
cache.estimatedRemainingPnlUsd = cache.estimatedPositionPnlUsd - cache.estimatedRealizedPnlUsd;
PositionUtils.WillPositionCollateralBeSufficientValues memory positionValues = PositionUtils.WillPositionCollateralBeSufficientValues(
params.position.sizeInUsd() - params.order.sizeDeltaUsd(), // positionSizeInUsd
params.position.collateralAmount() - params.order.initialCollateralDeltaAmount(), // positionCollateralAmount
cache.estimatedRealizedPnlUsd, // realizedPnlUsd-params.order.sizeDeltaUsd().toInt256() // openInterestDelta
);
(bool willBeSufficient, int256 estimatedRemainingCollateralUsd) = PositionUtils.willPositionCollateralBeSufficient(
params.contracts.dataStore,
params.market,
cache.prices,
params.position.collateralToken(),
params.position.isLong(),
positionValues
);
// do not allow withdrawal of collateral if it would lead to the position// having an insufficient amount of collateral// this helps to prevent gaming by opening a position then reducing collateral// to increase the leverage of the positionif (!willBeSufficient) {
if (params.order.sizeDeltaUsd() ==0) {
revert Errors.UnableToWithdrawCollateral(estimatedRemainingCollateralUsd);
}
OrderEventUtils.emitOrderCollateralDeltaAmountAutoUpdated(
params.contracts.eventEmitter,
params.orderKey,
params.order.initialCollateralDeltaAmount(),
0
);
// the estimatedRemainingCollateralUsd subtracts the initialCollateralDeltaAmount// since the initialCollateralDeltaAmount will be set to zero, the initialCollateralDeltaAmount// should be added back to the estimatedRemainingCollateralUsd
estimatedRemainingCollateralUsd += (params.order.initialCollateralDeltaAmount() * cache.collateralTokenPrice.min).toInt256();
params.order.setInitialCollateralDeltaAmount(0);
}
// if the remaining collateral including position pnl will be below// the min collateral usd value, then close the position//// if the position has sufficient remaining collateral including pnl// then allow the position to be partially closed and the updated// position to remain openif ((estimatedRemainingCollateralUsd + cache.estimatedRemainingPnlUsd) < params.contracts.dataStore.getUint(Keys.MIN_COLLATERAL_USD).toInt256()) {
OrderEventUtils.emitOrderSizeDeltaAutoUpdated(
params.contracts.eventEmitter,
params.orderKey,
params.order.sizeDeltaUsd(),
params.position.sizeInUsd()
);
params.order.setSizeDeltaUsd(params.position.sizeInUsd());
}
if (
params.position.sizeInUsd() > params.order.sizeDeltaUsd() &&
params.position.sizeInUsd() - params.order.sizeDeltaUsd() < params.contracts.dataStore.getUint(Keys.MIN_POSITION_SIZE_USD)
) {
OrderEventUtils.emitOrderSizeDeltaAutoUpdated(
params.contracts.eventEmitter,
params.orderKey,
params.order.sizeDeltaUsd(),
params.position.sizeInUsd()
);
params.order.setSizeDeltaUsd(params.position.sizeInUsd());
}
}
// if the position will be closed, set the initial collateral delta amount// to zero to help ensure that the order can be executedif (params.order.sizeDeltaUsd() == params.position.sizeInUsd() && params.order.initialCollateralDeltaAmount() >0) {
params.order.setInitialCollateralDeltaAmount(0);
}
cache.pnlToken = params.position.isLong() ? params.market.longToken : params.market.shortToken;
cache.pnlTokenPrice = params.position.isLong() ? cache.prices.longTokenPrice : cache.prices.shortTokenPrice;
if (params.order.decreasePositionSwapType() != Order.DecreasePositionSwapType.NoSwap &&
cache.pnlToken == params.position.collateralToken()) {
params.order.setDecreasePositionSwapType(Order.DecreasePositionSwapType.NoSwap);
}
if (BaseOrderUtils.isLiquidationOrder(params.order.orderType())) {
(bool isLiquidatable, stringmemory reason, PositionUtils.IsPositionLiquidatableInfo memory info) = PositionUtils.isPositionLiquidatable(
params.contracts.dataStore,
params.contracts.referralStorage,
params.position,
params.market,
cache.prices,
true// shouldValidateMinCollateralUsd
);
if (!isLiquidatable) {
revert Errors.PositionShouldNotBeLiquidated(
reason,
info.remainingCollateralUsd,
info.minCollateralUsd,
info.minCollateralUsdForLeverage
);
}
}
cache.initialCollateralAmount = params.position.collateralAmount();
(
PositionUtils.DecreasePositionCollateralValues memory values,
PositionPricingUtils.PositionFees memory fees
) = DecreasePositionCollateralUtils.processCollateral(
params,
cache
);
cache.nextPositionSizeInUsd = params.position.sizeInUsd() - params.order.sizeDeltaUsd();
cache.nextPositionBorrowingFactor = MarketUtils.getCumulativeBorrowingFactor(params.contracts.dataStore, params.market.marketToken, params.position.isLong());
PositionUtils.updateTotalBorrowing(
params,
cache.nextPositionSizeInUsd,
cache.nextPositionBorrowingFactor
);
params.position.setSizeInUsd(cache.nextPositionSizeInUsd);
params.position.setSizeInTokens(params.position.sizeInTokens() - values.sizeDeltaInTokens);
params.position.setCollateralAmount(values.remainingCollateralAmount);
params.position.setDecreasedAtTime(Chain.currentTimestamp());
PositionUtils.incrementClaimableFundingAmount(params, fees);
if (params.position.sizeInUsd() ==0|| params.position.sizeInTokens() ==0) {
// withdraw all collateral if the position will be closed
values.output.outputAmount += params.position.collateralAmount();
params.position.setSizeInUsd(0);
params.position.setSizeInTokens(0);
params.position.setCollateralAmount(0);
PositionStoreUtils.remove(params.contracts.dataStore, params.positionKey, params.order.account());
} else {
params.position.setBorrowingFactor(cache.nextPositionBorrowingFactor);
params.position.setFundingFeeAmountPerSize(fees.funding.latestFundingFeeAmountPerSize);
params.position.setLongTokenClaimableFundingAmountPerSize(fees.funding.latestLongTokenClaimableFundingAmountPerSize);
params.position.setShortTokenClaimableFundingAmountPerSize(fees.funding.latestShortTokenClaimableFundingAmountPerSize);
PositionStoreUtils.set(params.contracts.dataStore, params.positionKey, params.position);
}
MarketUtils.applyDeltaToCollateralSum(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.position.market(),
params.position.collateralToken(),
params.position.isLong(),
-(cache.initialCollateralAmount - params.position.collateralAmount()).toInt256()
);
PositionUtils.updateOpenInterest(
params,
-params.order.sizeDeltaUsd().toInt256(),
-values.sizeDeltaInTokens.toInt256()
);
// affiliate rewards are still distributed even if the order is a liquidation order// this is expected as a partial liquidation is considered the same as an automatic// closing of a position
PositionUtils.handleReferral(params, fees);
// validatePosition should be called after open interest and all other market variables// have been updatedif (params.position.sizeInUsd() !=0|| params.position.sizeInTokens() !=0) {
// validate position which validates liquidation state is only called// if the remaining position size is not zero// due to this, a user can still manually close their position if// it is in a partially liquidatable state// this should not cause any issues as a liquidation is the same// as automatically closing a position// the only difference is that if the position has insufficient / negative// collateral a liquidation transaction should still complete// while a manual close transaction should revert
PositionUtils.validatePosition(
params.contracts.dataStore,
params.contracts.referralStorage,
params.position,
params.market,
cache.prices,
false, // shouldValidateMinPositionSizefalse// shouldValidateMinCollateralUsd
);
}
PositionEventUtils.emitPositionFeesCollected(
params.contracts.eventEmitter,
params.orderKey,
params.positionKey,
params.market.marketToken,
params.position.collateralToken(),
params.order.sizeDeltaUsd(),
false,
fees
);
PositionEventUtils.emitPositionDecrease(
params.contracts.eventEmitter,
params.orderKey,
params.positionKey,
params.position,
params.order.sizeDeltaUsd(),
cache.initialCollateralAmount - params.position.collateralAmount(),
params.order.orderType(),
values,
cache.prices.indexTokenPrice,
cache.collateralTokenPrice
);
values = DecreasePositionSwapUtils.swapWithdrawnCollateralToPnlToken(params, values);
return DecreasePositionResult(
values.output.outputToken,
values.output.outputAmount,
values.output.secondaryOutputToken,
values.output.secondaryOutputAmount,
params.order.sizeDeltaUsd(),
params.order.initialCollateralDeltaAmount()
);
}
}
Contract Source Code
File 24 of 103: Deposit.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;// @title Deposit// @dev Struct for depositslibraryDeposit{
// @dev there is a limit on the number of fields a struct can have when being passed// or returned as a memory variable which can cause "Stack too deep" errors// use sub-structs to avoid this issue// @param addresses address values// @param numbers number values// @param flags boolean valuesstructProps {
Addresses addresses;
Numbers numbers;
Flags flags;
}
// @param account the account depositing liquidity// @param receiver the address to send the liquidity tokens to// @param callbackContract the callback contract// @param uiFeeReceiver the ui fee receiver// @param market the market to deposit tostructAddresses {
address account;
address receiver;
address callbackContract;
address uiFeeReceiver;
address market;
address initialLongToken;
address initialShortToken;
address[] longTokenSwapPath;
address[] shortTokenSwapPath;
}
// @param initialLongTokenAmount the amount of long tokens to deposit// @param initialShortTokenAmount the amount of short tokens to deposit// @param minMarketTokens the minimum acceptable number of liquidity tokens// sending funds back to the user in case the deposit gets cancelled// @param executionFee the execution fee for keepers// @param callbackGasLimit the gas limit for the callbackContractstructNumbers {
uint256 initialLongTokenAmount;
uint256 initialShortTokenAmount;
uint256 minMarketTokens;
uint256 updatedAtTime;
uint256 executionFee;
uint256 callbackGasLimit;
}
// @param shouldUnwrapNativeToken whether to unwrap the native token whenstructFlags {
bool shouldUnwrapNativeToken;
}
functionaccount(Props memory props) internalpurereturns (address) {
return props.addresses.account;
}
functionsetAccount(Props memory props, address value) internalpure{
props.addresses.account = value;
}
functionreceiver(Props memory props) internalpurereturns (address) {
return props.addresses.receiver;
}
functionsetReceiver(Props memory props, address value) internalpure{
props.addresses.receiver = value;
}
functioncallbackContract(Props memory props) internalpurereturns (address) {
return props.addresses.callbackContract;
}
functionsetCallbackContract(Props memory props, address value) internalpure{
props.addresses.callbackContract = value;
}
functionuiFeeReceiver(Props memory props) internalpurereturns (address) {
return props.addresses.uiFeeReceiver;
}
functionsetUiFeeReceiver(Props memory props, address value) internalpure{
props.addresses.uiFeeReceiver = value;
}
functionmarket(Props memory props) internalpurereturns (address) {
return props.addresses.market;
}
functionsetMarket(Props memory props, address value) internalpure{
props.addresses.market = value;
}
functioninitialLongToken(Props memory props) internalpurereturns (address) {
return props.addresses.initialLongToken;
}
functionsetInitialLongToken(Props memory props, address value) internalpure{
props.addresses.initialLongToken = value;
}
functioninitialShortToken(Props memory props) internalpurereturns (address) {
return props.addresses.initialShortToken;
}
functionsetInitialShortToken(Props memory props, address value) internalpure{
props.addresses.initialShortToken = value;
}
functionlongTokenSwapPath(Props memory props) internalpurereturns (address[] memory) {
return props.addresses.longTokenSwapPath;
}
functionsetLongTokenSwapPath(Props memory props, address[] memory value) internalpure{
props.addresses.longTokenSwapPath = value;
}
functionshortTokenSwapPath(Props memory props) internalpurereturns (address[] memory) {
return props.addresses.shortTokenSwapPath;
}
functionsetShortTokenSwapPath(Props memory props, address[] memory value) internalpure{
props.addresses.shortTokenSwapPath = value;
}
functioninitialLongTokenAmount(Props memory props) internalpurereturns (uint256) {
return props.numbers.initialLongTokenAmount;
}
functionsetInitialLongTokenAmount(Props memory props, uint256 value) internalpure{
props.numbers.initialLongTokenAmount = value;
}
functioninitialShortTokenAmount(Props memory props) internalpurereturns (uint256) {
return props.numbers.initialShortTokenAmount;
}
functionsetInitialShortTokenAmount(Props memory props, uint256 value) internalpure{
props.numbers.initialShortTokenAmount = value;
}
functionminMarketTokens(Props memory props) internalpurereturns (uint256) {
return props.numbers.minMarketTokens;
}
functionsetMinMarketTokens(Props memory props, uint256 value) internalpure{
props.numbers.minMarketTokens = value;
}
functionupdatedAtTime(Props memory props) internalpurereturns (uint256) {
return props.numbers.updatedAtTime;
}
functionsetUpdatedAtTime(Props memory props, uint256 value) internalpure{
props.numbers.updatedAtTime = value;
}
functionexecutionFee(Props memory props) internalpurereturns (uint256) {
return props.numbers.executionFee;
}
functionsetExecutionFee(Props memory props, uint256 value) internalpure{
props.numbers.executionFee = value;
}
functioncallbackGasLimit(Props memory props) internalpurereturns (uint256) {
return props.numbers.callbackGasLimit;
}
functionsetCallbackGasLimit(Props memory props, uint256 value) internalpure{
props.numbers.callbackGasLimit = value;
}
functionshouldUnwrapNativeToken(Props memory props) internalpurereturns (bool) {
return props.flags.shouldUnwrapNativeToken;
}
functionsetShouldUnwrapNativeToken(Props memory props, bool value) internalpure{
props.flags.shouldUnwrapNativeToken = value;
}
}
Contract Source Code
File 25 of 103: ERC20.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol)pragmasolidity ^0.8.0;import"./IERC20.sol";
import"./extensions/IERC20Metadata.sol";
import"../../utils/Context.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
* For a generic mechanism see {ERC20PresetMinterPauser}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC20
* applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*
* Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
* functions have been added to mitigate the well-known issues around setting
* allowances. See {IERC20-approve}.
*/contractERC20isContext, IERC20, IERC20Metadata{
mapping(address=>uint256) private _balances;
mapping(address=>mapping(address=>uint256)) private _allowances;
uint256private _totalSupply;
stringprivate _name;
stringprivate _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/constructor(stringmemory name_, stringmemory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/functionname() publicviewvirtualoverridereturns (stringmemory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/functionsymbol() publicviewvirtualoverridereturns (stringmemory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/functiondecimals() publicviewvirtualoverridereturns (uint8) {
return18;
}
/**
* @dev See {IERC20-totalSupply}.
*/functiontotalSupply() publicviewvirtualoverridereturns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/functionbalanceOf(address account) publicviewvirtualoverridereturns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/functiontransfer(address to, uint256 amount) publicvirtualoverridereturns (bool) {
address owner = _msgSender();
_transfer(owner, to, amount);
returntrue;
}
/**
* @dev See {IERC20-allowance}.
*/functionallowance(address owner, address spender) publicviewvirtualoverridereturns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/functionapprove(address spender, uint256 amount) publicvirtualoverridereturns (bool) {
address owner = _msgSender();
_approve(owner, spender, amount);
returntrue;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
* - the caller must have allowance for ``from``'s tokens of at least
* `amount`.
*/functiontransferFrom(addressfrom, address to, uint256 amount) publicvirtualoverridereturns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);
returntrue;
}
/**
* @dev Atomically increases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/functionincreaseAllowance(address spender, uint256 addedValue) publicvirtualreturns (bool) {
address owner = _msgSender();
_approve(owner, spender, allowance(owner, spender) + addedValue);
returntrue;
}
/**
* @dev Atomically decreases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `spender` must have allowance for the caller of at least
* `subtractedValue`.
*/functiondecreaseAllowance(address spender, uint256 subtractedValue) publicvirtualreturns (bool) {
address owner = _msgSender();
uint256 currentAllowance = allowance(owner, spender);
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
unchecked {
_approve(owner, spender, currentAllowance - subtractedValue);
}
returntrue;
}
/**
* @dev Moves `amount` of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
*/function_transfer(addressfrom, address to, uint256 amount) internalvirtual{
require(from!=address(0), "ERC20: transfer from the zero address");
require(to !=address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(from, to, amount);
uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[from] = fromBalance - amount;
// Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by// decrementing then incrementing.
_balances[to] += amount;
}
emit Transfer(from, to, amount);
_afterTokenTransfer(from, to, amount);
}
/** @dev Creates `amount` tokens and assigns them to `account`, increasing
* the total supply.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
*/function_mint(address account, uint256 amount) internalvirtual{
require(account !=address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply += amount;
unchecked {
// Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
_balances[account] += amount;
}
emit Transfer(address(0), account, amount);
_afterTokenTransfer(address(0), account, amount);
}
/**
* @dev Destroys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
* - `account` must have at least `amount` tokens.
*/function_burn(address account, uint256 amount) internalvirtual{
require(account !=address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
uint256 accountBalance = _balances[account];
require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
unchecked {
_balances[account] = accountBalance - amount;
// Overflow not possible: amount <= accountBalance <= totalSupply.
_totalSupply -= amount;
}
emit Transfer(account, address(0), amount);
_afterTokenTransfer(account, address(0), amount);
}
/**
* @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*/function_approve(address owner, address spender, uint256 amount) internalvirtual{
require(owner !=address(0), "ERC20: approve from the zero address");
require(spender !=address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `amount`.
*
* Does not update the allowance amount in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Might emit an {Approval} event.
*/function_spendAllowance(address owner, address spender, uint256 amount) internalvirtual{
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance !=type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
/**
* @dev Hook that is called before any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* will be transferred to `to`.
* - when `from` is zero, `amount` tokens will be minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/function_beforeTokenTransfer(addressfrom, address to, uint256 amount) internalvirtual{}
/**
* @dev Hook that is called after any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* has been transferred to `to`.
* - when `from` is zero, `amount` tokens have been minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens have been burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/function_afterTokenTransfer(addressfrom, address to, uint256 amount) internalvirtual{}
}
Contract Source Code
File 26 of 103: EnumerableSet.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/EnumerableSet.sol)// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.pragmasolidity ^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.
*
* ```solidity
* 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.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/libraryEnumerableSet{
// 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.structSet {
// Storage of set valuesbytes32[] _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) privatereturns (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;
returntrue;
} else {
returnfalse;
}
}
/**
* @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) privatereturns (bool) {
// We read and store the value's index to prevent multiple reads from the same storage slotuint256 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;
if (lastIndex != toDeleteIndex) {
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] = valueIndex; // Replace lastValue's index to valueIndex
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the index for the deleted slotdelete set._indexes[value];
returntrue;
} else {
returnfalse;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/function_contains(Set storage set, bytes32 value) privateviewreturns (bool) {
return set._indexes[value] !=0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/function_length(Set storage set) privateviewreturns (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) privateviewreturns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/function_values(Set storage set) privateviewreturns (bytes32[] memory) {
return set._values;
}
// Bytes32SetstructBytes32Set {
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.
*/functionadd(Bytes32Set storage set, bytes32 value) internalreturns (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.
*/functionremove(Bytes32Set storage set, bytes32 value) internalreturns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/functioncontains(Bytes32Set storage set, bytes32 value) internalviewreturns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/functionlength(Bytes32Set storage set) internalviewreturns (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}.
*/functionat(Bytes32Set storage set, uint256 index) internalviewreturns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/functionvalues(Bytes32Set storage set) internalviewreturns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
/// @solidity memory-safe-assemblyassembly {
result := store
}
return result;
}
// AddressSetstructAddressSet {
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.
*/functionadd(AddressSet storage set, address value) internalreturns (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.
*/functionremove(AddressSet storage set, address value) internalreturns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/functioncontains(AddressSet storage set, address value) internalviewreturns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/functionlength(AddressSet storage set) internalviewreturns (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}.
*/functionat(AddressSet storage set, uint256 index) internalviewreturns (address) {
returnaddress(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/functionvalues(AddressSet storage set) internalviewreturns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
/// @solidity memory-safe-assemblyassembly {
result := store
}
return result;
}
// UintSetstructUintSet {
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.
*/functionadd(UintSet storage set, uint256 value) internalreturns (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.
*/functionremove(UintSet storage set, uint256 value) internalreturns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/functioncontains(UintSet storage set, uint256 value) internalviewreturns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values in the set. O(1).
*/functionlength(UintSet storage set) internalviewreturns (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}.
*/functionat(UintSet storage set, uint256 index) internalviewreturns (uint256) {
returnuint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/functionvalues(UintSet storage set) internalviewreturns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
/// @solidity memory-safe-assemblyassembly {
result := store
}
return result;
}
}
Contract Source Code
File 27 of 103: EnumerableValues.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
/**
* @title EnumerableValues
* @dev Library to extend the EnumerableSet library with functions to get
* valuesAt for a range
*/libraryEnumerableValues{
usingEnumerableSetforEnumerableSet.Bytes32Set;
usingEnumerableSetforEnumerableSet.AddressSet;
usingEnumerableSetforEnumerableSet.UintSet;
/**
* Returns an array of bytes32 values from the given set, starting at the given
* start index and ending before the given end index.
*
* @param set The set to get the values from.
* @param start The starting index.
* @param end The ending index.
* @return An array of bytes32 values.
*/functionvaluesAt(EnumerableSet.Bytes32Set storage set, uint256 start, uint256 end) internalviewreturns (bytes32[] memory) {
uint256 max = set.length();
if (end > max) { end = max; }
bytes32[] memory items =newbytes32[](end - start);
for (uint256 i = start; i < end; i++) {
items[i - start] = set.at(i);
}
return items;
}
/**
* Returns an array of address values from the given set, starting at the given
* start index and ending before the given end index.
*
* @param set The set to get the values from.
* @param start The starting index.
* @param end The ending index.
* @return An array of address values.
*/functionvaluesAt(EnumerableSet.AddressSet storage set, uint256 start, uint256 end) internalviewreturns (address[] memory) {
uint256 max = set.length();
if (end > max) { end = max; }
address[] memory items =newaddress[](end - start);
for (uint256 i = start; i < end; i++) {
items[i - start] = set.at(i);
}
return items;
}
/**
* Returns an array of uint256 values from the given set, starting at the given
* start index and ending before the given end index, the item at the end index will not be returned.
*
* @param set The set to get the values from.
* @param start The starting index (inclusive, item at the start index will be returned).
* @param end The ending index (exclusive, item at the end index will not be returned).
* @return An array of uint256 values.
*/functionvaluesAt(EnumerableSet.UintSet storage set, uint256 start, uint256 end) internalviewreturns (uint256[] memory) {
if (start >= set.length()) {
returnnewuint256[](0);
}
uint256 max = set.length();
if (end > max) { end = max; }
uint256[] memory items =newuint256[](end - start);
for (uint256 i = start; i < end; i++) {
items[i - start] = set.at(i);
}
return items;
}
}
Contract Source Code
File 28 of 103: ErrorUtils.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;libraryErrorUtils{
// To get the revert reason, referenced from https://ethereum.stackexchange.com/a/83577functiongetRevertMessage(bytesmemory result) internalpurereturns (stringmemory, bool) {
// If the result length is less than 68, then the transaction either panicked or failed silentlyif (result.length<68) {
return ("", false);
}
bytes4 errorSelector = getErrorSelectorFromData(result);
// 0x08c379a0 is the selector for Error(string)// referenced from https://blog.soliditylang.org/2021/04/21/custom-errors/if (errorSelector ==bytes4(0x08c379a0)) {
assembly {
result :=add(result, 0x04)
}
return (abi.decode(result, (string)), true);
}
// error may be a custom error, return an empty string for this casereturn ("", false);
}
functiongetErrorSelectorFromData(bytesmemory data) internalpurereturns (bytes4) {
bytes4 errorSelector;
assembly {
errorSelector :=mload(add(data, 0x20))
}
return errorSelector;
}
functionrevertWithParsedMessage(bytesmemory result) internalpure{
(stringmemory revertMessage, bool hasRevertMessage) = getRevertMessage(result);
if (hasRevertMessage) {
revert(revertMessage);
} else {
revertWithCustomError(result);
}
}
functionrevertWithCustomError(bytesmemory result) internalpure{
// referenced from https://ethereum.stackexchange.com/a/123588uint256 length = result.length;
assembly {
revert(add(result, 0x20), length)
}
}
}
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"../data/DataStore.sol";
import"./Order.sol";
import"./OrderVault.sol";
import"./OrderStoreUtils.sol";
import"./OrderEventUtils.sol";
import"./OrderUtils.sol";
import"../oracle/Oracle.sol";
import"../event/EventEmitter.sol";
import"./IncreaseOrderUtils.sol";
import"./DecreaseOrderUtils.sol";
import"./SwapOrderUtils.sol";
import"./BaseOrderUtils.sol";
import"../gas/GasUtils.sol";
import"../callback/CallbackUtils.sol";
import"../utils/Array.sol";
libraryExecuteOrderUtils{
usingOrderforOrder.Props;
usingPositionforPosition.Props;
usingPriceforPrice.Props;
usingArrayforuint256[];
// @dev executes an order// @param params BaseOrderUtils.ExecuteOrderParamsfunctionexecuteOrder(BaseOrderUtils.ExecuteOrderParams memory params) external{
// 63/64 gas is forwarded to external calls, reduce the startingGas to account for this
params.startingGas -=gasleft() /63;
OrderStoreUtils.remove(params.contracts.dataStore, params.key, params.order.account());
BaseOrderUtils.validateNonEmptyOrder(params.order);
BaseOrderUtils.validateOrderTriggerPrice(
params.contracts.oracle,
params.market.indexToken,
params.order.orderType(),
params.order.triggerPrice(),
params.order.isLong()
);
BaseOrderUtils.validateOrderValidFromTime(
params.order.orderType(),
params.order.validFromTime()
);
MarketUtils.MarketPrices memory prices = MarketUtils.getMarketPrices(
params.contracts.oracle,
params.market
);
MarketUtils.distributePositionImpactPool(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.market.marketToken
);
PositionUtils.updateFundingAndBorrowingState(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.market,
prices
);
EventUtils.EventLogData memory eventData = processOrder(params);
// validate that internal state changes are correct before calling// external callbacks// if the native token was transferred to the receiver in a swap// it may be possible to invoke external contracts before the validations// are calledif (params.market.marketToken !=address(0)) {
MarketUtils.validateMarketTokenBalance(params.contracts.dataStore, params.market);
}
MarketUtils.validateMarketTokenBalance(params.contracts.dataStore, params.swapPathMarkets);
OrderUtils.updateAutoCancelList(params.contracts.dataStore, params.key, params.order, false);
OrderEventUtils.emitOrderExecuted(
params.contracts.eventEmitter,
params.key,
params.order.account(),
params.secondaryOrderType
);
CallbackUtils.afterOrderExecution(params.key, params.order, eventData);
// the order.executionFee for liquidation / adl orders is zero// gas costs for liquidations / adl is subsidised by the treasury
GasUtils.payExecutionFee(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.contracts.orderVault,
params.key,
params.order.callbackContract(),
params.order.executionFee(),
params.startingGas,
GasUtils.estimateOrderOraclePriceCount(params.order.swapPath().length),
params.keeper,
params.order.receiver()
);
// clearAutoCancelOrders should be called after the main execution fee// is called// this is because clearAutoCancelOrders loops through each order for// the associated position and calls cancelOrder, which pays the keeper// based on the gas usage for each cancel orderif (BaseOrderUtils.isDecreaseOrder(params.order.orderType())) {
bytes32 positionKey = BaseOrderUtils.getPositionKey(params.order);
uint256 sizeInUsd = params.contracts.dataStore.getUint(
keccak256(abi.encode(positionKey, PositionStoreUtils.SIZE_IN_USD))
);
if (sizeInUsd ==0) {
OrderUtils.clearAutoCancelOrders(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.contracts.orderVault,
positionKey,
params.keeper
);
}
}
}
// @dev process an order execution// @param params BaseOrderUtils.ExecuteOrderParamsfunctionprocessOrder(BaseOrderUtils.ExecuteOrderParams memory params) internalreturns (EventUtils.EventLogData memory) {
if (BaseOrderUtils.isIncreaseOrder(params.order.orderType())) {
return IncreaseOrderUtils.processOrder(params);
}
if (BaseOrderUtils.isDecreaseOrder(params.order.orderType())) {
return DecreaseOrderUtils.processOrder(params);
}
if (BaseOrderUtils.isSwapOrder(params.order.orderType())) {
return SwapOrderUtils.processOrder(params);
}
revert Errors.UnsupportedOrderType(uint256(params.order.orderType()));
}
}
Contract Source Code
File 33 of 103: FeatureUtils.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"../data/DataStore.sol";
// @title FeatureUtils// @dev Library to validate if a feature is enabled or disabled// disabling a feature should only be used if it is absolutely necessary// disabling of features could lead to unexpected effects, e.g. increasing / decreasing of orders// could be disabled while liquidations may remain enabled// this could also occur if the chain is not producing blocks and lead to liquidatable positions// when block production resumes// the effects of disabling features should be carefully consideredlibraryFeatureUtils{
// @dev get whether a feature is disabled// @param dataStore DataStore// @param key the feature key// @return whether the feature is disabledfunctionisFeatureDisabled(DataStore dataStore, bytes32 key) internalviewreturns (bool) {
return dataStore.getBool(key);
}
// @dev validate whether a feature is enabled, reverts if the feature is disabled// @param dataStore DataStore// @param key the feature keyfunctionvalidateFeature(DataStore dataStore, bytes32 key) internalview{
if (isFeatureDisabled(dataStore, key)) {
revert Errors.DisabledFeature(key);
}
}
}
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"../callback/CallbackUtils.sol";
import"../data/DataStore.sol";
import"../data/Keys.sol";
import"../utils/Precision.sol";
import"../deposit/Deposit.sol";
import"../withdrawal/Withdrawal.sol";
import"../shift/Shift.sol";
import"../order/Order.sol";
import"../order/BaseOrderUtils.sol";
import"../glv/glvWithdrawal/GlvWithdrawal.sol";
import"../bank/StrictBank.sol";
// @title GasUtils// @dev Library for execution fee estimation and paymentslibraryGasUtils{
usingDepositforDeposit.Props;
usingWithdrawalforWithdrawal.Props;
usingShiftforShift.Props;
usingOrderforOrder.Props;
usingGlvDepositforGlvDeposit.Props;
usingGlvWithdrawalforGlvWithdrawal.Props;
usingEventUtilsforEventUtils.AddressItems;
usingEventUtilsforEventUtils.UintItems;
usingEventUtilsforEventUtils.IntItems;
usingEventUtilsforEventUtils.BoolItems;
usingEventUtilsforEventUtils.Bytes32Items;
usingEventUtilsforEventUtils.BytesItems;
usingEventUtilsforEventUtils.StringItems;
// @param keeper address of the keeper// @param amount the amount of execution fee receivedeventKeeperExecutionFee(address keeper, uint256 amount);
// @param user address of the user// @param amount the amount of execution fee refundedeventUserRefundFee(address user, uint256 amount);
functiongetMinHandleExecutionErrorGas(DataStore dataStore) internalviewreturns (uint256) {
return dataStore.getUint(Keys.MIN_HANDLE_EXECUTION_ERROR_GAS);
}
functiongetMinHandleExecutionErrorGasToForward(DataStore dataStore) internalviewreturns (uint256) {
return dataStore.getUint(Keys.MIN_HANDLE_EXECUTION_ERROR_GAS_TO_FORWARD);
}
functiongetMinAdditionalGasForExecution(DataStore dataStore) internalviewreturns (uint256) {
return dataStore.getUint(Keys.MIN_ADDITIONAL_GAS_FOR_EXECUTION);
}
functiongetExecutionGas(DataStore dataStore, uint256 startingGas) internalviewreturns (uint256) {
uint256 minHandleExecutionErrorGasToForward = GasUtils.getMinHandleExecutionErrorGasToForward(dataStore);
if (startingGas < minHandleExecutionErrorGasToForward) {
revert Errors.InsufficientExecutionGasForErrorHandling(startingGas, minHandleExecutionErrorGasToForward);
}
return startingGas - minHandleExecutionErrorGasToForward;
}
functionvalidateExecutionGas(DataStore dataStore, uint256 startingGas, uint256 estimatedGasLimit) internalview{
uint256 minAdditionalGasForExecution = getMinAdditionalGasForExecution(dataStore);
if (startingGas < estimatedGasLimit + minAdditionalGasForExecution) {
revert Errors.InsufficientExecutionGas(startingGas, estimatedGasLimit, minAdditionalGasForExecution);
}
}
// a minimum amount of gas is required to be left for cancellation// to prevent potential blocking of cancellations by malicious contracts using e.g. large revert reasons//// during the estimateGas call by keepers, an insufficient amount of gas may be estimated// the amount estimated may be insufficient for execution but sufficient for cancellaton// this could lead to invalid cancellations due to insufficient gas used by keepers//// to help prevent this, out of gas errors are attempted to be caught and reverted for estimateGas calls//// a malicious user could cause the estimateGas call of a keeper to fail, in which case the keeper could// still attempt to execute the transaction with a reasonable gas limitfunctionvalidateExecutionErrorGas(DataStore dataStore, bytesmemory reasonBytes) internalview{
// skip the validation if the execution did not fail due to an out of gas error// also skip the validation if this is not invoked in an estimateGas call (tx.origin != address(0))if (reasonBytes.length!=0||tx.origin!=address(0)) { return; }
uint256 gas =gasleft();
uint256 minHandleExecutionErrorGas = getMinHandleExecutionErrorGas(dataStore);
if (gas < minHandleExecutionErrorGas) {
revert Errors.InsufficientHandleExecutionErrorGas(gas, minHandleExecutionErrorGas);
}
}
structPayExecutionFeeCache {
uint256 refundFeeAmount;
bool refundWasSent;
}
// @dev pay the keeper the execution fee and refund any excess amount//// @param dataStore DataStore// @param bank the StrictBank contract holding the execution fee// @param executionFee the executionFee amount// @param startingGas the starting gas// @param oraclePriceCount number of oracle prices// @param keeper the keeper to pay// @param refundReceiver the account that should receive any excess gas refundsfunctionpayExecutionFee(
DataStore dataStore,
EventEmitter eventEmitter,
StrictBank bank,
bytes32 key,
address callbackContract,
uint256 executionFee,
uint256 startingGas,
uint256 oraclePriceCount,
address keeper,
address refundReceiver
) external{
if (executionFee ==0) {
return;
}
// 63/64 gas is forwarded to external calls, reduce the startingGas to account for this
startingGas -=gasleft() /63;
uint256 gasUsed = startingGas -gasleft();
// each external call forwards 63/64 of the remaining gasuint256 executionFeeForKeeper = adjustGasUsage(dataStore, gasUsed, oraclePriceCount) *tx.gasprice;
if (executionFeeForKeeper > executionFee) {
executionFeeForKeeper = executionFee;
}
bank.transferOutNativeToken(
keeper,
executionFeeForKeeper
);
emitKeeperExecutionFee(eventEmitter, keeper, executionFeeForKeeper);
PayExecutionFeeCache memory cache;
cache.refundFeeAmount = executionFee - executionFeeForKeeper;
if (cache.refundFeeAmount ==0) {
return;
}
address _wnt = dataStore.getAddress(Keys.WNT);
bank.transferOut(
_wnt,
address(this),
cache.refundFeeAmount
);
IWNT(_wnt).withdraw(cache.refundFeeAmount);
EventUtils.EventLogData memory eventData;
cache.refundWasSent = CallbackUtils.refundExecutionFee(dataStore, key, callbackContract, cache.refundFeeAmount, eventData);
if (cache.refundWasSent) {
emitExecutionFeeRefundCallback(eventEmitter, callbackContract, cache.refundFeeAmount);
} else {
TokenUtils.sendNativeToken(dataStore, refundReceiver, cache.refundFeeAmount);
emitExecutionFeeRefund(eventEmitter, refundReceiver, cache.refundFeeAmount);
}
}
// @dev validate that the provided executionFee is sufficient based on the estimatedGasLimit// @param dataStore DataStore// @param estimatedGasLimit the estimated gas limit// @param executionFee the execution fee provided// @param oraclePriceCountfunctionvalidateExecutionFee(DataStore dataStore, uint256 estimatedGasLimit, uint256 executionFee, uint256 oraclePriceCount) internalview{
uint256 gasLimit = adjustGasLimitForEstimate(dataStore, estimatedGasLimit, oraclePriceCount);
uint256 minExecutionFee = gasLimit *tx.gasprice;
if (executionFee < minExecutionFee) {
revert Errors.InsufficientExecutionFee(minExecutionFee, executionFee);
}
}
// @dev adjust the gas usage to pay a small amount to keepers// @param dataStore DataStore// @param gasUsed the amount of gas used// @param oraclePriceCount number of oracle pricesfunctionadjustGasUsage(DataStore dataStore, uint256 gasUsed, uint256 oraclePriceCount) internalviewreturns (uint256) {
// gas measurements are done after the call to withOraclePrices// withOraclePrices may consume a significant amount of gas// the baseGasLimit used to calculate the execution cost// should be adjusted to account for this// additionally, a transaction could fail midway through an execution transaction// before being cancelled, the possibility of this additional gas cost should// be considered when setting the baseGasLimituint256 baseGasLimit = dataStore.getUint(Keys.EXECUTION_GAS_FEE_BASE_AMOUNT_V2_1);
baseGasLimit += dataStore.getUint(Keys.EXECUTION_GAS_FEE_PER_ORACLE_PRICE) * oraclePriceCount;
// the gas cost is estimated based on the gasprice of the request txn// the actual cost may be higher if the gasprice is higher in the execution txn// the multiplierFactor should be adjusted to account for thisuint256 multiplierFactor = dataStore.getUint(Keys.EXECUTION_GAS_FEE_MULTIPLIER_FACTOR);
uint256 gasLimit = baseGasLimit + Precision.applyFactor(gasUsed, multiplierFactor);
return gasLimit;
}
// @dev adjust the estimated gas limit to help ensure the execution fee is sufficient during// the actual execution// @param dataStore DataStore// @param estimatedGasLimit the estimated gas limitfunctionadjustGasLimitForEstimate(DataStore dataStore, uint256 estimatedGasLimit, uint256 oraclePriceCount) internalviewreturns (uint256) {
uint256 baseGasLimit = dataStore.getUint(Keys.ESTIMATED_GAS_FEE_BASE_AMOUNT_V2_1);
baseGasLimit += dataStore.getUint(Keys.ESTIMATED_GAS_FEE_PER_ORACLE_PRICE) * oraclePriceCount;
uint256 multiplierFactor = dataStore.getUint(Keys.ESTIMATED_GAS_FEE_MULTIPLIER_FACTOR);
uint256 gasLimit = baseGasLimit + Precision.applyFactor(estimatedGasLimit, multiplierFactor);
return gasLimit;
}
// @dev get estimated number of oracle prices for deposit// @param swapsCount number of swaps in the depositfunctionestimateDepositOraclePriceCount(uint256 swapsCount) internalpurereturns (uint256) {
return3+ swapsCount;
}
// @dev get estimated number of oracle prices for withdrawal// @param swapsCount number of swaps in the withdrawalfunctionestimateWithdrawalOraclePriceCount(uint256 swapsCount) internalpurereturns (uint256) {
return3+ swapsCount;
}
// @dev get estimated number of oracle prices for order// @param swapsCount number of swaps in the orderfunctionestimateOrderOraclePriceCount(uint256 swapsCount) internalpurereturns (uint256) {
return3+ swapsCount;
}
// @dev get estimated number of oracle prices for shiftfunctionestimateShiftOraclePriceCount() internalpurereturns (uint256) {
// for single asset markets only 3 prices will be required// and keeper will slightly overpay// it should not be an issue because execution fee goes back to keeperreturn4;
}
functionestimateGlvDepositOraclePriceCount(uint256 marketCount,
uint256 swapsCount
) internalpurereturns (uint256) {
// for single asset markets oracle price count will be overestimated by 1// it should not be an issue for GLV with multiple markets// because relative difference would be insignificantreturn2+ marketCount + swapsCount;
}
functionestimateGlvWithdrawalOraclePriceCount(uint256 marketCount,
uint256 swapsCount
) internalpurereturns (uint256) {
// for single asset markets oracle price count will be overestimated by 1// it should not be an issue for GLV with multiple markets// because relative difference would be insignificantreturn2+ marketCount + swapsCount;
}
// @dev the estimated gas limit for deposits// @param dataStore DataStore// @param deposit the deposit to estimate the gas limit forfunctionestimateExecuteDepositGasLimit(DataStore dataStore, Deposit.Props memory deposit) internalviewreturns (uint256) {
uint256 gasPerSwap = dataStore.getUint(Keys.singleSwapGasLimitKey());
uint256 swapCount = deposit.longTokenSwapPath().length+ deposit.shortTokenSwapPath().length;
uint256 gasForSwaps = swapCount * gasPerSwap;
return dataStore.getUint(Keys.depositGasLimitKey()) + deposit.callbackGasLimit() + gasForSwaps;
}
// @dev the estimated gas limit for withdrawals// @param dataStore DataStore// @param withdrawal the withdrawal to estimate the gas limit forfunctionestimateExecuteWithdrawalGasLimit(DataStore dataStore, Withdrawal.Props memory withdrawal) internalviewreturns (uint256) {
uint256 gasPerSwap = dataStore.getUint(Keys.singleSwapGasLimitKey());
uint256 swapCount = withdrawal.longTokenSwapPath().length+ withdrawal.shortTokenSwapPath().length;
uint256 gasForSwaps = swapCount * gasPerSwap;
return dataStore.getUint(Keys.withdrawalGasLimitKey()) + withdrawal.callbackGasLimit() + gasForSwaps;
}
// @dev the estimated gas limit for shifts// @param dataStore DataStore// @param shift the shift to estimate the gas limit forfunctionestimateExecuteShiftGasLimit(DataStore dataStore, Shift.Props memory shift) internalviewreturns (uint256) {
return dataStore.getUint(Keys.shiftGasLimitKey()) + shift.callbackGasLimit();
}
// @dev the estimated gas limit for orders// @param dataStore DataStore// @param order the order to estimate the gas limit forfunctionestimateExecuteOrderGasLimit(DataStore dataStore, Order.Props memory order) internalviewreturns (uint256) {
if (BaseOrderUtils.isIncreaseOrder(order.orderType())) {
return estimateExecuteIncreaseOrderGasLimit(dataStore, order);
}
if (BaseOrderUtils.isDecreaseOrder(order.orderType())) {
return estimateExecuteDecreaseOrderGasLimit(dataStore, order);
}
if (BaseOrderUtils.isSwapOrder(order.orderType())) {
return estimateExecuteSwapOrderGasLimit(dataStore, order);
}
revert Errors.UnsupportedOrderType(uint256(order.orderType()));
}
// @dev the estimated gas limit for increase orders// @param dataStore DataStore// @param order the order to estimate the gas limit forfunctionestimateExecuteIncreaseOrderGasLimit(DataStore dataStore, Order.Props memory order) internalviewreturns (uint256) {
uint256 gasPerSwap = dataStore.getUint(Keys.singleSwapGasLimitKey());
return dataStore.getUint(Keys.increaseOrderGasLimitKey()) + gasPerSwap * order.swapPath().length+ order.callbackGasLimit();
}
// @dev the estimated gas limit for decrease orders// @param dataStore DataStore// @param order the order to estimate the gas limit forfunctionestimateExecuteDecreaseOrderGasLimit(DataStore dataStore, Order.Props memory order) internalviewreturns (uint256) {
uint256 gasPerSwap = dataStore.getUint(Keys.singleSwapGasLimitKey());
uint256 swapCount = order.swapPath().length;
if (order.decreasePositionSwapType() != Order.DecreasePositionSwapType.NoSwap) {
swapCount +=1;
}
return dataStore.getUint(Keys.decreaseOrderGasLimitKey()) + gasPerSwap * swapCount + order.callbackGasLimit();
}
// @dev the estimated gas limit for swap orders// @param dataStore DataStore// @param order the order to estimate the gas limit forfunctionestimateExecuteSwapOrderGasLimit(DataStore dataStore, Order.Props memory order) internalviewreturns (uint256) {
uint256 gasPerSwap = dataStore.getUint(Keys.singleSwapGasLimitKey());
return dataStore.getUint(Keys.swapOrderGasLimitKey()) + gasPerSwap * order.swapPath().length+ order.callbackGasLimit();
}
// @dev the estimated gas limit for glv deposits// @param dataStore DataStore// @param deposit the deposit to estimate the gas limit forfunctionestimateExecuteGlvDepositGasLimit(DataStore dataStore, GlvDeposit.Props memory glvDeposit, uint256 marketCount) internalviewreturns (uint256) {
// glv deposit execution gas consumption depends on the amount of marketsuint256 gasPerGlvPerMarket = dataStore.getUint(Keys.glvPerMarketGasLimitKey());
uint256 gasForGlvMarkets = gasPerGlvPerMarket * marketCount;
uint256 glvDepositGasLimit = dataStore.getUint(Keys.glvDepositGasLimitKey());
uint256 gasLimit = glvDepositGasLimit + glvDeposit.callbackGasLimit() + gasForGlvMarkets;
if (glvDeposit.isMarketTokenDeposit()) {
// user provided GM, no separate deposit will be created and executed in this casereturn gasLimit;
}
uint256 gasPerSwap = dataStore.getUint(Keys.singleSwapGasLimitKey());
uint256 swapCount = glvDeposit.longTokenSwapPath().length+ glvDeposit.shortTokenSwapPath().length;
uint256 gasForSwaps = swapCount * gasPerSwap;
return gasLimit + dataStore.getUint(Keys.depositGasLimitKey()) + gasForSwaps;
}
// @dev the estimated gas limit for glv withdrawals// @param dataStore DataStore// @param withdrawal the withdrawal to estimate the gas limit forfunctionestimateExecuteGlvWithdrawalGasLimit(DataStore dataStore, GlvWithdrawal.Props memory glvWithdrawal, uint256 marketCount) internalviewreturns (uint256) {
// glv withdrawal execution gas consumption depends on the amount of marketsuint256 gasPerGlvPerMarket = dataStore.getUint(Keys.glvPerMarketGasLimitKey());
uint256 gasForGlvMarkets = gasPerGlvPerMarket * marketCount;
uint256 glvWithdrawalGasLimit = dataStore.getUint(Keys.glvWithdrawalGasLimitKey());
uint256 gasLimit = glvWithdrawalGasLimit + glvWithdrawal.callbackGasLimit() + gasForGlvMarkets;
uint256 gasPerSwap = dataStore.getUint(Keys.singleSwapGasLimitKey());
uint256 swapCount = glvWithdrawal.longTokenSwapPath().length+ glvWithdrawal.shortTokenSwapPath().length;
uint256 gasForSwaps = swapCount * gasPerSwap;
return gasLimit + dataStore.getUint(Keys.withdrawalGasLimitKey()) + gasForSwaps;
}
functionestimateExecuteGlvShiftGasLimit(DataStore dataStore) internalviewreturns (uint256) {
return dataStore.getUint(Keys.glvShiftGasLimitKey());
}
functionemitKeeperExecutionFee(
EventEmitter eventEmitter,
address keeper,
uint256 executionFeeAmount
) internal{
EventUtils.EventLogData memory eventData;
eventData.addressItems.initItems(1);
eventData.addressItems.setItem(0, "keeper", keeper);
eventData.uintItems.initItems(1);
eventData.uintItems.setItem(0, "executionFeeAmount", executionFeeAmount);
eventEmitter.emitEventLog1(
"KeeperExecutionFee",
Cast.toBytes32(keeper),
eventData
);
}
functionemitExecutionFeeRefund(
EventEmitter eventEmitter,
address receiver,
uint256 refundFeeAmount
) internal{
EventUtils.EventLogData memory eventData;
eventData.addressItems.initItems(1);
eventData.addressItems.setItem(0, "receiver", receiver);
eventData.uintItems.initItems(1);
eventData.uintItems.setItem(0, "refundFeeAmount", refundFeeAmount);
eventEmitter.emitEventLog1(
"ExecutionFeeRefund",
Cast.toBytes32(receiver),
eventData
);
}
functionemitExecutionFeeRefundCallback(
EventEmitter eventEmitter,
address callbackContract,
uint256 refundFeeAmount
) internal{
EventUtils.EventLogData memory eventData;
eventData.addressItems.initItems(1);
eventData.addressItems.setItem(0, "callbackContract", callbackContract);
eventData.uintItems.initItems(1);
eventData.uintItems.setItem(0, "refundFeeAmount", refundFeeAmount);
eventEmitter.emitEventLog1(
"ExecutionFeeRefundCallback",
Cast.toBytes32(callbackContract),
eventData
);
}
}
Contract Source Code
File 36 of 103: GlobalReentrancyGuard.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"../data/Keys.sol";
import"../data/DataStore.sol";
abstractcontractGlobalReentrancyGuard{
// 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.uint256privateconstant NOT_ENTERED =0;
uint256privateconstant ENTERED =1;
DataStore publicimmutable dataStore;
constructor(DataStore _dataStore) {
dataStore = _dataStore;
}
modifierglobalNonReentrant() {
_globalNonReentrantBefore();
_;
_globalNonReentrantAfter();
}
function_globalNonReentrantBefore() private{
uint256 status = dataStore.getUint(Keys.REENTRANCY_GUARD_STATUS);
require(status == NOT_ENTERED, "ReentrancyGuard: reentrant call");
dataStore.setUint(Keys.REENTRANCY_GUARD_STATUS, ENTERED);
}
function_globalNonReentrantAfter() private{
dataStore.setUint(Keys.REENTRANCY_GUARD_STATUS, NOT_ENTERED);
}
}
Contract Source Code
File 37 of 103: GlvDeposit.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;// @title GlvDeposit// @dev Struct for GLV depositslibraryGlvDeposit{
// @dev there is a limit on the number of fields a struct can have when being passed// or returned as a memory variable which can cause "Stack too deep" errors// use sub-structs to avoid this issue// large number of fields my also cause incorrect display in Tenderly// @param addresses address values// @param numbers number values// @param flags boolean valuesstructProps {
Addresses addresses;
Numbers numbers;
Flags flags;
}
// @param account the account depositing liquidity// @param receiver the address to send the liquidity tokens to// @param callbackContract the callback contract// @param uiFeeReceiver the ui fee receiver// @param market the market to deposit tostructAddresses {
address glv;
address account;
address receiver;
address callbackContract;
address uiFeeReceiver;
address market;
address initialLongToken;
address initialShortToken;
address[] longTokenSwapPath;
address[] shortTokenSwapPath;
}
// @param marketTokenAmount the amount of market tokens to deposit// @param initialLongTokenAmount the amount of long tokens to deposit// @param initialShortTokenAmount the amount of short tokens to deposit// @param minGlvTokens the minimum acceptable number of Glv tokens// sending funds back to the user in case the deposit gets cancelled// @param executionFee the execution fee for keepers// @param callbackGasLimit the gas limit for the callbackContractstructNumbers {
uint256 marketTokenAmount;
uint256 initialLongTokenAmount;
uint256 initialShortTokenAmount;
uint256 minGlvTokens;
uint256 updatedAtTime;
uint256 executionFee;
uint256 callbackGasLimit;
}
// @param shouldUnwrapNativeToken whether to unwrap the native token when// @param isMarketTokenDeposit whether to deposit market tokens or long/short tokensstructFlags {
bool shouldUnwrapNativeToken;
bool isMarketTokenDeposit;
}
functionaccount(Props memory props) internalpurereturns (address) {
return props.addresses.account;
}
functionsetAccount(Props memory props, address value) internalpure{
props.addresses.account = value;
}
functionreceiver(Props memory props) internalpurereturns (address) {
return props.addresses.receiver;
}
functionsetReceiver(Props memory props, address value) internalpure{
props.addresses.receiver = value;
}
functioncallbackContract(Props memory props) internalpurereturns (address) {
return props.addresses.callbackContract;
}
functionsetCallbackContract(Props memory props, address value) internalpure{
props.addresses.callbackContract = value;
}
functionuiFeeReceiver(Props memory props) internalpurereturns (address) {
return props.addresses.uiFeeReceiver;
}
functionsetUiFeeReceiver(Props memory props, address value) internalpure{
props.addresses.uiFeeReceiver = value;
}
functionglv(Props memory props) internalpurereturns (address) {
return props.addresses.glv;
}
functionsetGlv(Props memory props, address value) internalpure{
props.addresses.glv = value;
}
functionmarket(Props memory props) internalpurereturns (address) {
return props.addresses.market;
}
functionsetMarket(Props memory props, address value) internalpure{
props.addresses.market = value;
}
functioninitialLongToken(Props memory props) internalpurereturns (address) {
return props.addresses.initialLongToken;
}
functionsetInitialLongToken(Props memory props, address value) internalpure{
props.addresses.initialLongToken = value;
}
functioninitialShortToken(Props memory props) internalpurereturns (address) {
return props.addresses.initialShortToken;
}
functionsetInitialShortToken(Props memory props, address value) internalpure{
props.addresses.initialShortToken = value;
}
functionlongTokenSwapPath(Props memory props) internalpurereturns (address[] memory) {
return props.addresses.longTokenSwapPath;
}
functionsetLongTokenSwapPath(Props memory props, address[] memory value) internalpure{
props.addresses.longTokenSwapPath = value;
}
functionshortTokenSwapPath(Props memory props) internalpurereturns (address[] memory) {
return props.addresses.shortTokenSwapPath;
}
functionsetShortTokenSwapPath(Props memory props, address[] memory value) internalpure{
props.addresses.shortTokenSwapPath = value;
}
functionmarketTokenAmount(Props memory props) internalpurereturns (uint256) {
return props.numbers.marketTokenAmount;
}
functionsetMarketTokenAmount(Props memory props, uint256 value) internalpure{
props.numbers.marketTokenAmount = value;
}
functioninitialLongTokenAmount(Props memory props) internalpurereturns (uint256) {
return props.numbers.initialLongTokenAmount;
}
functionsetInitialLongTokenAmount(Props memory props, uint256 value) internalpure{
props.numbers.initialLongTokenAmount = value;
}
functioninitialShortTokenAmount(Props memory props) internalpurereturns (uint256) {
return props.numbers.initialShortTokenAmount;
}
functionsetInitialShortTokenAmount(Props memory props, uint256 value) internalpure{
props.numbers.initialShortTokenAmount = value;
}
functionminGlvTokens(Props memory props) internalpurereturns (uint256) {
return props.numbers.minGlvTokens;
}
functionsetMinGlvTokens(Props memory props, uint256 value) internalpure{
props.numbers.minGlvTokens = value;
}
functionupdatedAtTime(Props memory props) internalpurereturns (uint256) {
return props.numbers.updatedAtTime;
}
functionsetUpdatedAtTime(Props memory props, uint256 value) internalpure{
props.numbers.updatedAtTime = value;
}
functionexecutionFee(Props memory props) internalpurereturns (uint256) {
return props.numbers.executionFee;
}
functionsetExecutionFee(Props memory props, uint256 value) internalpure{
props.numbers.executionFee = value;
}
functioncallbackGasLimit(Props memory props) internalpurereturns (uint256) {
return props.numbers.callbackGasLimit;
}
functionsetCallbackGasLimit(Props memory props, uint256 value) internalpure{
props.numbers.callbackGasLimit = value;
}
functionshouldUnwrapNativeToken(Props memory props) internalpurereturns (bool) {
return props.flags.shouldUnwrapNativeToken;
}
functionsetShouldUnwrapNativeToken(Props memory props, bool value) internalpure{
props.flags.shouldUnwrapNativeToken = value;
}
functionisMarketTokenDeposit(Props memory props) internalpurereturns (bool) {
return props.flags.isMarketTokenDeposit;
}
functionsetIsMarketTokenDeposit(Props memory props, bool value) internalpure{
props.flags.isMarketTokenDeposit = value;
}
}
Contract Source Code
File 38 of 103: GlvWithdrawal.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;/**
* @title Withdrawal
* @dev Struct for withdrawals
*/libraryGlvWithdrawal{
// @dev there is a limit on the number of fields a struct can have when being passed// or returned as a memory variable which can cause "Stack too deep" errors// use sub-structs to avoid this issue// @param addresses address values// @param numbers number values// @param flags boolean valuesstructProps {
Addresses addresses;
Numbers numbers;
Flags flags;
}
// @param account The account to withdraw for.// @param receiver The address that will receive the withdrawn tokens.// @param callbackContract The contract that will be called back.// @param uiFeeReceiver The ui fee receiver.// @param market The market on which the withdrawal will be executed.// @param glvstructAddresses {
address glv;
address market;
address account;
address receiver;
address callbackContract;
address uiFeeReceiver;
address[] longTokenSwapPath;
address[] shortTokenSwapPath;
}
// @param glvTokenAmount The amount of market tokens that will be withdrawn.// @param minLongTokenAmount The minimum amount of long tokens that must be withdrawn.// @param minShortTokenAmount The minimum amount of short tokens that must be withdrawn.// @param executionFee The execution fee for the withdrawal.// @param callbackGasLimit The gas limit for calling the callback contract.structNumbers {
uint256 glvTokenAmount;
uint256 minLongTokenAmount;
uint256 minShortTokenAmount;
uint256 updatedAtTime;
uint256 executionFee;
uint256 callbackGasLimit;
}
// @param shouldUnwrapNativeToken whether to unwrap the native token whenstructFlags {
bool shouldUnwrapNativeToken;
}
functionaccount(Props memory props) internalpurereturns (address) {
return props.addresses.account;
}
functionsetAccount(Props memory props, address value) internalpure{
props.addresses.account = value;
}
functionreceiver(Props memory props) internalpurereturns (address) {
return props.addresses.receiver;
}
functionsetReceiver(Props memory props, address value) internalpure{
props.addresses.receiver = value;
}
functioncallbackContract(Props memory props) internalpurereturns (address) {
return props.addresses.callbackContract;
}
functionsetCallbackContract(Props memory props, address value) internalpure{
props.addresses.callbackContract = value;
}
functionuiFeeReceiver(Props memory props) internalpurereturns (address) {
return props.addresses.uiFeeReceiver;
}
functionsetUiFeeReceiver(Props memory props, address value) internalpure{
props.addresses.uiFeeReceiver = value;
}
functionmarket(Props memory props) internalpurereturns (address) {
return props.addresses.market;
}
functionsetMarket(Props memory props, address value) internalpure{
props.addresses.market = value;
}
functionglv(Props memory props) internalpurereturns (address) {
return props.addresses.glv;
}
functionsetGlv(Props memory props, address value) internalpure{
props.addresses.glv = value;
}
functionlongTokenSwapPath(Props memory props) internalpurereturns (address[] memory) {
return props.addresses.longTokenSwapPath;
}
functionsetLongTokenSwapPath(Props memory props, address[] memory value) internalpure{
props.addresses.longTokenSwapPath = value;
}
functionshortTokenSwapPath(Props memory props) internalpurereturns (address[] memory) {
return props.addresses.shortTokenSwapPath;
}
functionsetShortTokenSwapPath(Props memory props, address[] memory value) internalpure{
props.addresses.shortTokenSwapPath = value;
}
functionglvTokenAmount(Props memory props) internalpurereturns (uint256) {
return props.numbers.glvTokenAmount;
}
functionsetGlvTokenAmount(Props memory props, uint256 value) internalpure{
props.numbers.glvTokenAmount = value;
}
functionminLongTokenAmount(Props memory props) internalpurereturns (uint256) {
return props.numbers.minLongTokenAmount;
}
functionsetMinLongTokenAmount(Props memory props, uint256 value) internalpure{
props.numbers.minLongTokenAmount = value;
}
functionminShortTokenAmount(Props memory props) internalpurereturns (uint256) {
return props.numbers.minShortTokenAmount;
}
functionsetMinShortTokenAmount(Props memory props, uint256 value) internalpure{
props.numbers.minShortTokenAmount = value;
}
functionupdatedAtTime(Props memory props) internalpurereturns (uint256) {
return props.numbers.updatedAtTime;
}
functionsetUpdatedAtTime(Props memory props, uint256 value) internalpure{
props.numbers.updatedAtTime = value;
}
functionexecutionFee(Props memory props) internalpurereturns (uint256) {
return props.numbers.executionFee;
}
functionsetExecutionFee(Props memory props, uint256 value) internalpure{
props.numbers.executionFee = value;
}
functioncallbackGasLimit(Props memory props) internalpurereturns (uint256) {
return props.numbers.callbackGasLimit;
}
functionsetCallbackGasLimit(Props memory props, uint256 value) internalpure{
props.numbers.callbackGasLimit = value;
}
functionshouldUnwrapNativeToken(Props memory props) internalpurereturns (bool) {
return props.flags.shouldUnwrapNativeToken;
}
functionsetShouldUnwrapNativeToken(Props memory props, bool value) internalpure{
props.flags.shouldUnwrapNativeToken = value;
}
}
Contract Source Code
File 39 of 103: IBaseOrderUtils.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"./Order.sol";
interfaceIBaseOrderUtils{
// @dev CreateOrderParams struct used in createOrder to avoid stack// too deep errors//// @param addresses address values// @param numbers number values// @param orderType for order.orderType// @param decreasePositionSwapType for order.decreasePositionSwapType// @param isLong for order.isLong// @param shouldUnwrapNativeToken for order.shouldUnwrapNativeTokenstructCreateOrderParams {
CreateOrderParamsAddresses addresses;
CreateOrderParamsNumbers numbers;
Order.OrderType orderType;
Order.DecreasePositionSwapType decreasePositionSwapType;
bool isLong;
bool shouldUnwrapNativeToken;
bool autoCancel;
bytes32 referralCode;
}
structCreateOrderParamsAddresses {
address receiver;
address cancellationReceiver;
address callbackContract;
address uiFeeReceiver;
address market;
address initialCollateralToken;
address[] swapPath;
}
// @param sizeDeltaUsd for order.sizeDeltaUsd// @param triggerPrice for order.triggerPrice// @param acceptablePrice for order.acceptablePrice// @param executionFee for order.executionFee// @param callbackGasLimit for order.callbackGasLimit// @param minOutputAmount for order.minOutputAmount// @param validFromTime for order.validFromTimestructCreateOrderParamsNumbers {
uint256 sizeDeltaUsd;
uint256 initialCollateralDeltaAmount;
uint256 triggerPrice;
uint256 acceptablePrice;
uint256 executionFee;
uint256 callbackGasLimit;
uint256 minOutputAmount;
uint256 validFromTime;
}
}
Contract Source Code
File 40 of 103: IDepositCallbackReceiver.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"../event/EventUtils.sol";
import"../deposit/Deposit.sol";
// @title IDepositCallbackReceiver// @dev interface for a deposit callback contractinterfaceIDepositCallbackReceiver{
// @dev called after a deposit execution// @param key the key of the deposit// @param deposit the deposit that was executedfunctionafterDepositExecution(bytes32 key, Deposit.Props memory deposit, EventUtils.EventLogData memory eventData) external;
// @dev called after a deposit cancellation// @param key the key of the deposit// @param deposit the deposit that was cancelledfunctionafterDepositCancellation(bytes32 key, Deposit.Props memory deposit, EventUtils.EventLogData memory eventData) external;
}
Contract Source Code
File 41 of 103: IERC20.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)pragmasolidity ^0.8.0;/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/interfaceIERC20{
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/eventTransfer(addressindexedfrom, addressindexed 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.
*/eventApproval(addressindexed owner, addressindexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/functiontotalSupply() externalviewreturns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/functionbalanceOf(address account) externalviewreturns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/functiontransfer(address to, uint256 amount) externalreturns (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.
*/functionallowance(address owner, address spender) externalviewreturns (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.
*/functionapprove(address spender, uint256 amount) externalreturns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` 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.
*/functiontransferFrom(addressfrom, address to, uint256 amount) externalreturns (bool);
}
Contract Source Code
File 42 of 103: IERC20Metadata.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)pragmasolidity ^0.8.0;import"../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/interfaceIERC20MetadataisIERC20{
/**
* @dev Returns the name of the token.
*/functionname() externalviewreturns (stringmemory);
/**
* @dev Returns the symbol of the token.
*/functionsymbol() externalviewreturns (stringmemory);
/**
* @dev Returns the decimals places of the token.
*/functiondecimals() externalviewreturns (uint8);
}
Contract Source Code
File 43 of 103: IERC20Permit.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol)pragmasolidity ^0.8.0;/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/interfaceIERC20Permit{
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*/functionpermit(address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/functionnonces(address owner) externalviewreturns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/// solhint-disable-next-line func-name-mixedcasefunctionDOMAIN_SEPARATOR() externalviewreturns (bytes32);
}
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"../event/EventUtils.sol";
import"../glv/glvDeposit/GlvDeposit.sol";
// @title IGlvDepositCallbackReceiver// @dev interface for a glvDeposit callback contractinterfaceIGlvDepositCallbackReceiver{
// @dev called after a glvDeposit execution// @param key the key of the glvDeposit// @param glvDeposit the glvDeposit that was executedfunctionafterGlvDepositExecution(bytes32 key,
GlvDeposit.Props memory glvDeposit,
EventUtils.EventLogData memory eventData
) external;
// @dev called after a glvDeposit cancellation// @param key the key of the glvDeposit// @param glvDeposit the glvDeposit that was cancelledfunctionafterGlvDepositCancellation(bytes32 key,
GlvDeposit.Props memory glvDeposit,
EventUtils.EventLogData memory eventData
) external;
}
Contract Source Code
File 46 of 103: IGlvWithdrawalCallbackReceiver.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"../event/EventUtils.sol";
import"../glv/glvWithdrawal/GlvWithdrawal.sol";
// @title IGlvWithdrawalCallbackReceiver// @dev interface for a glvWithdrawal callback contractinterfaceIGlvWithdrawalCallbackReceiver{
// @dev called after a glvWithdrawal execution// @param key the key of the glvWithdrawal// @param glvWithdrawal the glvWithdrawal that was executedfunctionafterGlvWithdrawalExecution(bytes32 key,
GlvWithdrawal.Props memory glvWithdrawal,
EventUtils.EventLogData memory eventData
) external;
// @dev called after a glvWithdrawal cancellation// @param key the key of the glvWithdrawal// @param glvWithdrawal the glvWithdrawal that was cancelledfunctionafterGlvWithdrawalCancellation(bytes32 key,
GlvWithdrawal.Props memory glvWithdrawal,
EventUtils.EventLogData memory eventData
) external;
}
Contract Source Code
File 47 of 103: IOracleProvider.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.0;import"./OracleUtils.sol";
// @title IOracleProvider// @dev Interface for an oracle providerinterfaceIOracleProvider{
functiongetOraclePrice(address token,
bytesmemory data
) externalreturns (OracleUtils.ValidatedPrice memory);
}
Contract Source Code
File 48 of 103: IOrderCallbackReceiver.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"../event/EventUtils.sol";
import"../order/Order.sol";
// @title IOrderCallbackReceiver// @dev interface for an order callback contractinterfaceIOrderCallbackReceiver{
// @dev called after an order execution// @param key the key of the order// @param order the order that was executedfunctionafterOrderExecution(bytes32 key, Order.Props memory order, EventUtils.EventLogData memory eventData) external;
// @dev called after an order cancellation// @param key the key of the order// @param order the order that was cancelledfunctionafterOrderCancellation(bytes32 key, Order.Props memory order, EventUtils.EventLogData memory eventData) external;
// @dev called after an order has been frozen, see OrderUtils.freezeOrder in OrderHandler for more info// @param key the key of the order// @param order the order that was frozenfunctionafterOrderFrozen(bytes32 key, Order.Props memory order, EventUtils.EventLogData memory eventData) external;
}
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;// @title IReferralStorage// @dev Interface for ReferralStorageinterfaceIReferralStorage{
// @dev get the owner of a referral code// @param _code the referral code// @return the owner of the referral codefunctioncodeOwners(bytes32 _code) externalviewreturns (address);
// @dev get the referral code of a trader// @param _account the address of the trader// @return the referral codefunctiontraderReferralCodes(address _account) externalviewreturns (bytes32);
// @dev get the trader discount share for an affiliate// @param _account the address of the affiliate// @return the trader discount sharefunctionreferrerDiscountShares(address _account) externalviewreturns (uint256);
// @dev get the tier level of an affiliate// @param _account the address of the affiliate// @return the tier level of the affiliatefunctionreferrerTiers(address _account) externalviewreturns (uint256);
// @dev get the referral info for a trader// @param _account the address of the trader// @return (referral code, affiliate)functiongetTraderReferralInfo(address _account) externalviewreturns (bytes32, address);
// @dev set the referral code for a trader// @param _account the address of the trader// @param _code the referral codefunctionsetTraderReferralCode(address _account, bytes32 _code) external;
// @dev set the values for a tier// @param _tierId the tier level// @param _totalRebate the total rebate for the tier (affiliate reward + trader discount)// @param _discountShare the share of the totalRebate for tradersfunctionsetTier(uint256 _tierId, uint256 _totalRebate, uint256 _discountShare) external;
// @dev set the tier for an affiliate// @param _tierId the tier levelfunctionsetReferrerTier(address _referrer, uint256 _tierId) external;
// @dev set the owner for a referral code// @param _code the referral code// @param _newAccount the new ownerfunctiongovSetCodeOwner(bytes32 _code, address _newAccount) external;
// @dev get the tier values for a tier level// @param _tierLevel the tier level// @return (totalRebate, discountShare)functiontiers(uint256 _tierLevel) externalviewreturns (uint256, uint256);
}
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;/**
* @title IWNT
* @dev Interface for Wrapped Native Tokens, e.g. WETH
* The contract is named WNT instead of WETH for a more general reference name
* that can be used on any blockchain
*/interfaceIWNT{
functiondeposit() externalpayable;
functionwithdraw(uint256 amount) external;
}
Contract Source Code
File 55 of 103: IWithdrawalCallbackReceiver.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"../event/EventUtils.sol";
import"../withdrawal/Withdrawal.sol";
// @title IWithdrawalCallbackReceiver// @dev interface for a withdrawal callback contractinterfaceIWithdrawalCallbackReceiver{
// @dev called after a withdrawal execution// @param key the key of the withdrawal// @param withdrawal the withdrawal that was executedfunctionafterWithdrawalExecution(bytes32 key, Withdrawal.Props memory withdrawal, EventUtils.EventLogData memory eventData) external;
// @dev called after a withdrawal cancellation// @param key the key of the withdrawal// @param withdrawal the withdrawal that was cancelledfunctionafterWithdrawalCancellation(bytes32 key, Withdrawal.Props memory withdrawal, EventUtils.EventLogData memory eventData) external;
}
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"../data/DataStore.sol";
import"../event/EventEmitter.sol";
import"../oracle/Oracle.sol";
import"../pricing/PositionPricingUtils.sol";
import"./Position.sol";
import"./PositionStoreUtils.sol";
import"./PositionUtils.sol";
import"./PositionEventUtils.sol";
// @title IncreasePositionUtils// @dev Library for functions to help with increasing a positionlibraryIncreasePositionUtils{
usingSafeCastforuint256;
usingSafeCastforint256;
usingPositionforPosition.Props;
usingOrderforOrder.Props;
usingPriceforPrice.Props;
// @dev IncreasePositionCache struct used in increasePosition to// avoid stack too deep errors// @param collateralDeltaAmount the change in collateral amount// @param executionPrice the execution price// @param collateralTokenPrice the price of the collateral token// @param priceImpactUsd the price impact in USD// @param priceImpactAmount the price impact of the position increase in tokens// @param sizeDeltaInTokens the change in position size in tokens// @param nextPositionSizeInUsd the new position size in USD// @param nextPositionBorrowingFactor the new position borrowing factorstructIncreasePositionCache {
int256 collateralDeltaAmount;
uint256 executionPrice;
Price.Props collateralTokenPrice;
int256 priceImpactUsd;
int256 priceImpactAmount;
uint256 sizeDeltaInTokens;
uint256 nextPositionSizeInUsd;
uint256 nextPositionBorrowingFactor;
}
// @dev increase a position// The increasePosition function is used to increase the size of a position// in a market. This involves updating the position's collateral amount,// calculating the price impact of the size increase, and updating the position's// size and borrowing factor. This function also applies fees to the position// and updates the market's liquidity pool based on the new position size.// @param params PositionUtils.UpdatePositionParamsfunctionincreasePosition(
PositionUtils.UpdatePositionParams memory params,
uint256 collateralIncrementAmount
) external{
// get the market prices for the given position
MarketUtils.MarketPrices memory prices = MarketUtils.getMarketPrices(
params.contracts.oracle,
params.market
);
// create a new cache for holding intermediate results
IncreasePositionCache memory cache;
cache.collateralTokenPrice = MarketUtils.getCachedTokenPrice(
params.position.collateralToken(),
params.market,
prices
);
if (params.position.sizeInUsd() ==0) {
params.position.setFundingFeeAmountPerSize(
MarketUtils.getFundingFeeAmountPerSize(
params.contracts.dataStore,
params.market.marketToken,
params.position.collateralToken(),
params.position.isLong()
)
);
params.position.setLongTokenClaimableFundingAmountPerSize(
MarketUtils.getClaimableFundingAmountPerSize(
params.contracts.dataStore,
params.market.marketToken,
params.market.longToken,
params.position.isLong()
)
);
params.position.setShortTokenClaimableFundingAmountPerSize(
MarketUtils.getClaimableFundingAmountPerSize(
params.contracts.dataStore,
params.market.marketToken,
params.market.shortToken,
params.position.isLong()
)
);
}
(cache.priceImpactUsd, cache.priceImpactAmount, cache.sizeDeltaInTokens, cache.executionPrice) = PositionUtils.getExecutionPriceForIncrease(params, prices.indexTokenPrice);
// process the collateral for the given position and order
PositionPricingUtils.PositionFees memory fees;
(cache.collateralDeltaAmount, fees) = processCollateral(
params,
cache.collateralTokenPrice,
collateralIncrementAmount.toInt256(),
cache.priceImpactUsd
);
// check if there is sufficient collateral for the positionif (
cache.collateralDeltaAmount <0&&
params.position.collateralAmount() < SafeCast.toUint256(-cache.collateralDeltaAmount)
) {
revert Errors.InsufficientCollateralAmount(params.position.collateralAmount(), cache.collateralDeltaAmount);
}
params.position.setCollateralAmount(Calc.sumReturnUint256(params.position.collateralAmount(), cache.collateralDeltaAmount));
// if there is a positive impact, the impact pool amount should be reduced// if there is a negative impact, the impact pool amount should be increased
MarketUtils.applyDeltaToPositionImpactPool(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.market.marketToken,
-cache.priceImpactAmount
);
cache.nextPositionSizeInUsd = params.position.sizeInUsd() + params.order.sizeDeltaUsd();
cache.nextPositionBorrowingFactor = MarketUtils.getCumulativeBorrowingFactor(
params.contracts.dataStore,
params.market.marketToken,
params.position.isLong()
);
PositionUtils.updateTotalBorrowing(
params,
cache.nextPositionSizeInUsd,
cache.nextPositionBorrowingFactor
);
PositionUtils.incrementClaimableFundingAmount(params, fees);
params.position.setSizeInUsd(cache.nextPositionSizeInUsd);
params.position.setSizeInTokens(params.position.sizeInTokens() + cache.sizeDeltaInTokens);
params.position.setFundingFeeAmountPerSize(fees.funding.latestFundingFeeAmountPerSize);
params.position.setLongTokenClaimableFundingAmountPerSize(fees.funding.latestLongTokenClaimableFundingAmountPerSize);
params.position.setShortTokenClaimableFundingAmountPerSize(fees.funding.latestShortTokenClaimableFundingAmountPerSize);
params.position.setBorrowingFactor(cache.nextPositionBorrowingFactor);
params.position.setIncreasedAtTime(Chain.currentTimestamp());
PositionStoreUtils.set(params.contracts.dataStore, params.positionKey, params.position);
PositionUtils.updateOpenInterest(
params,
params.order.sizeDeltaUsd().toInt256(),
cache.sizeDeltaInTokens.toInt256()
);
if (params.order.sizeDeltaUsd() >0) {
// reserves are only validated if the sizeDeltaUsd is more than zero// this helps to ensure that deposits of collateral into positions// should still succeed even if pool tokens are fully reserved
MarketUtils.validateReserve(
params.contracts.dataStore,
params.market,
prices,
params.order.isLong()
);
MarketUtils.validateOpenInterestReserve(
params.contracts.dataStore,
params.market,
prices,
params.order.isLong()
);
PositionUtils.WillPositionCollateralBeSufficientValues memory positionValues = PositionUtils.WillPositionCollateralBeSufficientValues(
params.position.sizeInUsd(), // positionSizeInUsd
params.position.collateralAmount(), // positionCollateralAmount0, // realizedPnlUsd0// openInterestDelta
);
(bool willBeSufficient, int256 remainingCollateralUsd) = PositionUtils.willPositionCollateralBeSufficient(
params.contracts.dataStore,
params.market,
prices,
params.position.collateralToken(),
params.position.isLong(),
positionValues
);
if (!willBeSufficient) {
revert Errors.InsufficientCollateralUsd(remainingCollateralUsd);
}
}
PositionUtils.handleReferral(params, fees);
// validatePosition should be called after open interest and all other market variables// have been updated
PositionUtils.validatePosition(
params.contracts.dataStore,
params.contracts.referralStorage,
params.position,
params.market,
prices,
true, // shouldValidateMinPositionSizetrue// shouldValidateMinCollateralUsd
);
PositionEventUtils.emitPositionFeesCollected(
params.contracts.eventEmitter,
params.orderKey,
params.positionKey,
params.market.marketToken,
params.position.collateralToken(),
params.order.sizeDeltaUsd(),
true,
fees
);
PositionEventUtils.PositionIncreaseParams memory eventParams;
eventParams.eventEmitter = params.contracts.eventEmitter;
eventParams.orderKey = params.orderKey;
eventParams.positionKey = params.positionKey;
eventParams.position = params.position;
eventParams.indexTokenPrice = prices.indexTokenPrice;
eventParams.executionPrice = cache.executionPrice;
eventParams.collateralTokenPrice = cache.collateralTokenPrice;
eventParams.sizeDeltaUsd = params.order.sizeDeltaUsd();
eventParams.sizeDeltaInTokens = cache.sizeDeltaInTokens;
eventParams.collateralDeltaAmount = cache.collateralDeltaAmount;
eventParams.priceImpactUsd = cache.priceImpactUsd;
eventParams.priceImpactAmount = cache.priceImpactAmount;
eventParams.orderType = params.order.orderType();
PositionEventUtils.emitPositionIncrease(eventParams);
}
// @dev handle the collateral changes of the position// @param params PositionUtils.UpdatePositionParams// @param prices the prices of the tokens in the market// @param position the position to process collateral for// @param collateralDeltaAmount the change in the position's collateralfunctionprocessCollateral(
PositionUtils.UpdatePositionParams memory params,
Price.Props memory collateralTokenPrice,
int256 collateralDeltaAmount,
int256 priceImpactUsd
) internalreturns (int256, PositionPricingUtils.PositionFees memory) {
PositionPricingUtils.GetPositionFeesParams memory getPositionFeesParams = PositionPricingUtils.GetPositionFeesParams(
params.contracts.dataStore, // dataStore
params.contracts.referralStorage, // referralStorage
params.position, // position
collateralTokenPrice, // collateralTokenPrice
priceImpactUsd >0, // forPositiveImpact
params.market.longToken, // longToken
params.market.shortToken, // shortToken
params.order.sizeDeltaUsd(), // sizeDeltaUsd
params.order.uiFeeReceiver(), // uiFeeReceiverfalse// isLiquidation
);
PositionPricingUtils.PositionFees memory fees = PositionPricingUtils.getPositionFees(getPositionFeesParams);
FeeUtils.incrementClaimableFeeAmount(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.market.marketToken,
params.position.collateralToken(),
fees.feeReceiverAmount,
Keys.POSITION_FEE_TYPE
);
FeeUtils.incrementClaimableUiFeeAmount(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.order.uiFeeReceiver(),
params.market.marketToken,
params.position.collateralToken(),
fees.ui.uiFeeAmount,
Keys.UI_POSITION_FEE_TYPE
);
collateralDeltaAmount -= fees.totalCostAmount.toInt256();
MarketUtils.applyDeltaToCollateralSum(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.order.market(),
params.position.collateralToken(),
params.order.isLong(),
collateralDeltaAmount
);
MarketUtils.applyDeltaToPoolAmount(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.market,
params.position.collateralToken(),
fees.feeAmountForPool.toInt256()
);
return (collateralDeltaAmount, fees);
}
}
Contract Source Code
File 58 of 103: Keys.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;// @title Keys// @dev Keys for values in the DataStorelibraryKeys{
// @dev key for the address of the wrapped native tokenbytes32publicconstant WNT =keccak256(abi.encode("WNT"));
// @dev key for the nonce value used in NonceUtilsbytes32publicconstant NONCE =keccak256(abi.encode("NONCE"));
// @dev for sending received feesbytes32publicconstant FEE_RECEIVER =keccak256(abi.encode("FEE_RECEIVER"));
// @dev for holding tokens that could not be sent outbytes32publicconstant HOLDING_ADDRESS =keccak256(abi.encode("HOLDING_ADDRESS"));
// @dev key for the minimum gas for execution errorbytes32publicconstant MIN_HANDLE_EXECUTION_ERROR_GAS =keccak256(abi.encode("MIN_HANDLE_EXECUTION_ERROR_GAS"));
// @dev key for the minimum gas that should be forwarded for execution error handlingbytes32publicconstant MIN_HANDLE_EXECUTION_ERROR_GAS_TO_FORWARD =keccak256(abi.encode("MIN_HANDLE_EXECUTION_ERROR_GAS_TO_FORWARD"));
// @dev key for the min additional gas for executionbytes32publicconstant MIN_ADDITIONAL_GAS_FOR_EXECUTION =keccak256(abi.encode("MIN_ADDITIONAL_GAS_FOR_EXECUTION"));
// @dev for a global reentrancy guardbytes32publicconstant REENTRANCY_GUARD_STATUS =keccak256(abi.encode("REENTRANCY_GUARD_STATUS"));
// @dev key for deposit feesbytes32publicconstant DEPOSIT_FEE_TYPE =keccak256(abi.encode("DEPOSIT_FEE_TYPE"));
// @dev key for withdrawal feesbytes32publicconstant WITHDRAWAL_FEE_TYPE =keccak256(abi.encode("WITHDRAWAL_FEE_TYPE"));
// @dev key for swap feesbytes32publicconstant SWAP_FEE_TYPE =keccak256(abi.encode("SWAP_FEE_TYPE"));
// @dev key for position feesbytes32publicconstant POSITION_FEE_TYPE =keccak256(abi.encode("POSITION_FEE_TYPE"));
// @dev key for ui deposit feesbytes32publicconstant UI_DEPOSIT_FEE_TYPE =keccak256(abi.encode("UI_DEPOSIT_FEE_TYPE"));
// @dev key for ui withdrawal feesbytes32publicconstant UI_WITHDRAWAL_FEE_TYPE =keccak256(abi.encode("UI_WITHDRAWAL_FEE_TYPE"));
// @dev key for ui swap feesbytes32publicconstant UI_SWAP_FEE_TYPE =keccak256(abi.encode("UI_SWAP_FEE_TYPE"));
// @dev key for ui position feesbytes32publicconstant UI_POSITION_FEE_TYPE =keccak256(abi.encode("UI_POSITION_FEE_TYPE"));
// @dev key for ui fee factorbytes32publicconstant UI_FEE_FACTOR =keccak256(abi.encode("UI_FEE_FACTOR"));
// @dev key for max ui fee receiver factorbytes32publicconstant MAX_UI_FEE_FACTOR =keccak256(abi.encode("MAX_UI_FEE_FACTOR"));
// @dev key for the claimable fee amountbytes32publicconstant CLAIMABLE_FEE_AMOUNT =keccak256(abi.encode("CLAIMABLE_FEE_AMOUNT"));
// @dev key for the claimable ui fee amountbytes32publicconstant CLAIMABLE_UI_FEE_AMOUNT =keccak256(abi.encode("CLAIMABLE_UI_FEE_AMOUNT"));
// @dev key for the max number of auto cancel ordersbytes32publicconstant MAX_AUTO_CANCEL_ORDERS =keccak256(abi.encode("MAX_AUTO_CANCEL_ORDERS"));
// @dev key for the max total callback gas limit for auto cancel ordersbytes32publicconstant MAX_TOTAL_CALLBACK_GAS_LIMIT_FOR_AUTO_CANCEL_ORDERS =keccak256(abi.encode("MAX_TOTAL_CALLBACK_GAS_LIMIT_FOR_AUTO_CANCEL_ORDERS"));
// @dev key for the market listbytes32publicconstant MARKET_LIST =keccak256(abi.encode("MARKET_LIST"));
// @dev key for the fee batch listbytes32publicconstant FEE_BATCH_LIST =keccak256(abi.encode("FEE_BATCH_LIST"));
// @dev key for the deposit listbytes32publicconstant DEPOSIT_LIST =keccak256(abi.encode("DEPOSIT_LIST"));
// @dev key for the account deposit listbytes32publicconstant ACCOUNT_DEPOSIT_LIST =keccak256(abi.encode("ACCOUNT_DEPOSIT_LIST"));
// @dev key for the withdrawal listbytes32publicconstant WITHDRAWAL_LIST =keccak256(abi.encode("WITHDRAWAL_LIST"));
// @dev key for the account withdrawal listbytes32publicconstant ACCOUNT_WITHDRAWAL_LIST =keccak256(abi.encode("ACCOUNT_WITHDRAWAL_LIST"));
// @dev key for the shift listbytes32publicconstant SHIFT_LIST =keccak256(abi.encode("SHIFT_LIST"));
// @dev key for the account shift listbytes32publicconstant ACCOUNT_SHIFT_LIST =keccak256(abi.encode("ACCOUNT_SHIFT_LIST"));
bytes32publicconstant GLV_LIST =keccak256(abi.encode("GLV_LIST"));
bytes32publicconstant GLV_DEPOSIT_LIST =keccak256(abi.encode("GLV_DEPOSIT_LIST"));
bytes32publicconstant GLV_SHIFT_LIST =keccak256(abi.encode("GLV_SHIFT_LIST"));
bytes32publicconstant ACCOUNT_GLV_DEPOSIT_LIST =keccak256(abi.encode("ACCOUNT_GLV_DEPOSIT_LIST"));
bytes32publicconstant GLV_WITHDRAWAL_LIST =keccak256(abi.encode("GLV_WITHDRAWAL_LIST"));
bytes32publicconstant ACCOUNT_GLV_WITHDRAWAL_LIST =keccak256(abi.encode("ACCOUNT_GLV_WITHDRAWAL_LIST"));
bytes32publicconstant GLV_SUPPORTED_MARKET_LIST =keccak256(abi.encode("GLV_SUPPORTED_MARKET_LIST"));
// @dev key for the position listbytes32publicconstant POSITION_LIST =keccak256(abi.encode("POSITION_LIST"));
// @dev key for the account position listbytes32publicconstant ACCOUNT_POSITION_LIST =keccak256(abi.encode("ACCOUNT_POSITION_LIST"));
// @dev key for the order listbytes32publicconstant ORDER_LIST =keccak256(abi.encode("ORDER_LIST"));
// @dev key for the account order listbytes32publicconstant ACCOUNT_ORDER_LIST =keccak256(abi.encode("ACCOUNT_ORDER_LIST"));
// @dev key for the subaccount listbytes32publicconstant SUBACCOUNT_LIST =keccak256(abi.encode("SUBACCOUNT_LIST"));
// @dev key for the auto cancel order listbytes32publicconstant AUTO_CANCEL_ORDER_LIST =keccak256(abi.encode("AUTO_CANCEL_ORDER_LIST"));
// @dev key for is market disabledbytes32publicconstant IS_MARKET_DISABLED =keccak256(abi.encode("IS_MARKET_DISABLED"));
// @dev key for the max swap path length allowedbytes32publicconstant MAX_SWAP_PATH_LENGTH =keccak256(abi.encode("MAX_SWAP_PATH_LENGTH"));
// @dev key used to store markets observed in a swap path, to ensure that a swap path contains unique marketsbytes32publicconstant SWAP_PATH_MARKET_FLAG =keccak256(abi.encode("SWAP_PATH_MARKET_FLAG"));
// @dev key used to store the min market tokens for the first deposit for a marketbytes32publicconstant MIN_MARKET_TOKENS_FOR_FIRST_DEPOSIT =keccak256(abi.encode("MIN_MARKET_TOKENS_FOR_FIRST_DEPOSIT"));
bytes32publicconstant CREATE_GLV_DEPOSIT_FEATURE_DISABLED =keccak256(abi.encode("CREATE_GLV_DEPOSIT_FEATURE_DISABLED"));
bytes32publicconstant CANCEL_GLV_DEPOSIT_FEATURE_DISABLED =keccak256(abi.encode("CANCEL_GLV_DEPOSIT_FEATURE_DISABLED"));
bytes32publicconstant EXECUTE_GLV_DEPOSIT_FEATURE_DISABLED =keccak256(abi.encode("EXECUTE_GLV_DEPOSIT_FEATURE_DISABLED"));
bytes32publicconstant CREATE_GLV_WITHDRAWAL_FEATURE_DISABLED =keccak256(abi.encode("CREATE_GLV_WITHDRAWAL_FEATURE_DISABLED"));
bytes32publicconstant CANCEL_GLV_WITHDRAWAL_FEATURE_DISABLED =keccak256(abi.encode("CANCEL_GLV_WITHDRAWAL_FEATURE_DISABLED"));
bytes32publicconstant EXECUTE_GLV_WITHDRAWAL_FEATURE_DISABLED =keccak256(abi.encode("EXECUTE_GLV_WITHDRAWAL_FEATURE_DISABLED"));
bytes32publicconstant CREATE_GLV_SHIFT_FEATURE_DISABLED =keccak256(abi.encode("CREATE_GLV_SHIFT_FEATURE_DISABLED"));
bytes32publicconstant EXECUTE_GLV_SHIFT_FEATURE_DISABLED =keccak256(abi.encode("EXECUTE_GLV_SHIFT_FEATURE_DISABLED"));
// @dev key for whether the create deposit feature is disabledbytes32publicconstant CREATE_DEPOSIT_FEATURE_DISABLED =keccak256(abi.encode("CREATE_DEPOSIT_FEATURE_DISABLED"));
// @dev key for whether the cancel deposit feature is disabledbytes32publicconstant CANCEL_DEPOSIT_FEATURE_DISABLED =keccak256(abi.encode("CANCEL_DEPOSIT_FEATURE_DISABLED"));
// @dev key for whether the execute deposit feature is disabledbytes32publicconstant EXECUTE_DEPOSIT_FEATURE_DISABLED =keccak256(abi.encode("EXECUTE_DEPOSIT_FEATURE_DISABLED"));
// @dev key for whether the create withdrawal feature is disabledbytes32publicconstant CREATE_WITHDRAWAL_FEATURE_DISABLED =keccak256(abi.encode("CREATE_WITHDRAWAL_FEATURE_DISABLED"));
// @dev key for whether the cancel withdrawal feature is disabledbytes32publicconstant CANCEL_WITHDRAWAL_FEATURE_DISABLED =keccak256(abi.encode("CANCEL_WITHDRAWAL_FEATURE_DISABLED"));
// @dev key for whether the execute withdrawal feature is disabledbytes32publicconstant EXECUTE_WITHDRAWAL_FEATURE_DISABLED =keccak256(abi.encode("EXECUTE_WITHDRAWAL_FEATURE_DISABLED"));
// @dev key for whether the execute atomic withdrawal feature is disabledbytes32publicconstant EXECUTE_ATOMIC_WITHDRAWAL_FEATURE_DISABLED =keccak256(abi.encode("EXECUTE_ATOMIC_WITHDRAWAL_FEATURE_DISABLED"));
// @dev key for whether the create shift feature is disabledbytes32publicconstant CREATE_SHIFT_FEATURE_DISABLED =keccak256(abi.encode("CREATE_SHIFT_FEATURE_DISABLED"));
// @dev key for whether the cancel shift feature is disabledbytes32publicconstant CANCEL_SHIFT_FEATURE_DISABLED =keccak256(abi.encode("CANCEL_SHIFT_FEATURE_DISABLED"));
// @dev key for whether the execute shift feature is disabledbytes32publicconstant EXECUTE_SHIFT_FEATURE_DISABLED =keccak256(abi.encode("EXECUTE_SHIFT_FEATURE_DISABLED"));
// @dev key for whether the create order feature is disabledbytes32publicconstant CREATE_ORDER_FEATURE_DISABLED =keccak256(abi.encode("CREATE_ORDER_FEATURE_DISABLED"));
// @dev key for whether the execute order feature is disabledbytes32publicconstant EXECUTE_ORDER_FEATURE_DISABLED =keccak256(abi.encode("EXECUTE_ORDER_FEATURE_DISABLED"));
// @dev key for whether the execute adl feature is disabled// for liquidations, it can be disabled by using the EXECUTE_ORDER_FEATURE_DISABLED key with the Liquidation// order type, ADL orders have a MarketDecrease order type, so a separate key is needed to disable itbytes32publicconstant EXECUTE_ADL_FEATURE_DISABLED =keccak256(abi.encode("EXECUTE_ADL_FEATURE_DISABLED"));
// @dev key for whether the update order feature is disabledbytes32publicconstant UPDATE_ORDER_FEATURE_DISABLED =keccak256(abi.encode("UPDATE_ORDER_FEATURE_DISABLED"));
// @dev key for whether the cancel order feature is disabledbytes32publicconstant CANCEL_ORDER_FEATURE_DISABLED =keccak256(abi.encode("CANCEL_ORDER_FEATURE_DISABLED"));
// @dev key for whether the claim funding fees feature is disabledbytes32publicconstant CLAIM_FUNDING_FEES_FEATURE_DISABLED =keccak256(abi.encode("CLAIM_FUNDING_FEES_FEATURE_DISABLED"));
// @dev key for whether the claim collateral feature is disabledbytes32publicconstant CLAIM_COLLATERAL_FEATURE_DISABLED =keccak256(abi.encode("CLAIM_COLLATERAL_FEATURE_DISABLED"));
// @dev key for whether the claim affiliate rewards feature is disabledbytes32publicconstant CLAIM_AFFILIATE_REWARDS_FEATURE_DISABLED =keccak256(abi.encode("CLAIM_AFFILIATE_REWARDS_FEATURE_DISABLED"));
// @dev key for whether the claim ui fees feature is disabledbytes32publicconstant CLAIM_UI_FEES_FEATURE_DISABLED =keccak256(abi.encode("CLAIM_UI_FEES_FEATURE_DISABLED"));
// @dev key for whether the subaccount feature is disabledbytes32publicconstant SUBACCOUNT_FEATURE_DISABLED =keccak256(abi.encode("SUBACCOUNT_FEATURE_DISABLED"));
// @dev key for the minimum required oracle signers for an oracle observationbytes32publicconstant MIN_ORACLE_SIGNERS =keccak256(abi.encode("MIN_ORACLE_SIGNERS"));
// @dev key for the minimum block confirmations before blockhash can be excluded for oracle signature validationbytes32publicconstant MIN_ORACLE_BLOCK_CONFIRMATIONS =keccak256(abi.encode("MIN_ORACLE_BLOCK_CONFIRMATIONS"));
// @dev key for the maximum usable oracle price age in secondsbytes32publicconstant MAX_ORACLE_PRICE_AGE =keccak256(abi.encode("MAX_ORACLE_PRICE_AGE"));
// @dev key for the maximum oracle timestamp rangebytes32publicconstant MAX_ORACLE_TIMESTAMP_RANGE =keccak256(abi.encode("MAX_ORACLE_TIMESTAMP_RANGE"));
// @dev key for the maximum oracle price deviation factor from the ref pricebytes32publicconstant MAX_ORACLE_REF_PRICE_DEVIATION_FACTOR =keccak256(abi.encode("MAX_ORACLE_REF_PRICE_DEVIATION_FACTOR"));
// @dev key for whether an oracle provider is enabledbytes32publicconstant IS_ORACLE_PROVIDER_ENABLED =keccak256(abi.encode("IS_ORACLE_PROVIDER_ENABLED"));
// @dev key for whether an oracle provider can be used for atomic actionsbytes32publicconstant IS_ATOMIC_ORACLE_PROVIDER =keccak256(abi.encode("IS_ATOMIC_ORACLE_PROVIDER"));
// @dev key for oracle timestamp adjustmentbytes32publicconstant ORACLE_TIMESTAMP_ADJUSTMENT =keccak256(abi.encode("ORACLE_TIMESTAMP_ADJUSTMENT"));
// @dev key for oracle provider for tokenbytes32publicconstant ORACLE_PROVIDER_FOR_TOKEN =keccak256(abi.encode("ORACLE_PROVIDER_FOR_TOKEN"));
// @dev key for the chainlink payment tokenbytes32publicconstant CHAINLINK_PAYMENT_TOKEN =keccak256(abi.encode("CHAINLINK_PAYMENT_TOKEN"));
// @dev key for the sequencer grace durationbytes32publicconstant SEQUENCER_GRACE_DURATION =keccak256(abi.encode("SEQUENCER_GRACE_DURATION"));
// @dev key for the percentage amount of position fees to be receivedbytes32publicconstant POSITION_FEE_RECEIVER_FACTOR =keccak256(abi.encode("POSITION_FEE_RECEIVER_FACTOR"));
// @dev key for the percentage amount of liquidation fees to be receivedbytes32publicconstant LIQUIDATION_FEE_RECEIVER_FACTOR =keccak256(abi.encode("LIQUIDATION_FEE_RECEIVER_FACTOR"));
// @dev key for the percentage amount of swap fees to be receivedbytes32publicconstant SWAP_FEE_RECEIVER_FACTOR =keccak256(abi.encode("SWAP_FEE_RECEIVER_FACTOR"));
// @dev key for the percentage amount of borrowing fees to be receivedbytes32publicconstant BORROWING_FEE_RECEIVER_FACTOR =keccak256(abi.encode("BORROWING_FEE_RECEIVER_FACTOR"));
// @dev key for the base gas limit used when estimating execution feebytes32publicconstant ESTIMATED_GAS_FEE_BASE_AMOUNT_V2_1 =keccak256(abi.encode("ESTIMATED_GAS_FEE_BASE_AMOUNT_V2_1"));
// @dev key for the gas limit used for each oracle price when estimating execution feebytes32publicconstant ESTIMATED_GAS_FEE_PER_ORACLE_PRICE =keccak256(abi.encode("ESTIMATED_GAS_FEE_PER_ORACLE_PRICE"));
// @dev key for the multiplier used when estimating execution feebytes32publicconstant ESTIMATED_GAS_FEE_MULTIPLIER_FACTOR =keccak256(abi.encode("ESTIMATED_GAS_FEE_MULTIPLIER_FACTOR"));
// @dev key for the base gas limit used when calculating execution feebytes32publicconstant EXECUTION_GAS_FEE_BASE_AMOUNT_V2_1 =keccak256(abi.encode("EXECUTION_GAS_FEE_BASE_AMOUNT_V2_1"));
// @dev key for the gas limit used for each oracle pricebytes32publicconstant EXECUTION_GAS_FEE_PER_ORACLE_PRICE =keccak256(abi.encode("EXECUTION_GAS_FEE_PER_ORACLE_PRICE"));
// @dev key for the multiplier used when calculating execution feebytes32publicconstant EXECUTION_GAS_FEE_MULTIPLIER_FACTOR =keccak256(abi.encode("EXECUTION_GAS_FEE_MULTIPLIER_FACTOR"));
// @dev key for the estimated gas limit for depositsbytes32publicconstant DEPOSIT_GAS_LIMIT =keccak256(abi.encode("DEPOSIT_GAS_LIMIT"));
// @dev key for the estimated gas limit for withdrawalsbytes32publicconstant WITHDRAWAL_GAS_LIMIT =keccak256(abi.encode("WITHDRAWAL_GAS_LIMIT"));
bytes32publicconstant GLV_DEPOSIT_GAS_LIMIT =keccak256(abi.encode("GLV_DEPOSIT_GAS_LIMIT"));
bytes32publicconstant GLV_WITHDRAWAL_GAS_LIMIT =keccak256(abi.encode("GLV_WITHDRAWAL_GAS_LIMIT"));
bytes32publicconstant GLV_SHIFT_GAS_LIMIT =keccak256(abi.encode("GLV_SHIFT_GAS_LIMIT"));
bytes32publicconstant GLV_PER_MARKET_GAS_LIMIT =keccak256(abi.encode("GLV_PER_MARKET_GAS_LIMIT"));
// @dev key for the estimated gas limit for shiftsbytes32publicconstant SHIFT_GAS_LIMIT =keccak256(abi.encode("SHIFT_GAS_LIMIT"));
// @dev key for the estimated gas limit for single swapsbytes32publicconstant SINGLE_SWAP_GAS_LIMIT =keccak256(abi.encode("SINGLE_SWAP_GAS_LIMIT"));
// @dev key for the estimated gas limit for increase ordersbytes32publicconstant INCREASE_ORDER_GAS_LIMIT =keccak256(abi.encode("INCREASE_ORDER_GAS_LIMIT"));
// @dev key for the estimated gas limit for decrease ordersbytes32publicconstant DECREASE_ORDER_GAS_LIMIT =keccak256(abi.encode("DECREASE_ORDER_GAS_LIMIT"));
// @dev key for the estimated gas limit for swap ordersbytes32publicconstant SWAP_ORDER_GAS_LIMIT =keccak256(abi.encode("SWAP_ORDER_GAS_LIMIT"));
// @dev key for the amount of gas to forward for token transfersbytes32publicconstant TOKEN_TRANSFER_GAS_LIMIT =keccak256(abi.encode("TOKEN_TRANSFER_GAS_LIMIT"));
// @dev key for the amount of gas to forward for native token transfersbytes32publicconstant NATIVE_TOKEN_TRANSFER_GAS_LIMIT =keccak256(abi.encode("NATIVE_TOKEN_TRANSFER_GAS_LIMIT"));
// @dev key for the request expiration time, after which the request will be considered expiredbytes32publicconstant REQUEST_EXPIRATION_TIME =keccak256(abi.encode("REQUEST_EXPIRATION_TIME"));
bytes32publicconstant MAX_CALLBACK_GAS_LIMIT =keccak256(abi.encode("MAX_CALLBACK_GAS_LIMIT"));
bytes32publicconstant REFUND_EXECUTION_FEE_GAS_LIMIT =keccak256(abi.encode("REFUND_EXECUTION_FEE_GAS_LIMIT"));
bytes32publicconstant SAVED_CALLBACK_CONTRACT =keccak256(abi.encode("SAVED_CALLBACK_CONTRACT"));
// @dev key for the min collateral factorbytes32publicconstant MIN_COLLATERAL_FACTOR =keccak256(abi.encode("MIN_COLLATERAL_FACTOR"));
// @dev key for the min collateral factor for open interest multiplierbytes32publicconstant MIN_COLLATERAL_FACTOR_FOR_OPEN_INTEREST_MULTIPLIER =keccak256(abi.encode("MIN_COLLATERAL_FACTOR_FOR_OPEN_INTEREST_MULTIPLIER"));
// @dev key for the min allowed collateral in USDbytes32publicconstant MIN_COLLATERAL_USD =keccak256(abi.encode("MIN_COLLATERAL_USD"));
// @dev key for the min allowed position size in USDbytes32publicconstant MIN_POSITION_SIZE_USD =keccak256(abi.encode("MIN_POSITION_SIZE_USD"));
// @dev key for the virtual id of tokensbytes32publicconstant VIRTUAL_TOKEN_ID =keccak256(abi.encode("VIRTUAL_TOKEN_ID"));
// @dev key for the virtual id of marketsbytes32publicconstant VIRTUAL_MARKET_ID =keccak256(abi.encode("VIRTUAL_MARKET_ID"));
// @dev key for the virtual inventory for swapsbytes32publicconstant VIRTUAL_INVENTORY_FOR_SWAPS =keccak256(abi.encode("VIRTUAL_INVENTORY_FOR_SWAPS"));
// @dev key for the virtual inventory for positionsbytes32publicconstant VIRTUAL_INVENTORY_FOR_POSITIONS =keccak256(abi.encode("VIRTUAL_INVENTORY_FOR_POSITIONS"));
// @dev key for the position impact factorbytes32publicconstant POSITION_IMPACT_FACTOR =keccak256(abi.encode("POSITION_IMPACT_FACTOR"));
// @dev key for the position impact exponent factorbytes32publicconstant POSITION_IMPACT_EXPONENT_FACTOR =keccak256(abi.encode("POSITION_IMPACT_EXPONENT_FACTOR"));
// @dev key for the max decrease position impact factorbytes32publicconstant MAX_POSITION_IMPACT_FACTOR =keccak256(abi.encode("MAX_POSITION_IMPACT_FACTOR"));
// @dev key for the max position impact factor for liquidationsbytes32publicconstant MAX_POSITION_IMPACT_FACTOR_FOR_LIQUIDATIONS =keccak256(abi.encode("MAX_POSITION_IMPACT_FACTOR_FOR_LIQUIDATIONS"));
// @dev key for the position fee factorbytes32publicconstant POSITION_FEE_FACTOR =keccak256(abi.encode("POSITION_FEE_FACTOR"));
bytes32publicconstant PRO_TRADER_TIER =keccak256(abi.encode("PRO_TRADER_TIER"));
bytes32publicconstant PRO_DISCOUNT_FACTOR =keccak256(abi.encode("PRO_DISCOUNT_FACTOR"));
// @dev key for the liquidation fee factorbytes32publicconstant LIQUIDATION_FEE_FACTOR =keccak256(abi.encode("LIQUIDATION_FEE_FACTOR"));
// @dev key for the swap impact factorbytes32publicconstant SWAP_IMPACT_FACTOR =keccak256(abi.encode("SWAP_IMPACT_FACTOR"));
// @dev key for the swap impact exponent factorbytes32publicconstant SWAP_IMPACT_EXPONENT_FACTOR =keccak256(abi.encode("SWAP_IMPACT_EXPONENT_FACTOR"));
// @dev key for the swap fee factorbytes32publicconstant SWAP_FEE_FACTOR =keccak256(abi.encode("SWAP_FEE_FACTOR"));
// @dev key for the atomic swap fee factorbytes32publicconstant ATOMIC_SWAP_FEE_FACTOR =keccak256(abi.encode("ATOMIC_SWAP_FEE_FACTOR"));
bytes32publicconstant DEPOSIT_FEE_FACTOR =keccak256(abi.encode("DEPOSIT_FEE_FACTOR"));
bytes32publicconstant WITHDRAWAL_FEE_FACTOR =keccak256(abi.encode("WITHDRAWAL_FEE_FACTOR"));
// @dev key for the oracle typebytes32publicconstant ORACLE_TYPE =keccak256(abi.encode("ORACLE_TYPE"));
// @dev key for open interestbytes32publicconstant OPEN_INTEREST =keccak256(abi.encode("OPEN_INTEREST"));
// @dev key for open interest in tokensbytes32publicconstant OPEN_INTEREST_IN_TOKENS =keccak256(abi.encode("OPEN_INTEREST_IN_TOKENS"));
// @dev key for collateral sum for a marketbytes32publicconstant COLLATERAL_SUM =keccak256(abi.encode("COLLATERAL_SUM"));
// @dev key for pool amountbytes32publicconstant POOL_AMOUNT =keccak256(abi.encode("POOL_AMOUNT"));
// @dev key for max pool amountbytes32publicconstant MAX_POOL_AMOUNT =keccak256(abi.encode("MAX_POOL_AMOUNT"));
// @dev key for max pool usd for depositbytes32publicconstant MAX_POOL_USD_FOR_DEPOSIT =keccak256(abi.encode("MAX_POOL_USD_FOR_DEPOSIT"));
// @dev key for max open interestbytes32publicconstant MAX_OPEN_INTEREST =keccak256(abi.encode("MAX_OPEN_INTEREST"));
// @dev key for position impact pool amountbytes32publicconstant POSITION_IMPACT_POOL_AMOUNT =keccak256(abi.encode("POSITION_IMPACT_POOL_AMOUNT"));
// @dev key for min position impact pool amountbytes32publicconstant MIN_POSITION_IMPACT_POOL_AMOUNT =keccak256(abi.encode("MIN_POSITION_IMPACT_POOL_AMOUNT"));
// @dev key for position impact pool distribution ratebytes32publicconstant POSITION_IMPACT_POOL_DISTRIBUTION_RATE =keccak256(abi.encode("POSITION_IMPACT_POOL_DISTRIBUTION_RATE"));
// @dev key for position impact pool distributed atbytes32publicconstant POSITION_IMPACT_POOL_DISTRIBUTED_AT =keccak256(abi.encode("POSITION_IMPACT_POOL_DISTRIBUTED_AT"));
// @dev key for swap impact pool amountbytes32publicconstant SWAP_IMPACT_POOL_AMOUNT =keccak256(abi.encode("SWAP_IMPACT_POOL_AMOUNT"));
// @dev key for price feedbytes32publicconstant PRICE_FEED =keccak256(abi.encode("PRICE_FEED"));
// @dev key for price feed multiplierbytes32publicconstant PRICE_FEED_MULTIPLIER =keccak256(abi.encode("PRICE_FEED_MULTIPLIER"));
// @dev key for price feed heartbeatbytes32publicconstant PRICE_FEED_HEARTBEAT_DURATION =keccak256(abi.encode("PRICE_FEED_HEARTBEAT_DURATION"));
// @dev key for data stream feed idbytes32publicconstant DATA_STREAM_ID =keccak256(abi.encode("DATA_STREAM_ID"));
// @dev key for data stream feed multiplerbytes32publicconstant DATA_STREAM_MULTIPLIER =keccak256(abi.encode("DATA_STREAM_MULTIPLIER"));
// @dev key for stable pricebytes32publicconstant STABLE_PRICE =keccak256(abi.encode("STABLE_PRICE"));
// @dev key for reserve factorbytes32publicconstant RESERVE_FACTOR =keccak256(abi.encode("RESERVE_FACTOR"));
// @dev key for open interest reserve factorbytes32publicconstant OPEN_INTEREST_RESERVE_FACTOR =keccak256(abi.encode("OPEN_INTEREST_RESERVE_FACTOR"));
// @dev key for max pnl factorbytes32publicconstant MAX_PNL_FACTOR =keccak256(abi.encode("MAX_PNL_FACTOR"));
// @dev key for max pnl factorbytes32publicconstant MAX_PNL_FACTOR_FOR_TRADERS =keccak256(abi.encode("MAX_PNL_FACTOR_FOR_TRADERS"));
// @dev key for max pnl factor for adlbytes32publicconstant MAX_PNL_FACTOR_FOR_ADL =keccak256(abi.encode("MAX_PNL_FACTOR_FOR_ADL"));
// @dev key for min pnl factor for adlbytes32publicconstant MIN_PNL_FACTOR_AFTER_ADL =keccak256(abi.encode("MIN_PNL_FACTOR_AFTER_ADL"));
// @dev key for max pnl factorbytes32publicconstant MAX_PNL_FACTOR_FOR_DEPOSITS =keccak256(abi.encode("MAX_PNL_FACTOR_FOR_DEPOSITS"));
// @dev key for max pnl factor for withdrawalsbytes32publicconstant MAX_PNL_FACTOR_FOR_WITHDRAWALS =keccak256(abi.encode("MAX_PNL_FACTOR_FOR_WITHDRAWALS"));
// @dev key for latest ADL atbytes32publicconstant LATEST_ADL_AT =keccak256(abi.encode("LATEST_ADL_AT"));
// @dev key for whether ADL is enabledbytes32publicconstant IS_ADL_ENABLED =keccak256(abi.encode("IS_ADL_ENABLED"));
// @dev key for funding factorbytes32publicconstant FUNDING_FACTOR =keccak256(abi.encode("FUNDING_FACTOR"));
// @dev key for funding exponent factorbytes32publicconstant FUNDING_EXPONENT_FACTOR =keccak256(abi.encode("FUNDING_EXPONENT_FACTOR"));
// @dev key for saved funding factorbytes32publicconstant SAVED_FUNDING_FACTOR_PER_SECOND =keccak256(abi.encode("SAVED_FUNDING_FACTOR_PER_SECOND"));
// @dev key for funding increase factorbytes32publicconstant FUNDING_INCREASE_FACTOR_PER_SECOND =keccak256(abi.encode("FUNDING_INCREASE_FACTOR_PER_SECOND"));
// @dev key for funding decrease factorbytes32publicconstant FUNDING_DECREASE_FACTOR_PER_SECOND =keccak256(abi.encode("FUNDING_DECREASE_FACTOR_PER_SECOND"));
// @dev key for min funding factorbytes32publicconstant MIN_FUNDING_FACTOR_PER_SECOND =keccak256(abi.encode("MIN_FUNDING_FACTOR_PER_SECOND"));
// @dev key for max funding factorbytes32publicconstant MAX_FUNDING_FACTOR_PER_SECOND =keccak256(abi.encode("MAX_FUNDING_FACTOR_PER_SECOND"));
// @dev key for threshold for stable fundingbytes32publicconstant THRESHOLD_FOR_STABLE_FUNDING =keccak256(abi.encode("THRESHOLD_FOR_STABLE_FUNDING"));
// @dev key for threshold for decrease fundingbytes32publicconstant THRESHOLD_FOR_DECREASE_FUNDING =keccak256(abi.encode("THRESHOLD_FOR_DECREASE_FUNDING"));
// @dev key for funding fee amount per sizebytes32publicconstant FUNDING_FEE_AMOUNT_PER_SIZE =keccak256(abi.encode("FUNDING_FEE_AMOUNT_PER_SIZE"));
// @dev key for claimable funding amount per sizebytes32publicconstant CLAIMABLE_FUNDING_AMOUNT_PER_SIZE =keccak256(abi.encode("CLAIMABLE_FUNDING_AMOUNT_PER_SIZE"));
// @dev key for when funding was last updated atbytes32publicconstant FUNDING_UPDATED_AT =keccak256(abi.encode("FUNDING_UPDATED_AT"));
// @dev key for claimable funding amountbytes32publicconstant CLAIMABLE_FUNDING_AMOUNT =keccak256(abi.encode("CLAIMABLE_FUNDING_AMOUNT"));
// @dev key for claimable collateral amountbytes32publicconstant CLAIMABLE_COLLATERAL_AMOUNT =keccak256(abi.encode("CLAIMABLE_COLLATERAL_AMOUNT"));
// @dev key for claimable collateral factorbytes32publicconstant CLAIMABLE_COLLATERAL_FACTOR =keccak256(abi.encode("CLAIMABLE_COLLATERAL_FACTOR"));
// @dev key for claimable collateral time divisorbytes32publicconstant CLAIMABLE_COLLATERAL_TIME_DIVISOR =keccak256(abi.encode("CLAIMABLE_COLLATERAL_TIME_DIVISOR"));
// @dev key for claimed collateral amountbytes32publicconstant CLAIMED_COLLATERAL_AMOUNT =keccak256(abi.encode("CLAIMED_COLLATERAL_AMOUNT"));
bytes32publicconstant IGNORE_OPEN_INTEREST_FOR_USAGE_FACTOR =keccak256(abi.encode("IGNORE_OPEN_INTEREST_FOR_USAGE_FACTOR"));
// @dev key for optimal usage factorbytes32publicconstant OPTIMAL_USAGE_FACTOR =keccak256(abi.encode("OPTIMAL_USAGE_FACTOR"));
// @dev key for base borrowing factorbytes32publicconstant BASE_BORROWING_FACTOR =keccak256(abi.encode("BASE_BORROWING_FACTOR"));
// @dev key for above optimal usage borrowing factorbytes32publicconstant ABOVE_OPTIMAL_USAGE_BORROWING_FACTOR =keccak256(abi.encode("ABOVE_OPTIMAL_USAGE_BORROWING_FACTOR"));
// @dev key for borrowing factorbytes32publicconstant BORROWING_FACTOR =keccak256(abi.encode("BORROWING_FACTOR"));
// @dev key for borrowing factorbytes32publicconstant BORROWING_EXPONENT_FACTOR =keccak256(abi.encode("BORROWING_EXPONENT_FACTOR"));
// @dev key for skipping the borrowing factor for the smaller sidebytes32publicconstant SKIP_BORROWING_FEE_FOR_SMALLER_SIDE =keccak256(abi.encode("SKIP_BORROWING_FEE_FOR_SMALLER_SIDE"));
// @dev key for cumulative borrowing factorbytes32publicconstant CUMULATIVE_BORROWING_FACTOR =keccak256(abi.encode("CUMULATIVE_BORROWING_FACTOR"));
// @dev key for when the cumulative borrowing factor was last updated atbytes32publicconstant CUMULATIVE_BORROWING_FACTOR_UPDATED_AT =keccak256(abi.encode("CUMULATIVE_BORROWING_FACTOR_UPDATED_AT"));
// @dev key for total borrowing amountbytes32publicconstant TOTAL_BORROWING =keccak256(abi.encode("TOTAL_BORROWING"));
// @dev key for affiliate rewardbytes32publicconstant MIN_AFFILIATE_REWARD_FACTOR =keccak256(abi.encode("MIN_AFFILIATE_REWARD_FACTOR"));
bytes32publicconstant AFFILIATE_REWARD =keccak256(abi.encode("AFFILIATE_REWARD"));
// @dev key for max allowed subaccount action countbytes32publicconstant MAX_ALLOWED_SUBACCOUNT_ACTION_COUNT =keccak256(abi.encode("MAX_ALLOWED_SUBACCOUNT_ACTION_COUNT"));
// @dev key for subaccount action countbytes32publicconstant SUBACCOUNT_ACTION_COUNT =keccak256(abi.encode("SUBACCOUNT_ACTION_COUNT"));
// @dev key for subaccount auto top up amountbytes32publicconstant SUBACCOUNT_AUTO_TOP_UP_AMOUNT =keccak256(abi.encode("SUBACCOUNT_AUTO_TOP_UP_AMOUNT"));
// @dev key for subaccount order actionbytes32publicconstant SUBACCOUNT_ORDER_ACTION =keccak256(abi.encode("SUBACCOUNT_ORDER_ACTION"));
// @dev key for fee distributor swap order token indexbytes32publicconstant FEE_DISTRIBUTOR_SWAP_TOKEN_INDEX =keccak256(abi.encode("FEE_DISTRIBUTOR_SWAP_TOKEN_INDEX"));
// @dev key for fee distributor swap fee batchbytes32publicconstant FEE_DISTRIBUTOR_SWAP_FEE_BATCH =keccak256(abi.encode("FEE_DISTRIBUTOR_SWAP_FEE_BATCH"));
bytes32publicconstant GLV_MAX_MARKET_COUNT =keccak256(abi.encode("GLV_MAX_MARKET_COUNT"));
bytes32publicconstant GLV_MAX_MARKET_TOKEN_BALANCE_USD =keccak256(abi.encode("GLV_MAX_MARKET_TOKEN_BALANCE_USD"));
bytes32publicconstant GLV_MAX_MARKET_TOKEN_BALANCE_AMOUNT =keccak256(abi.encode("GLV_MAX_MARKET_TOKEN_BALANCE_AMOUNT"));
bytes32publicconstant IS_GLV_MARKET_DISABLED =keccak256(abi.encode("IS_GLV_MARKET_DISABLED"));
bytes32publicconstant GLV_SHIFT_MAX_PRICE_IMPACT_FACTOR =keccak256(abi.encode("GLV_SHIFT_MAX_PRICE_IMPACT_FACTOR"));
bytes32publicconstant GLV_SHIFT_LAST_EXECUTED_AT =keccak256(abi.encode("GLV_SHIFT_LAST_EXECUTED_AT"));
bytes32publicconstant GLV_SHIFT_MIN_INTERVAL =keccak256(abi.encode("GLV_SHIFT_MIN_INTERVAL"));
bytes32publicconstant MIN_GLV_TOKENS_FOR_FIRST_DEPOSIT =keccak256(abi.encode("MIN_GLV_TOKENS_FOR_FIRST_DEPOSIT"));
// @dev key for disabling automatic parameter updates via ConfigSyncerbytes32publicconstant SYNC_CONFIG_FEATURE_DISABLED =keccak256(abi.encode("SYNC_CONFIG_FEATURE_DISABLED"));
// @dev key for disabling all parameter updates for a specific market via ConfigSyncerbytes32publicconstant SYNC_CONFIG_MARKET_DISABLED =keccak256(abi.encode("SYNC_CONFIG_MARKET_DISABLED"));
// @dev key for disabling all updates for a specific parameter via ConfigSyncerbytes32publicconstant SYNC_CONFIG_PARAMETER_DISABLED =keccak256(abi.encode("SYNC_CONFIG_PARAMETER_DISABLED"));
// @dev key for disabling all updates for a specific market parameter via ConfigSyncerbytes32publicconstant SYNC_CONFIG_MARKET_PARAMETER_DISABLED =keccak256(abi.encode("SYNC_CONFIG_MARKET_PARAMETER_DISABLED"));
// @dev key for tracking which updateIds have already been applied by ConfigSyncerbytes32publicconstant SYNC_CONFIG_UPDATE_COMPLETED =keccak256(abi.encode("SYNC_CONFIG_UPDATE_COMPLETED"));
// @dev key for the latest updateId that has been applied by ConfigSyncerbytes32publicconstant SYNC_CONFIG_LATEST_UPDATE_ID =keccak256(abi.encode("SYNC_CONFIG_LATEST_UPDATE_ID"));
// @dev key for the contributor account listbytes32publicconstant CONTRIBUTOR_ACCOUNT_LIST =keccak256(abi.encode("CONTRIBUTOR_ACCOUNT_LIST"));
// @dev key for the contributor token listbytes32publicconstant CONTRIBUTOR_TOKEN_LIST =keccak256(abi.encode("CONTRIBUTOR_TOKEN_LIST"));
// @dev key for the contributor token amountbytes32publicconstant CONTRIBUTOR_TOKEN_AMOUNT =keccak256(abi.encode("CONTRIBUTOR_TOKEN_AMOUNT"));
// @dev key for the max total contributor token amountbytes32publicconstant MAX_TOTAL_CONTRIBUTOR_TOKEN_AMOUNT =keccak256(abi.encode("MAX_TOTAL_CONTRIBUTOR_TOKEN_AMOUNT"));
// @dev key for the contributor token vaultbytes32publicconstant CONTRIBUTOR_TOKEN_VAULT =keccak256(abi.encode("CONTRIBUTOR_TOKEN_VAULT"));
// @dev key for the contributor last payment atbytes32publicconstant CONTRIBUTOR_LAST_PAYMENT_AT =keccak256(abi.encode("CONTRIBUTOR_LAST_PAYMENT_AT"));
// @dev key for the min contributor payment intervalbytes32publicconstant MIN_CONTRIBUTOR_PAYMENT_INTERVAL =keccak256(abi.encode("MIN_CONTRIBUTOR_PAYMENT_INTERVAL"));
// @dev key for the buyback batch amount used when claiming and swapping feesbytes32publicconstant BUYBACK_BATCH_AMOUNT =keccak256(abi.encode("BUYBACK_BATCH_AMOUNT"));
// @dev key for the buyback available feesbytes32publicconstant BUYBACK_AVAILABLE_FEE_AMOUNT =keccak256(abi.encode("BUYBACK_AVAILABLE_FEE_AMOUNT"));
// @dev key for the buyback gmx fee factor used in calculating fees by GMX/WNTbytes32publicconstant BUYBACK_GMX_FACTOR =keccak256(abi.encode("BUYBACK_GMX_FACTOR"));
// @dev key for the FeeHandler max price impact when buying back feesbytes32publicconstant BUYBACK_MAX_PRICE_IMPACT_FACTOR =keccak256(abi.encode("BUYBACK_MAX_PRICE_IMPACT_FACTOR"));
// @dev key for the maximum price delay in seconds when buying back feesbytes32publicconstant BUYBACK_MAX_PRICE_AGE =keccak256(abi.encode("BUYBACK_MAX_PRICE_AGE"));
// @dev key for the buyback withdrawable feesbytes32publicconstant WITHDRAWABLE_BUYBACK_TOKEN_AMOUNT =keccak256(abi.encode("WITHDRAWABLE_BUYBACK_TOKEN_AMOUNT"));
// @dev constant for user initiated cancel reasonstringpublicconstant USER_INITIATED_CANCEL ="USER_INITIATED_CANCEL";
// @dev function used to calculate fullKey for a given market parameter// @param baseKey the base key for the market parameter// @param data the additional data for the market parameterfunctiongetFullKey(bytes32 baseKey, bytesmemory data) internalpurereturns (bytes32) {
if (data.length==0) {
return baseKey;
}
returnkeccak256(bytes.concat(baseKey, data));
}
// @dev key for the account deposit list// @param account the account for the listfunctionaccountDepositListKey(address account) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(ACCOUNT_DEPOSIT_LIST, account));
}
// @dev key for the account withdrawal list// @param account the account for the listfunctionaccountWithdrawalListKey(address account) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(ACCOUNT_WITHDRAWAL_LIST, account));
}
// @dev key for the account shift list// @param account the account for the listfunctionaccountShiftListKey(address account) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(ACCOUNT_SHIFT_LIST, account));
}
// @dev key for the account glv deposit list// @param account the account for the listfunctionaccountGlvDepositListKey(address account) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(ACCOUNT_GLV_DEPOSIT_LIST, account));
}
// @dev key for the account glv deposit list// @param account the account for the listfunctionaccountGlvWithdrawalListKey(address account) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(ACCOUNT_GLV_WITHDRAWAL_LIST, account));
}
// @dev key for the glv supported market list// @param glv the glv for the supported market listfunctionglvSupportedMarketListKey(address glv) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(GLV_SUPPORTED_MARKET_LIST, glv));
}
// @dev key for the account position list// @param account the account for the listfunctionaccountPositionListKey(address account) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(ACCOUNT_POSITION_LIST, account));
}
// @dev key for the account order list// @param account the account for the listfunctionaccountOrderListKey(address account) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(ACCOUNT_ORDER_LIST, account));
}
// @dev key for the subaccount list// @param account the account for the listfunctionsubaccountListKey(address account) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(SUBACCOUNT_LIST, account));
}
// @dev key for the auto cancel order list// @param position key the position key for the listfunctionautoCancelOrderListKey(bytes32 positionKey) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(AUTO_CANCEL_ORDER_LIST, positionKey));
}
// @dev key for the claimable fee amount// @param market the market for the fee// @param token the token for the feefunctionclaimableFeeAmountKey(address market, address token) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(CLAIMABLE_FEE_AMOUNT, market, token));
}
// @dev key for the claimable ui fee amount// @param market the market for the fee// @param token the token for the fee// @param account the account that can claim the ui feefunctionclaimableUiFeeAmountKey(address market, address token) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(CLAIMABLE_UI_FEE_AMOUNT, market, token));
}
// @dev key for the claimable ui fee amount for account// @param market the market for the fee// @param token the token for the fee// @param account the account that can claim the ui feefunctionclaimableUiFeeAmountKey(address market, address token, address account) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(CLAIMABLE_UI_FEE_AMOUNT, market, token, account));
}
// @dev key for deposit gas limit// @param singleToken whether a single token or pair tokens are being deposited// @return key for deposit gas limitfunctiondepositGasLimitKey() internalpurereturns (bytes32) {
return DEPOSIT_GAS_LIMIT;
}
// @dev key for withdrawal gas limit// @return key for withdrawal gas limitfunctionwithdrawalGasLimitKey() internalpurereturns (bytes32) {
return WITHDRAWAL_GAS_LIMIT;
}
// @dev key for shift gas limit// @return key for shift gas limitfunctionshiftGasLimitKey() internalpurereturns (bytes32) {
return SHIFT_GAS_LIMIT;
}
functionglvDepositGasLimitKey() internalpurereturns (bytes32) {
return GLV_DEPOSIT_GAS_LIMIT;
}
functionglvWithdrawalGasLimitKey() internalpurereturns (bytes32) {
return GLV_WITHDRAWAL_GAS_LIMIT;
}
functionglvShiftGasLimitKey() internalpurereturns (bytes32) {
return GLV_SHIFT_GAS_LIMIT;
}
functionglvPerMarketGasLimitKey() internalpurereturns (bytes32) {
return GLV_PER_MARKET_GAS_LIMIT;
}
// @dev key for single swap gas limit// @return key for single swap gas limitfunctionsingleSwapGasLimitKey() internalpurereturns (bytes32) {
return SINGLE_SWAP_GAS_LIMIT;
}
// @dev key for increase order gas limit// @return key for increase order gas limitfunctionincreaseOrderGasLimitKey() internalpurereturns (bytes32) {
return INCREASE_ORDER_GAS_LIMIT;
}
// @dev key for decrease order gas limit// @return key for decrease order gas limitfunctiondecreaseOrderGasLimitKey() internalpurereturns (bytes32) {
return DECREASE_ORDER_GAS_LIMIT;
}
// @dev key for swap order gas limit// @return key for swap order gas limitfunctionswapOrderGasLimitKey() internalpurereturns (bytes32) {
return SWAP_ORDER_GAS_LIMIT;
}
functionswapPathMarketFlagKey(address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
SWAP_PATH_MARKET_FLAG,
market
));
}
// @dev key for whether create glv deposit is disabled// @param the create deposit module// @return key for whether create deposit is disabledfunctioncreateGlvDepositFeatureDisabledKey(address module) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CREATE_GLV_DEPOSIT_FEATURE_DISABLED,
module
));
}
// @dev key for whether cancel glv deposit is disabled// @param the cancel deposit module// @return key for whether cancel deposit is disabledfunctioncancelGlvDepositFeatureDisabledKey(address module) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CANCEL_GLV_DEPOSIT_FEATURE_DISABLED,
module
));
}
// @dev key for whether execute glv deposit is disabled// @param the execute deposit module// @return key for whether execute deposit is disabledfunctionexecuteGlvDepositFeatureDisabledKey(address module) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
EXECUTE_GLV_DEPOSIT_FEATURE_DISABLED,
module
));
}
// @dev key for whether create glv withdrawal is disabled// @param the create withdrawal module// @return key for whether create withdrawal is disabledfunctioncreateGlvWithdrawalFeatureDisabledKey(address module) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CREATE_GLV_WITHDRAWAL_FEATURE_DISABLED,
module
));
}
// @dev key for whether cancel glv withdrawal is disabled// @param the cancel withdrawal module// @return key for whether cancel withdrawal is disabledfunctioncancelGlvWithdrawalFeatureDisabledKey(address module) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CANCEL_GLV_WITHDRAWAL_FEATURE_DISABLED,
module
));
}
// @dev key for whether execute glv withdrawal is disabled// @param the execute withdrawal module// @return key for whether execute withdrawal is disabledfunctionexecuteGlvWithdrawalFeatureDisabledKey(address module) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
EXECUTE_GLV_WITHDRAWAL_FEATURE_DISABLED,
module
));
}
functioncreateGlvShiftFeatureDisabledKey(address module) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CREATE_GLV_SHIFT_FEATURE_DISABLED,
module
));
}
functionexecuteGlvShiftFeatureDisabledKey(address module) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
EXECUTE_GLV_SHIFT_FEATURE_DISABLED,
module
));
}
// @dev key for whether create deposit is disabled// @param the create deposit module// @return key for whether create deposit is disabledfunctioncreateDepositFeatureDisabledKey(address module) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CREATE_DEPOSIT_FEATURE_DISABLED,
module
));
}
// @dev key for whether cancel deposit is disabled// @param the cancel deposit module// @return key for whether cancel deposit is disabledfunctioncancelDepositFeatureDisabledKey(address module) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CANCEL_DEPOSIT_FEATURE_DISABLED,
module
));
}
// @dev key for whether execute deposit is disabled// @param the execute deposit module// @return key for whether execute deposit is disabledfunctionexecuteDepositFeatureDisabledKey(address module) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
EXECUTE_DEPOSIT_FEATURE_DISABLED,
module
));
}
// @dev key for whether create withdrawal is disabled// @param the create withdrawal module// @return key for whether create withdrawal is disabledfunctioncreateWithdrawalFeatureDisabledKey(address module) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CREATE_WITHDRAWAL_FEATURE_DISABLED,
module
));
}
// @dev key for whether cancel withdrawal is disabled// @param the cancel withdrawal module// @return key for whether cancel withdrawal is disabledfunctioncancelWithdrawalFeatureDisabledKey(address module) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CANCEL_WITHDRAWAL_FEATURE_DISABLED,
module
));
}
// @dev key for whether execute withdrawal is disabled// @param the execute withdrawal module// @return key for whether execute withdrawal is disabledfunctionexecuteWithdrawalFeatureDisabledKey(address module) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
EXECUTE_WITHDRAWAL_FEATURE_DISABLED,
module
));
}
// @dev key for whether execute atomic withdrawal is disabled// @param the execute atomic withdrawal module// @return key for whether execute atomic withdrawal is disabledfunctionexecuteAtomicWithdrawalFeatureDisabledKey(address module) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
EXECUTE_ATOMIC_WITHDRAWAL_FEATURE_DISABLED,
module
));
}
// @dev key for whether create shift is disabled// @param the create shift module// @return key for whether create shift is disabledfunctioncreateShiftFeatureDisabledKey(address module) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CREATE_SHIFT_FEATURE_DISABLED,
module
));
}
// @dev key for whether cancel shift is disabled// @param the cancel shift module// @return key for whether cancel shift is disabledfunctioncancelShiftFeatureDisabledKey(address module) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CANCEL_SHIFT_FEATURE_DISABLED,
module
));
}
// @dev key for whether execute shift is disabled// @param the execute shift module// @return key for whether execute shift is disabledfunctionexecuteShiftFeatureDisabledKey(address module) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
EXECUTE_SHIFT_FEATURE_DISABLED,
module
));
}
// @dev key for whether create order is disabled// @param the create order module// @return key for whether create order is disabledfunctioncreateOrderFeatureDisabledKey(address module, uint256 orderType) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CREATE_ORDER_FEATURE_DISABLED,
module,
orderType
));
}
// @dev key for whether execute order is disabled// @param the execute order module// @return key for whether execute order is disabledfunctionexecuteOrderFeatureDisabledKey(address module, uint256 orderType) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
EXECUTE_ORDER_FEATURE_DISABLED,
module,
orderType
));
}
// @dev key for whether execute adl is disabled// @param the execute adl module// @return key for whether execute adl is disabledfunctionexecuteAdlFeatureDisabledKey(address module, uint256 orderType) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
EXECUTE_ADL_FEATURE_DISABLED,
module,
orderType
));
}
// @dev key for whether update order is disabled// @param the update order module// @return key for whether update order is disabledfunctionupdateOrderFeatureDisabledKey(address module, uint256 orderType) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
UPDATE_ORDER_FEATURE_DISABLED,
module,
orderType
));
}
// @dev key for whether cancel order is disabled// @param the cancel order module// @return key for whether cancel order is disabledfunctioncancelOrderFeatureDisabledKey(address module, uint256 orderType) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CANCEL_ORDER_FEATURE_DISABLED,
module,
orderType
));
}
// @dev key for whether claim funding fees is disabled// @param the claim funding fees modulefunctionclaimFundingFeesFeatureDisabledKey(address module) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CLAIM_FUNDING_FEES_FEATURE_DISABLED,
module
));
}
// @dev key for whether claim colltareral is disabled// @param the claim funding fees modulefunctionclaimCollateralFeatureDisabledKey(address module) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CLAIM_COLLATERAL_FEATURE_DISABLED,
module
));
}
// @dev key for whether claim affiliate rewards is disabled// @param the claim affiliate rewards modulefunctionclaimAffiliateRewardsFeatureDisabledKey(address module) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CLAIM_AFFILIATE_REWARDS_FEATURE_DISABLED,
module
));
}
// @dev key for whether claim ui fees is disabled// @param the claim ui fees modulefunctionclaimUiFeesFeatureDisabledKey(address module) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CLAIM_UI_FEES_FEATURE_DISABLED,
module
));
}
// @dev key for whether subaccounts are disabled// @param the subaccount modulefunctionsubaccountFeatureDisabledKey(address module) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
SUBACCOUNT_FEATURE_DISABLED,
module
));
}
// @dev key for ui fee factor// @param account the fee receiver account// @return key for ui fee factorfunctionuiFeeFactorKey(address account) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
UI_FEE_FACTOR,
account
));
}
// @dev key for whether an oracle provider is enabled// @param provider the oracle provider// @return key for whether an oracle provider is enabledfunctionisOracleProviderEnabledKey(address provider) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
IS_ORACLE_PROVIDER_ENABLED,
provider
));
}
// @dev key for whether an oracle provider is allowed to be used for atomic actions// @param provider the oracle provider// @return key for whether an oracle provider is allowed to be used for atomic actionsfunctionisAtomicOracleProviderKey(address provider) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
IS_ATOMIC_ORACLE_PROVIDER,
provider
));
}
// @dev key for oracle timestamp adjustment// @param provider the oracle provider// @param token the token// @return key for oracle timestamp adjustmentfunctionoracleTimestampAdjustmentKey(address provider, address token) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
ORACLE_TIMESTAMP_ADJUSTMENT,
provider,
token
));
}
// @dev key for oracle provider for token// @param token the token// @return key for oracle provider for tokenfunctionoracleProviderForTokenKey(address token) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
ORACLE_PROVIDER_FOR_TOKEN,
token
));
}
// @dev key for gas to forward for token transfer// @param the token to check// @return key for gas to forward for token transferfunctiontokenTransferGasLimit(address token) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
TOKEN_TRANSFER_GAS_LIMIT,
token
));
}
// @dev the default callback contract// @param account the user's account// @param market the address of the market// @param callbackContract the callback contractfunctionsavedCallbackContract(address account, address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
SAVED_CALLBACK_CONTRACT,
account,
market
));
}
// @dev the min collateral factor key// @param the market for the min collateral factorfunctionminCollateralFactorKey(address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
MIN_COLLATERAL_FACTOR,
market
));
}
// @dev the min collateral factor for open interest multiplier key// @param the market for the factorfunctionminCollateralFactorForOpenInterestMultiplierKey(address market, bool isLong) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
MIN_COLLATERAL_FACTOR_FOR_OPEN_INTEREST_MULTIPLIER,
market,
isLong
));
}
// @dev the key for the virtual token id// @param the token to get the virtual id forfunctionvirtualTokenIdKey(address token) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
VIRTUAL_TOKEN_ID,
token
));
}
// @dev the key for the virtual market id// @param the market to get the virtual id forfunctionvirtualMarketIdKey(address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
VIRTUAL_MARKET_ID,
market
));
}
// @dev the key for the virtual inventory for positions// @param the virtualTokenId the virtual token idfunctionvirtualInventoryForPositionsKey(bytes32 virtualTokenId) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
VIRTUAL_INVENTORY_FOR_POSITIONS,
virtualTokenId
));
}
// @dev the key for the virtual inventory for swaps// @param the virtualMarketId the virtual market id// @param the token to check the inventory forfunctionvirtualInventoryForSwapsKey(bytes32 virtualMarketId, bool isLongToken) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
VIRTUAL_INVENTORY_FOR_SWAPS,
virtualMarketId,
isLongToken
));
}
// @dev key for position impact factor// @param market the market address to check// @param isPositive whether the impact is positive or negative// @return key for position impact factorfunctionpositionImpactFactorKey(address market, bool isPositive) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
POSITION_IMPACT_FACTOR,
market,
isPositive
));
}
// @dev key for position impact exponent factor// @param market the market address to check// @return key for position impact exponent factorfunctionpositionImpactExponentFactorKey(address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
POSITION_IMPACT_EXPONENT_FACTOR,
market
));
}
// @dev key for the max position impact factor// @param market the market address to check// @return key for the max position impact factorfunctionmaxPositionImpactFactorKey(address market, bool isPositive) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
MAX_POSITION_IMPACT_FACTOR,
market,
isPositive
));
}
// @dev key for the max position impact factor for liquidations// @param market the market address to check// @return key for the max position impact factorfunctionmaxPositionImpactFactorForLiquidationsKey(address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
MAX_POSITION_IMPACT_FACTOR_FOR_LIQUIDATIONS,
market
));
}
// @dev key for position fee factor// @param market the market address to check// @param forPositiveImpact whether the fee is for an action that has a positive price impact// @return key for position fee factorfunctionpositionFeeFactorKey(address market, bool forPositiveImpact) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
POSITION_FEE_FACTOR,
market,
forPositiveImpact
));
}
// @dev key for pro trader's tierfunctionproTraderTierKey(address account) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
PRO_TRADER_TIER,
account
));
}
// @dev key for pro discount factor for specific tierfunctionproDiscountFactorKey(uint256 proTier) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
PRO_DISCOUNT_FACTOR,
proTier
));
}
// @dev key for liquidation fee factor// @param market the market address to check// @param forPositiveImpact whether the fee is for an action that has a positive price impact// @return key for liquidation fee factorfunctionliquidationFeeFactorKey(address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
LIQUIDATION_FEE_FACTOR,
market
));
}
// @dev key for swap impact factor// @param market the market address to check// @param isPositive whether the impact is positive or negative// @return key for swap impact factorfunctionswapImpactFactorKey(address market, bool isPositive) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
SWAP_IMPACT_FACTOR,
market,
isPositive
));
}
// @dev key for swap impact exponent factor// @param market the market address to check// @return key for swap impact exponent factorfunctionswapImpactExponentFactorKey(address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
SWAP_IMPACT_EXPONENT_FACTOR,
market
));
}
// @dev key for swap fee factor// @param market the market address to check// @return key for swap fee factorfunctionswapFeeFactorKey(address market, bool forPositiveImpact) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
SWAP_FEE_FACTOR,
market,
forPositiveImpact
));
}
// @dev key for atomic swap fee factor// @param market the market address to check// @return key for atomic swap fee factorfunctionatomicSwapFeeFactorKey(address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
ATOMIC_SWAP_FEE_FACTOR,
market
));
}
functiondepositFeeFactorKey(address market, bool forPositiveImpact) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
DEPOSIT_FEE_FACTOR,
market,
forPositiveImpact
));
}
functionwithdrawalFeeFactorKey(address market, bool forPositiveImpact) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
WITHDRAWAL_FEE_FACTOR,
market,
forPositiveImpact
));
}
// @dev key for oracle type// @param token the token to check// @return key for oracle typefunctionoracleTypeKey(address token) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
ORACLE_TYPE,
token
));
}
// @dev key for open interest// @param market the market to check// @param collateralToken the collateralToken to check// @param isLong whether to check the long or short open interest// @return key for open interestfunctionopenInterestKey(address market, address collateralToken, bool isLong) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
OPEN_INTEREST,
market,
collateralToken,
isLong
));
}
// @dev key for open interest in tokens// @param market the market to check// @param collateralToken the collateralToken to check// @param isLong whether to check the long or short open interest// @return key for open interest in tokensfunctionopenInterestInTokensKey(address market, address collateralToken, bool isLong) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
OPEN_INTEREST_IN_TOKENS,
market,
collateralToken,
isLong
));
}
// @dev key for collateral sum for a market// @param market the market to check// @param collateralToken the collateralToken to check// @param isLong whether to check the long or short open interest// @return key for collateral sumfunctioncollateralSumKey(address market, address collateralToken, bool isLong) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
COLLATERAL_SUM,
market,
collateralToken,
isLong
));
}
// @dev key for amount of tokens in a market's pool// @param market the market to check// @param token the token to check// @return key for amount of tokens in a market's poolfunctionpoolAmountKey(address market, address token) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
POOL_AMOUNT,
market,
token
));
}
// @dev the key for the max amount of pool tokens// @param market the market for the pool// @param token the token for the poolfunctionmaxPoolAmountKey(address market, address token) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
MAX_POOL_AMOUNT,
market,
token
));
}
// @dev the key for the max usd of pool tokens for deposits// @param market the market for the pool// @param token the token for the poolfunctionmaxPoolUsdForDepositKey(address market, address token) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
MAX_POOL_USD_FOR_DEPOSIT,
market,
token
));
}
// @dev the key for the max open interest// @param market the market for the pool// @param isLong whether the key is for the long or short sidefunctionmaxOpenInterestKey(address market, bool isLong) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
MAX_OPEN_INTEREST,
market,
isLong
));
}
// @dev key for amount of tokens in a market's position impact pool// @param market the market to check// @return key for amount of tokens in a market's position impact poolfunctionpositionImpactPoolAmountKey(address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
POSITION_IMPACT_POOL_AMOUNT,
market
));
}
// @dev key for min amount of tokens in a market's position impact pool// @param market the market to check// @return key for min amount of tokens in a market's position impact poolfunctionminPositionImpactPoolAmountKey(address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
MIN_POSITION_IMPACT_POOL_AMOUNT,
market
));
}
// @dev key for position impact pool distribution rate// @param market the market to check// @return key for position impact pool distribution ratefunctionpositionImpactPoolDistributionRateKey(address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
POSITION_IMPACT_POOL_DISTRIBUTION_RATE,
market
));
}
// @dev key for position impact pool distributed at// @param market the market to check// @return key for position impact pool distributed atfunctionpositionImpactPoolDistributedAtKey(address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
POSITION_IMPACT_POOL_DISTRIBUTED_AT,
market
));
}
// @dev key for amount of tokens in a market's swap impact pool// @param market the market to check// @param token the token to check// @return key for amount of tokens in a market's swap impact poolfunctionswapImpactPoolAmountKey(address market, address token) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
SWAP_IMPACT_POOL_AMOUNT,
market,
token
));
}
// @dev key for reserve factor// @param market the market to check// @param isLong whether to get the key for the long or short side// @return key for reserve factorfunctionreserveFactorKey(address market, bool isLong) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
RESERVE_FACTOR,
market,
isLong
));
}
// @dev key for open interest reserve factor// @param market the market to check// @param isLong whether to get the key for the long or short side// @return key for open interest reserve factorfunctionopenInterestReserveFactorKey(address market, bool isLong) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
OPEN_INTEREST_RESERVE_FACTOR,
market,
isLong
));
}
// @dev key for max pnl factor// @param market the market to check// @param isLong whether to get the key for the long or short side// @return key for max pnl factorfunctionmaxPnlFactorKey(bytes32 pnlFactorType, address market, bool isLong) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
MAX_PNL_FACTOR,
pnlFactorType,
market,
isLong
));
}
// @dev the key for min PnL factor after ADL// @param market the market for the pool// @param isLong whether the key is for the long or short sidefunctionminPnlFactorAfterAdlKey(address market, bool isLong) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
MIN_PNL_FACTOR_AFTER_ADL,
market,
isLong
));
}
// @dev key for latest adl time// @param market the market to check// @param isLong whether to get the key for the long or short side// @return key for latest adl timefunctionlatestAdlAtKey(address market, bool isLong) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
LATEST_ADL_AT,
market,
isLong
));
}
// @dev key for whether adl is enabled// @param market the market to check// @param isLong whether to get the key for the long or short side// @return key for whether adl is enabledfunctionisAdlEnabledKey(address market, bool isLong) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
IS_ADL_ENABLED,
market,
isLong
));
}
// @dev key for funding factor// @param market the market to check// @return key for funding factorfunctionfundingFactorKey(address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
FUNDING_FACTOR,
market
));
}
// @dev the key for funding exponent// @param market the market for the poolfunctionfundingExponentFactorKey(address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
FUNDING_EXPONENT_FACTOR,
market
));
}
// @dev the key for saved funding factor// @param market the market for the poolfunctionsavedFundingFactorPerSecondKey(address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
SAVED_FUNDING_FACTOR_PER_SECOND,
market
));
}
// @dev the key for funding increase factor// @param market the market for the poolfunctionfundingIncreaseFactorPerSecondKey(address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
FUNDING_INCREASE_FACTOR_PER_SECOND,
market
));
}
// @dev the key for funding decrease factor// @param market the market for the poolfunctionfundingDecreaseFactorPerSecondKey(address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
FUNDING_DECREASE_FACTOR_PER_SECOND,
market
));
}
// @dev the key for min funding factor// @param market the market for the poolfunctionminFundingFactorPerSecondKey(address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
MIN_FUNDING_FACTOR_PER_SECOND,
market
));
}
// @dev the key for max funding factor// @param market the market for the poolfunctionmaxFundingFactorPerSecondKey(address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
MAX_FUNDING_FACTOR_PER_SECOND,
market
));
}
// @dev the key for threshold for stable funding// @param market the market for the poolfunctionthresholdForStableFundingKey(address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
THRESHOLD_FOR_STABLE_FUNDING,
market
));
}
// @dev the key for threshold for decreasing funding// @param market the market for the poolfunctionthresholdForDecreaseFundingKey(address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
THRESHOLD_FOR_DECREASE_FUNDING,
market
));
}
// @dev key for funding fee amount per size// @param market the market to check// @param collateralToken the collateralToken to get the key for// @param isLong whether to get the key for the long or short side// @return key for funding fee amount per sizefunctionfundingFeeAmountPerSizeKey(address market, address collateralToken, bool isLong) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
FUNDING_FEE_AMOUNT_PER_SIZE,
market,
collateralToken,
isLong
));
}
// @dev key for claimabel funding amount per size// @param market the market to check// @param collateralToken the collateralToken to get the key for// @param isLong whether to get the key for the long or short side// @return key for claimable funding amount per sizefunctionclaimableFundingAmountPerSizeKey(address market, address collateralToken, bool isLong) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CLAIMABLE_FUNDING_AMOUNT_PER_SIZE,
market,
collateralToken,
isLong
));
}
// @dev key for when funding was last updated// @param market the market to check// @return key for when funding was last updatedfunctionfundingUpdatedAtKey(address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
FUNDING_UPDATED_AT,
market
));
}
// @dev key for claimable funding amount// @param market the market to check// @param token the token to check// @return key for claimable funding amountfunctionclaimableFundingAmountKey(address market, address token) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CLAIMABLE_FUNDING_AMOUNT,
market,
token
));
}
// @dev key for claimable funding amount by account// @param market the market to check// @param token the token to check// @param account the account to check// @return key for claimable funding amountfunctionclaimableFundingAmountKey(address market, address token, address account) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CLAIMABLE_FUNDING_AMOUNT,
market,
token,
account
));
}
// @dev key for claimable collateral amount// @param market the market to check// @param token the token to check// @param account the account to check// @param timeKey the time key for the claimable amount// @return key for claimable funding amountfunctionclaimableCollateralAmountKey(address market, address token) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CLAIMABLE_COLLATERAL_AMOUNT,
market,
token
));
}
// @dev key for claimable collateral amount for a timeKey for an account// @param market the market to check// @param token the token to check// @param account the account to check// @param timeKey the time key for the claimable amount// @return key for claimable funding amountfunctionclaimableCollateralAmountKey(address market, address token, uint256 timeKey, address account) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CLAIMABLE_COLLATERAL_AMOUNT,
market,
token,
timeKey,
account
));
}
// @dev key for claimable collateral factor for a timeKey// @param market the market to check// @param token the token to check// @param timeKey the time key for the claimable amount// @return key for claimable funding amountfunctionclaimableCollateralFactorKey(address market, address token, uint256 timeKey) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CLAIMABLE_COLLATERAL_FACTOR,
market,
token,
timeKey
));
}
// @dev key for claimable collateral factor for a timeKey for an account// @param market the market to check// @param token the token to check// @param timeKey the time key for the claimable amount// @param account the account to check// @return key for claimable funding amountfunctionclaimableCollateralFactorKey(address market, address token, uint256 timeKey, address account) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CLAIMABLE_COLLATERAL_FACTOR,
market,
token,
timeKey,
account
));
}
// @dev key for claimable collateral factor// @param market the market to check// @param token the token to check// @param account the account to check// @param timeKey the time key for the claimable amount// @return key for claimable funding amountfunctionclaimedCollateralAmountKey(address market, address token, uint256 timeKey, address account) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CLAIMED_COLLATERAL_AMOUNT,
market,
token,
timeKey,
account
));
}
// @dev key for optimal usage factor// @param market the market to check// @param isLong whether to get the key for the long or short side// @return key for optimal usage factorfunctionoptimalUsageFactorKey(address market, bool isLong) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
OPTIMAL_USAGE_FACTOR,
market,
isLong
));
}
// @dev key for base borrowing factor// @param market the market to check// @param isLong whether to get the key for the long or short side// @return key for base borrowing factorfunctionbaseBorrowingFactorKey(address market, bool isLong) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
BASE_BORROWING_FACTOR,
market,
isLong
));
}
// @dev key for above optimal usage borrowing factor// @param market the market to check// @param isLong whether to get the key for the long or short side// @return key for above optimal usage borrowing factorfunctionaboveOptimalUsageBorrowingFactorKey(address market, bool isLong) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
ABOVE_OPTIMAL_USAGE_BORROWING_FACTOR,
market,
isLong
));
}
// @dev key for borrowing factor// @param market the market to check// @param isLong whether to get the key for the long or short side// @return key for borrowing factorfunctionborrowingFactorKey(address market, bool isLong) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
BORROWING_FACTOR,
market,
isLong
));
}
// @dev the key for borrowing exponent// @param market the market for the pool// @param isLong whether to get the key for the long or short sidefunctionborrowingExponentFactorKey(address market, bool isLong) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
BORROWING_EXPONENT_FACTOR,
market,
isLong
));
}
// @dev key for cumulative borrowing factor// @param market the market to check// @param isLong whether to get the key for the long or short side// @return key for cumulative borrowing factorfunctioncumulativeBorrowingFactorKey(address market, bool isLong) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CUMULATIVE_BORROWING_FACTOR,
market,
isLong
));
}
// @dev key for cumulative borrowing factor updated at// @param market the market to check// @param isLong whether to get the key for the long or short side// @return key for cumulative borrowing factor updated atfunctioncumulativeBorrowingFactorUpdatedAtKey(address market, bool isLong) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CUMULATIVE_BORROWING_FACTOR_UPDATED_AT,
market,
isLong
));
}
// @dev key for total borrowing amount// @param market the market to check// @param isLong whether to get the key for the long or short side// @return key for total borrowing amountfunctiontotalBorrowingKey(address market, bool isLong) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
TOTAL_BORROWING,
market,
isLong
));
}
// @dev key for affiliate reward amount// @param market the market to check// @param token the token to get the key for// @param account the account to get the key for// @return key for affiliate reward amountfunctionaffiliateRewardKey(address market, address token) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
AFFILIATE_REWARD,
market,
token
));
}
functionminAffiliateRewardFactorKey(uint256 referralTierLevel) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
MIN_AFFILIATE_REWARD_FACTOR,
referralTierLevel
));
}
functionmaxAllowedSubaccountActionCountKey(address account, address subaccount, bytes32 actionType) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
MAX_ALLOWED_SUBACCOUNT_ACTION_COUNT,
account,
subaccount,
actionType
));
}
functionsubaccountActionCountKey(address account, address subaccount, bytes32 actionType) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
SUBACCOUNT_ACTION_COUNT,
account,
subaccount,
actionType
));
}
functionsubaccountAutoTopUpAmountKey(address account, address subaccount) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
SUBACCOUNT_AUTO_TOP_UP_AMOUNT,
account,
subaccount
));
}
// @dev key for affiliate reward amount for an account// @param market the market to check// @param token the token to get the key for// @param account the account to get the key for// @return key for affiliate reward amountfunctionaffiliateRewardKey(address market, address token, address account) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
AFFILIATE_REWARD,
market,
token,
account
));
}
// @dev key for is market disabled// @param market the market to check// @return key for is market disabledfunctionisMarketDisabledKey(address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
IS_MARKET_DISABLED,
market
));
}
// @dev key for min market tokens for first deposit// @param market the market to check// @return key for min market tokens for first depositfunctionminMarketTokensForFirstDepositKey(address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
MIN_MARKET_TOKENS_FOR_FIRST_DEPOSIT,
market
));
}
// @dev key for price feed address// @param token the token to get the key for// @return key for price feed addressfunctionpriceFeedKey(address token) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
PRICE_FEED,
token
));
}
// @dev key for data stream feed ID// @param token the token to get the key for// @return key for data stream feed IDfunctiondataStreamIdKey(address token) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
DATA_STREAM_ID,
token
));
}
// @dev key for data stream feed multiplier// @param token the token to get the key for// @return key for data stream feed multiplierfunctiondataStreamMultiplierKey(address token) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
DATA_STREAM_MULTIPLIER,
token
));
}
// @dev key for price feed multiplier// @param token the token to get the key for// @return key for price feed multiplierfunctionpriceFeedMultiplierKey(address token) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
PRICE_FEED_MULTIPLIER,
token
));
}
functionpriceFeedHeartbeatDurationKey(address token) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
PRICE_FEED_HEARTBEAT_DURATION,
token
));
}
// @dev key for stable price value// @param token the token to get the key for// @return key for stable price valuefunctionstablePriceKey(address token) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
STABLE_PRICE,
token
));
}
// @dev key for fee distributor swap token index// @param orderKey the swap order key// @return key for fee distributor swap token indexfunctionfeeDistributorSwapTokenIndexKey(bytes32 orderKey) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
FEE_DISTRIBUTOR_SWAP_TOKEN_INDEX,
orderKey
));
}
// @dev key for fee distributor swap fee batch key// @param orderKey the swap order key// @return key for fee distributor swap fee batch keyfunctionfeeDistributorSwapFeeBatchKey(bytes32 orderKey) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
FEE_DISTRIBUTOR_SWAP_FEE_BATCH,
orderKey
));
}
// @dev key for max market token balance usd// it is used to limit amount of funds deposited into each marketfunctionglvMaxMarketTokenBalanceUsdKey(address glv, address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(GLV_MAX_MARKET_TOKEN_BALANCE_USD, glv, market));
}
// @dev key for max market token balance amount// it is used to limit amount of funds deposited into each marketfunctionglvMaxMarketTokenBalanceAmountKey(address glv, address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(GLV_MAX_MARKET_TOKEN_BALANCE_AMOUNT, glv, market));
}
// @dev key for is glv market disabledfunctionisGlvMarketDisabledKey(address glv, address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
IS_GLV_MARKET_DISABLED,
glv,
market
));
}
// @dev key for max allowed price impact for glv shifts// if effective price impact exceeds max price impact then glv shift failsfunctionglvShiftMaxPriceImpactFactorKey(address glv) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
GLV_SHIFT_MAX_PRICE_IMPACT_FACTOR,
glv
));
}
// @dev key for time when glv shift was executed last// used to validate glv shifts are not executed too frequentlyfunctionglvShiftLastExecutedAtKey(address glv) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
GLV_SHIFT_LAST_EXECUTED_AT,
glv
));
}
// @dev key for min time interval between glv shifts in secondsfunctionglvShiftMinIntervalKey(address glv) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
GLV_SHIFT_MIN_INTERVAL,
glv
));
}
functionminGlvTokensForFirstGlvDepositKey(address glv) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
MIN_GLV_TOKENS_FOR_FIRST_DEPOSIT,
glv
));
}
// @dev key for whether the sync config feature is disabled// @param module the sync config module// @return key for sync config feature disabledfunctionsyncConfigFeatureDisabledKey(address module) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
SYNC_CONFIG_FEATURE_DISABLED,
module
));
}
// @dev key for whether sync config updates are disabled for a market// @param market the market to check// @return key for sync config market disabledfunctionsyncConfigMarketDisabledKey(address market) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
SYNC_CONFIG_MARKET_DISABLED,
market
));
}
// @dev key for whether sync config updates are disabled for a parameter// @param parameter the parameter to check// @return key for sync config parameter disabledfunctionsyncConfigParameterDisabledKey(stringmemory parameter) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
SYNC_CONFIG_PARAMETER_DISABLED,
parameter
));
}
// @dev key for whether sync config updates are disabled for a market parameter// @param market the market to check// @param parameter the parameter to check// @return key for sync config market parameter disabledfunctionsyncConfigMarketParameterDisabledKey(address market, stringmemory parameter) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
SYNC_CONFIG_MARKET_PARAMETER_DISABLED,
market,
parameter
));
}
// @dev key for whether a sync config update is completed// @param updateId the update id to check// @return key for sync config market update completedfunctionsyncConfigUpdateCompletedKey(uint256 updateId) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
SYNC_CONFIG_UPDATE_COMPLETED,
updateId
));
}
// @dev key for the latest sync config update that was completed// @return key for sync config latest update idfunctionsyncConfigLatestUpdateIdKey() internalpurereturns (bytes32) {
return SYNC_CONFIG_LATEST_UPDATE_ID;
}
// @dev key for the contributor token amount// @param account the contributor account// @param token the contributor token// @return key for the contributor token amountfunctioncontributorTokenAmountKey(address account, address token) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CONTRIBUTOR_TOKEN_AMOUNT,
account,
token
));
}
// @dev key for the max total contributor token amount// @param token the contributor token// @return key for the max contributor token amountfunctionmaxTotalContributorTokenAmountKey(address token) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
MAX_TOTAL_CONTRIBUTOR_TOKEN_AMOUNT,
token
));
}
// @dev key for the contributor token vault// @param token the contributor token// @return key for the contributor token vaultfunctioncontributorTokenVaultKey(address token) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
CONTRIBUTOR_TOKEN_VAULT,
token
));
}
// @dev key for the buyback batch amount// @param token the token for which to retrieve batch amount (GMX or WNT)// @return key for buyback batch amount for a given tokenfunctionbuybackBatchAmountKey(address token) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
BUYBACK_BATCH_AMOUNT,
token
));
}
// @dev key for the buyback available fee amount// @param feeToken the token in which the fees are denominated// @param swapToken the token for which fees are accumulated (GMX or WNT)// @return key for buyback available fee amount for a given token and feeTokenfunctionbuybackAvailableFeeAmountKey(address feeToken, address swapToken) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
BUYBACK_AVAILABLE_FEE_AMOUNT,
feeToken,
swapToken
));
}
// @dev key for the buyback withdrawable fee amount// @param buybackToken the token that was bought back// @return key for the buyback withdrawable fee amountfunctionwithdrawableBuybackTokenAmountKey(address buybackToken) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
WITHDRAWABLE_BUYBACK_TOKEN_AMOUNT,
buybackToken
));
}
// @dev key for the buyback gmx fee factor// @param version the version for which to retrieve the fee numerator// @return key for buyback gmx fee factor for a given versionfunctionbuybackGmxFactorKey(uint256 version) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
BUYBACK_GMX_FACTOR,
version
));
}
// @dev key for the buyback max price impact factor// @param token the token for which to retrieve the max price impact factor key// @return key for buyback max price impact factor for a given tokenfunctionbuybackMaxPriceImpactFactorKey(address token) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(
BUYBACK_MAX_PRICE_IMPACT_FACTOR,
token
));
}
}
Contract Source Code
File 59 of 103: Market.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;// @title Market// @dev Struct for markets//// Markets support both spot and perp trading, they are created by specifying a// long collateral token, short collateral token and index token.//// Examples://// - ETH/USD market with long collateral as ETH, short collateral as a stablecoin, index token as ETH// - BTC/USD market with long collateral as WBTC, short collateral as a stablecoin, index token as BTC// - SOL/USD market with long collateral as ETH, short collateral as a stablecoin, index token as SOL//// Liquidity providers can deposit either the long or short collateral token or// both to mint liquidity tokens.//// The long collateral token is used to back long positions, while the short// collateral token is used to back short positions.//// Liquidity providers take on the profits and losses of traders for the market// that they provide liquidity for.//// Having separate markets allows for risk isolation, liquidity providers are// only exposed to the markets that they deposit into, this potentially allow// for permissionless listings.//// Traders can use either the long or short token as collateral for the market.libraryMarket{
// @param marketToken address of the market token for the market// @param indexToken address of the index token for the market// @param longToken address of the long token for the market// @param shortToken address of the short token for the market// @param data for any additional datastructProps {
address marketToken;
address indexToken;
address longToken;
address shortToken;
}
}
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;// @title MarketPoolInfolibraryMarketPoolValueInfo{
// @dev struct to avoid stack too deep errors for the getPoolValue call// @param value the pool value// @param longTokenAmount the amount of long token in the pool// @param shortTokenAmount the amount of short token in the pool// @param longTokenUsd the USD value of the long tokens in the pool// @param shortTokenUsd the USD value of the short tokens in the pool// @param totalBorrowingFees the total pending borrowing fees for the market// @param borrowingFeePoolFactor the pool factor for borrowing fees// @param impactPoolAmount the amount of tokens in the impact pool// @param longPnl the pending pnl of long positions// @param shortPnl the pending pnl of short positions// @param netPnl the net pnl of long and short positionsstructProps {
int256 poolValue;
int256 longPnl;
int256 shortPnl;
int256 netPnl;
uint256 longTokenAmount;
uint256 shortTokenAmount;
uint256 longTokenUsd;
uint256 shortTokenUsd;
uint256 totalBorrowingFees;
uint256 borrowingFeePoolFactor;
uint256 impactPoolAmount;
}
}
Contract Source Code
File 62 of 103: MarketStoreUtils.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"../data/Keys.sol";
import"../data/DataStore.sol";
import"./Market.sol";
/**
* @title MarketStoreUtils
* @dev Library for market storage functions
*/libraryMarketStoreUtils{
usingMarketforMarket.Props;
bytes32publicconstant MARKET_SALT =keccak256(abi.encode("MARKET_SALT"));
bytes32publicconstant MARKET_KEY =keccak256(abi.encode("MARKET_KEY"));
bytes32publicconstant MARKET_TOKEN =keccak256(abi.encode("MARKET_TOKEN"));
bytes32publicconstant INDEX_TOKEN =keccak256(abi.encode("INDEX_TOKEN"));
bytes32publicconstant LONG_TOKEN =keccak256(abi.encode("LONG_TOKEN"));
bytes32publicconstant SHORT_TOKEN =keccak256(abi.encode("SHORT_TOKEN"));
functionget(DataStore dataStore, address key) publicviewreturns (Market.Props memory) {
Market.Props memory market;
if (!dataStore.containsAddress(Keys.MARKET_LIST, key)) {
return market;
}
market.marketToken = dataStore.getAddress(
keccak256(abi.encode(key, MARKET_TOKEN))
);
market.indexToken = dataStore.getAddress(
keccak256(abi.encode(key, INDEX_TOKEN))
);
market.longToken = dataStore.getAddress(
keccak256(abi.encode(key, LONG_TOKEN))
);
market.shortToken = dataStore.getAddress(
keccak256(abi.encode(key, SHORT_TOKEN))
);
return market;
}
functiongetBySalt(DataStore dataStore, bytes32 salt) externalviewreturns (Market.Props memory) {
address key = dataStore.getAddress(getMarketSaltHash(salt));
return get(dataStore, key);
}
functionset(DataStore dataStore, address key, bytes32 salt, Market.Props memory market) external{
dataStore.addAddress(
Keys.MARKET_LIST,
key
);
// the salt is based on the market props while the key gives the market's address// use the salt to store a reference to the key to allow the key to be retrieved// using just the salt value
dataStore.setAddress(
getMarketSaltHash(salt),
key
);
dataStore.setAddress(
keccak256(abi.encode(key, MARKET_TOKEN)),
market.marketToken
);
dataStore.setAddress(
keccak256(abi.encode(key, INDEX_TOKEN)),
market.indexToken
);
dataStore.setAddress(
keccak256(abi.encode(key, LONG_TOKEN)),
market.longToken
);
dataStore.setAddress(
keccak256(abi.encode(key, SHORT_TOKEN)),
market.shortToken
);
}
functionremove(DataStore dataStore, address key) external{
if (!dataStore.containsAddress(Keys.MARKET_LIST, key)) {
revert Errors.MarketNotFound(key);
}
dataStore.removeAddress(
Keys.MARKET_LIST,
key
);
dataStore.removeAddress(
keccak256(abi.encode(key, MARKET_TOKEN))
);
dataStore.removeAddress(
keccak256(abi.encode(key, INDEX_TOKEN))
);
dataStore.removeAddress(
keccak256(abi.encode(key, LONG_TOKEN))
);
dataStore.removeAddress(
keccak256(abi.encode(key, SHORT_TOKEN))
);
}
functiongetMarketSaltHash(bytes32 salt) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(MARKET_SALT, salt));
}
functiongetMarketCount(DataStore dataStore) internalviewreturns (uint256) {
return dataStore.getAddressCount(Keys.MARKET_LIST);
}
functiongetMarketKeys(DataStore dataStore, uint256 start, uint256 end) internalviewreturns (address[] memory) {
return dataStore.getAddressValuesAt(Keys.MARKET_LIST, start, end);
}
}
Contract Source Code
File 63 of 103: MarketToken.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"@openzeppelin/contracts/token/ERC20/ERC20.sol";
import"../bank/Bank.sol";
// @title MarketToken// @dev The market token for a market, stores funds for the market and keeps track// of the liquidity ownerscontractMarketTokenisERC20, Bank{
constructor(RoleStore _roleStore, DataStore _dataStore) ERC20("GMX Market", "GM") Bank(_roleStore, _dataStore) {
}
// @dev mint market tokens to an account// @param account the account to mint to// @param amount the amount of tokens to mintfunctionmint(address account, uint256 amount) externalonlyController{
_mint(account, amount);
}
// @dev burn market tokens from an account// @param account the account to burn tokens for// @param amount the amount of tokens to burnfunctionburn(address account, uint256 amount) externalonlyController{
_burn(account, amount);
}
}
Contract Source Code
File 64 of 103: MarketUtils.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"@openzeppelin/contracts/utils/math/SafeCast.sol";
import"../data/DataStore.sol";
import"../event/EventEmitter.sol";
import"./Market.sol";
import"./MarketPoolValueInfo.sol";
import"./MarketToken.sol";
import"./MarketEventUtils.sol";
import"./MarketStoreUtils.sol";
import"../position/Position.sol";
import"../order/Order.sol";
import"../oracle/Oracle.sol";
import"../price/Price.sol";
import"../utils/Calc.sol";
import"../utils/Precision.sol";
// @title MarketUtils// @dev Library for market functionslibraryMarketUtils{
usingSignedMathforint256;
usingSafeCastforint256;
usingSafeCastforuint256;
usingMarketforMarket.Props;
usingPositionforPosition.Props;
usingOrderforOrder.Props;
usingPriceforPrice.Props;
enumFundingRateChangeType {
NoChange,
Increase,
Decrease
}
// @dev struct to store the prices of tokens of a market// @param indexTokenPrice price of the market's index token// @param longTokenPrice price of the market's long token// @param shortTokenPrice price of the market's short tokenstructMarketPrices {
Price.Props indexTokenPrice;
Price.Props longTokenPrice;
Price.Props shortTokenPrice;
}
structCollateralType {
uint256 longToken;
uint256 shortToken;
}
structPositionType {
CollateralType long;
CollateralType short;
}
// @dev struct for the result of the getNextFundingAmountPerSize call// note that abs(nextSavedFundingFactorPerSecond) may not equal the fundingFactorPerSecond// see getNextFundingFactorPerSecond for more infostructGetNextFundingAmountPerSizeResult {
bool longsPayShorts;
uint256 fundingFactorPerSecond;
int256 nextSavedFundingFactorPerSecond;
PositionType fundingFeeAmountPerSizeDelta;
PositionType claimableFundingAmountPerSizeDelta;
}
structGetNextFundingAmountPerSizeCache {
PositionType openInterest;
uint256 longOpenInterest;
uint256 shortOpenInterest;
uint256 durationInSeconds;
uint256 sizeOfLargerSide;
uint256 fundingUsd;
uint256 fundingUsdForLongCollateral;
uint256 fundingUsdForShortCollateral;
}
structGetNextFundingFactorPerSecondCache {
uint256 diffUsd;
uint256 totalOpenInterest;
uint256 fundingFactor;
uint256 fundingExponentFactor;
uint256 diffUsdAfterExponent;
uint256 diffUsdToOpenInterestFactor;
int256 savedFundingFactorPerSecond;
uint256 savedFundingFactorPerSecondMagnitude;
int256 nextSavedFundingFactorPerSecond;
int256 nextSavedFundingFactorPerSecondWithMinBound;
}
structFundingConfigCache {
uint256 thresholdForStableFunding;
uint256 thresholdForDecreaseFunding;
uint256 fundingIncreaseFactorPerSecond;
uint256 fundingDecreaseFactorPerSecond;
uint256 minFundingFactorPerSecond;
uint256 maxFundingFactorPerSecond;
}
structGetExpectedMinTokenBalanceCache {
uint256 poolAmount;
uint256 swapImpactPoolAmount;
uint256 claimableCollateralAmount;
uint256 claimableFeeAmount;
uint256 claimableUiFeeAmount;
uint256 affiliateRewardAmount;
}
// @dev get the market token's price// @param dataStore DataStore// @param market the market to check// @param longTokenPrice the price of the long token// @param shortTokenPrice the price of the short token// @param indexTokenPrice the price of the index token// @param maximize whether to maximize or minimize the market token price// @return returns (the market token's price, MarketPoolValueInfo.Props)functiongetMarketTokenPrice(
DataStore dataStore,
Market.Props memory market,
Price.Props memory indexTokenPrice,
Price.Props memory longTokenPrice,
Price.Props memory shortTokenPrice,
bytes32 pnlFactorType,
bool maximize
) externalviewreturns (int256, MarketPoolValueInfo.Props memory) {
uint256 supply = getMarketTokenSupply(MarketToken(payable(market.marketToken)));
MarketPoolValueInfo.Props memory poolValueInfo = getPoolValueInfo(
dataStore,
market,
indexTokenPrice,
longTokenPrice,
shortTokenPrice,
pnlFactorType,
maximize
);
// if the supply is zero then treat the market token price as 1 USDif (supply ==0) {
return (Precision.FLOAT_PRECISION.toInt256(), poolValueInfo);
}
if (poolValueInfo.poolValue ==0) { return (0, poolValueInfo); }
int256 marketTokenPrice = Precision.mulDiv(Precision.WEI_PRECISION, poolValueInfo.poolValue, supply);
return (marketTokenPrice, poolValueInfo);
}
// @dev get the total supply of the marketToken// @param marketToken the marketToken// @return the total supply of the marketTokenfunctiongetMarketTokenSupply(MarketToken marketToken) internalviewreturns (uint256) {
return marketToken.totalSupply();
}
// @dev get the opposite token of the market// if the inputToken is the longToken return the shortToken and vice versa// @param inputToken the input token// @param market the market values// @return the opposite tokenfunctiongetOppositeToken(address inputToken, Market.Props memory market) internalpurereturns (address) {
if (inputToken == market.longToken) {
return market.shortToken;
}
if (inputToken == market.shortToken) {
return market.longToken;
}
revert Errors.UnableToGetOppositeToken(inputToken, market.marketToken);
}
functionvalidateSwapMarket(DataStore dataStore, address marketAddress) internalview{
Market.Props memory market = MarketStoreUtils.get(dataStore, marketAddress);
validateSwapMarket(dataStore, market);
}
functionvalidateSwapMarket(DataStore dataStore, Market.Props memory market) internalview{
validateEnabledMarket(dataStore, market);
if (market.longToken == market.shortToken) {
revert Errors.InvalidSwapMarket(market.marketToken);
}
}
// @dev get the token price from the stored MarketPrices// @param token the token to get the price for// @param the market values// @param the market token prices// @return the token price from the stored MarketPricesfunctiongetCachedTokenPrice(address token, Market.Props memory market, MarketPrices memory prices) internalpurereturns (Price.Props memory) {
if (token == market.longToken) {
return prices.longTokenPrice;
}
if (token == market.shortToken) {
return prices.shortTokenPrice;
}
if (token == market.indexToken) {
return prices.indexTokenPrice;
}
revert Errors.UnableToGetCachedTokenPrice(token, market.marketToken);
}
// @dev return the primary prices for the market tokens// @param oracle Oracle// @param market the market valuesfunctiongetMarketPrices(Oracle oracle, Market.Props memory market) internalviewreturns (MarketPrices memory) {
return MarketPrices(
oracle.getPrimaryPrice(market.indexToken),
oracle.getPrimaryPrice(market.longToken),
oracle.getPrimaryPrice(market.shortToken)
);
}
// @dev get the usd value of either the long or short tokens in the pool// without accounting for the pnl of open positions// @param dataStore DataStore// @param market the market values// @param prices the prices of the market tokens// @param whether to return the value for the long or short token// @return the usd value of either the long or short tokens in the poolfunctiongetPoolUsdWithoutPnl(
DataStore dataStore,
Market.Props memory market,
MarketPrices memory prices,
bool isLong,
bool maximize
) internalviewreturns (uint256) {
address token = isLong ? market.longToken : market.shortToken;
// note that if it is a single token market, the poolAmount returned will be// the amount of tokens in the pool divided by 2uint256 poolAmount = getPoolAmount(dataStore, market, token);
uint256 tokenPrice;
if (maximize) {
tokenPrice = isLong ? prices.longTokenPrice.max : prices.shortTokenPrice.max;
} else {
tokenPrice = isLong ? prices.longTokenPrice.min : prices.shortTokenPrice.min;
}
return poolAmount * tokenPrice;
}
// @dev get the USD value of a pool// the value of a pool is the worth of the liquidity provider tokens in the pool - pending trader pnl// we use the token index prices to calculate this and ignore price impact since if all positions were closed the// net price impact should be zero// @param dataStore DataStore// @param market the market values// @param longTokenPrice price of the long token// @param shortTokenPrice price of the short token// @param indexTokenPrice price of the index token// @param maximize whether to maximize or minimize the pool value// @return the value information of a poolfunctiongetPoolValueInfo(
DataStore dataStore,
Market.Props memory market,
Price.Props memory indexTokenPrice,
Price.Props memory longTokenPrice,
Price.Props memory shortTokenPrice,
bytes32 pnlFactorType,
bool maximize
) publicviewreturns (MarketPoolValueInfo.Props memory) {
MarketPoolValueInfo.Props memory result;
result.longTokenAmount = getPoolAmount(dataStore, market, market.longToken);
result.shortTokenAmount = getPoolAmount(dataStore, market, market.shortToken);
result.longTokenUsd = result.longTokenAmount * longTokenPrice.pickPrice(maximize);
result.shortTokenUsd = result.shortTokenAmount * shortTokenPrice.pickPrice(maximize);
result.poolValue = (result.longTokenUsd + result.shortTokenUsd).toInt256();
MarketPrices memory prices = MarketPrices(
indexTokenPrice,
longTokenPrice,
shortTokenPrice
);
result.totalBorrowingFees = getTotalPendingBorrowingFees(
dataStore,
market,
prices,
true
);
result.totalBorrowingFees += getTotalPendingBorrowingFees(
dataStore,
market,
prices,
false
);
result.borrowingFeePoolFactor = Precision.FLOAT_PRECISION - dataStore.getUint(Keys.BORROWING_FEE_RECEIVER_FACTOR);
result.poolValue += Precision.applyFactor(result.totalBorrowingFees, result.borrowingFeePoolFactor).toInt256();
// !maximize should be used for net pnl as a larger pnl leads to a smaller pool value// and a smaller pnl leads to a larger pool value//// while positions will always be closed at the less favourable price// using the inverse of maximize for the getPnl calls would help prevent// gaming of market token values by increasing the spread//// liquidations could be triggerred by manipulating a large spread but// that should be more difficult to execute
result.longPnl = getPnl(
dataStore,
market,
indexTokenPrice,
true, // isLong!maximize // maximize
);
result.longPnl = getCappedPnl(
dataStore,
market.marketToken,
true,
result.longPnl,
result.longTokenUsd,
pnlFactorType
);
result.shortPnl = getPnl(
dataStore,
market,
indexTokenPrice,
false, // isLong!maximize // maximize
);
result.shortPnl = getCappedPnl(
dataStore,
market.marketToken,
false,
result.shortPnl,
result.shortTokenUsd,
pnlFactorType
);
result.netPnl = result.longPnl + result.shortPnl;
result.poolValue = result.poolValue - result.netPnl;
result.impactPoolAmount = getNextPositionImpactPoolAmount(dataStore, market.marketToken);
// use !maximize for pickPrice since the impactPoolUsd is deducted from the poolValueuint256 impactPoolUsd = result.impactPoolAmount * indexTokenPrice.pickPrice(!maximize);
result.poolValue -= impactPoolUsd.toInt256();
return result;
}
// @dev get the net pending pnl for a market// @param dataStore DataStore// @param market the market to check// @param longToken the long token of the market// @param shortToken the short token of the market// @param indexTokenPrice the price of the index token// @param maximize whether to maximize or minimize the net pnl// @return the net pending pnl for a marketfunctiongetNetPnl(
DataStore dataStore,
Market.Props memory market,
Price.Props memory indexTokenPrice,
bool maximize
) internalviewreturns (int256) {
int256 longPnl = getPnl(dataStore, market, indexTokenPrice, true, maximize);
int256 shortPnl = getPnl(dataStore, market, indexTokenPrice, false, maximize);
return longPnl + shortPnl;
}
// @dev get the capped pending pnl for a market// @param dataStore DataStore// @param market the market to check// @param isLong whether to check for the long or short side// @param pnl the uncapped pnl of the market// @param poolUsd the USD value of the pool// @param pnlFactorType the pnl factor type to usefunctiongetCappedPnl(
DataStore dataStore,
address market,
bool isLong,
int256 pnl,
uint256 poolUsd,
bytes32 pnlFactorType
) internalviewreturns (int256) {
if (pnl <0) { return pnl; }
uint256 maxPnlFactor = getMaxPnlFactor(dataStore, pnlFactorType, market, isLong);
int256 maxPnl = Precision.applyFactor(poolUsd, maxPnlFactor).toInt256();
return pnl > maxPnl ? maxPnl : pnl;
}
// @dev get the pending pnl for a market// @param dataStore DataStore// @param market the market to check// @param longToken the long token of the market// @param shortToken the short token of the market// @param indexTokenPrice the price of the index token// @param isLong whether to check for the long or short side// @param maximize whether to maximize or minimize the pnlfunctiongetPnl(
DataStore dataStore,
Market.Props memory market,
uint256 indexTokenPrice,
bool isLong,
bool maximize
) internalviewreturns (int256) {
Price.Props memory _indexTokenPrice = Price.Props(indexTokenPrice, indexTokenPrice);
return getPnl(
dataStore,
market,
_indexTokenPrice,
isLong,
maximize
);
}
// @dev get the pending pnl for a market for either longs or shorts// @param dataStore DataStore// @param market the market to check// @param longToken the long token of the market// @param shortToken the short token of the market// @param indexTokenPrice the price of the index token// @param isLong whether to get the pnl for longs or shorts// @param maximize whether to maximize or minimize the net pnl// @return the pending pnl for a market for either longs or shortsfunctiongetPnl(
DataStore dataStore,
Market.Props memory market,
Price.Props memory indexTokenPrice,
bool isLong,
bool maximize
) internalviewreturns (int256) {
int256 openInterest = getOpenInterest(dataStore, market, isLong).toInt256();
uint256 openInterestInTokens = getOpenInterestInTokens(dataStore, market, isLong);
if (openInterest ==0|| openInterestInTokens ==0) {
return0;
}
uint256 price = indexTokenPrice.pickPriceForPnl(isLong, maximize);
// openInterest is the cost of all positions, openInterestValue is the current worth of all positionsint256 openInterestValue = (openInterestInTokens * price).toInt256();
int256 pnl = isLong ? openInterestValue - openInterest : openInterest - openInterestValue;
return pnl;
}
// @dev get the amount of tokens in the pool// @param dataStore DataStore// @param market the market to check// @param token the token to check// @return the amount of tokens in the poolfunctiongetPoolAmount(DataStore dataStore, Market.Props memory market, address token) internalviewreturns (uint256) {
/* Market.Props memory market = MarketStoreUtils.get(dataStore, marketAddress); */// if the longToken and shortToken are the same, return half of the token amount, so that// calculations of pool value, etc would be correctuint256 divisor = getPoolDivisor(market.longToken, market.shortToken);
return dataStore.getUint(Keys.poolAmountKey(market.marketToken, token)) / divisor;
}
// @dev get the max amount of tokens allowed to be in the pool// @param dataStore DataStore// @param market the market to check// @param token the token to check// @return the max amount of tokens that are allowed in the poolfunctiongetMaxPoolAmount(DataStore dataStore, address market, address token) internalviewreturns (uint256) {
return dataStore.getUint(Keys.maxPoolAmountKey(market, token));
}
functiongetMaxPoolUsdForDeposit(DataStore dataStore, address market, address token) internalviewreturns (uint256) {
return dataStore.getUint(Keys.maxPoolUsdForDepositKey(market, token));
}
functiongetUsageFactor(
DataStore dataStore,
Market.Props memory market,
bool isLong,
uint256 reservedUsd,
uint256 poolUsd
) internalviewreturns (uint256) {
uint256 reserveFactor = getOpenInterestReserveFactor(dataStore, market.marketToken, isLong);
uint256 maxReservedUsd = Precision.applyFactor(poolUsd, reserveFactor);
uint256 reserveUsageFactor = Precision.toFactor(reservedUsd, maxReservedUsd);
if (dataStore.getBool(Keys.IGNORE_OPEN_INTEREST_FOR_USAGE_FACTOR)) {
return reserveUsageFactor;
}
uint256 maxOpenInterest = getMaxOpenInterest(dataStore, market.marketToken, isLong);
uint256 openInterest = getOpenInterest(dataStore, market, isLong);
uint256 openInterestUsageFactor = Precision.toFactor(openInterest, maxOpenInterest);
return reserveUsageFactor > openInterestUsageFactor ? reserveUsageFactor : openInterestUsageFactor;
}
// @dev get the max open interest allowed for the market// @param dataStore DataStore// @param market the market to check// @param isLong whether this is for the long or short side// @return the max open interest allowed for the marketfunctiongetMaxOpenInterest(DataStore dataStore, address market, bool isLong) internalviewreturns (uint256) {
return dataStore.getUint(Keys.maxOpenInterestKey(market, isLong));
}
// @dev increment the claimable collateral amount// @param dataStore DataStore// @param eventEmitter EventEmitter// @param market the market to increment the claimable collateral for// @param token the claimable token// @param account the account to increment the claimable collateral for// @param delta the amount to incrementfunctionincrementClaimableCollateralAmount(
DataStore dataStore,
EventEmitter eventEmitter,
address market,
address token,
address account,
uint256 delta
) internal{
uint256 divisor = dataStore.getUint(Keys.CLAIMABLE_COLLATERAL_TIME_DIVISOR);
uint256 timeKey = Chain.currentTimestamp() / divisor;
uint256 nextValue = dataStore.incrementUint(
Keys.claimableCollateralAmountKey(market, token, timeKey, account),
delta
);
uint256 nextPoolValue = dataStore.incrementUint(
Keys.claimableCollateralAmountKey(market, token),
delta
);
MarketEventUtils.emitClaimableCollateralUpdated(
eventEmitter,
market,
token,
timeKey,
account,
delta,
nextValue,
nextPoolValue
);
}
// @dev increment the claimable funding amount// @param dataStore DataStore// @param eventEmitter EventEmitter// @param market the trading market// @param token the claimable token// @param account the account to increment for// @param delta the amount to incrementfunctionincrementClaimableFundingAmount(
DataStore dataStore,
EventEmitter eventEmitter,
address market,
address token,
address account,
uint256 delta
) internal{
uint256 nextValue = dataStore.incrementUint(
Keys.claimableFundingAmountKey(market, token, account),
delta
);
uint256 nextPoolValue = dataStore.incrementUint(
Keys.claimableFundingAmountKey(market, token),
delta
);
MarketEventUtils.emitClaimableFundingUpdated(
eventEmitter,
market,
token,
account,
delta,
nextValue,
nextPoolValue
);
}
// @dev claim funding fees// @param dataStore DataStore// @param eventEmitter EventEmitter// @param market the market to claim for// @param token the token to claim// @param account the account to claim for// @param receiver the receiver to send the amount tofunctionclaimFundingFees(
DataStore dataStore,
EventEmitter eventEmitter,
address market,
address token,
address account,
address receiver
) internalreturns (uint256) {
bytes32 key = Keys.claimableFundingAmountKey(market, token, account);
uint256 claimableAmount = dataStore.getUint(key);
dataStore.setUint(key, 0);
uint256 nextPoolValue = dataStore.decrementUint(
Keys.claimableFundingAmountKey(market, token),
claimableAmount
);
MarketToken(payable(market)).transferOut(
token,
receiver,
claimableAmount
);
validateMarketTokenBalance(dataStore, market);
MarketEventUtils.emitFundingFeesClaimed(
eventEmitter,
market,
token,
account,
receiver,
claimableAmount,
nextPoolValue
);
return claimableAmount;
}
// @dev claim collateral// @param dataStore DataStore// @param eventEmitter EventEmitter// @param market the market to claim for// @param token the token to claim// @param timeKey the time key// @param account the account to claim for// @param receiver the receiver to send the amount tofunctionclaimCollateral(
DataStore dataStore,
EventEmitter eventEmitter,
address market,
address token,
uint256 timeKey,
address account,
address receiver
) internalreturns (uint256) {
uint256 claimableAmount = dataStore.getUint(Keys.claimableCollateralAmountKey(market, token, timeKey, account));
uint256 claimableFactor;
{
uint256 claimableFactorForTime = dataStore.getUint(Keys.claimableCollateralFactorKey(market, token, timeKey));
uint256 claimableFactorForAccount = dataStore.getUint(Keys.claimableCollateralFactorKey(market, token, timeKey, account));
claimableFactor = claimableFactorForTime > claimableFactorForAccount ? claimableFactorForTime : claimableFactorForAccount;
}
if (claimableFactor > Precision.FLOAT_PRECISION) {
revert Errors.InvalidClaimableFactor(claimableFactor);
}
uint256 claimedAmount = dataStore.getUint(Keys.claimedCollateralAmountKey(market, token, timeKey, account));
uint256 adjustedClaimableAmount = Precision.applyFactor(claimableAmount, claimableFactor);
if (adjustedClaimableAmount <= claimedAmount) {
revert Errors.CollateralAlreadyClaimed(adjustedClaimableAmount, claimedAmount);
}
uint256 amountToBeClaimed = adjustedClaimableAmount - claimedAmount;
dataStore.setUint(
Keys.claimedCollateralAmountKey(market, token, timeKey, account),
adjustedClaimableAmount
);
uint256 nextPoolValue = dataStore.decrementUint(
Keys.claimableCollateralAmountKey(market, token),
amountToBeClaimed
);
MarketToken(payable(market)).transferOut(
token,
receiver,
amountToBeClaimed
);
validateMarketTokenBalance(dataStore, market);
MarketEventUtils.emitCollateralClaimed(
eventEmitter,
market,
token,
timeKey,
account,
receiver,
amountToBeClaimed,
nextPoolValue
);
return amountToBeClaimed;
}
// @dev apply a delta to the pool amount// validatePoolAmount is not called in this function since applyDeltaToPoolAmount// is called when receiving fees// @param dataStore DataStore// @param eventEmitter EventEmitter// @param market the market to apply to// @param token the token to apply to// @param delta the delta amountfunctionapplyDeltaToPoolAmount(
DataStore dataStore,
EventEmitter eventEmitter,
Market.Props memory market,
address token,
int256 delta
) internalreturns (uint256) {
uint256 nextValue = dataStore.applyDeltaToUint(
Keys.poolAmountKey(market.marketToken, token),
delta,
"Invalid state, negative poolAmount"
);
applyDeltaToVirtualInventoryForSwaps(
dataStore,
eventEmitter,
market,
token,
delta
);
MarketEventUtils.emitPoolAmountUpdated(eventEmitter, market.marketToken, token, delta, nextValue);
return nextValue;
}
functiongetAdjustedSwapImpactFactor(DataStore dataStore, address market, bool isPositive) internalviewreturns (uint256) {
(uint256 positiveImpactFactor, uint256 negativeImpactFactor) = getAdjustedSwapImpactFactors(dataStore, market);
return isPositive ? positiveImpactFactor : negativeImpactFactor;
}
functiongetAdjustedSwapImpactFactors(DataStore dataStore, address market) internalviewreturns (uint256, uint256) {
uint256 positiveImpactFactor = dataStore.getUint(Keys.swapImpactFactorKey(market, true));
uint256 negativeImpactFactor = dataStore.getUint(Keys.swapImpactFactorKey(market, false));
// if the positive impact factor is more than the negative impact factor, positions could be opened// and closed immediately for a profit if the difference is sufficient to cover the position feesif (positiveImpactFactor > negativeImpactFactor) {
positiveImpactFactor = negativeImpactFactor;
}
return (positiveImpactFactor, negativeImpactFactor);
}
functiongetAdjustedPositionImpactFactor(DataStore dataStore, address market, bool isPositive) internalviewreturns (uint256) {
(uint256 positiveImpactFactor, uint256 negativeImpactFactor) = getAdjustedPositionImpactFactors(dataStore, market);
return isPositive ? positiveImpactFactor : negativeImpactFactor;
}
functiongetAdjustedPositionImpactFactors(DataStore dataStore, address market) internalviewreturns (uint256, uint256) {
uint256 positiveImpactFactor = dataStore.getUint(Keys.positionImpactFactorKey(market, true));
uint256 negativeImpactFactor = dataStore.getUint(Keys.positionImpactFactorKey(market, false));
// if the positive impact factor is more than the negative impact factor, positions could be opened// and closed immediately for a profit if the difference is sufficient to cover the position feesif (positiveImpactFactor > negativeImpactFactor) {
positiveImpactFactor = negativeImpactFactor;
}
return (positiveImpactFactor, negativeImpactFactor);
}
// @dev cap the input priceImpactUsd by the available amount in the position// impact pool and the max positive position impact factor// @param dataStore DataStore// @param market the trading market// @param tokenPrice the price of the token// @param priceImpactUsd the calculated USD price impact// @return the capped priceImpactUsdfunctiongetCappedPositionImpactUsd(
DataStore dataStore,
address market,
Price.Props memory indexTokenPrice,
int256 priceImpactUsd,
uint256 sizeDeltaUsd
) internalviewreturns (int256) {
if (priceImpactUsd <0) {
return priceImpactUsd;
}
uint256 impactPoolAmount = getPositionImpactPoolAmount(dataStore, market);
int256 maxPriceImpactUsdBasedOnImpactPool = (impactPoolAmount * indexTokenPrice.min).toInt256();
if (priceImpactUsd > maxPriceImpactUsdBasedOnImpactPool) {
priceImpactUsd = maxPriceImpactUsdBasedOnImpactPool;
}
uint256 maxPriceImpactFactor = getMaxPositionImpactFactor(dataStore, market, true);
int256 maxPriceImpactUsdBasedOnMaxPriceImpactFactor = Precision.applyFactor(sizeDeltaUsd, maxPriceImpactFactor).toInt256();
if (priceImpactUsd > maxPriceImpactUsdBasedOnMaxPriceImpactFactor) {
priceImpactUsd = maxPriceImpactUsdBasedOnMaxPriceImpactFactor;
}
return priceImpactUsd;
}
// @dev get the position impact pool amount// @param dataStore DataStore// @param market the market to check// @return the position impact pool amountfunctiongetPositionImpactPoolAmount(DataStore dataStore, address market) internalviewreturns (uint256) {
return dataStore.getUint(Keys.positionImpactPoolAmountKey(market));
}
// @dev get the swap impact pool amount// @param dataStore DataStore// @param market the market to check// @param token the token to check// @return the swap impact pool amountfunctiongetSwapImpactPoolAmount(DataStore dataStore, address market, address token) internalviewreturns (uint256) {
return dataStore.getUint(Keys.swapImpactPoolAmountKey(market, token));
}
// @dev apply a delta to the swap impact pool// @param dataStore DataStore// @param eventEmitter EventEmitter// @param market the market to apply to// @param token the token to apply to// @param delta the delta amountfunctionapplyDeltaToSwapImpactPool(
DataStore dataStore,
EventEmitter eventEmitter,
address market,
address token,
int256 delta
) internalreturns (uint256) {
uint256 nextValue = dataStore.applyBoundedDeltaToUint(
Keys.swapImpactPoolAmountKey(market, token),
delta
);
MarketEventUtils.emitSwapImpactPoolAmountUpdated(eventEmitter, market, token, delta, nextValue);
return nextValue;
}
// @dev apply a delta to the position impact pool// @param dataStore DataStore// @param eventEmitter EventEmitter// @param market the market to apply to// @param delta the delta amountfunctionapplyDeltaToPositionImpactPool(
DataStore dataStore,
EventEmitter eventEmitter,
address market,
int256 delta
) internalreturns (uint256) {
uint256 nextValue = dataStore.applyBoundedDeltaToUint(
Keys.positionImpactPoolAmountKey(market),
delta
);
MarketEventUtils.emitPositionImpactPoolAmountUpdated(eventEmitter, market, delta, nextValue);
return nextValue;
}
// @dev apply a delta to the open interest// @param dataStore DataStore// @param eventEmitter EventEmitter// @param market the market to apply to// @param collateralToken the collateralToken to apply to// @param isLong whether to apply to the long or short side// @param delta the delta amountfunctionapplyDeltaToOpenInterest(
DataStore dataStore,
EventEmitter eventEmitter,
Market.Props memory market,
address collateralToken,
bool isLong,
int256 delta
) internalreturns (uint256) {
if (market.indexToken ==address(0)) {
revert Errors.OpenInterestCannotBeUpdatedForSwapOnlyMarket(market.marketToken);
}
uint256 nextValue = dataStore.applyDeltaToUint(
Keys.openInterestKey(market.marketToken, collateralToken, isLong),
delta,
"Invalid state: negative open interest"
);
// if the open interest for longs is increased then tokens were virtually bought from the pool// so the virtual inventory should be decreased// if the open interest for longs is decreased then tokens were virtually sold to the pool// so the virtual inventory should be increased// if the open interest for shorts is increased then tokens were virtually sold to the pool// so the virtual inventory should be increased// if the open interest for shorts is decreased then tokens were virtually bought from the pool// so the virtual inventory should be decreased
applyDeltaToVirtualInventoryForPositions(
dataStore,
eventEmitter,
market.indexToken,
isLong ? -delta : delta
);
if (delta >0) {
validateOpenInterest(
dataStore,
market,
isLong
);
}
MarketEventUtils.emitOpenInterestUpdated(eventEmitter, market.marketToken, collateralToken, isLong, delta, nextValue);
return nextValue;
}
// @dev apply a delta to the open interest in tokens// @param dataStore DataStore// @param eventEmitter EventEmitter// @param market the market to apply to// @param collateralToken the collateralToken to apply to// @param isLong whether to apply to the long or short side// @param delta the delta amountfunctionapplyDeltaToOpenInterestInTokens(
DataStore dataStore,
EventEmitter eventEmitter,
address market,
address collateralToken,
bool isLong,
int256 delta
) internalreturns (uint256) {
uint256 nextValue = dataStore.applyDeltaToUint(
Keys.openInterestInTokensKey(market, collateralToken, isLong),
delta,
"Invalid state: negative open interest in tokens"
);
MarketEventUtils.emitOpenInterestInTokensUpdated(eventEmitter, market, collateralToken, isLong, delta, nextValue);
return nextValue;
}
// @dev apply a delta to the collateral sum// @param dataStore DataStore// @param eventEmitter EventEmitter// @param market the market to apply to// @param collateralToken the collateralToken to apply to// @param isLong whether to apply to the long or short side// @param delta the delta amountfunctionapplyDeltaToCollateralSum(
DataStore dataStore,
EventEmitter eventEmitter,
address market,
address collateralToken,
bool isLong,
int256 delta
) internalreturns (uint256) {
uint256 nextValue = dataStore.applyDeltaToUint(
Keys.collateralSumKey(market, collateralToken, isLong),
delta,
"Invalid state: negative collateralSum"
);
MarketEventUtils.emitCollateralSumUpdated(eventEmitter, market, collateralToken, isLong, delta, nextValue);
return nextValue;
}
// @dev update the funding state// @param dataStore DataStore// @param market the market to update// @param prices the prices of the market tokensfunctionupdateFundingState(
DataStore dataStore,
EventEmitter eventEmitter,
Market.Props memory market,
MarketPrices memory prices
) external{
GetNextFundingAmountPerSizeResult memory result = getNextFundingAmountPerSize(dataStore, market, prices);
applyDeltaToFundingFeeAmountPerSize(
dataStore,
eventEmitter,
market.marketToken,
market.longToken,
true,
result.fundingFeeAmountPerSizeDelta.long.longToken
);
applyDeltaToFundingFeeAmountPerSize(
dataStore,
eventEmitter,
market.marketToken,
market.longToken,
false,
result.fundingFeeAmountPerSizeDelta.short.longToken
);
applyDeltaToFundingFeeAmountPerSize(
dataStore,
eventEmitter,
market.marketToken,
market.shortToken,
true,
result.fundingFeeAmountPerSizeDelta.long.shortToken
);
applyDeltaToFundingFeeAmountPerSize(
dataStore,
eventEmitter,
market.marketToken,
market.shortToken,
false,
result.fundingFeeAmountPerSizeDelta.short.shortToken
);
applyDeltaToClaimableFundingAmountPerSize(
dataStore,
eventEmitter,
market.marketToken,
market.longToken,
true,
result.claimableFundingAmountPerSizeDelta.long.longToken
);
applyDeltaToClaimableFundingAmountPerSize(
dataStore,
eventEmitter,
market.marketToken,
market.longToken,
false,
result.claimableFundingAmountPerSizeDelta.short.longToken
);
applyDeltaToClaimableFundingAmountPerSize(
dataStore,
eventEmitter,
market.marketToken,
market.shortToken,
true,
result.claimableFundingAmountPerSizeDelta.long.shortToken
);
applyDeltaToClaimableFundingAmountPerSize(
dataStore,
eventEmitter,
market.marketToken,
market.shortToken,
false,
result.claimableFundingAmountPerSizeDelta.short.shortToken
);
setSavedFundingFactorPerSecond(dataStore, market.marketToken, result.nextSavedFundingFactorPerSecond);
dataStore.setUint(Keys.fundingUpdatedAtKey(market.marketToken), Chain.currentTimestamp());
}
// @dev get the next funding amount per size values// @param dataStore DataStore// @param prices the prices of the market tokens// @param market the market to update// @param longToken the market's long token// @param shortToken the market's short tokenfunctiongetNextFundingAmountPerSize(
DataStore dataStore,
Market.Props memory market,
MarketPrices memory prices
) internalviewreturns (GetNextFundingAmountPerSizeResult memory) {
GetNextFundingAmountPerSizeResult memory result;
GetNextFundingAmountPerSizeCache memory cache;
uint256 divisor = getPoolDivisor(market.longToken, market.shortToken);
// get the open interest values by long / short and by collateral used
cache.openInterest.long.longToken = getOpenInterest(dataStore, market.marketToken, market.longToken, true, divisor);
cache.openInterest.long.shortToken = getOpenInterest(dataStore, market.marketToken, market.shortToken, true, divisor);
cache.openInterest.short.longToken = getOpenInterest(dataStore, market.marketToken, market.longToken, false, divisor);
cache.openInterest.short.shortToken = getOpenInterest(dataStore, market.marketToken, market.shortToken, false, divisor);
// sum the open interest values to get the total long and short open interest values
cache.longOpenInterest = cache.openInterest.long.longToken + cache.openInterest.long.shortToken;
cache.shortOpenInterest = cache.openInterest.short.longToken + cache.openInterest.short.shortToken;
// if either long or short open interest is zero, then funding should not be updated// as there would not be any user to pay the funding toif (cache.longOpenInterest ==0|| cache.shortOpenInterest ==0) {
return result;
}
// if the blockchain is not progressing / a market is disabled, funding fees// will continue to accumulate// this should be a rare occurrence so funding fees are not adjusted for this case
cache.durationInSeconds = getSecondsSinceFundingUpdated(dataStore, market.marketToken);
cache.sizeOfLargerSide = cache.longOpenInterest > cache.shortOpenInterest ? cache.longOpenInterest : cache.shortOpenInterest;
(result.fundingFactorPerSecond, result.longsPayShorts, result.nextSavedFundingFactorPerSecond) = getNextFundingFactorPerSecond(
dataStore,
market.marketToken,
cache.longOpenInterest,
cache.shortOpenInterest,
cache.durationInSeconds
);
// for single token markets, if there is $200,000 long open interest// and $100,000 short open interest and if the fundingUsd is $8:// fundingUsdForLongCollateral: $4// fundingUsdForShortCollateral: $4// fundingFeeAmountPerSizeDelta.long.longToken: 4 / 100,000// fundingFeeAmountPerSizeDelta.long.shortToken: 4 / 100,000// claimableFundingAmountPerSizeDelta.short.longToken: 4 / 100,000// claimableFundingAmountPerSizeDelta.short.shortToken: 4 / 100,000//// the divisor for fundingFeeAmountPerSizeDelta is 100,000 because the// cache.openInterest.long.longOpenInterest and cache.openInterest.long.shortOpenInterest is divided by 2//// when the fundingFeeAmountPerSize value is incremented, it would be incremented twice:// 4 / 100,000 + 4 / 100,000 = 8 / 100,000//// since the actual long open interest is $200,000, this would result in a total of 8 / 100,000 * 200,000 = $16 being charged//// when the claimableFundingAmountPerSize value is incremented, it would similarly be incremented twice:// 4 / 100,000 + 4 / 100,000 = 8 / 100,000//// when calculating the amount to be claimed, the longTokenClaimableFundingAmountPerSize and shortTokenClaimableFundingAmountPerSize// are compared against the market's claimableFundingAmountPerSize for the longToken and claimableFundingAmountPerSize for the shortToken//// since both these values will be duplicated, the amount claimable would be:// (8 / 100,000 + 8 / 100,000) * 100,000 = $16//// due to these, the fundingUsd should be divided by the divisor
cache.fundingUsd = Precision.applyFactor(cache.sizeOfLargerSide, cache.durationInSeconds * result.fundingFactorPerSecond);
cache.fundingUsd = cache.fundingUsd / divisor;
// split the fundingUsd value by long and short collateral// e.g. if the fundingUsd value is $500, and there is $1000 of long open interest using long collateral and $4000 of long open interest// with short collateral, then $100 of funding fees should be paid from long positions using long collateral, $400 of funding fees// should be paid from long positions using short collateral// short positions should receive $100 of funding fees in long collateral and $400 of funding fees in short collateralif (result.longsPayShorts) {
cache.fundingUsdForLongCollateral = Precision.mulDiv(cache.fundingUsd, cache.openInterest.long.longToken, cache.longOpenInterest);
cache.fundingUsdForShortCollateral = Precision.mulDiv(cache.fundingUsd, cache.openInterest.long.shortToken, cache.longOpenInterest);
} else {
cache.fundingUsdForLongCollateral = Precision.mulDiv(cache.fundingUsd, cache.openInterest.short.longToken, cache.shortOpenInterest);
cache.fundingUsdForShortCollateral = Precision.mulDiv(cache.fundingUsd, cache.openInterest.short.shortToken, cache.shortOpenInterest);
}
// calculate the change in funding amount per size values// for example, if the fundingUsdForLongCollateral is $100, the longToken price is $2000, the longOpenInterest is $10,000, shortOpenInterest is $5000// if longs pay shorts then the fundingFeeAmountPerSize.long.longToken should be increased by 0.05 tokens per $10,000 or 0.000005 tokens per $1// the claimableFundingAmountPerSize.short.longToken should be increased by 0.05 tokens per $5000 or 0.00001 tokens per $1if (result.longsPayShorts) {
// use the same longTokenPrice.max and shortTokenPrice.max to calculate the amount to be paid and received// positions only pay funding in the position's collateral token// so the fundingUsdForLongCollateral is divided by the total long open interest for long positions using the longToken as collateral// and the fundingUsdForShortCollateral is divided by the total long open interest for long positions using the shortToken as collateral
result.fundingFeeAmountPerSizeDelta.long.longToken = getFundingAmountPerSizeDelta(
cache.fundingUsdForLongCollateral,
cache.openInterest.long.longToken,
prices.longTokenPrice.max,
true// roundUpMagnitude
);
result.fundingFeeAmountPerSizeDelta.long.shortToken = getFundingAmountPerSizeDelta(
cache.fundingUsdForShortCollateral,
cache.openInterest.long.shortToken,
prices.shortTokenPrice.max,
true// roundUpMagnitude
);
// positions receive funding in both the longToken and shortToken// so the fundingUsdForLongCollateral and fundingUsdForShortCollateral is divided by the total short open interest
result.claimableFundingAmountPerSizeDelta.short.longToken = getFundingAmountPerSizeDelta(
cache.fundingUsdForLongCollateral,
cache.shortOpenInterest,
prices.longTokenPrice.max,
false// roundUpMagnitude
);
result.claimableFundingAmountPerSizeDelta.short.shortToken = getFundingAmountPerSizeDelta(
cache.fundingUsdForShortCollateral,
cache.shortOpenInterest,
prices.shortTokenPrice.max,
false// roundUpMagnitude
);
} else {
// use the same longTokenPrice.max and shortTokenPrice.max to calculate the amount to be paid and received// positions only pay funding in the position's collateral token// so the fundingUsdForLongCollateral is divided by the total short open interest for short positions using the longToken as collateral// and the fundingUsdForShortCollateral is divided by the total short open interest for short positions using the shortToken as collateral
result.fundingFeeAmountPerSizeDelta.short.longToken = getFundingAmountPerSizeDelta(
cache.fundingUsdForLongCollateral,
cache.openInterest.short.longToken,
prices.longTokenPrice.max,
true// roundUpMagnitude
);
result.fundingFeeAmountPerSizeDelta.short.shortToken = getFundingAmountPerSizeDelta(
cache.fundingUsdForShortCollateral,
cache.openInterest.short.shortToken,
prices.shortTokenPrice.max,
true// roundUpMagnitude
);
// positions receive funding in both the longToken and shortToken// so the fundingUsdForLongCollateral and fundingUsdForShortCollateral is divided by the total long open interest
result.claimableFundingAmountPerSizeDelta.long.longToken = getFundingAmountPerSizeDelta(
cache.fundingUsdForLongCollateral,
cache.longOpenInterest,
prices.longTokenPrice.max,
false// roundUpMagnitude
);
result.claimableFundingAmountPerSizeDelta.long.shortToken = getFundingAmountPerSizeDelta(
cache.fundingUsdForShortCollateral,
cache.longOpenInterest,
prices.shortTokenPrice.max,
false// roundUpMagnitude
);
}
return result;
}
// @dev get the next funding factor per second// in case the minFundingFactorPerSecond is not zero, and the long / short skew has flipped// if orders are being created frequently it is possible that the minFundingFactorPerSecond prevents// the nextSavedFundingFactorPerSecond from being decreased fast enough for the sign to eventually flip// if it is bound by minFundingFactorPerSecond// for that reason, only the nextFundingFactorPerSecond is bound by minFundingFactorPerSecond// and the nextSavedFundingFactorPerSecond is not bound by minFundingFactorPerSecond// @return nextFundingFactorPerSecond, longsPayShorts, nextSavedFundingFactorPerSecondfunctiongetNextFundingFactorPerSecond(
DataStore dataStore,
address market,
uint256 longOpenInterest,
uint256 shortOpenInterest,
uint256 durationInSeconds
) internalviewreturns (uint256, bool, int256) {
GetNextFundingFactorPerSecondCache memory cache;
cache.diffUsd = Calc.diff(longOpenInterest, shortOpenInterest);
cache.totalOpenInterest = longOpenInterest + shortOpenInterest;
FundingConfigCache memory configCache;
configCache.fundingIncreaseFactorPerSecond = dataStore.getUint(Keys.fundingIncreaseFactorPerSecondKey(market));
// if the open interest difference is zero and adaptive funding// is not enabled, then return zero as the funding factorif (cache.diffUsd ==0&& configCache.fundingIncreaseFactorPerSecond ==0) {
return (0, true, 0);
}
if (cache.totalOpenInterest ==0) {
revert Errors.UnableToGetFundingFactorEmptyOpenInterest();
}
cache.fundingExponentFactor = getFundingExponentFactor(dataStore, market);
cache.diffUsdAfterExponent = Precision.applyExponentFactor(cache.diffUsd, cache.fundingExponentFactor);
cache.diffUsdToOpenInterestFactor = Precision.toFactor(cache.diffUsdAfterExponent, cache.totalOpenInterest);
if (configCache.fundingIncreaseFactorPerSecond ==0) {
cache.fundingFactor = getFundingFactor(dataStore, market);
uint256 maxFundingFactorPerSecond = dataStore.getUint(Keys.maxFundingFactorPerSecondKey(market));
// if there is no fundingIncreaseFactorPerSecond then return the static fundingFactor based on open interest differenceuint256 fundingFactorPerSecond = Precision.applyFactor(cache.diffUsdToOpenInterestFactor, cache.fundingFactor);
if (fundingFactorPerSecond > maxFundingFactorPerSecond) {
fundingFactorPerSecond = maxFundingFactorPerSecond;
}
return (
fundingFactorPerSecond,
longOpenInterest > shortOpenInterest,
0
);
}
// if the savedFundingFactorPerSecond is positive then longs pay shorts// if the savedFundingFactorPerSecond is negative then shorts pay longs
cache.savedFundingFactorPerSecond = getSavedFundingFactorPerSecond(dataStore, market);
cache.savedFundingFactorPerSecondMagnitude = cache.savedFundingFactorPerSecond.abs();
configCache.thresholdForStableFunding = dataStore.getUint(Keys.thresholdForStableFundingKey(market));
configCache.thresholdForDecreaseFunding = dataStore.getUint(Keys.thresholdForDecreaseFundingKey(market));
// set the default of nextSavedFundingFactorPerSecond as the savedFundingFactorPerSecond
cache.nextSavedFundingFactorPerSecond = cache.savedFundingFactorPerSecond;
// the default will be NoChange
FundingRateChangeType fundingRateChangeType;
bool isSkewTheSameDirectionAsFunding = (cache.savedFundingFactorPerSecond >0&& longOpenInterest > shortOpenInterest) || (cache.savedFundingFactorPerSecond <0&& shortOpenInterest > longOpenInterest);
if (isSkewTheSameDirectionAsFunding) {
if (cache.diffUsdToOpenInterestFactor > configCache.thresholdForStableFunding) {
fundingRateChangeType = FundingRateChangeType.Increase;
} elseif (cache.diffUsdToOpenInterestFactor < configCache.thresholdForDecreaseFunding) {
// if thresholdForDecreaseFunding is zero and diffUsdToOpenInterestFactor is also zero// then the fundingRateChangeType would be NoChange
fundingRateChangeType = FundingRateChangeType.Decrease;
}
} else {
// if the skew has changed, then the funding should increase in the opposite direction
fundingRateChangeType = FundingRateChangeType.Increase;
}
if (fundingRateChangeType == FundingRateChangeType.Increase) {
// increase funding rateint256 increaseValue = Precision.applyFactor(cache.diffUsdToOpenInterestFactor, configCache.fundingIncreaseFactorPerSecond).toInt256() * durationInSeconds.toInt256();
// if there are more longs than shorts, then the savedFundingFactorPerSecond should increase// otherwise the savedFundingFactorPerSecond should increase in the opposite direction / decreaseif (longOpenInterest < shortOpenInterest) {
increaseValue =-increaseValue;
}
cache.nextSavedFundingFactorPerSecond = cache.savedFundingFactorPerSecond + increaseValue;
}
if (fundingRateChangeType == FundingRateChangeType.Decrease && cache.savedFundingFactorPerSecondMagnitude !=0) {
configCache.fundingDecreaseFactorPerSecond = dataStore.getUint(Keys.fundingDecreaseFactorPerSecondKey(market));
uint256 decreaseValue = configCache.fundingDecreaseFactorPerSecond * durationInSeconds;
if (cache.savedFundingFactorPerSecondMagnitude <= decreaseValue) {
// set the funding factor to 1 or -1 depending on the original savedFundingFactorPerSecond
cache.nextSavedFundingFactorPerSecond = cache.savedFundingFactorPerSecond / cache.savedFundingFactorPerSecondMagnitude.toInt256();
} else {
// reduce the original savedFundingFactorPerSecond while keeping the original sign of the savedFundingFactorPerSecondint256 sign = cache.savedFundingFactorPerSecond / cache.savedFundingFactorPerSecondMagnitude.toInt256();
cache.nextSavedFundingFactorPerSecond = (cache.savedFundingFactorPerSecondMagnitude - decreaseValue).toInt256() * sign;
}
}
configCache.minFundingFactorPerSecond = dataStore.getUint(Keys.minFundingFactorPerSecondKey(market));
configCache.maxFundingFactorPerSecond = dataStore.getUint(Keys.maxFundingFactorPerSecondKey(market));
cache.nextSavedFundingFactorPerSecond = Calc.boundMagnitude(
cache.nextSavedFundingFactorPerSecond,
0,
configCache.maxFundingFactorPerSecond
);
cache.nextSavedFundingFactorPerSecondWithMinBound = Calc.boundMagnitude(
cache.nextSavedFundingFactorPerSecond,
configCache.minFundingFactorPerSecond,
configCache.maxFundingFactorPerSecond
);
return (
cache.nextSavedFundingFactorPerSecondWithMinBound.abs(),
cache.nextSavedFundingFactorPerSecondWithMinBound >0,
cache.nextSavedFundingFactorPerSecond
);
}
// store funding values as token amount per (Precision.FLOAT_PRECISION_SQRT / Precision.FLOAT_PRECISION) of USD sizefunctiongetFundingAmountPerSizeDelta(uint256 fundingUsd,
uint256 openInterest,
uint256 tokenPrice,
bool roundUpMagnitude
) internalpurereturns (uint256) {
if (fundingUsd ==0|| openInterest ==0) { return0; }
uint256 fundingUsdPerSize = Precision.mulDiv(
fundingUsd,
Precision.FLOAT_PRECISION * Precision.FLOAT_PRECISION_SQRT,
openInterest,
roundUpMagnitude
);
if (roundUpMagnitude) {
return Calc.roundUpDivision(fundingUsdPerSize, tokenPrice);
} else {
return fundingUsdPerSize / tokenPrice;
}
}
// @dev update the cumulative borrowing factor for a market// @param dataStore DataStore// @param market the market to update// @param longToken the market's long token// @param shortToken the market's short token// @param prices the prices of the market tokens// @param isLong whether to update the long or short sidefunctionupdateCumulativeBorrowingFactor(
DataStore dataStore,
EventEmitter eventEmitter,
Market.Props memory market,
MarketPrices memory prices,
bool isLong
) external{
(/* uint256 nextCumulativeBorrowingFactor */, uint256 delta) = getNextCumulativeBorrowingFactor(
dataStore,
market,
prices,
isLong
);
incrementCumulativeBorrowingFactor(
dataStore,
eventEmitter,
market.marketToken,
isLong,
delta
);
dataStore.setUint(Keys.cumulativeBorrowingFactorUpdatedAtKey(market.marketToken, isLong), Chain.currentTimestamp());
}
// @dev get the ratio of pnl to pool value// @param dataStore DataStore// @param oracle Oracle// @param market the trading market// @param isLong whether to get the value for the long or short side// @param maximize whether to maximize the factor// @return (pnl of positions) / (long or short pool value)functiongetPnlToPoolFactor(
DataStore dataStore,
Oracle oracle,
address market,
bool isLong,
bool maximize
) internalviewreturns (int256) {
Market.Props memory _market = getEnabledMarket(dataStore, market);
MarketPrices memory prices = MarketPrices(
oracle.getPrimaryPrice(_market.indexToken),
oracle.getPrimaryPrice(_market.longToken),
oracle.getPrimaryPrice(_market.shortToken)
);
return getPnlToPoolFactor(dataStore, _market, prices, isLong, maximize);
}
// @dev get the ratio of pnl to pool value// @param dataStore DataStore// @param market the market values// @param prices the prices of the market tokens// @param isLong whether to get the value for the long or short side// @param maximize whether to maximize the factor// @return (pnl of positions) / (long or short pool value)functiongetPnlToPoolFactor(
DataStore dataStore,
Market.Props memory market,
MarketPrices memory prices,
bool isLong,
bool maximize
) internalviewreturns (int256) {
uint256 poolUsd = getPoolUsdWithoutPnl(dataStore, market, prices, isLong, !maximize);
if (poolUsd ==0) {
return0;
}
int256 pnl = getPnl(
dataStore,
market,
prices.indexTokenPrice,
isLong,
maximize
);
return Precision.toFactor(pnl, poolUsd);
}
functionvalidateOpenInterest(
DataStore dataStore,
Market.Props memory market,
bool isLong
) internalview{
uint256 openInterest = getOpenInterest(dataStore, market, isLong);
uint256 maxOpenInterest = getMaxOpenInterest(dataStore, market.marketToken, isLong);
if (openInterest > maxOpenInterest) {
revert Errors.MaxOpenInterestExceeded(openInterest, maxOpenInterest);
}
}
// @dev validate that the pool amount is within the max allowed amount// @param dataStore DataStore// @param market the market to check// @param token the token to checkfunctionvalidatePoolAmount(
DataStore dataStore,
Market.Props memory market,
address token
) internalview{
uint256 poolAmount = getPoolAmount(dataStore, market, token);
uint256 maxPoolAmount = getMaxPoolAmount(dataStore, market.marketToken, token);
if (poolAmount > maxPoolAmount) {
revert Errors.MaxPoolAmountExceeded(poolAmount, maxPoolAmount);
}
}
functionvalidatePoolUsdForDeposit(
DataStore dataStore,
Market.Props memory market,
address token,
uint256 tokenPrice
) internalview{
uint256 poolAmount = getPoolAmount(dataStore, market, token);
uint256 poolUsd = poolAmount * tokenPrice;
uint256 maxPoolUsd = getMaxPoolUsdForDeposit(dataStore, market.marketToken, token);
if (poolUsd > maxPoolUsd) {
revert Errors.MaxPoolUsdForDepositExceeded(poolUsd, maxPoolUsd);
}
}
// @dev validate that the amount of tokens required to be reserved// is below the configured threshold// @param dataStore DataStore// @param market the market values// @param prices the prices of the market tokens// @param isLong whether to check the long or short sidefunctionvalidateReserve(
DataStore dataStore,
Market.Props memory market,
MarketPrices memory prices,
bool isLong
) internalview{
// poolUsd is used instead of pool amount as the indexToken may not match the longToken// additionally, the shortToken may not be a stablecoinuint256 poolUsd = getPoolUsdWithoutPnl(dataStore, market, prices, isLong, false);
uint256 reserveFactor = getReserveFactor(dataStore, market.marketToken, isLong);
uint256 maxReservedUsd = Precision.applyFactor(poolUsd, reserveFactor);
uint256 reservedUsd = getReservedUsd(
dataStore,
market,
prices,
isLong
);
if (reservedUsd > maxReservedUsd) {
revert Errors.InsufficientReserve(reservedUsd, maxReservedUsd);
}
}
// @dev validate that the amount of tokens required to be reserved for open interest// is below the configured threshold// @param dataStore DataStore// @param market the market values// @param prices the prices of the market tokens// @param isLong whether to check the long or short sidefunctionvalidateOpenInterestReserve(
DataStore dataStore,
Market.Props memory market,
MarketPrices memory prices,
bool isLong
) internalview{
// poolUsd is used instead of pool amount as the indexToken may not match the longToken// additionally, the shortToken may not be a stablecoinuint256 poolUsd = getPoolUsdWithoutPnl(dataStore, market, prices, isLong, false);
uint256 reserveFactor = getOpenInterestReserveFactor(dataStore, market.marketToken, isLong);
uint256 maxReservedUsd = Precision.applyFactor(poolUsd, reserveFactor);
uint256 reservedUsd = getReservedUsd(
dataStore,
market,
prices,
isLong
);
if (reservedUsd > maxReservedUsd) {
revert Errors.InsufficientReserveForOpenInterest(reservedUsd, maxReservedUsd);
}
}
// @dev update the swap impact pool amount, if it is a positive impact amount// cap the impact amount to the amount available in the swap impact pool// @param dataStore DataStore// @param eventEmitter EventEmitter// @param market the market to apply to// @param token the token to apply to// @param tokenPrice the price of the token// @param priceImpactUsd the USD price impactfunctionapplySwapImpactWithCap(
DataStore dataStore,
EventEmitter eventEmitter,
address market,
address token,
Price.Props memory tokenPrice,
int256 priceImpactUsd
) internalreturns (int256, uint256) {
(int256 impactAmount, uint256 cappedDiffUsd) = getSwapImpactAmountWithCap(
dataStore,
market,
token,
tokenPrice,
priceImpactUsd
);
// if there is a positive impact, the impact pool amount should be reduced// if there is a negative impact, the impact pool amount should be increased
applyDeltaToSwapImpactPool(
dataStore,
eventEmitter,
market,
token,
-impactAmount
);
return (impactAmount, cappedDiffUsd);
}
functiongetSwapImpactAmountWithCap(
DataStore dataStore,
address market,
address token,
Price.Props memory tokenPrice,
int256 priceImpactUsd
) internalviewreturns (int256, uint256) {
int256 impactAmount;
uint256 cappedDiffUsd;
if (priceImpactUsd >0) {
// positive impact: minimize impactAmount, use tokenPrice.max// round positive impactAmount down, this will be deducted from the swap impact pool for the user
impactAmount = priceImpactUsd / tokenPrice.max.toInt256();
int256 maxImpactAmount = getSwapImpactPoolAmount(dataStore, market, token).toInt256();
if (impactAmount > maxImpactAmount) {
cappedDiffUsd = (impactAmount - maxImpactAmount).toUint256() * tokenPrice.max;
impactAmount = maxImpactAmount;
}
} else {
// negative impact: maximize impactAmount, use tokenPrice.min// round negative impactAmount up, this will be deducted from the user
impactAmount = Calc.roundUpMagnitudeDivision(priceImpactUsd, tokenPrice.min);
}
return (impactAmount, cappedDiffUsd);
}
// @dev get the funding amount to be deducted or distributed//// @param latestFundingAmountPerSize the latest funding amount per size// @param positionFundingAmountPerSize the funding amount per size for the position// @param positionSizeInUsd the position size in USD// @param roundUpMagnitude whether the round up the result//// @return fundingAmountfunctiongetFundingAmount(uint256 latestFundingAmountPerSize,
uint256 positionFundingAmountPerSize,
uint256 positionSizeInUsd,
bool roundUpMagnitude
) internalpurereturns (uint256) {
uint256 fundingDiffFactor = (latestFundingAmountPerSize - positionFundingAmountPerSize);
// a user could avoid paying funding fees by continually updating the position// before the funding fee becomes large enough to be chargeable// to avoid this, funding fee amounts should be rounded up//// this could lead to large additional charges if the token has a low number of decimals// or if the token's value is very high, so care should be taken to inform users of this//// if the calculation is for the claimable amount, the amount should be rounded down instead// divide the result by Precision.FLOAT_PRECISION * Precision.FLOAT_PRECISION_SQRT as the fundingAmountPerSize values// are stored based on FLOAT_PRECISION_SQRT valuesreturn Precision.mulDiv(
positionSizeInUsd,
fundingDiffFactor,
Precision.FLOAT_PRECISION * Precision.FLOAT_PRECISION_SQRT,
roundUpMagnitude
);
}
// @dev get the borrowing fees for a position, assumes that cumulativeBorrowingFactor// has already been updated to the latest value// @param dataStore DataStore// @param position Position.Props// @return the borrowing fees for a positionfunctiongetBorrowingFees(DataStore dataStore, Position.Props memory position) internalviewreturns (uint256) {
uint256 cumulativeBorrowingFactor = getCumulativeBorrowingFactor(dataStore, position.market(), position.isLong());
if (position.borrowingFactor() > cumulativeBorrowingFactor) {
revert Errors.UnexpectedBorrowingFactor(position.borrowingFactor(), cumulativeBorrowingFactor);
}
uint256 diffFactor = cumulativeBorrowingFactor - position.borrowingFactor();
return Precision.applyFactor(position.sizeInUsd(), diffFactor);
}
// @dev get the borrowing fees for a position by calculating the latest cumulativeBorrowingFactor// @param dataStore DataStore// @param position Position.Props// @param market the position's market// @param prices the prices of the market tokens// @return the borrowing fees for a positionfunctiongetNextBorrowingFees(DataStore dataStore, Position.Props memory position, Market.Props memory market, MarketPrices memory prices) internalviewreturns (uint256) {
(uint256 nextCumulativeBorrowingFactor, /* uint256 delta */) = getNextCumulativeBorrowingFactor(
dataStore,
market,
prices,
position.isLong()
);
if (position.borrowingFactor() > nextCumulativeBorrowingFactor) {
revert Errors.UnexpectedBorrowingFactor(position.borrowingFactor(), nextCumulativeBorrowingFactor);
}
uint256 diffFactor = nextCumulativeBorrowingFactor - position.borrowingFactor();
return Precision.applyFactor(position.sizeInUsd(), diffFactor);
}
// @dev get the total reserved USD required for positions// @param market the market to check// @param prices the prices of the market tokens// @param isLong whether to get the value for the long or short sidefunctiongetReservedUsd(
DataStore dataStore,
Market.Props memory market,
MarketPrices memory prices,
bool isLong
) internalviewreturns (uint256) {
uint256 reservedUsd;
if (isLong) {
// for longs calculate the reserved USD based on the open interest and current indexTokenPrice// this works well for e.g. an ETH / USD market with long collateral token as WETH// the available amount to be reserved would scale with the price of ETH// this also works for e.g. a SOL / USD market with long collateral token as WETH// if the price of SOL increases more than the price of ETH, additional amounts would be// automatically reserveduint256 openInterestInTokens = getOpenInterestInTokens(dataStore, market, isLong);
reservedUsd = openInterestInTokens * prices.indexTokenPrice.max;
} else {
// for shorts use the open interest as the reserved USD value// this works well for e.g. an ETH / USD market with short collateral token as USDC// the available amount to be reserved would not change with the price of ETH
reservedUsd = getOpenInterest(dataStore, market, isLong);
}
return reservedUsd;
}
// @dev get the virtual inventory for swaps// @param dataStore DataStore// @param market the market to check// @return returns (has virtual inventory, virtual long token inventory, virtual short token inventory)functiongetVirtualInventoryForSwaps(DataStore dataStore, address market) internalviewreturns (bool, uint256, uint256) {
bytes32 virtualMarketId = dataStore.getBytes32(Keys.virtualMarketIdKey(market));
if (virtualMarketId ==bytes32(0)) {
return (false, 0, 0);
}
return (
true,
dataStore.getUint(Keys.virtualInventoryForSwapsKey(virtualMarketId, true)),
dataStore.getUint(Keys.virtualInventoryForSwapsKey(virtualMarketId, false))
);
}
functiongetIsLongToken(Market.Props memory market, address token) internalpurereturns (bool) {
if (token != market.longToken && token != market.shortToken) {
revert Errors.UnexpectedTokenForVirtualInventory(token, market.marketToken);
}
return token == market.longToken;
}
// @dev get the virtual inventory for positions// @param dataStore DataStore// @param token the token to checkfunctiongetVirtualInventoryForPositions(DataStore dataStore, address token) internalviewreturns (bool, int256) {
bytes32 virtualTokenId = dataStore.getBytes32(Keys.virtualTokenIdKey(token));
if (virtualTokenId ==bytes32(0)) {
return (false, 0);
}
return (true, dataStore.getInt(Keys.virtualInventoryForPositionsKey(virtualTokenId)));
}
// @dev update the virtual inventory for swaps// @param dataStore DataStore// @param marketAddress the market to update// @param token the token to update// @param delta the update amountfunctionapplyDeltaToVirtualInventoryForSwaps(
DataStore dataStore,
EventEmitter eventEmitter,
Market.Props memory market,
address token,
int256 delta
) internalreturns (bool, uint256) {
bytes32 virtualMarketId = dataStore.getBytes32(Keys.virtualMarketIdKey(market.marketToken));
if (virtualMarketId ==bytes32(0)) {
return (false, 0);
}
bool isLongToken = getIsLongToken(market, token);
uint256 nextValue = dataStore.applyBoundedDeltaToUint(
Keys.virtualInventoryForSwapsKey(virtualMarketId, isLongToken),
delta
);
MarketEventUtils.emitVirtualSwapInventoryUpdated(eventEmitter, market.marketToken, isLongToken, virtualMarketId, delta, nextValue);
return (true, nextValue);
}
// @dev update the virtual inventory for positions// @param dataStore DataStore// @param eventEmitter EventEmitter// @param token the token to update// @param delta the update amountfunctionapplyDeltaToVirtualInventoryForPositions(
DataStore dataStore,
EventEmitter eventEmitter,
address token,
int256 delta
) internalreturns (bool, int256) {
bytes32 virtualTokenId = dataStore.getBytes32(Keys.virtualTokenIdKey(token));
if (virtualTokenId ==bytes32(0)) {
return (false, 0);
}
int256 nextValue = dataStore.applyDeltaToInt(
Keys.virtualInventoryForPositionsKey(virtualTokenId),
delta
);
MarketEventUtils.emitVirtualPositionInventoryUpdated(eventEmitter, token, virtualTokenId, delta, nextValue);
return (true, nextValue);
}
// @dev get the open interest of a market// @param dataStore DataStore// @param market the market to check// @param longToken the long token of the market// @param shortToken the short token of the marketfunctiongetOpenInterest(
DataStore dataStore,
Market.Props memory market
) internalviewreturns (uint256) {
uint256 longOpenInterest = getOpenInterest(dataStore, market, true);
uint256 shortOpenInterest = getOpenInterest(dataStore, market, false);
return longOpenInterest + shortOpenInterest;
}
// @dev get either the long or short open interest for a market// @param dataStore DataStore// @param market the market to check// @param longToken the long token of the market// @param shortToken the short token of the market// @param isLong whether to get the long or short open interest// @return the long or short open interest for a marketfunctiongetOpenInterest(
DataStore dataStore,
Market.Props memory market,
bool isLong
) internalviewreturns (uint256) {
uint256 divisor = getPoolDivisor(market.longToken, market.shortToken);
uint256 openInterestUsingLongTokenAsCollateral = getOpenInterest(dataStore, market.marketToken, market.longToken, isLong, divisor);
uint256 openInterestUsingShortTokenAsCollateral = getOpenInterest(dataStore, market.marketToken, market.shortToken, isLong, divisor);
return openInterestUsingLongTokenAsCollateral + openInterestUsingShortTokenAsCollateral;
}
// @dev the long and short open interest for a market based on the collateral token used// @param dataStore DataStore// @param market the market to check// @param collateralToken the collateral token to check// @param isLong whether to check the long or short sidefunctiongetOpenInterest(
DataStore dataStore,
address market,
address collateralToken,
bool isLong,
uint256 divisor
) internalviewreturns (uint256) {
return dataStore.getUint(Keys.openInterestKey(market, collateralToken, isLong)) / divisor;
}
// this is used to divide the values of getPoolAmount and getOpenInterest// if the longToken and shortToken are the same, then these values have to be divided by two// to avoid double countingfunctiongetPoolDivisor(address longToken, address shortToken) internalpurereturns (uint256) {
return longToken == shortToken ? 2 : 1;
}
// @dev the long and short open interest in tokens for a market// @param dataStore DataStore// @param market the market to check// @param longToken the long token of the market// @param shortToken the short token of the market// @param isLong whether to check the long or short sidefunctiongetOpenInterestInTokens(
DataStore dataStore,
Market.Props memory market,
bool isLong
) internalviewreturns (uint256) {
uint256 divisor = getPoolDivisor(market.longToken, market.shortToken);
uint256 openInterestUsingLongTokenAsCollateral = getOpenInterestInTokens(dataStore, market.marketToken, market.longToken, isLong, divisor);
uint256 openInterestUsingShortTokenAsCollateral = getOpenInterestInTokens(dataStore, market.marketToken, market.shortToken, isLong, divisor);
return openInterestUsingLongTokenAsCollateral + openInterestUsingShortTokenAsCollateral;
}
// @dev the long and short open interest in tokens for a market based on the collateral token used// @param dataStore DataStore// @param market the market to check// @param collateralToken the collateral token to check// @param isLong whether to check the long or short sidefunctiongetOpenInterestInTokens(
DataStore dataStore,
address market,
address collateralToken,
bool isLong,
uint256 divisor
) internalviewreturns (uint256) {
return dataStore.getUint(Keys.openInterestInTokensKey(market, collateralToken, isLong)) / divisor;
}
// @dev get the sum of open interest and pnl for a market// getOpenInterestInTokens * tokenPrice would not reflect pending positive pnl// for short positions, so getOpenInterestWithPnl should be used if that info is needed// @param dataStore DataStore// @param market the market to check// @param longToken the long token of the market// @param shortToken the short token of the market// @param indexTokenPrice the price of the index token// @param isLong whether to check the long or short side// @param maximize whether to maximize or minimize the value// @return the sum of open interest and pnl for a marketfunctiongetOpenInterestWithPnl(
DataStore dataStore,
Market.Props memory market,
Price.Props memory indexTokenPrice,
bool isLong,
bool maximize
) internalviewreturns (int256) {
uint256 openInterest = getOpenInterest(dataStore, market, isLong);
int256 pnl = getPnl(dataStore, market, indexTokenPrice, isLong, maximize);
return Calc.sumReturnInt256(openInterest, pnl);
}
// @dev get the max position impact factor for decreasing position// @param dataStore DataStore// @param market the market to check// @param isPositive whether the price impact is positive or negativefunctiongetMaxPositionImpactFactor(DataStore dataStore, address market, bool isPositive) internalviewreturns (uint256) {
(uint256 maxPositiveImpactFactor, uint256 maxNegativeImpactFactor) = getMaxPositionImpactFactors(dataStore, market);
return isPositive ? maxPositiveImpactFactor : maxNegativeImpactFactor;
}
functiongetMaxPositionImpactFactors(DataStore dataStore, address market) internalviewreturns (uint256, uint256) {
uint256 maxPositiveImpactFactor = dataStore.getUint(Keys.maxPositionImpactFactorKey(market, true));
uint256 maxNegativeImpactFactor = dataStore.getUint(Keys.maxPositionImpactFactorKey(market, false));
if (maxPositiveImpactFactor > maxNegativeImpactFactor) {
maxPositiveImpactFactor = maxNegativeImpactFactor;
}
return (maxPositiveImpactFactor, maxNegativeImpactFactor);
}
// @dev get the max position impact factor for liquidations// @param dataStore DataStore// @param market the market to checkfunctiongetMaxPositionImpactFactorForLiquidations(DataStore dataStore, address market) internalviewreturns (uint256) {
return dataStore.getUint(Keys.maxPositionImpactFactorForLiquidationsKey(market));
}
// @dev get the min collateral factor// @param dataStore DataStore// @param market the market to checkfunctiongetMinCollateralFactor(DataStore dataStore, address market) internalviewreturns (uint256) {
return dataStore.getUint(Keys.minCollateralFactorKey(market));
}
// @dev get the min collateral factor for open interest multiplier// @param dataStore DataStore// @param market the market to check// @param isLong whether it is for the long or short sidefunctiongetMinCollateralFactorForOpenInterestMultiplier(DataStore dataStore, address market, bool isLong) internalviewreturns (uint256) {
return dataStore.getUint(Keys.minCollateralFactorForOpenInterestMultiplierKey(market, isLong));
}
// @dev get the min collateral factor for open interest// @param dataStore DataStore// @param market the market to check// @param longToken the long token of the market// @param shortToken the short token of the market// @param openInterestDelta the change in open interest// @param isLong whether it is for the long or short sidefunctiongetMinCollateralFactorForOpenInterest(
DataStore dataStore,
Market.Props memory market,
int256 openInterestDelta,
bool isLong
) internalviewreturns (uint256) {
uint256 openInterest = getOpenInterest(dataStore, market, isLong);
openInterest = Calc.sumReturnUint256(openInterest, openInterestDelta);
uint256 multiplierFactor = getMinCollateralFactorForOpenInterestMultiplier(dataStore, market.marketToken, isLong);
return Precision.applyFactor(openInterest, multiplierFactor);
}
// @dev get the total amount of position collateral for a market// @param dataStore DataStore// @param market the market to check// @param collateralToken the collateralToken to check// @param isLong whether to get the value for longs or shorts// @return the total amount of position collateral for a marketfunctiongetCollateralSum(DataStore dataStore, address market, address collateralToken, bool isLong, uint256 divisor) internalviewreturns (uint256) {
return dataStore.getUint(Keys.collateralSumKey(market, collateralToken, isLong)) / divisor;
}
// @dev get the reserve factor for a market// @param dataStore DataStore// @param market the market to check// @param isLong whether to get the value for longs or shorts// @return the reserve factor for a marketfunctiongetReserveFactor(DataStore dataStore, address market, bool isLong) internalviewreturns (uint256) {
return dataStore.getUint(Keys.reserveFactorKey(market, isLong));
}
// @dev get the open interest reserve factor for a market// @param dataStore DataStore// @param market the market to check// @param isLong whether to get the value for longs or shorts// @return the open interest reserve factor for a marketfunctiongetOpenInterestReserveFactor(DataStore dataStore, address market, bool isLong) internalviewreturns (uint256) {
return dataStore.getUint(Keys.openInterestReserveFactorKey(market, isLong));
}
// @dev get the max pnl factor for a market// @param dataStore DataStore// @param pnlFactorType the type of the pnl factor// @param market the market to check// @param isLong whether to get the value for longs or shorts// @return the max pnl factor for a marketfunctiongetMaxPnlFactor(DataStore dataStore, bytes32 pnlFactorType, address market, bool isLong) internalviewreturns (uint256) {
return dataStore.getUint(Keys.maxPnlFactorKey(pnlFactorType, market, isLong));
}
// @dev get the min pnl factor after ADL// @param dataStore DataStore// @param market the market to check// @param isLong whether to check the long or short sidefunctiongetMinPnlFactorAfterAdl(DataStore dataStore, address market, bool isLong) internalviewreturns (uint256) {
return dataStore.getUint(Keys.minPnlFactorAfterAdlKey(market, isLong));
}
// @dev get the funding factor for a market// @param dataStore DataStore// @param market the market to check// @return the funding factor for a marketfunctiongetFundingFactor(DataStore dataStore, address market) internalviewreturns (uint256) {
return dataStore.getUint(Keys.fundingFactorKey(market));
}
// @dev get the saved funding factor for a market// @param dataStore DataStore// @param market the market to check// @return the saved funding factor for a marketfunctiongetSavedFundingFactorPerSecond(DataStore dataStore, address market) internalviewreturns (int256) {
return dataStore.getInt(Keys.savedFundingFactorPerSecondKey(market));
}
// @dev set the saved funding factor// @param dataStore DataStore// @param market the market to set the funding factor forfunctionsetSavedFundingFactorPerSecond(DataStore dataStore, address market, int256 value) internalreturns (int256) {
return dataStore.setInt(Keys.savedFundingFactorPerSecondKey(market), value);
}
// @dev get the funding exponent factor for a market// @param dataStore DataStore// @param market the market to check// @return the funding exponent factor for a marketfunctiongetFundingExponentFactor(DataStore dataStore, address market) internalviewreturns (uint256) {
return dataStore.getUint(Keys.fundingExponentFactorKey(market));
}
// @dev get the funding fee amount per size for a market// @param dataStore DataStore// @param market the market to check// @param collateralToken the collateralToken to check// @param isLong whether to check the long or short size// @return the funding fee amount per size for a market based on collateralTokenfunctiongetFundingFeeAmountPerSize(DataStore dataStore, address market, address collateralToken, bool isLong) internalviewreturns (uint256) {
return dataStore.getUint(Keys.fundingFeeAmountPerSizeKey(market, collateralToken, isLong));
}
// @dev get the claimable funding amount per size for a market// @param dataStore DataStore// @param market the market to check// @param collateralToken the collateralToken to check// @param isLong whether to check the long or short size// @return the claimable funding amount per size for a market based on collateralTokenfunctiongetClaimableFundingAmountPerSize(DataStore dataStore, address market, address collateralToken, bool isLong) internalviewreturns (uint256) {
return dataStore.getUint(Keys.claimableFundingAmountPerSizeKey(market, collateralToken, isLong));
}
// @dev apply delta to the funding fee amount per size for a market// @param dataStore DataStore// @param market the market to set// @param collateralToken the collateralToken to set// @param isLong whether to set it for the long or short side// @param delta the delta to increment byfunctionapplyDeltaToFundingFeeAmountPerSize(
DataStore dataStore,
EventEmitter eventEmitter,
address market,
address collateralToken,
bool isLong,
uint256 delta
) internal{
if (delta ==0) { return; }
uint256 nextValue = dataStore.applyDeltaToUint(
Keys.fundingFeeAmountPerSizeKey(market, collateralToken, isLong),
delta
);
MarketEventUtils.emitFundingFeeAmountPerSizeUpdated(
eventEmitter,
market,
collateralToken,
isLong,
delta,
nextValue
);
}
// @dev apply delta to the claimable funding amount per size for a market// @param dataStore DataStore// @param market the market to set// @param collateralToken the collateralToken to set// @param isLong whether to set it for the long or short side// @param delta the delta to increment byfunctionapplyDeltaToClaimableFundingAmountPerSize(
DataStore dataStore,
EventEmitter eventEmitter,
address market,
address collateralToken,
bool isLong,
uint256 delta
) internal{
if (delta ==0) { return; }
uint256 nextValue = dataStore.applyDeltaToUint(
Keys.claimableFundingAmountPerSizeKey(market, collateralToken, isLong),
delta
);
MarketEventUtils.emitClaimableFundingAmountPerSizeUpdated(
eventEmitter,
market,
collateralToken,
isLong,
delta,
nextValue
);
}
// @dev get the number of seconds since funding was updated for a market// @param market the market to check// @return the number of seconds since funding was updated for a marketfunctiongetSecondsSinceFundingUpdated(DataStore dataStore, address market) internalviewreturns (uint256) {
uint256 updatedAt = dataStore.getUint(Keys.fundingUpdatedAtKey(market));
if (updatedAt ==0) { return0; }
return Chain.currentTimestamp() - updatedAt;
}
// @dev get the borrowing factor for a market// @param dataStore DataStore// @param market the market to check// @param isLong whether to check the long or short side// @return the borrowing factor for a marketfunctiongetBorrowingFactor(DataStore dataStore, address market, bool isLong) internalviewreturns (uint256) {
return dataStore.getUint(Keys.borrowingFactorKey(market, isLong));
}
functiongetOptimalUsageFactor(DataStore dataStore, address market, bool isLong) internalviewreturns (uint256) {
return dataStore.getUint(Keys.optimalUsageFactorKey(market, isLong));
}
// @dev get the borrowing exponent factor for a market// @param dataStore DataStore// @param market the market to check// @param isLong whether to check the long or short side// @return the borrowing exponent factor for a marketfunctiongetBorrowingExponentFactor(DataStore dataStore, address market, bool isLong) internalviewreturns (uint256) {
return dataStore.getUint(Keys.borrowingExponentFactorKey(market, isLong));
}
// @dev get the cumulative borrowing factor for a market// @param dataStore DataStore// @param market the market to check// @param isLong whether to check the long or short side// @return the cumulative borrowing factor for a marketfunctiongetCumulativeBorrowingFactor(DataStore dataStore, address market, bool isLong) internalviewreturns (uint256) {
return dataStore.getUint(Keys.cumulativeBorrowingFactorKey(market, isLong));
}
// @dev increase the cumulative borrowing factor// @param dataStore DataStore// @param eventEmitter EventEmitter// @param market the market to increment the borrowing factor for// @param isLong whether to increment the long or short side// @param delta the increase amountfunctionincrementCumulativeBorrowingFactor(
DataStore dataStore,
EventEmitter eventEmitter,
address market,
bool isLong,
uint256 delta
) internal{
uint256 nextCumulativeBorrowingFactor = dataStore.incrementUint(
Keys.cumulativeBorrowingFactorKey(market, isLong),
delta
);
MarketEventUtils.emitBorrowingFactorUpdated(
eventEmitter,
market,
isLong,
delta,
nextCumulativeBorrowingFactor
);
}
// @dev get the timestamp of when the cumulative borrowing factor was last updated// @param dataStore DataStore// @param market the market to check// @param isLong whether to check the long or short side// @return the timestamp of when the cumulative borrowing factor was last updatedfunctiongetCumulativeBorrowingFactorUpdatedAt(DataStore dataStore, address market, bool isLong) internalviewreturns (uint256) {
return dataStore.getUint(Keys.cumulativeBorrowingFactorUpdatedAtKey(market, isLong));
}
// @dev get the number of seconds since the cumulative borrowing factor was last updated// @param dataStore DataStore// @param market the market to check// @param isLong whether to check the long or short side// @return the number of seconds since the cumulative borrowing factor was last updatedfunctiongetSecondsSinceCumulativeBorrowingFactorUpdated(DataStore dataStore, address market, bool isLong) internalviewreturns (uint256) {
uint256 updatedAt = getCumulativeBorrowingFactorUpdatedAt(dataStore, market, isLong);
if (updatedAt ==0) { return0; }
return Chain.currentTimestamp() - updatedAt;
}
// @dev update the total borrowing amount after a position changes size// this is the sum of all position.borrowingFactor * position.sizeInUsd// @param dataStore DataStore// @param market the market to update// @param isLong whether to update the long or short side// @param prevPositionSizeInUsd the previous position size in USD// @param prevPositionBorrowingFactor the previous position borrowing factor// @param nextPositionSizeInUsd the next position size in USD// @param nextPositionBorrowingFactor the next position borrowing factorfunctionupdateTotalBorrowing(
DataStore dataStore,
address market,
bool isLong,
uint256 prevPositionSizeInUsd,
uint256 prevPositionBorrowingFactor,
uint256 nextPositionSizeInUsd,
uint256 nextPositionBorrowingFactor
) external{
uint256 totalBorrowing = getNextTotalBorrowing(
dataStore,
market,
isLong,
prevPositionSizeInUsd,
prevPositionBorrowingFactor,
nextPositionSizeInUsd,
nextPositionBorrowingFactor
);
setTotalBorrowing(dataStore, market, isLong, totalBorrowing);
}
// @dev get the next total borrowing amount after a position changes size// @param dataStore DataStore// @param market the market to check// @param isLong whether to check the long or short side// @param prevPositionSizeInUsd the previous position size in USD// @param prevPositionBorrowingFactor the previous position borrowing factor// @param nextPositionSizeInUsd the next position size in USD// @param nextPositionBorrowingFactor the next position borrowing factorfunctiongetNextTotalBorrowing(
DataStore dataStore,
address market,
bool isLong,
uint256 prevPositionSizeInUsd,
uint256 prevPositionBorrowingFactor,
uint256 nextPositionSizeInUsd,
uint256 nextPositionBorrowingFactor
) internalviewreturns (uint256) {
uint256 totalBorrowing = getTotalBorrowing(dataStore, market, isLong);
totalBorrowing -= Precision.applyFactor(prevPositionSizeInUsd, prevPositionBorrowingFactor);
totalBorrowing += Precision.applyFactor(nextPositionSizeInUsd, nextPositionBorrowingFactor);
return totalBorrowing;
}
// @dev get the next cumulative borrowing factor// @param dataStore DataStore// @param prices the prices of the market tokens// @param market the market to check// @param longToken the long token of the market// @param shortToken the short token of the market// @param isLong whether to check the long or short sidefunctiongetNextCumulativeBorrowingFactor(
DataStore dataStore,
Market.Props memory market,
MarketPrices memory prices,
bool isLong
) internalviewreturns (uint256, uint256) {
uint256 durationInSeconds = getSecondsSinceCumulativeBorrowingFactorUpdated(dataStore, market.marketToken, isLong);
uint256 borrowingFactorPerSecond = getBorrowingFactorPerSecond(
dataStore,
market,
prices,
isLong
);
uint256 cumulativeBorrowingFactor = getCumulativeBorrowingFactor(dataStore, market.marketToken, isLong);
uint256 delta = durationInSeconds * borrowingFactorPerSecond;
uint256 nextCumulativeBorrowingFactor = cumulativeBorrowingFactor + delta;
return (nextCumulativeBorrowingFactor, delta);
}
// @dev get the borrowing factor per second// @param dataStore DataStore// @param market the market to get the borrowing factor per second for// @param prices the prices of the market tokens// @param isLong whether to get the factor for the long or short sidefunctiongetBorrowingFactorPerSecond(
DataStore dataStore,
Market.Props memory market,
MarketPrices memory prices,
bool isLong
) internalviewreturns (uint256) {
uint256 reservedUsd = getReservedUsd(
dataStore,
market,
prices,
isLong
);
if (reservedUsd ==0) { return0; }
// check if the borrowing fee for the smaller side should be skipped// if skipBorrowingFeeForSmallerSide is true, and the longOpenInterest is exactly the same as the shortOpenInterest// then the borrowing fee would be charged for both sides, this should be very rarebool skipBorrowingFeeForSmallerSide = dataStore.getBool(Keys.SKIP_BORROWING_FEE_FOR_SMALLER_SIDE);
if (skipBorrowingFeeForSmallerSide) {
uint256 longOpenInterest = getOpenInterest(dataStore, market, true);
uint256 shortOpenInterest = getOpenInterest(dataStore, market, false);
// if getting the borrowing factor for longs and if the longOpenInterest// is smaller than the shortOpenInterest, then return zeroif (isLong && longOpenInterest < shortOpenInterest) {
return0;
}
// if getting the borrowing factor for shorts and if the shortOpenInterest// is smaller than the longOpenInterest, then return zeroif (!isLong && shortOpenInterest < longOpenInterest) {
return0;
}
}
uint256 poolUsd = getPoolUsdWithoutPnl(dataStore, market, prices, isLong, false);
if (poolUsd ==0) {
revert Errors.UnableToGetBorrowingFactorEmptyPoolUsd();
}
uint256 optimalUsageFactor = getOptimalUsageFactor(dataStore, market.marketToken, isLong);
if (optimalUsageFactor !=0) {
return getKinkBorrowingFactor(
dataStore,
market,
isLong,
reservedUsd,
poolUsd,
optimalUsageFactor
);
}
uint256 borrowingExponentFactor = getBorrowingExponentFactor(dataStore, market.marketToken, isLong);
uint256 reservedUsdAfterExponent = Precision.applyExponentFactor(reservedUsd, borrowingExponentFactor);
uint256 reservedUsdToPoolFactor = Precision.toFactor(reservedUsdAfterExponent, poolUsd);
uint256 borrowingFactor = getBorrowingFactor(dataStore, market.marketToken, isLong);
return Precision.applyFactor(reservedUsdToPoolFactor, borrowingFactor);
}
functiongetKinkBorrowingFactor(
DataStore dataStore,
Market.Props memory market,
bool isLong,
uint256 reservedUsd,
uint256 poolUsd,
uint256 optimalUsageFactor
) internalviewreturns (uint256) {
uint256 usageFactor = getUsageFactor(
dataStore,
market,
isLong,
reservedUsd,
poolUsd
);
uint256 baseBorrowingFactor = dataStore.getUint(Keys.baseBorrowingFactorKey(market.marketToken, isLong));
uint256 borrowingFactorPerSecond = Precision.applyFactor(
usageFactor,
baseBorrowingFactor
);
if (usageFactor > optimalUsageFactor && Precision.FLOAT_PRECISION > optimalUsageFactor) {
uint256 diff = usageFactor - optimalUsageFactor;
uint256 aboveOptimalUsageBorrowingFactor = dataStore.getUint(Keys.aboveOptimalUsageBorrowingFactorKey(market.marketToken, isLong));
uint256 additionalBorrowingFactorPerSecond;
if (aboveOptimalUsageBorrowingFactor > baseBorrowingFactor) {
additionalBorrowingFactorPerSecond = aboveOptimalUsageBorrowingFactor - baseBorrowingFactor;
}
uint256 divisor = Precision.FLOAT_PRECISION - optimalUsageFactor;
borrowingFactorPerSecond += additionalBorrowingFactorPerSecond * diff / divisor;
}
return borrowingFactorPerSecond;
}
functiondistributePositionImpactPool(
DataStore dataStore,
EventEmitter eventEmitter,
address market
) external{
(uint256 distributionAmount, uint256 nextPositionImpactPoolAmount) = getPendingPositionImpactPoolDistributionAmount(dataStore, market);
if (distributionAmount !=0) {
applyDeltaToPositionImpactPool(
dataStore,
eventEmitter,
market,
-distributionAmount.toInt256()
);
MarketEventUtils.emitPositionImpactPoolDistributed(
eventEmitter,
market,
distributionAmount,
nextPositionImpactPoolAmount
);
}
dataStore.setUint(Keys.positionImpactPoolDistributedAtKey(market), Chain.currentTimestamp());
}
functiongetNextPositionImpactPoolAmount(
DataStore dataStore,
address market
) internalviewreturns (uint256) {
(/* uint256 distributionAmount */, uint256 nextPositionImpactPoolAmount) = getPendingPositionImpactPoolDistributionAmount(dataStore, market);
return nextPositionImpactPoolAmount;
}
// @return (distributionAmount, nextPositionImpactPoolAmount)functiongetPendingPositionImpactPoolDistributionAmount(
DataStore dataStore,
address market
) internalviewreturns (uint256, uint256) {
uint256 positionImpactPoolAmount = getPositionImpactPoolAmount(dataStore, market);
if (positionImpactPoolAmount ==0) { return (0, positionImpactPoolAmount); }
uint256 distributionRate = dataStore.getUint(Keys.positionImpactPoolDistributionRateKey(market));
if (distributionRate ==0) { return (0, positionImpactPoolAmount); }
uint256 minPositionImpactPoolAmount = dataStore.getUint(Keys.minPositionImpactPoolAmountKey(market));
if (positionImpactPoolAmount <= minPositionImpactPoolAmount) { return (0, positionImpactPoolAmount); }
uint256 maxDistributionAmount = positionImpactPoolAmount - minPositionImpactPoolAmount;
uint256 durationInSeconds = getSecondsSincePositionImpactPoolDistributed(dataStore, market);
uint256 distributionAmount = Precision.applyFactor(durationInSeconds, distributionRate);
if (distributionAmount > maxDistributionAmount) {
distributionAmount = maxDistributionAmount;
}
return (distributionAmount, positionImpactPoolAmount - distributionAmount);
}
functiongetSecondsSincePositionImpactPoolDistributed(
DataStore dataStore,
address market
) internalviewreturns (uint256) {
uint256 distributedAt = dataStore.getUint(Keys.positionImpactPoolDistributedAtKey(market));
if (distributedAt ==0) { return0; }
return Chain.currentTimestamp() - distributedAt;
}
// @dev get the total pending borrowing fees// @param dataStore DataStore// @param market the market to check// @param longToken the long token of the market// @param shortToken the short token of the market// @param isLong whether to check the long or short sidefunctiongetTotalPendingBorrowingFees(
DataStore dataStore,
Market.Props memory market,
MarketPrices memory prices,
bool isLong
) internalviewreturns (uint256) {
uint256 openInterest = getOpenInterest(
dataStore,
market,
isLong
);
(uint256 nextCumulativeBorrowingFactor, /* uint256 delta */) = getNextCumulativeBorrowingFactor(
dataStore,
market,
prices,
isLong
);
uint256 totalBorrowing = getTotalBorrowing(dataStore, market.marketToken, isLong);
return Precision.applyFactor(openInterest, nextCumulativeBorrowingFactor) - totalBorrowing;
}
// @dev get the total borrowing value// the total borrowing value is the sum of position.borrowingFactor * position.size / (10 ^ 30)// for all positions of the market// if borrowing APR is 1000% for 100 years, the cumulativeBorrowingFactor could be as high as 100 * 1000 * (10 ** 30)// since position.size is a USD value with 30 decimals, under this scenario, there may be overflow issues// if open interest exceeds (2 ** 256) / (10 ** 30) / (100 * 1000 * (10 ** 30)) => 1,157,920,900,000 USD// @param dataStore DataStore// @param market the market to check// @param isLong whether to check the long or short side// @return the total borrowing valuefunctiongetTotalBorrowing(DataStore dataStore, address market, bool isLong) internalviewreturns (uint256) {
return dataStore.getUint(Keys.totalBorrowingKey(market, isLong));
}
// @dev set the total borrowing value// @param dataStore DataStore// @param market the market to set// @param isLong whether to set the long or short side// @param value the value to set tofunctionsetTotalBorrowing(DataStore dataStore, address market, bool isLong, uint256 value) internalreturns (uint256) {
return dataStore.setUint(Keys.totalBorrowingKey(market, isLong), value);
}
// @dev convert a USD value to number of market tokens// @param usdValue the input USD value// @param poolValue the value of the pool// @param supply the supply of market tokens// @return the number of market tokensfunctionusdToMarketTokenAmount(uint256 usdValue,
uint256 poolValue,
uint256 supply
) internalpurereturns (uint256) {
// if the supply and poolValue is zero, use 1 USD as the token priceif (supply ==0&& poolValue ==0) {
return Precision.floatToWei(usdValue);
}
// if the supply is zero and the poolValue is more than zero,// then include the poolValue for the amount of tokens minted so that// the market token price after mint would be 1 USDif (supply ==0&& poolValue >0) {
return Precision.floatToWei(poolValue + usdValue);
}
// round market tokens downreturn Precision.mulDiv(supply, usdValue, poolValue);
}
// @dev convert a number of market tokens to its USD value// @param marketTokenAmount the input number of market tokens// @param poolValue the value of the pool// @param supply the supply of market tokens// @return the USD value of the market tokensfunctionmarketTokenAmountToUsd(uint256 marketTokenAmount,
uint256 poolValue,
uint256 supply
) internalpurereturns (uint256) {
if (supply ==0) { revert Errors.EmptyMarketTokenSupply(); }
return Precision.mulDiv(poolValue, marketTokenAmount, supply);
}
// @dev validate that the specified market exists and is enabled// @param dataStore DataStore// @param marketAddress the address of the marketfunctionvalidateEnabledMarket(DataStore dataStore, address marketAddress) internalview{
Market.Props memory market = MarketStoreUtils.get(dataStore, marketAddress);
validateEnabledMarket(dataStore, market);
}
// @dev validate that the specified market exists and is enabled// @param dataStore DataStore// @param market the market to checkfunctionvalidateEnabledMarket(DataStore dataStore, Market.Props memory market) internalview{
if (market.marketToken ==address(0)) {
revert Errors.EmptyMarket();
}
bool isMarketDisabled = dataStore.getBool(Keys.isMarketDisabledKey(market.marketToken));
if (isMarketDisabled) {
revert Errors.DisabledMarket(market.marketToken);
}
}
// @dev validate that the positions can be opened in the given market// @param market the market to checkfunctionvalidatePositionMarket(DataStore dataStore, Market.Props memory market) internalview{
validateEnabledMarket(dataStore, market);
if (isSwapOnlyMarket(market)) {
revert Errors.InvalidPositionMarket(market.marketToken);
}
}
functionvalidatePositionMarket(DataStore dataStore, address marketAddress) internalview{
Market.Props memory market = MarketStoreUtils.get(dataStore, marketAddress);
validatePositionMarket(dataStore, market);
}
// @dev check if a market only supports swaps and not positions// @param market the market to checkfunctionisSwapOnlyMarket(Market.Props memory market) internalpurereturns (bool) {
return market.indexToken ==address(0);
}
// @dev check if the given token is a collateral token of the market// @param market the market to check// @param token the token to checkfunctionisMarketCollateralToken(Market.Props memory market, address token) internalpurereturns (bool) {
return token == market.longToken || token == market.shortToken;
}
// @dev validate if the given token is a collateral token of the market// @param market the market to check// @param token the token to checkfunctionvalidateMarketCollateralToken(Market.Props memory market, address token) internalpure{
if (!isMarketCollateralToken(market, token)) {
revert Errors.InvalidCollateralTokenForMarket(market.marketToken, token);
}
}
// @dev get the enabled market, revert if the market does not exist or is not enabled// @param dataStore DataStore// @param marketAddress the address of the marketfunctiongetEnabledMarket(DataStore dataStore, address marketAddress) internalviewreturns (Market.Props memory) {
Market.Props memory market = MarketStoreUtils.get(dataStore, marketAddress);
validateEnabledMarket(dataStore, market);
return market;
}
functiongetSwapPathMarket(DataStore dataStore, address marketAddress) internalviewreturns (Market.Props memory) {
Market.Props memory market = MarketStoreUtils.get(dataStore, marketAddress);
validateSwapMarket(dataStore, market);
return market;
}
// @dev get a list of market values based on an input array of market addresses// @param swapPath list of market addressesfunctiongetSwapPathMarkets(DataStore dataStore, address[] memory swapPath) internalviewreturns (Market.Props[] memory) {
Market.Props[] memory markets =new Market.Props[](swapPath.length);
for (uint256 i; i < swapPath.length; i++) {
address marketAddress = swapPath[i];
markets[i] = getSwapPathMarket(dataStore, marketAddress);
}
return markets;
}
functionvalidateSwapPath(DataStore dataStore, address[] memory swapPath) internalview{
uint256 maxSwapPathLength = dataStore.getUint(Keys.MAX_SWAP_PATH_LENGTH);
if (swapPath.length> maxSwapPathLength) {
revert Errors.MaxSwapPathLengthExceeded(swapPath.length, maxSwapPathLength);
}
for (uint256 i; i < swapPath.length; i++) {
address marketAddress = swapPath[i];
validateSwapMarket(dataStore, marketAddress);
}
}
// @dev validate that the pending pnl is below the allowed amount// @param dataStore DataStore// @param market the market to check// @param prices the prices of the market tokens// @param pnlFactorType the pnl factor type to checkfunctionvalidateMaxPnl(
DataStore dataStore,
Market.Props memory market,
MarketPrices memory prices,
bytes32 pnlFactorTypeForLongs,
bytes32 pnlFactorTypeForShorts
) internalview{
(bool isPnlFactorExceededForLongs, int256 pnlToPoolFactorForLongs, uint256 maxPnlFactorForLongs) = isPnlFactorExceeded(
dataStore,
market,
prices,
true,
pnlFactorTypeForLongs
);
if (isPnlFactorExceededForLongs) {
revert Errors.PnlFactorExceededForLongs(pnlToPoolFactorForLongs, maxPnlFactorForLongs);
}
(bool isPnlFactorExceededForShorts, int256 pnlToPoolFactorForShorts, uint256 maxPnlFactorForShorts) = isPnlFactorExceeded(
dataStore,
market,
prices,
false,
pnlFactorTypeForShorts
);
if (isPnlFactorExceededForShorts) {
revert Errors.PnlFactorExceededForShorts(pnlToPoolFactorForShorts, maxPnlFactorForShorts);
}
}
// @dev check if the pending pnl exceeds the allowed amount// @param dataStore DataStore// @param oracle Oracle// @param market the market to check// @param isLong whether to check the long or short side// @param pnlFactorType the pnl factor type to checkfunctionisPnlFactorExceeded(
DataStore dataStore,
Oracle oracle,
address market,
bool isLong,
bytes32 pnlFactorType
) internalviewreturns (bool, int256, uint256) {
Market.Props memory _market = getEnabledMarket(dataStore, market);
MarketPrices memory prices = getMarketPrices(oracle, _market);
return isPnlFactorExceeded(
dataStore,
_market,
prices,
isLong,
pnlFactorType
);
}
// @dev check if the pending pnl exceeds the allowed amount// @param dataStore DataStore// @param _market the market to check// @param prices the prices of the market tokens// @param isLong whether to check the long or short side// @param pnlFactorType the pnl factor type to checkfunctionisPnlFactorExceeded(
DataStore dataStore,
Market.Props memory market,
MarketPrices memory prices,
bool isLong,
bytes32 pnlFactorType
) internalviewreturns (bool, int256, uint256) {
int256 pnlToPoolFactor = getPnlToPoolFactor(dataStore, market, prices, isLong, true);
uint256 maxPnlFactor = getMaxPnlFactor(dataStore, pnlFactorType, market.marketToken, isLong);
bool isExceeded = pnlToPoolFactor >0&& pnlToPoolFactor.toUint256() > maxPnlFactor;
return (isExceeded, pnlToPoolFactor, maxPnlFactor);
}
functiongetUiFeeFactor(DataStore dataStore, address account) internalviewreturns (uint256) {
uint256 maxUiFeeFactor = dataStore.getUint(Keys.MAX_UI_FEE_FACTOR);
uint256 uiFeeFactor = dataStore.getUint(Keys.uiFeeFactorKey(account));
return uiFeeFactor < maxUiFeeFactor ? uiFeeFactor : maxUiFeeFactor;
}
functionsetUiFeeFactor(
DataStore dataStore,
EventEmitter eventEmitter,
address account,
uint256 uiFeeFactor
) internal{
uint256 maxUiFeeFactor = dataStore.getUint(Keys.MAX_UI_FEE_FACTOR);
if (uiFeeFactor > maxUiFeeFactor) {
revert Errors.InvalidUiFeeFactor(uiFeeFactor, maxUiFeeFactor);
}
dataStore.setUint(
Keys.uiFeeFactorKey(account),
uiFeeFactor
);
MarketEventUtils.emitUiFeeFactorUpdated(eventEmitter, account, uiFeeFactor);
}
functionvalidateMarketTokenBalance(
DataStore dataStore,
Market.Props[] memory markets
) publicview{
for (uint256 i; i < markets.length; i++) {
validateMarketTokenBalance(dataStore, markets[i]);
}
}
functionvalidateMarketTokenBalance(
DataStore dataStore,
address _market
) publicview{
Market.Props memory market = getEnabledMarket(dataStore, _market);
validateMarketTokenBalance(dataStore, market);
}
functionvalidateMarketTokenBalance(
DataStore dataStore,
Market.Props memory market
) publicview{
validateMarketTokenBalance(dataStore, market, market.longToken);
if (market.longToken == market.shortToken) {
return;
}
validateMarketTokenBalance(dataStore, market, market.shortToken);
}
functionvalidateMarketTokenBalance(
DataStore dataStore,
Market.Props memory market,
address token
) internalview{
if (market.marketToken ==address(0) || token ==address(0)) {
revert Errors.EmptyAddressInMarketTokenBalanceValidation(market.marketToken, token);
}
uint256 balance = IERC20(token).balanceOf(market.marketToken);
uint256 expectedMinBalance = getExpectedMinTokenBalance(dataStore, market, token);
if (balance < expectedMinBalance) {
revert Errors.InvalidMarketTokenBalance(market.marketToken, token, balance, expectedMinBalance);
}
// funding fees can be claimed even if the collateral for positions that should pay funding fees// hasn't been reduced yet// due to that, funding fees and collateral is excluded from the expectedMinBalance calculation// and validated separately// use 1 for the getCollateralSum divisor since getCollateralSum does not sum over both the// longToken and shortTokenuint256 collateralAmount = getCollateralSum(dataStore, market.marketToken, token, true, 1);
collateralAmount += getCollateralSum(dataStore, market.marketToken, token, false, 1);
if (balance < collateralAmount) {
revert Errors.InvalidMarketTokenBalanceForCollateralAmount(market.marketToken, token, balance, collateralAmount);
}
uint256 claimableFundingFeeAmount = dataStore.getUint(Keys.claimableFundingAmountKey(market.marketToken, token));
// in case of late liquidations, it may be possible for the claimableFundingFeeAmount to exceed the market token balance// but this should be very rareif (balance < claimableFundingFeeAmount) {
revert Errors.InvalidMarketTokenBalanceForClaimableFunding(market.marketToken, token, balance, claimableFundingFeeAmount);
}
}
functiongetExpectedMinTokenBalance(
DataStore dataStore,
Market.Props memory market,
address token
) internalviewreturns (uint256) {
GetExpectedMinTokenBalanceCache memory cache;
// get the pool amount directly as MarketUtils.getPoolAmount will divide the amount by 2// for markets with the same long and short token
cache.poolAmount = dataStore.getUint(Keys.poolAmountKey(market.marketToken, token));
cache.swapImpactPoolAmount = getSwapImpactPoolAmount(dataStore, market.marketToken, token);
cache.claimableCollateralAmount = dataStore.getUint(Keys.claimableCollateralAmountKey(market.marketToken, token));
cache.claimableFeeAmount = dataStore.getUint(Keys.claimableFeeAmountKey(market.marketToken, token));
cache.claimableUiFeeAmount = dataStore.getUint(Keys.claimableUiFeeAmountKey(market.marketToken, token));
cache.affiliateRewardAmount = dataStore.getUint(Keys.affiliateRewardKey(market.marketToken, token));
// funding fees are excluded from this summation as claimable funding fees// are incremented without a corresponding decrease of the collateral of// other positions, the collateral of other positions is decreased when// those positions are updatedreturn
cache.poolAmount
+ cache.swapImpactPoolAmount
+ cache.claimableCollateralAmount
+ cache.claimableFeeAmount
+ cache.claimableUiFeeAmount
+ cache.affiliateRewardAmount;
}
}
Contract Source Code
File 65 of 103: Math.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)pragmasolidity ^0.8.0;/**
* @dev Standard math utilities missing in the Solidity language.
*/libraryMath{
enumRounding {
Down, // Toward negative infinity
Up, // Toward infinity
Zero // Toward zero
}
/**
* @dev Returns the largest of two numbers.
*/functionmax(uint256 a, uint256 b) internalpurereturns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/functionmin(uint256 a, uint256 b) internalpurereturns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/functionaverage(uint256 a, uint256 b) internalpurereturns (uint256) {
// (a + b) / 2 can overflow.return (a & b) + (a ^ b) /2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds up instead
* of rounding down.
*/functionceilDiv(uint256 a, uint256 b) internalpurereturns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.return a ==0 ? 0 : (a -1) / b +1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
* with further edits by Uniswap Labs also under MIT license.
*/functionmulDiv(uint256 x, uint256 y, uint256 denominator) internalpurereturns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256// variables such that product = prod1 * 2^256 + prod0.uint256 prod0; // Least significant 256 bits of the productuint256 prod1; // Most significant 256 bits of the productassembly {
let mm :=mulmod(x, y, not(0))
prod0 :=mul(x, y)
prod1 :=sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.if (prod1 ==0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.// The surrounding unchecked block does not change this fact.// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.require(denominator > prod1, "Math: mulDiv overflow");
///////////////////////////////////////////////// 512 by 256 division.///////////////////////////////////////////////// Make division exact by subtracting the remainder from [prod1 prod0].uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder :=mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 :=sub(prod1, gt(remainder, prod0))
prod0 :=sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.// See https://cs.stackexchange.com/q/138556/92363.// Does not overflow because the denominator cannot be zero at this stage in the function.uint256 twos = denominator & (~denominator +1);
assembly {
// Divide denominator by twos.
denominator :=div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 :=div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos :=add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for// four bits. That is, denominator * inv = 1 mod 2^4.uint256 inverse = (3* denominator) ^2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works// in modular arithmetic, doubling the correct bits in each step.
inverse *=2- denominator * inverse; // inverse mod 2^8
inverse *=2- denominator * inverse; // inverse mod 2^16
inverse *=2- denominator * inverse; // inverse mod 2^32
inverse *=2- denominator * inverse; // inverse mod 2^64
inverse *=2- denominator * inverse; // inverse mod 2^128
inverse *=2- denominator * inverse; // inverse mod 2^256// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/functionmulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internalpurereturns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up &&mulmod(x, y, denominator) >0) {
result +=1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/functionsqrt(uint256 a) internalpurereturns (uint256) {
if (a ==0) {
return0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.//// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.//// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`//// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.uint256 result =1<< (log2(a) >>1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision// into the expected uint128 result.unchecked {
result = (result + a / result) >>1;
result = (result + a / result) >>1;
result = (result + a / result) >>1;
result = (result + a / result) >>1;
result = (result + a / result) >>1;
result = (result + a / result) >>1;
result = (result + a / result) >>1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/functionsqrt(uint256 a, Rounding rounding) internalpurereturns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2, rounded down, of a positive value.
* Returns 0 if given 0.
*/functionlog2(uint256 value) internalpurereturns (uint256) {
uint256 result =0;
unchecked {
if (value >>128>0) {
value >>=128;
result +=128;
}
if (value >>64>0) {
value >>=64;
result +=64;
}
if (value >>32>0) {
value >>=32;
result +=32;
}
if (value >>16>0) {
value >>=16;
result +=16;
}
if (value >>8>0) {
value >>=8;
result +=8;
}
if (value >>4>0) {
value >>=4;
result +=4;
}
if (value >>2>0) {
value >>=2;
result +=2;
}
if (value >>1>0) {
result +=1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/functionlog2(uint256 value, Rounding rounding) internalpurereturns (uint256) {
unchecked {
uint256 result =log2(value);
return result + (rounding == Rounding.Up &&1<< result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10, rounded down, of a positive value.
* Returns 0 if given 0.
*/functionlog10(uint256 value) internalpurereturns (uint256) {
uint256 result =0;
unchecked {
if (value >=10**64) {
value /=10**64;
result +=64;
}
if (value >=10**32) {
value /=10**32;
result +=32;
}
if (value >=10**16) {
value /=10**16;
result +=16;
}
if (value >=10**8) {
value /=10**8;
result +=8;
}
if (value >=10**4) {
value /=10**4;
result +=4;
}
if (value >=10**2) {
value /=10**2;
result +=2;
}
if (value >=10**1) {
result +=1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/functionlog10(uint256 value, Rounding rounding) internalpurereturns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (rounding == Rounding.Up &&10** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256, rounded down, of a positive value.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/functionlog256(uint256 value) internalpurereturns (uint256) {
uint256 result =0;
unchecked {
if (value >>128>0) {
value >>=128;
result +=16;
}
if (value >>64>0) {
value >>=64;
result +=8;
}
if (value >>32>0) {
value >>=32;
result +=4;
}
if (value >>16>0) {
value >>=16;
result +=2;
}
if (value >>8>0) {
result +=1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/functionlog256(uint256 value, Rounding rounding) internalpurereturns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (rounding == Rounding.Up &&1<< (result <<3) < value ? 1 : 0);
}
}
}
Contract Source Code
File 66 of 103: NonceUtils.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"../data/DataStore.sol";
import"../data/Keys.sol";
// @title NonceUtils// @dev Library to keep track of an incrementing nonce valuelibraryNonceUtils{
// @dev get the current nonce value// @param dataStore DataStorefunctiongetCurrentNonce(DataStore dataStore) internalviewreturns (uint256) {
return dataStore.getUint(Keys.NONCE);
}
// @dev increment the current nonce value// @param dataStore DataStore// @return the new nonce valuefunctionincrementNonce(DataStore dataStore) internalreturns (uint256) {
return dataStore.incrementUint(Keys.NONCE, 1);
}
// @dev convenience function to create a bytes32 hash using the next nonce// it would be possible to use the nonce directly as an ID / key// however, for positions the key is a bytes32 value based on a hash of// the position values// so bytes32 is used instead for a standard key type// @param dataStore DataStore// @return bytes32 hash using the next nonce valuefunctiongetNextKey(DataStore dataStore) internalreturns (bytes32) {
uint256 nonce = incrementNonce(dataStore);
bytes32 key = getKey(dataStore, nonce);
return key;
}
functiongetCurrentKey(DataStore dataStore) internalviewreturns (bytes32) {
uint256 nonce = getCurrentNonce(dataStore);
bytes32 key = getKey(dataStore, nonce);
return key;
}
functiongetKey(DataStore dataStore, uint256 nonce) internalpurereturns (bytes32) {
returnkeccak256(abi.encode(address(dataStore), nonce));
}
}
Contract Source Code
File 67 of 103: Oracle.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import { AggregatorV2V3Interface } from"@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV2V3Interface.sol";
import"../role/RoleModule.sol";
import"./OracleUtils.sol";
import"./IOracleProvider.sol";
import"./ChainlinkPriceFeedUtils.sol";
import"../price/Price.sol";
import"../chain/Chain.sol";
import"../data/DataStore.sol";
import"../data/Keys.sol";
import"../event/EventEmitter.sol";
import"../event/EventUtils.sol";
import"../utils/Precision.sol";
import"../utils/Cast.sol";
import"../utils/Uint256Mask.sol";
// @title Oracle// @dev Contract to validate and store signed values// Some calculations e.g. calculating the size in tokens for a position// may not work with zero / negative prices// as a result, zero / negative prices are considered empty / invalid// A market may need to be manually settled in this casecontractOracleisRoleModule{
usingEnumerableSetforEnumerableSet.AddressSet;
usingEnumerableValuesforEnumerableSet.AddressSet;
usingPriceforPrice.Props;
usingUint256MaskforUint256Mask.Mask;
usingEventUtilsforEventUtils.AddressItems;
usingEventUtilsforEventUtils.UintItems;
usingEventUtilsforEventUtils.IntItems;
usingEventUtilsforEventUtils.BoolItems;
usingEventUtilsforEventUtils.Bytes32Items;
usingEventUtilsforEventUtils.BytesItems;
usingEventUtilsforEventUtils.StringItems;
DataStore publicimmutable dataStore;
EventEmitter publicimmutable eventEmitter;
AggregatorV2V3Interface publicimmutable sequencerUptimeFeed;
// tokensWithPrices stores the tokens with prices that have been set// this is used in clearAllPrices to help ensure that all token prices// set in setPrices are cleared after use
EnumerableSet.AddressSet internal tokensWithPrices;
mapping(address=> Price.Props) public primaryPrices;
uint256public minTimestamp;
uint256public maxTimestamp;
constructor(
RoleStore _roleStore,
DataStore _dataStore,
EventEmitter _eventEmitter,
AggregatorV2V3Interface _sequencerUptimeFeed
) RoleModule(_roleStore) {
dataStore = _dataStore;
eventEmitter = _eventEmitter;
sequencerUptimeFeed = _sequencerUptimeFeed;
}
// this can be used to help ensure that on-chain prices are updated// before actions dependent on those on-chain prices are allowed// additionally, this can also be used to provide a grace period for// users to top up collateral before liquidations occurfunctionvalidateSequencerUp() externalview{
if (address(sequencerUptimeFeed) ==address(0)) {
return;
}
(
/*uint80 roundID*/,
int256 answer,
uint256 startedAt,
/*uint256 updatedAt*/,
/*uint80 answeredInRound*/
) = sequencerUptimeFeed.latestRoundData();
// answer == 0: sequencer is up// answer == 1: sequencer is downbool isSequencerUp = answer ==0;
if (!isSequencerUp) {
revert Errors.SequencerDown();
}
uint256 sequencerGraceDuration = dataStore.getUint(Keys.SEQUENCER_GRACE_DURATION);
// ensure the grace duration has passed after the// sequencer is back up.uint256 timeSinceUp =block.timestamp- startedAt;
if (timeSinceUp <= sequencerGraceDuration) {
revert Errors.SequencerGraceDurationNotYetPassed(timeSinceUp, sequencerGraceDuration);
}
}
functionsetPrices(
OracleUtils.SetPricesParams memory params
) externalonlyController{
OracleUtils.ValidatedPrice[] memory prices = _validatePrices(params, false);
_setPrices(prices);
}
functionsetPricesForAtomicAction(
OracleUtils.SetPricesParams memory params
) externalonlyController{
OracleUtils.ValidatedPrice[] memory prices = _validatePrices(params, true);
_setPrices(prices);
}
// @dev set the primary price// @param token the token to set the price for// @param price the price value to set tofunctionsetPrimaryPrice(address token, Price.Props memory price) externalonlyController{
_setPrimaryPrice(token, price);
}
functionsetTimestamps(uint256 _minTimestamp, uint256 _maxTimestamp) externalonlyController{
minTimestamp = _minTimestamp;
maxTimestamp = _maxTimestamp;
}
// @dev clear all pricesfunctionclearAllPrices() externalonlyController{
uint256 length = tokensWithPrices.length();
for (uint256 i; i < length; i++) {
address token = tokensWithPrices.at(0);
_removePrimaryPrice(token);
}
minTimestamp =0;
maxTimestamp =0;
}
// @dev get the length of tokensWithPrices// @return the length of tokensWithPricesfunctiongetTokensWithPricesCount() externalviewreturns (uint256) {
return tokensWithPrices.length();
}
// @dev get the tokens of tokensWithPrices for the specified indexes// @param start the start index, the value for this index will be included// @param end the end index, the value for this index will not be included// @return the tokens of tokensWithPrices for the specified indexesfunctiongetTokensWithPrices(uint256 start, uint256 end) externalviewreturns (address[] memory) {
return tokensWithPrices.valuesAt(start, end);
}
// @dev get the primary price of a token// @param token the token to get the price for// @return the primary price of a tokenfunctiongetPrimaryPrice(address token) externalviewreturns (Price.Props memory) {
if (token ==address(0)) { return Price.Props(0, 0); }
Price.Props memory price = primaryPrices[token];
if (price.isEmpty()) {
revert Errors.EmptyPrimaryPrice(token);
}
return price;
}
functionvalidatePrices(
OracleUtils.SetPricesParams memory params,
bool forAtomicAction
) externalonlyControllerreturns (OracleUtils.ValidatedPrice[] memory) {
return _validatePrices(params, forAtomicAction);
}
// @dev validate and set prices// @param params OracleUtils.SetPricesParamsfunction_setPrices(
OracleUtils.ValidatedPrice[] memory prices
) internalreturns (OracleUtils.ValidatedPrice[] memory) {
if (tokensWithPrices.length() !=0) {
revert Errors.NonEmptyTokensWithPrices(tokensWithPrices.length());
}
if (prices.length==0) {
revert Errors.EmptyValidatedPrices();
}
uint256 _minTimestamp = prices[0].timestamp;
uint256 _maxTimestamp = prices[0].timestamp;
for (uint256 i; i < prices.length; i++) {
OracleUtils.ValidatedPrice memory validatedPrice = prices[i];
_setPrimaryPrice(validatedPrice.token, Price.Props(
validatedPrice.min,
validatedPrice.max
));
if (validatedPrice.timestamp < _minTimestamp) {
_minTimestamp = validatedPrice.timestamp;
}
if (validatedPrice.timestamp > _maxTimestamp) {
_maxTimestamp = validatedPrice.timestamp;
}
_emitOraclePriceUpdated(
validatedPrice.token,
validatedPrice.min,
validatedPrice.max,
validatedPrice.timestamp,
validatedPrice.provider
);
}
uint256 maxRange = dataStore.getUint(Keys.MAX_ORACLE_TIMESTAMP_RANGE);
if (_maxTimestamp - _minTimestamp > maxRange) {
revert Errors.MaxOracleTimestampRangeExceeded(_maxTimestamp - _minTimestamp, maxRange);
}
minTimestamp = _minTimestamp;
maxTimestamp = _maxTimestamp;
return prices;
}
function_validatePrices(
OracleUtils.SetPricesParams memory params,
bool forAtomicAction
) internalreturns (OracleUtils.ValidatedPrice[] memory) {
if (params.tokens.length!= params.providers.length) {
revert Errors.InvalidOracleSetPricesProvidersParam(params.tokens.length, params.providers.length);
}
if (params.tokens.length!= params.data.length) {
revert Errors.InvalidOracleSetPricesDataParam(params.tokens.length, params.data.length);
}
OracleUtils.ValidatedPrice[] memory prices =new OracleUtils.ValidatedPrice[](params.tokens.length);
uint256 maxPriceAge = dataStore.getUint(Keys.MAX_ORACLE_PRICE_AGE);
uint256 maxRefPriceDeviationFactor = dataStore.getUint(Keys.MAX_ORACLE_REF_PRICE_DEVIATION_FACTOR);
for (uint256 i; i < params.tokens.length; i++) {
address provider = params.providers[i];
if (!dataStore.getBool(Keys.isOracleProviderEnabledKey(provider))) {
revert Errors.InvalidOracleProvider(provider);
}
address token = params.tokens[i];
bool isAtomicProvider = dataStore.getBool(Keys.isAtomicOracleProviderKey(provider));
// if the action is atomic then only validate that the provider is an// atomic provider// else, validate that the provider matches the oracleProviderForToken//// since for atomic actions, any atomic provider can be used, it is// recommended that only one atomic provider is configured per token// otherwise there is a risk that if there is a difference in pricing// between atomic oracle providers for a token, a user could use that// to gain a profit by alternating actions between the two atomic// providersif (forAtomicAction) {
if (!isAtomicProvider) {
revert Errors.NonAtomicOracleProvider(provider);
}
} else {
address expectedProvider = dataStore.getAddress(Keys.oracleProviderForTokenKey(token));
if (provider != expectedProvider) {
revert Errors.InvalidOracleProviderForToken(provider, expectedProvider);
}
}
bytesmemory data = params.data[i];
OracleUtils.ValidatedPrice memory validatedPrice = IOracleProvider(provider).getOraclePrice(
token,
data
);
// for atomic providers, the timestamp will be the current block's timestamp// the timestamp should not be adjustedif (!isAtomicProvider) {
uint256 timestampAdjustment = dataStore.getUint(Keys.oracleTimestampAdjustmentKey(provider, token));
validatedPrice.timestamp -= timestampAdjustment;
}
if (validatedPrice.timestamp + maxPriceAge < Chain.currentTimestamp()) {
revert Errors.MaxPriceAgeExceeded(validatedPrice.timestamp, Chain.currentTimestamp());
}
// for atomic providers, assume that Chainlink would be the main provider// so it would be redundant to re-fetch the Chainlink price for validationif (!isAtomicProvider) {
(bool hasRefPrice, uint256 refPrice) = ChainlinkPriceFeedUtils.getPriceFeedPrice(dataStore, token);
if (hasRefPrice) {
_validateRefPrice(
token,
validatedPrice.min,
refPrice,
maxRefPriceDeviationFactor
);
_validateRefPrice(
token,
validatedPrice.max,
refPrice,
maxRefPriceDeviationFactor
);
}
}
prices[i] = validatedPrice;
}
return prices;
}
function_validateRefPrice(address token,
uint256 price,
uint256 refPrice,
uint256 maxRefPriceDeviationFactor
) internalpure{
uint256 diff = Calc.diff(price, refPrice);
uint256 diffFactor = Precision.toFactor(diff, refPrice);
if (diffFactor > maxRefPriceDeviationFactor) {
revert Errors.MaxRefPriceDeviationExceeded(
token,
price,
refPrice,
maxRefPriceDeviationFactor
);
}
}
function_setPrimaryPrice(address token, Price.Props memory price) internal{
if (price.min> price.max) {
revert Errors.InvalidMinMaxForPrice(token, price.min, price.max);
}
Price.Props memory existingPrice = primaryPrices[token];
if (!existingPrice.isEmpty()) {
revert Errors.PriceAlreadySet(token, existingPrice.min, existingPrice.max);
}
primaryPrices[token] = price;
tokensWithPrices.add(token);
}
function_removePrimaryPrice(address token) internal{
delete primaryPrices[token];
tokensWithPrices.remove(token);
}
function_emitOraclePriceUpdated(address token,
uint256 minPrice,
uint256 maxPrice,
uint256 timestamp,
address provider
) internal{
EventUtils.EventLogData memory eventData;
eventData.addressItems.initItems(2);
eventData.addressItems.setItem(0, "token", token);
eventData.addressItems.setItem(1, "provider", provider);
eventData.uintItems.initItems(3);
eventData.uintItems.setItem(0, "minPrice", minPrice);
eventData.uintItems.setItem(1, "maxPrice", maxPrice);
eventData.uintItems.setItem(2, "timestamp", timestamp);
eventEmitter.emitEventLog1(
"OraclePriceUpdate",
Cast.toBytes32(token),
eventData
);
}
}
Contract Source Code
File 68 of 103: OracleModule.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"./Oracle.sol";
// @title OracleModule// @dev Provides convenience functions for interacting with the OraclecontractOracleModule{
Oracle publicimmutable oracle;
constructor(Oracle _oracle) {
oracle = _oracle;
}
// @dev sets oracle prices, perform any additional tasks required,// and clear the oracle prices after//// care should be taken to avoid re-entrancy while using this call// since re-entrancy could allow functions to be called with prices// meant for a different type of transaction// the tokensWithPrices.length check in oracle.setPrices should help// mitigate this//// @param params OracleUtils.SetPricesParamsmodifierwithOraclePrices(
OracleUtils.SetPricesParams memory params
) {
oracle.setPrices(params);
_;
oracle.clearAllPrices();
}
modifierwithOraclePricesForAtomicAction(
OracleUtils.SetPricesParams memory params
) {
oracle.setPricesForAtomicAction(params);
_;
oracle.clearAllPrices();
}
// @dev set oracle prices for a simulation// tokensWithPrices is not set in this function// it is possible for withSimulatedOraclePrices to be called and a function// using withOraclePrices to be called after// or for a function using withOraclePrices to be called and withSimulatedOraclePrices// called after// this should not cause an issue because this transaction should always revert// and any state changes based on simulated prices as well as the setting of simulated// prices should not be persisted// @param params OracleUtils.SimulatePricesParamsmodifierwithSimulatedOraclePrices(
OracleUtils.SimulatePricesParams memory params
) {
if (params.primaryTokens.length!= params.primaryPrices.length) {
revert Errors.InvalidPrimaryPricesForSimulation(params.primaryTokens.length, params.primaryPrices.length);
}
for (uint256 i; i < params.primaryTokens.length; i++) {
address token = params.primaryTokens[i];
Price.Props memory price = params.primaryPrices[i];
oracle.setPrimaryPrice(token, price);
}
oracle.setTimestamps(params.minTimestamp, params.maxTimestamp);
_;
revert Errors.EndOfOracleSimulation();
}
}
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"../chain/Chain.sol";
// @title Order// @dev Struct for orderslibraryOrder{
usingOrderforProps;
enumOrderType {
// @dev MarketSwap: swap token A to token B at the current market price// the order will be cancelled if the minOutputAmount cannot be fulfilled
MarketSwap,
// @dev LimitSwap: swap token A to token B if the minOutputAmount can be fulfilled
LimitSwap,
// @dev MarketIncrease: increase position at the current market price// the order will be cancelled if the position cannot be increased at the acceptablePrice
MarketIncrease,
// @dev LimitIncrease: increase position if the triggerPrice is reached and the acceptablePrice can be fulfilled
LimitIncrease,
// @dev MarketDecrease: decrease position at the current market price// the order will be cancelled if the position cannot be decreased at the acceptablePrice
MarketDecrease,
// @dev LimitDecrease: decrease position if the triggerPrice is reached and the acceptablePrice can be fulfilled
LimitDecrease,
// @dev StopLossDecrease: decrease position if the triggerPrice is reached and the acceptablePrice can be fulfilled
StopLossDecrease,
// @dev Liquidation: allows liquidation of positions if the criteria for liquidation are met
Liquidation,
// @dev StopIncrease: increase position if the triggerPrice is reached and the acceptablePrice can be fulfilled
StopIncrease
}
// to help further differentiate ordersenumSecondaryOrderType {
None,
Adl
}
enumDecreasePositionSwapType {
NoSwap,
SwapPnlTokenToCollateralToken,
SwapCollateralTokenToPnlToken
}
// @dev there is a limit on the number of fields a struct can have when being passed// or returned as a memory variable which can cause "Stack too deep" errors// use sub-structs to avoid this issue// @param addresses address values// @param numbers number values// @param flags boolean valuesstructProps {
Addresses addresses;
Numbers numbers;
Flags flags;
}
// @param account the account of the order// @param receiver the receiver for any token transfers// this field is meant to allow the output of an order to be// received by an address that is different from the creator of the// order whether this is for swaps or whether the account is the owner// of a position// for funding fees and claimable collateral, the funds are still// credited to the owner of the position indicated by order.account// @param callbackContract the contract to call for callbacks// @param uiFeeReceiver the ui fee receiver// @param market the trading market// @param initialCollateralToken for increase orders, initialCollateralToken// is the token sent in by the user, the token will be swapped through the// specified swapPath, before being deposited into the position as collateral// for decrease orders, initialCollateralToken is the collateral token of the position// withdrawn collateral from the decrease of the position will be swapped// through the specified swapPath// for swaps, initialCollateralToken is the initial token sent for the swap// @param swapPath an array of market addresses to swap throughstructAddresses {
address account;
address receiver;
address cancellationReceiver;
address callbackContract;
address uiFeeReceiver;
address market;
address initialCollateralToken;
address[] swapPath;
}
// @param sizeDeltaUsd the requested change in position size// @param initialCollateralDeltaAmount for increase orders, initialCollateralDeltaAmount// is the amount of the initialCollateralToken sent in by the user// for decrease orders, initialCollateralDeltaAmount is the amount of the position's// collateralToken to withdraw// for swaps, initialCollateralDeltaAmount is the amount of initialCollateralToken sent// in for the swap// @param orderType the order type// @param triggerPrice the trigger price for non-market orders// @param acceptablePrice the acceptable execution price for increase / decrease orders// @param executionFee the execution fee for keepers// @param callbackGasLimit the gas limit for the callbackContract// @param minOutputAmount the minimum output amount for decrease orders and swaps// note that for decrease orders, multiple tokens could be received, for this reason, the// minOutputAmount value is treated as a USD value for validation in decrease ordersstructNumbers {
OrderType orderType;
DecreasePositionSwapType decreasePositionSwapType;
uint256 sizeDeltaUsd;
uint256 initialCollateralDeltaAmount;
uint256 triggerPrice;
uint256 acceptablePrice;
uint256 executionFee;
uint256 callbackGasLimit;
uint256 minOutputAmount;
uint256 updatedAtTime;
uint256 validFromTime;
}
// @param isLong whether the order is for a long or short// @param shouldUnwrapNativeToken whether to unwrap native tokens before// transferring to the user// @param isFrozen whether the order is frozenstructFlags {
bool isLong;
bool shouldUnwrapNativeToken;
bool isFrozen;
bool autoCancel;
}
// @dev the order account// @param props Props// @return the order accountfunctionaccount(Props memory props) internalpurereturns (address) {
return props.addresses.account;
}
// @dev set the order account// @param props Props// @param value the value to set tofunctionsetAccount(Props memory props, address value) internalpure{
props.addresses.account = value;
}
// @dev the order receiver// @param props Props// @return the order receiverfunctionreceiver(Props memory props) internalpurereturns (address) {
return props.addresses.receiver;
}
// @dev set the order receiver// @param props Props// @param value the value to set tofunctionsetReceiver(Props memory props, address value) internalpure{
props.addresses.receiver = value;
}
functioncancellationReceiver(Props memory props) internalpurereturns (address) {
return props.addresses.cancellationReceiver;
}
functionsetCancellationReceiver(Props memory props, address value) internalpure{
props.addresses.cancellationReceiver = value;
}
// @dev the order callbackContract// @param props Props// @return the order callbackContractfunctioncallbackContract(Props memory props) internalpurereturns (address) {
return props.addresses.callbackContract;
}
// @dev set the order callbackContract// @param props Props// @param value the value to set tofunctionsetCallbackContract(Props memory props, address value) internalpure{
props.addresses.callbackContract = value;
}
// @dev the order market// @param props Props// @return the order marketfunctionmarket(Props memory props) internalpurereturns (address) {
return props.addresses.market;
}
// @dev set the order market// @param props Props// @param value the value to set tofunctionsetMarket(Props memory props, address value) internalpure{
props.addresses.market = value;
}
// @dev the order initialCollateralToken// @param props Props// @return the order initialCollateralTokenfunctioninitialCollateralToken(Props memory props) internalpurereturns (address) {
return props.addresses.initialCollateralToken;
}
// @dev set the order initialCollateralToken// @param props Props// @param value the value to set tofunctionsetInitialCollateralToken(Props memory props, address value) internalpure{
props.addresses.initialCollateralToken = value;
}
// @dev the order uiFeeReceiver// @param props Props// @return the order uiFeeReceiverfunctionuiFeeReceiver(Props memory props) internalpurereturns (address) {
return props.addresses.uiFeeReceiver;
}
// @dev set the order uiFeeReceiver// @param props Props// @param value the value to set tofunctionsetUiFeeReceiver(Props memory props, address value) internalpure{
props.addresses.uiFeeReceiver = value;
}
// @dev the order swapPath// @param props Props// @return the order swapPathfunctionswapPath(Props memory props) internalpurereturns (address[] memory) {
return props.addresses.swapPath;
}
// @dev set the order swapPath// @param props Props// @param value the value to set tofunctionsetSwapPath(Props memory props, address[] memory value) internalpure{
props.addresses.swapPath = value;
}
// @dev the order type// @param props Props// @return the order typefunctionorderType(Props memory props) internalpurereturns (OrderType) {
return props.numbers.orderType;
}
// @dev set the order type// @param props Props// @param value the value to set tofunctionsetOrderType(Props memory props, OrderType value) internalpure{
props.numbers.orderType = value;
}
functiondecreasePositionSwapType(Props memory props) internalpurereturns (DecreasePositionSwapType) {
return props.numbers.decreasePositionSwapType;
}
functionsetDecreasePositionSwapType(Props memory props, DecreasePositionSwapType value) internalpure{
props.numbers.decreasePositionSwapType = value;
}
// @dev the order sizeDeltaUsd// @param props Props// @return the order sizeDeltaUsdfunctionsizeDeltaUsd(Props memory props) internalpurereturns (uint256) {
return props.numbers.sizeDeltaUsd;
}
// @dev set the order sizeDeltaUsd// @param props Props// @param value the value to set tofunctionsetSizeDeltaUsd(Props memory props, uint256 value) internalpure{
props.numbers.sizeDeltaUsd = value;
}
// @dev the order initialCollateralDeltaAmount// @param props Props// @return the order initialCollateralDeltaAmountfunctioninitialCollateralDeltaAmount(Props memory props) internalpurereturns (uint256) {
return props.numbers.initialCollateralDeltaAmount;
}
// @dev set the order initialCollateralDeltaAmount// @param props Props// @param value the value to set tofunctionsetInitialCollateralDeltaAmount(Props memory props, uint256 value) internalpure{
props.numbers.initialCollateralDeltaAmount = value;
}
// @dev the order triggerPrice// @param props Props// @return the order triggerPricefunctiontriggerPrice(Props memory props) internalpurereturns (uint256) {
return props.numbers.triggerPrice;
}
// @dev set the order triggerPrice// @param props Props// @param value the value to set tofunctionsetTriggerPrice(Props memory props, uint256 value) internalpure{
props.numbers.triggerPrice = value;
}
// @dev the order acceptablePrice// @param props Props// @return the order acceptablePricefunctionacceptablePrice(Props memory props) internalpurereturns (uint256) {
return props.numbers.acceptablePrice;
}
// @dev set the order acceptablePrice// @param props Props// @param value the value to set tofunctionsetAcceptablePrice(Props memory props, uint256 value) internalpure{
props.numbers.acceptablePrice = value;
}
// @dev set the order executionFee// @param props Props// @param value the value to set tofunctionsetExecutionFee(Props memory props, uint256 value) internalpure{
props.numbers.executionFee = value;
}
// @dev the order executionFee// @param props Props// @return the order executionFeefunctionexecutionFee(Props memory props) internalpurereturns (uint256) {
return props.numbers.executionFee;
}
// @dev the order callbackGasLimit// @param props Props// @return the order callbackGasLimitfunctioncallbackGasLimit(Props memory props) internalpurereturns (uint256) {
return props.numbers.callbackGasLimit;
}
// @dev set the order callbackGasLimit// @param props Props// @param value the value to set tofunctionsetCallbackGasLimit(Props memory props, uint256 value) internalpure{
props.numbers.callbackGasLimit = value;
}
// @dev the order minOutputAmount// @param props Props// @return the order minOutputAmountfunctionminOutputAmount(Props memory props) internalpurereturns (uint256) {
return props.numbers.minOutputAmount;
}
// @dev set the order minOutputAmount// @param props Props// @param value the value to set tofunctionsetMinOutputAmount(Props memory props, uint256 value) internalpure{
props.numbers.minOutputAmount = value;
}
// @dev the order updatedAtTime// @param props Props// @return the order updatedAtTimefunctionupdatedAtTime(Props memory props) internalpurereturns (uint256) {
return props.numbers.updatedAtTime;
}
// @dev set the order updatedAtTime// @param props Props// @param value the value to set tofunctionsetUpdatedAtTime(Props memory props, uint256 value) internalpure{
props.numbers.updatedAtTime = value;
}
functionvalidFromTime(Props memory props) internalpurereturns (uint256) {
return props.numbers.validFromTime;
}
functionsetValidFromTime(Props memory props, uint256 value) internalpure{
props.numbers.validFromTime = value;
}
// @dev whether the order is for a long or short// @param props Props// @return whether the order is for a long or shortfunctionisLong(Props memory props) internalpurereturns (bool) {
return props.flags.isLong;
}
// @dev set whether the order is for a long or short// @param props Props// @param value the value to set tofunctionsetIsLong(Props memory props, bool value) internalpure{
props.flags.isLong = value;
}
// @dev whether to unwrap the native token before transfers to the user// @param props Props// @return whether to unwrap the native token before transfers to the userfunctionshouldUnwrapNativeToken(Props memory props) internalpurereturns (bool) {
return props.flags.shouldUnwrapNativeToken;
}
// @dev set whether the native token should be unwrapped before being// transferred to the receiver// @param props Props// @param value the value to set tofunctionsetShouldUnwrapNativeToken(Props memory props, bool value) internalpure{
props.flags.shouldUnwrapNativeToken = value;
}
// @dev whether the order is frozen// @param props Props// @return whether the order is frozenfunctionisFrozen(Props memory props) internalpurereturns (bool) {
return props.flags.isFrozen;
}
// @dev set whether the order is frozen// transferred to the receiver// @param props Props// @param value the value to set tofunctionsetIsFrozen(Props memory props, bool value) internalpure{
props.flags.isFrozen = value;
}
functionautoCancel(Props memory props) internalpurereturns (bool) {
return props.flags.autoCancel;
}
functionsetAutoCancel(Props memory props, bool value) internalpure{
props.flags.autoCancel = value;
}
// @param props Propsfunctiontouch(Props memory props) internalview{
props.setUpdatedAtTime(Chain.currentTimestamp());
}
}
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"./BaseOrderHandler.sol";
import"../error/ErrorUtils.sol";
import"./IOrderHandler.sol";
import"../order/OrderUtils.sol";
import"../order/ExecuteOrderUtils.sol";
// @title OrderHandler// @dev Contract to handle creation, execution and cancellation of orderscontractOrderHandlerisIOrderHandler, BaseOrderHandler{
usingSafeCastforuint256;
usingOrderforOrder.Props;
usingArrayforuint256[];
constructor(
RoleStore _roleStore,
DataStore _dataStore,
EventEmitter _eventEmitter,
Oracle _oracle,
OrderVault _orderVault,
SwapHandler _swapHandler,
IReferralStorage _referralStorage
) BaseOrderHandler(
_roleStore,
_dataStore,
_eventEmitter,
_oracle,
_orderVault,
_swapHandler,
_referralStorage
) {}
// @dev creates an order in the order store// @param account the order's account// @param params BaseOrderUtils.CreateOrderParamsfunctioncreateOrder(address account,
IBaseOrderUtils.CreateOrderParams calldata params
) externaloverrideglobalNonReentrantonlyControllerreturns (bytes32) {
FeatureUtils.validateFeature(dataStore, Keys.createOrderFeatureDisabledKey(address(this), uint256(params.orderType)));
return OrderUtils.createOrder(
dataStore,
eventEmitter,
orderVault,
referralStorage,
account,
params
);
}
/**
* @dev Updates the given order with the specified size delta, acceptable price, and trigger price.
* The `updateOrder()` feature must be enabled for the given order type. The caller must be the owner
* of the order, and the order must not be a market order. The size delta, trigger price, and
* acceptable price are updated on the order, and the order is unfrozen. Any additional WNT that is
* transferred to the contract is added to the order's execution fee. The updated order is then saved
* in the order store, and an `OrderUpdated` event is emitted.
*
* A user may be able to observe exchange prices and prevent order execution by updating the order's
* trigger price or acceptable price
*
* The main front-running concern is if a user knows whether the price is going to move up or down
* then positions accordingly, e.g. if price is going to move up then the user opens a long position
*
* With updating of orders, a user may know that price could be lower and delays the execution of an
* order by updating it, this should not be a significant front-running concern since it is similar
* to observing prices then creating a market order as price is decreasing
*
* @param key The unique ID of the order to be updated
* @param sizeDeltaUsd The new size delta for the order
* @param acceptablePrice The new acceptable price for the order
* @param triggerPrice The new trigger price for the order
*/functionupdateOrder(bytes32 key,
uint256 sizeDeltaUsd,
uint256 acceptablePrice,
uint256 triggerPrice,
uint256 minOutputAmount,
uint256 validFromTime,
bool autoCancel,
Order.Props memory order
) externaloverrideglobalNonReentrantonlyController{
FeatureUtils.validateFeature(dataStore, Keys.updateOrderFeatureDisabledKey(address(this), uint256(order.orderType())));
if (BaseOrderUtils.isMarketOrder(order.orderType())) {
revert Errors.OrderNotUpdatable(uint256(order.orderType()));
}
// this could happen if the order was created in new contracts that support new order types// but the order is being updated in old contractsif (!BaseOrderUtils.isSupportedOrder(order.orderType())) {
revert Errors.UnsupportedOrderType(uint256(order.orderType()));
}
// allow topping up of executionFee as frozen orders// will have their executionFee reducedaddress wnt = TokenUtils.wnt(dataStore);
uint256 receivedWnt = orderVault.recordTransferIn(wnt);
order.setExecutionFee(order.executionFee() + receivedWnt);
uint256 estimatedGasLimit = GasUtils.estimateExecuteOrderGasLimit(dataStore, order);
uint256 oraclePriceCount = GasUtils.estimateOrderOraclePriceCount(order.swapPath().length);
GasUtils.validateExecutionFee(dataStore, estimatedGasLimit, order.executionFee(), oraclePriceCount);
if (order.autoCancel() != autoCancel) {
OrderUtils.updateAutoCancelList(dataStore, key, order, autoCancel);
OrderUtils.validateTotalCallbackGasLimitForAutoCancelOrders(dataStore, order);
}
order.setAutoCancel(autoCancel);
order.setSizeDeltaUsd(sizeDeltaUsd);
order.setTriggerPrice(triggerPrice);
order.setAcceptablePrice(acceptablePrice);
order.setMinOutputAmount(minOutputAmount);
order.setValidFromTime(validFromTime);
order.setIsFrozen(false);
order.touch();
BaseOrderUtils.validateNonEmptyOrder(order);
OrderStoreUtils.set(dataStore, key, order);
OrderEventUtils.emitOrderUpdated(
eventEmitter,
key,
order
);
}
/**
* @dev Cancels the given order. The `cancelOrder()` feature must be enabled for the given order
* type. The caller must be the owner of the order. The order is cancelled by calling the `cancelOrder()`
* function in the `OrderUtils` contract. This function also records the starting gas amount and the
* reason for cancellation, which is passed to the `cancelOrder()` function.
*
* @param key The unique ID of the order to be cancelled
*/functioncancelOrder(bytes32 key) externaloverrideglobalNonReentrantonlyController{
uint256 startingGas =gasleft();
DataStore _dataStore = dataStore;
Order.Props memory order = OrderStoreUtils.get(_dataStore, key);
FeatureUtils.validateFeature(_dataStore, Keys.cancelOrderFeatureDisabledKey(address(this), uint256(order.orderType())));
if (BaseOrderUtils.isMarketOrder(order.orderType())) {
validateRequestCancellation(
order.updatedAtTime(),
"Order"
);
}
OrderUtils.cancelOrder(
OrderUtils.CancelOrderParams(
dataStore,
eventEmitter,
orderVault,
key,
order.account(),
startingGas,
true, // isExternalCall
Keys.USER_INITIATED_CANCEL,
""
)
);
}
// @dev simulate execution of an order to check for any errors// @param key the order key// @param params OracleUtils.SimulatePricesParamsfunctionsimulateExecuteOrder(bytes32 key,
OracleUtils.SimulatePricesParams memory params
) externaloverrideonlyControllerwithSimulatedOraclePrices(params)
globalNonReentrant{
Order.Props memory order = OrderStoreUtils.get(dataStore, key);
this._executeOrder(
key,
order,
msg.sender
);
}
// @dev executes an order// @param key the key of the order to execute// @param oracleParams OracleUtils.SetPricesParamsfunctionexecuteOrder(bytes32 key,
OracleUtils.SetPricesParams calldata oracleParams
) externalglobalNonReentrantonlyOrderKeeperwithOraclePrices(oracleParams)
{
uint256 startingGas =gasleft();
Order.Props memory order = OrderStoreUtils.get(dataStore, key);
uint256 estimatedGasLimit = GasUtils.estimateExecuteOrderGasLimit(dataStore, order);
GasUtils.validateExecutionGas(dataStore, startingGas, estimatedGasLimit);
uint256 executionGas = GasUtils.getExecutionGas(dataStore, startingGas);
trythis._executeOrder{ gas: executionGas }(
key,
order,
msg.sender
) {
} catch (bytesmemory reasonBytes) {
_handleOrderError(key, startingGas, reasonBytes);
}
}
// @dev executes an order// @param key the key of the order to execute// @param oracleParams OracleUtils.SetPricesParams// @param keeper the keeper executing the order// @param startingGas the starting gasfunction_executeOrder(bytes32 key,
Order.Props memory order,
address keeper
) externalonlySelf{
uint256 startingGas =gasleft();
BaseOrderUtils.ExecuteOrderParams memory params = _getExecuteOrderParams(
key,
order,
keeper,
startingGas,
Order.SecondaryOrderType.None
);
// limit swaps require frozen order keeper for execution since on creation it can fail due to output amount// which would automatically cause the order to be frozen// limit increase and limit / trigger decrease orders may fail due to output amount as well and become frozen// but only if their acceptablePrice is reachedif (params.order.isFrozen() || params.order.orderType() == Order.OrderType.LimitSwap) {
_validateFrozenOrderKeeper(keeper);
}
FeatureUtils.validateFeature(params.contracts.dataStore, Keys.executeOrderFeatureDisabledKey(address(this), uint256(params.order.orderType())));
ExecuteOrderUtils.executeOrder(params);
}
// @dev handle a caught order error// @param key the order's key// @param startingGas the starting gas// @param reason the error reason// @param reasonKey the hash or the error reasonfunction_handleOrderError(bytes32 key,
uint256 startingGas,
bytesmemory reasonBytes
) internal{
GasUtils.validateExecutionErrorGas(dataStore, reasonBytes);
bytes4 errorSelector = ErrorUtils.getErrorSelectorFromData(reasonBytes);
validateNonKeeperError(errorSelector, reasonBytes);
Order.Props memory order = OrderStoreUtils.get(dataStore, key);
bool isMarketOrder = BaseOrderUtils.isMarketOrder(order.orderType());
if (
// if the order is already frozen, revert with the custom error to provide more information// on why the order cannot be executed
order.isFrozen() ||// for market orders, the EmptyPosition error should still lead to the// order being cancelled// for limit, trigger orders, the EmptyPosition error should lead to the transaction// being reverted instead// if the position is created or increased later, the oracle prices used to fulfill the order// must be after the position was last increased, this is validated in DecreaseOrderUtils
(!isMarketOrder && errorSelector == Errors.EmptyPosition.selector) ||
errorSelector == Errors.EmptyOrder.selector||// if the order execution feature is disabled, it may be possible// for a user to cancel their orders after the feature is re-enabled// or they may be able to execute the order at an outdated price// depending on the order keeper// disabling of features should be a rare occurrence, it may be// preferrable to still execute the orders when the feature is re-enabled// instead of cancelling / freezing the orders// if features are not frequently disabled, the amount of front-running// from this should not be significant// based on this it may also be advisable to disable the cancelling of orders// if the execution of orders is disabled
errorSelector == Errors.InvalidKeeperForFrozenOrder.selector||
errorSelector == Errors.UnsupportedOrderType.selector||// the transaction is reverted for InvalidOrderPrices since the oracle prices// do not fulfill the specified trigger price
errorSelector == Errors.InvalidOrderPrices.selector||// order should not be cancelled or frozen in this case// otherwise malicious keepers can cancel orders before valid from time is reached
errorSelector == Errors.OrderValidFromTimeNotReached.selector
) {
ErrorUtils.revertWithCustomError(reasonBytes);
}
(stringmemory reason, /* bool hasRevertMessage */) = ErrorUtils.getRevertMessage(reasonBytes);
if (
isMarketOrder ||
errorSelector == Errors.InvalidPositionMarket.selector||
errorSelector == Errors.InvalidCollateralTokenForMarket.selector||
errorSelector == Errors.InvalidPositionSizeValues.selector
) {
OrderUtils.cancelOrder(
OrderUtils.CancelOrderParams(
dataStore,
eventEmitter,
orderVault,
key,
msg.sender,
startingGas,
true, // isExternalCall
reason,
reasonBytes
)
);
return;
}
// freeze unfulfillable orders to prevent the order system from being gamed// an example of gaming would be if a user creates a limit order// with size greater than the available amount in the pool// the user waits for their limit price to be hit, and if price// moves in their favour after, they can deposit into the pool// to allow the order to be executed then close the order for a profit//// frozen order keepers are expected to execute orders only if the// latest prices match the trigger price//// a user can also call updateOrder to unfreeze an order
OrderUtils.freezeOrder(
dataStore,
eventEmitter,
orderVault,
key,
msg.sender,
startingGas,
reason,
reasonBytes
);
}
// @dev validate that the keeper is a frozen order keeper// @param keeper address of the keeperfunction_validateFrozenOrderKeeper(address keeper) internalview{
if (!roleStore.hasRole(keeper, Role.FROZEN_ORDER_KEEPER)) {
revert Errors.InvalidKeeperForFrozenOrder(keeper);
}
}
}
// SPDX-License-Identifier: Unlicensepragmasolidity >=0.8.4;/// @notice Emitted when the result overflows uint256.errorPRBMath__MulDivFixedPointOverflow(uint256 prod1);
/// @notice Emitted when the result overflows uint256.errorPRBMath__MulDivOverflow(uint256 prod1, uint256 denominator);
/// @notice Emitted when one of the inputs is type(int256).min.errorPRBMath__MulDivSignedInputTooSmall();
/// @notice Emitted when the intermediary absolute result overflows int256.errorPRBMath__MulDivSignedOverflow(uint256 rAbs);
/// @notice Emitted when the input is MIN_SD59x18.errorPRBMathSD59x18__AbsInputTooSmall();
/// @notice Emitted when ceiling a number overflows SD59x18.errorPRBMathSD59x18__CeilOverflow(int256 x);
/// @notice Emitted when one of the inputs is MIN_SD59x18.errorPRBMathSD59x18__DivInputTooSmall();
/// @notice Emitted when one of the intermediary unsigned results overflows SD59x18.errorPRBMathSD59x18__DivOverflow(uint256 rAbs);
/// @notice Emitted when the input is greater than 133.084258667509499441.errorPRBMathSD59x18__ExpInputTooBig(int256 x);
/// @notice Emitted when the input is greater than 192.errorPRBMathSD59x18__Exp2InputTooBig(int256 x);
/// @notice Emitted when flooring a number underflows SD59x18.errorPRBMathSD59x18__FloorUnderflow(int256 x);
/// @notice Emitted when converting a basic integer to the fixed-point format overflows SD59x18.errorPRBMathSD59x18__FromIntOverflow(int256 x);
/// @notice Emitted when converting a basic integer to the fixed-point format underflows SD59x18.errorPRBMathSD59x18__FromIntUnderflow(int256 x);
/// @notice Emitted when the product of the inputs is negative.errorPRBMathSD59x18__GmNegativeProduct(int256 x, int256 y);
/// @notice Emitted when multiplying the inputs overflows SD59x18.errorPRBMathSD59x18__GmOverflow(int256 x, int256 y);
/// @notice Emitted when the input is less than or equal to zero.errorPRBMathSD59x18__LogInputTooSmall(int256 x);
/// @notice Emitted when one of the inputs is MIN_SD59x18.errorPRBMathSD59x18__MulInputTooSmall();
/// @notice Emitted when the intermediary absolute result overflows SD59x18.errorPRBMathSD59x18__MulOverflow(uint256 rAbs);
/// @notice Emitted when the intermediary absolute result overflows SD59x18.errorPRBMathSD59x18__PowuOverflow(uint256 rAbs);
/// @notice Emitted when the input is negative.errorPRBMathSD59x18__SqrtNegativeInput(int256 x);
/// @notice Emitted when the calculating the square root overflows SD59x18.errorPRBMathSD59x18__SqrtOverflow(int256 x);
/// @notice Emitted when addition overflows UD60x18.errorPRBMathUD60x18__AddOverflow(uint256 x, uint256 y);
/// @notice Emitted when ceiling a number overflows UD60x18.errorPRBMathUD60x18__CeilOverflow(uint256 x);
/// @notice Emitted when the input is greater than 133.084258667509499441.errorPRBMathUD60x18__ExpInputTooBig(uint256 x);
/// @notice Emitted when the input is greater than 192.errorPRBMathUD60x18__Exp2InputTooBig(uint256 x);
/// @notice Emitted when converting a basic integer to the fixed-point format format overflows UD60x18.errorPRBMathUD60x18__FromUintOverflow(uint256 x);
/// @notice Emitted when multiplying the inputs overflows UD60x18.errorPRBMathUD60x18__GmOverflow(uint256 x, uint256 y);
/// @notice Emitted when the input is less than 1.errorPRBMathUD60x18__LogInputTooSmall(uint256 x);
/// @notice Emitted when the calculating the square root overflows UD60x18.errorPRBMathUD60x18__SqrtOverflow(uint256 x);
/// @notice Emitted when subtraction underflows UD60x18.errorPRBMathUD60x18__SubUnderflow(uint256 x, uint256 y);
/// @dev Common mathematical functions used in both PRBMathSD59x18 and PRBMathUD60x18. Note that this shared library/// does not always assume the signed 59.18-decimal fixed-point or the unsigned 60.18-decimal fixed-point/// representation. When it does not, it is explicitly mentioned in the NatSpec documentation.libraryPRBMath{
/// STRUCTS ///structSD59x18 {
int256 value;
}
structUD60x18 {
uint256 value;
}
/// STORAGE ////// @dev How many trailing decimals can be represented.uint256internalconstant SCALE =1e18;
/// @dev Largest power of two divisor of SCALE.uint256internalconstant SCALE_LPOTD =262144;
/// @dev SCALE inverted mod 2^256.uint256internalconstant SCALE_INVERSE =78156646155174841979727994598816262306175212592076161876661_508869554232690281;
/// FUNCTIONS ////// @notice Calculates the binary exponent of x using the binary fraction method./// @dev Has to use 192.64-bit fixed-point numbers./// See https://ethereum.stackexchange.com/a/96594/24693./// @param x The exponent as an unsigned 192.64-bit fixed-point number./// @return result The result as an unsigned 60.18-decimal fixed-point number.functionexp2(uint256 x) internalpurereturns (uint256 result) {
unchecked {
// Start from 0.5 in the 192.64-bit fixed-point format.
result =0x800000000000000000000000000000000000000000000000;
// Multiply the result by root(2, 2^-i) when the bit at position i is 1. None of the intermediary results overflows// because the initial result is 2^191 and all magic factors are less than 2^65.if (x &0x8000000000000000>0) {
result = (result *0x16A09E667F3BCC909) >>64;
}
if (x &0x4000000000000000>0) {
result = (result *0x1306FE0A31B7152DF) >>64;
}
if (x &0x2000000000000000>0) {
result = (result *0x1172B83C7D517ADCE) >>64;
}
if (x &0x1000000000000000>0) {
result = (result *0x10B5586CF9890F62A) >>64;
}
if (x &0x800000000000000>0) {
result = (result *0x1059B0D31585743AE) >>64;
}
if (x &0x400000000000000>0) {
result = (result *0x102C9A3E778060EE7) >>64;
}
if (x &0x200000000000000>0) {
result = (result *0x10163DA9FB33356D8) >>64;
}
if (x &0x100000000000000>0) {
result = (result *0x100B1AFA5ABCBED61) >>64;
}
if (x &0x80000000000000>0) {
result = (result *0x10058C86DA1C09EA2) >>64;
}
if (x &0x40000000000000>0) {
result = (result *0x1002C605E2E8CEC50) >>64;
}
if (x &0x20000000000000>0) {
result = (result *0x100162F3904051FA1) >>64;
}
if (x &0x10000000000000>0) {
result = (result *0x1000B175EFFDC76BA) >>64;
}
if (x &0x8000000000000>0) {
result = (result *0x100058BA01FB9F96D) >>64;
}
if (x &0x4000000000000>0) {
result = (result *0x10002C5CC37DA9492) >>64;
}
if (x &0x2000000000000>0) {
result = (result *0x1000162E525EE0547) >>64;
}
if (x &0x1000000000000>0) {
result = (result *0x10000B17255775C04) >>64;
}
if (x &0x800000000000>0) {
result = (result *0x1000058B91B5BC9AE) >>64;
}
if (x &0x400000000000>0) {
result = (result *0x100002C5C89D5EC6D) >>64;
}
if (x &0x200000000000>0) {
result = (result *0x10000162E43F4F831) >>64;
}
if (x &0x100000000000>0) {
result = (result *0x100000B1721BCFC9A) >>64;
}
if (x &0x80000000000>0) {
result = (result *0x10000058B90CF1E6E) >>64;
}
if (x &0x40000000000>0) {
result = (result *0x1000002C5C863B73F) >>64;
}
if (x &0x20000000000>0) {
result = (result *0x100000162E430E5A2) >>64;
}
if (x &0x10000000000>0) {
result = (result *0x1000000B172183551) >>64;
}
if (x &0x8000000000>0) {
result = (result *0x100000058B90C0B49) >>64;
}
if (x &0x4000000000>0) {
result = (result *0x10000002C5C8601CC) >>64;
}
if (x &0x2000000000>0) {
result = (result *0x1000000162E42FFF0) >>64;
}
if (x &0x1000000000>0) {
result = (result *0x10000000B17217FBB) >>64;
}
if (x &0x800000000>0) {
result = (result *0x1000000058B90BFCE) >>64;
}
if (x &0x400000000>0) {
result = (result *0x100000002C5C85FE3) >>64;
}
if (x &0x200000000>0) {
result = (result *0x10000000162E42FF1) >>64;
}
if (x &0x100000000>0) {
result = (result *0x100000000B17217F8) >>64;
}
if (x &0x80000000>0) {
result = (result *0x10000000058B90BFC) >>64;
}
if (x &0x40000000>0) {
result = (result *0x1000000002C5C85FE) >>64;
}
if (x &0x20000000>0) {
result = (result *0x100000000162E42FF) >>64;
}
if (x &0x10000000>0) {
result = (result *0x1000000000B17217F) >>64;
}
if (x &0x8000000>0) {
result = (result *0x100000000058B90C0) >>64;
}
if (x &0x4000000>0) {
result = (result *0x10000000002C5C860) >>64;
}
if (x &0x2000000>0) {
result = (result *0x1000000000162E430) >>64;
}
if (x &0x1000000>0) {
result = (result *0x10000000000B17218) >>64;
}
if (x &0x800000>0) {
result = (result *0x1000000000058B90C) >>64;
}
if (x &0x400000>0) {
result = (result *0x100000000002C5C86) >>64;
}
if (x &0x200000>0) {
result = (result *0x10000000000162E43) >>64;
}
if (x &0x100000>0) {
result = (result *0x100000000000B1721) >>64;
}
if (x &0x80000>0) {
result = (result *0x10000000000058B91) >>64;
}
if (x &0x40000>0) {
result = (result *0x1000000000002C5C8) >>64;
}
if (x &0x20000>0) {
result = (result *0x100000000000162E4) >>64;
}
if (x &0x10000>0) {
result = (result *0x1000000000000B172) >>64;
}
if (x &0x8000>0) {
result = (result *0x100000000000058B9) >>64;
}
if (x &0x4000>0) {
result = (result *0x10000000000002C5D) >>64;
}
if (x &0x2000>0) {
result = (result *0x1000000000000162E) >>64;
}
if (x &0x1000>0) {
result = (result *0x10000000000000B17) >>64;
}
if (x &0x800>0) {
result = (result *0x1000000000000058C) >>64;
}
if (x &0x400>0) {
result = (result *0x100000000000002C6) >>64;
}
if (x &0x200>0) {
result = (result *0x10000000000000163) >>64;
}
if (x &0x100>0) {
result = (result *0x100000000000000B1) >>64;
}
if (x &0x80>0) {
result = (result *0x10000000000000059) >>64;
}
if (x &0x40>0) {
result = (result *0x1000000000000002C) >>64;
}
if (x &0x20>0) {
result = (result *0x10000000000000016) >>64;
}
if (x &0x10>0) {
result = (result *0x1000000000000000B) >>64;
}
if (x &0x8>0) {
result = (result *0x10000000000000006) >>64;
}
if (x &0x4>0) {
result = (result *0x10000000000000003) >>64;
}
if (x &0x2>0) {
result = (result *0x10000000000000001) >>64;
}
if (x &0x1>0) {
result = (result *0x10000000000000001) >>64;
}
// We're doing two things at the same time://// 1. Multiply the result by 2^n + 1, where "2^n" is the integer part and the one is added to account for// the fact that we initially set the result to 0.5. This is accomplished by subtracting from 191// rather than 192.// 2. Convert the result to the unsigned 60.18-decimal fixed-point format.//// This works because 2^(191-ip) = 2^ip / 2^191, where "ip" is the integer part "2^n".
result *= SCALE;
result >>= (191- (x >>64));
}
}
/// @notice Finds the zero-based index of the first one in the binary representation of x./// @dev See the note on msb in the "Find First Set" Wikipedia article https://en.wikipedia.org/wiki/Find_first_set/// @param x The uint256 number for which to find the index of the most significant bit./// @return msb The index of the most significant bit as an uint256.functionmostSignificantBit(uint256 x) internalpurereturns (uint256 msb) {
if (x >=2**128) {
x >>=128;
msb +=128;
}
if (x >=2**64) {
x >>=64;
msb +=64;
}
if (x >=2**32) {
x >>=32;
msb +=32;
}
if (x >=2**16) {
x >>=16;
msb +=16;
}
if (x >=2**8) {
x >>=8;
msb +=8;
}
if (x >=2**4) {
x >>=4;
msb +=4;
}
if (x >=2**2) {
x >>=2;
msb +=2;
}
if (x >=2**1) {
// No need to shift x any more.
msb +=1;
}
}
/// @notice Calculates floor(x*y÷denominator) with full precision.////// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv.////// Requirements:/// - The denominator cannot be zero./// - The result must fit within uint256.////// Caveats:/// - This function does not work with fixed-point numbers.////// @param x The multiplicand as an uint256./// @param y The multiplier as an uint256./// @param denominator The divisor as an uint256./// @return result The result as an uint256.functionmulDiv(uint256 x,
uint256 y,
uint256 denominator
) internalpurereturns (uint256 result) {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256// variables such that product = prod1 * 2^256 + prod0.uint256 prod0; // Least significant 256 bits of the productuint256 prod1; // Most significant 256 bits of the productassembly {
let mm :=mulmod(x, y, not(0))
prod0 :=mul(x, y)
prod1 :=sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.if (prod1 ==0) {
unchecked {
result = prod0 / denominator;
}
return result;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.if (prod1 >= denominator) {
revert PRBMath__MulDivOverflow(prod1, denominator);
}
///////////////////////////////////////////////// 512 by 256 division.///////////////////////////////////////////////// Make division exact by subtracting the remainder from [prod1 prod0].uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder :=mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 :=sub(prod1, gt(remainder, prod0))
prod0 :=sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.// See https://cs.stackexchange.com/q/138556/92363.unchecked {
// Does not overflow because the denominator cannot be zero at this stage in the function.uint256 lpotdod = denominator & (~denominator +1);
assembly {
// Divide denominator by lpotdod.
denominator :=div(denominator, lpotdod)
// Divide [prod1 prod0] by lpotdod.
prod0 :=div(prod0, lpotdod)
// Flip lpotdod such that it is 2^256 / lpotdod. If lpotdod is zero, then it becomes one.
lpotdod :=add(div(sub(0, lpotdod), lpotdod), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * lpotdod;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for// four bits. That is, denominator * inv = 1 mod 2^4.uint256 inverse = (3* denominator) ^2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works// in modular arithmetic, doubling the correct bits in each step.
inverse *=2- denominator * inverse; // inverse mod 2^8
inverse *=2- denominator * inverse; // inverse mod 2^16
inverse *=2- denominator * inverse; // inverse mod 2^32
inverse *=2- denominator * inverse; // inverse mod 2^64
inverse *=2- denominator * inverse; // inverse mod 2^128
inverse *=2- denominator * inverse; // inverse mod 2^256// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1// is no longer required.
result = prod0 * inverse;
return result;
}
}
/// @notice Calculates floor(x*y÷1e18) with full precision.////// @dev Variant of "mulDiv" with constant folding, i.e. in which the denominator is always 1e18. Before returning the/// final result, we add 1 if (x * y) % SCALE >= HALF_SCALE. Without this, 6.6e-19 would be truncated to 0 instead of/// being rounded to 1e-18. See "Listing 6" and text above it at https://accu.org/index.php/journals/1717.////// Requirements:/// - The result must fit within uint256.////// Caveats:/// - The body is purposely left uncommented; see the NatSpec comments in "PRBMath.mulDiv" to understand how this works./// - It is assumed that the result can never be type(uint256).max when x and y solve the following two equations:/// 1. x * y = type(uint256).max * SCALE/// 2. (x * y) % SCALE >= SCALE / 2////// @param x The multiplicand as an unsigned 60.18-decimal fixed-point number./// @param y The multiplier as an unsigned 60.18-decimal fixed-point number./// @return result The result as an unsigned 60.18-decimal fixed-point number.functionmulDivFixedPoint(uint256 x, uint256 y) internalpurereturns (uint256 result) {
uint256 prod0;
uint256 prod1;
assembly {
let mm :=mulmod(x, y, not(0))
prod0 :=mul(x, y)
prod1 :=sub(sub(mm, prod0), lt(mm, prod0))
}
if (prod1 >= SCALE) {
revert PRBMath__MulDivFixedPointOverflow(prod1);
}
uint256 remainder;
uint256 roundUpUnit;
assembly {
remainder :=mulmod(x, y, SCALE)
roundUpUnit :=gt(remainder, 499999999999999999)
}
if (prod1 ==0) {
unchecked {
result = (prod0 / SCALE) + roundUpUnit;
return result;
}
}
assembly {
result :=add(
mul(
or(
div(sub(prod0, remainder), SCALE_LPOTD),
mul(sub(prod1, gt(remainder, prod0)), add(div(sub(0, SCALE_LPOTD), SCALE_LPOTD), 1))
),
SCALE_INVERSE
),
roundUpUnit
)
}
}
/// @notice Calculates floor(x*y÷denominator) with full precision.////// @dev An extension of "mulDiv" for signed numbers. Works by computing the signs and the absolute values separately.////// Requirements:/// - None of the inputs can be type(int256).min./// - The result must fit within int256.////// @param x The multiplicand as an int256./// @param y The multiplier as an int256./// @param denominator The divisor as an int256./// @return result The result as an int256.functionmulDivSigned(int256 x,
int256 y,
int256 denominator
) internalpurereturns (int256 result) {
if (x ==type(int256).min|| y ==type(int256).min|| denominator ==type(int256).min) {
revert PRBMath__MulDivSignedInputTooSmall();
}
// Get hold of the absolute values of x, y and the denominator.uint256 ax;
uint256 ay;
uint256 ad;
unchecked {
ax = x <0 ? uint256(-x) : uint256(x);
ay = y <0 ? uint256(-y) : uint256(y);
ad = denominator <0 ? uint256(-denominator) : uint256(denominator);
}
// Compute the absolute value of (x*y)÷denominator. The result must fit within int256.uint256 rAbs = mulDiv(ax, ay, ad);
if (rAbs >uint256(type(int256).max)) {
revert PRBMath__MulDivSignedOverflow(rAbs);
}
// Get the signs of x, y and the denominator.uint256 sx;
uint256 sy;
uint256 sd;
assembly {
sx :=sgt(x, sub(0, 1))
sy :=sgt(y, sub(0, 1))
sd :=sgt(denominator, sub(0, 1))
}
// XOR over sx, sy and sd. This is checking whether there are one or three negative signs in the inputs.// If yes, the result should be negative.
result = sx ^ sy ^ sd ==0 ? -int256(rAbs) : int256(rAbs);
}
/// @notice Calculates the square root of x, rounding down./// @dev Uses the Babylonian method https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method.////// Caveats:/// - This function does not work with fixed-point numbers.////// @param x The uint256 number for which to calculate the square root./// @return result The result as an uint256.functionsqrt(uint256 x) internalpurereturns (uint256 result) {
if (x ==0) {
return0;
}
// Set the initial guess to the least power of two that is greater than or equal to sqrt(x).uint256 xAux =uint256(x);
result =1;
if (xAux >=0x100000000000000000000000000000000) {
xAux >>=128;
result <<=64;
}
if (xAux >=0x10000000000000000) {
xAux >>=64;
result <<=32;
}
if (xAux >=0x100000000) {
xAux >>=32;
result <<=16;
}
if (xAux >=0x10000) {
xAux >>=16;
result <<=8;
}
if (xAux >=0x100) {
xAux >>=8;
result <<=4;
}
if (xAux >=0x10) {
xAux >>=4;
result <<=2;
}
if (xAux >=0x8) {
result <<=1;
}
// The operations can never overflow because the result is max 2^127 when it enters this block.unchecked {
result = (result + x / result) >>1;
result = (result + x / result) >>1;
result = (result + x / result) >>1;
result = (result + x / result) >>1;
result = (result + x / result) >>1;
result = (result + x / result) >>1;
result = (result + x / result) >>1; // Seven iterations should be enoughuint256 roundedDownResult = x / result;
return result >= roundedDownResult ? roundedDownResult : result;
}
}
}
Contract Source Code
File 77 of 103: PRBMathUD60x18.sol
// SPDX-License-Identifier: Unlicensepragmasolidity >=0.8.4;import"./PRBMath.sol";
/// @title PRBMathUD60x18/// @author Paul Razvan Berg/// @notice Smart contract library for advanced fixed-point math that works with uint256 numbers considered to have 18/// trailing decimals. We call this number representation unsigned 60.18-decimal fixed-point, since there can be up to 60/// digits in the integer part and up to 18 decimals in the fractional part. The numbers are bound by the minimum and the/// maximum values permitted by the Solidity type uint256.libraryPRBMathUD60x18{
/// @dev Half the SCALE number.uint256internalconstant HALF_SCALE =5e17;
/// @dev log2(e) as an unsigned 60.18-decimal fixed-point number.uint256internalconstant LOG2_E =1_442695040888963407;
/// @dev The maximum value an unsigned 60.18-decimal fixed-point number can have.uint256internalconstant MAX_UD60x18 =115792089237316195423570985008687907853269984665640564039457_584007913129639935;
/// @dev The maximum whole value an unsigned 60.18-decimal fixed-point number can have.uint256internalconstant MAX_WHOLE_UD60x18 =115792089237316195423570985008687907853269984665640564039457_000000000000000000;
/// @dev How many trailing decimals can be represented.uint256internalconstant SCALE =1e18;
/// @notice Calculates the arithmetic average of x and y, rounding down./// @param x The first operand as an unsigned 60.18-decimal fixed-point number./// @param y The second operand as an unsigned 60.18-decimal fixed-point number./// @return result The arithmetic average as an unsigned 60.18-decimal fixed-point number.functionavg(uint256 x, uint256 y) internalpurereturns (uint256 result) {
// The operations can never overflow.unchecked {
// The last operand checks if both x and y are odd and if that is the case, we add 1 to the result. We need// to do this because if both numbers are odd, the 0.5 remainder gets truncated twice.
result = (x >>1) + (y >>1) + (x & y &1);
}
}
/// @notice Yields the least unsigned 60.18 decimal fixed-point number greater than or equal to x.////// @dev Optimized for fractional value inputs, because for every whole value there are (1e18 - 1) fractional counterparts./// See https://en.wikipedia.org/wiki/Floor_and_ceiling_functions.////// Requirements:/// - x must be less than or equal to MAX_WHOLE_UD60x18.////// @param x The unsigned 60.18-decimal fixed-point number to ceil./// @param result The least integer greater than or equal to x, as an unsigned 60.18-decimal fixed-point number.functionceil(uint256 x) internalpurereturns (uint256 result) {
if (x > MAX_WHOLE_UD60x18) {
revert PRBMathUD60x18__CeilOverflow(x);
}
assembly {
// Equivalent to "x % SCALE" but faster.let remainder :=mod(x, SCALE)
// Equivalent to "SCALE - remainder" but faster.let delta :=sub(SCALE, remainder)
// Equivalent to "x + delta * (remainder > 0 ? 1 : 0)" but faster.
result :=add(x, mul(delta, gt(remainder, 0)))
}
}
/// @notice Divides two unsigned 60.18-decimal fixed-point numbers, returning a new unsigned 60.18-decimal fixed-point number.////// @dev Uses mulDiv to enable overflow-safe multiplication and division.////// Requirements:/// - The denominator cannot be zero.////// @param x The numerator as an unsigned 60.18-decimal fixed-point number./// @param y The denominator as an unsigned 60.18-decimal fixed-point number./// @param result The quotient as an unsigned 60.18-decimal fixed-point number.functiondiv(uint256 x, uint256 y) internalpurereturns (uint256 result) {
result = PRBMath.mulDiv(x, SCALE, y);
}
/// @notice Returns Euler's number as an unsigned 60.18-decimal fixed-point number./// @dev See https://en.wikipedia.org/wiki/E_(mathematical_constant).functione() internalpurereturns (uint256 result) {
result =2_718281828459045235;
}
/// @notice Calculates the natural exponent of x.////// @dev Based on the insight that e^x = 2^(x * log2(e)).////// Requirements:/// - All from "log2"./// - x must be less than 133.084258667509499441.////// @param x The exponent as an unsigned 60.18-decimal fixed-point number./// @return result The result as an unsigned 60.18-decimal fixed-point number.functionexp(uint256 x) internalpurereturns (uint256 result) {
// Without this check, the value passed to "exp2" would be greater than 192.if (x >=133_084258667509499441) {
revert PRBMathUD60x18__ExpInputTooBig(x);
}
// Do the fixed-point multiplication inline to save gas.unchecked {
uint256 doubleScaleProduct = x * LOG2_E;
result = exp2((doubleScaleProduct + HALF_SCALE) / SCALE);
}
}
/// @notice Calculates the binary exponent of x using the binary fraction method.////// @dev See https://ethereum.stackexchange.com/q/79903/24693.////// Requirements:/// - x must be 192 or less./// - The result must fit within MAX_UD60x18.////// @param x The exponent as an unsigned 60.18-decimal fixed-point number./// @return result The result as an unsigned 60.18-decimal fixed-point number.functionexp2(uint256 x) internalpurereturns (uint256 result) {
// 2^192 doesn't fit within the 192.64-bit format used internally in this function.if (x >=192e18) {
revert PRBMathUD60x18__Exp2InputTooBig(x);
}
unchecked {
// Convert x to the 192.64-bit fixed-point format.uint256 x192x64 = (x <<64) / SCALE;
// Pass x to the PRBMath.exp2 function, which uses the 192.64-bit fixed-point number representation.
result = PRBMath.exp2(x192x64);
}
}
/// @notice Yields the greatest unsigned 60.18 decimal fixed-point number less than or equal to x./// @dev Optimized for fractional value inputs, because for every whole value there are (1e18 - 1) fractional counterparts./// See https://en.wikipedia.org/wiki/Floor_and_ceiling_functions./// @param x The unsigned 60.18-decimal fixed-point number to floor./// @param result The greatest integer less than or equal to x, as an unsigned 60.18-decimal fixed-point number.functionfloor(uint256 x) internalpurereturns (uint256 result) {
assembly {
// Equivalent to "x % SCALE" but faster.let remainder :=mod(x, SCALE)
// Equivalent to "x - remainder * (remainder > 0 ? 1 : 0)" but faster.
result :=sub(x, mul(remainder, gt(remainder, 0)))
}
}
/// @notice Yields the excess beyond the floor of x./// @dev Based on the odd function definition https://en.wikipedia.org/wiki/Fractional_part./// @param x The unsigned 60.18-decimal fixed-point number to get the fractional part of./// @param result The fractional part of x as an unsigned 60.18-decimal fixed-point number.functionfrac(uint256 x) internalpurereturns (uint256 result) {
assembly {
result :=mod(x, SCALE)
}
}
/// @notice Converts a number from basic integer form to unsigned 60.18-decimal fixed-point representation.////// @dev Requirements:/// - x must be less than or equal to MAX_UD60x18 divided by SCALE.////// @param x The basic integer to convert./// @param result The same number in unsigned 60.18-decimal fixed-point representation.functionfromUint(uint256 x) internalpurereturns (uint256 result) {
unchecked {
if (x > MAX_UD60x18 / SCALE) {
revert PRBMathUD60x18__FromUintOverflow(x);
}
result = x * SCALE;
}
}
/// @notice Calculates geometric mean of x and y, i.e. sqrt(x * y), rounding down.////// @dev Requirements:/// - x * y must fit within MAX_UD60x18, lest it overflows.////// @param x The first operand as an unsigned 60.18-decimal fixed-point number./// @param y The second operand as an unsigned 60.18-decimal fixed-point number./// @return result The result as an unsigned 60.18-decimal fixed-point number.functiongm(uint256 x, uint256 y) internalpurereturns (uint256 result) {
if (x ==0) {
return0;
}
unchecked {
// Checking for overflow this way is faster than letting Solidity do it.uint256 xy = x * y;
if (xy / x != y) {
revert PRBMathUD60x18__GmOverflow(x, y);
}
// We don't need to multiply by the SCALE here because the x*y product had already picked up a factor of SCALE// during multiplication. See the comments within the "sqrt" function.
result = PRBMath.sqrt(xy);
}
}
/// @notice Calculates 1 / x, rounding toward zero.////// @dev Requirements:/// - x cannot be zero.////// @param x The unsigned 60.18-decimal fixed-point number for which to calculate the inverse./// @return result The inverse as an unsigned 60.18-decimal fixed-point number.functioninv(uint256 x) internalpurereturns (uint256 result) {
unchecked {
// 1e36 is SCALE * SCALE.
result =1e36/ x;
}
}
/// @notice Calculates the natural logarithm of x.////// @dev Based on the insight that ln(x) = log2(x) / log2(e).////// Requirements:/// - All from "log2".////// Caveats:/// - All from "log2"./// - This doesn't return exactly 1 for 2.718281828459045235, for that we would need more fine-grained precision.////// @param x The unsigned 60.18-decimal fixed-point number for which to calculate the natural logarithm./// @return result The natural logarithm as an unsigned 60.18-decimal fixed-point number.functionln(uint256 x) internalpurereturns (uint256 result) {
// Do the fixed-point multiplication inline to save gas. This is overflow-safe because the maximum value that log2(x)// can return is 196205294292027477728.unchecked {
result = (log2(x) * SCALE) / LOG2_E;
}
}
/// @notice Calculates the common logarithm of x.////// @dev First checks if x is an exact power of ten and it stops if yes. If it's not, calculates the common/// logarithm based on the insight that log10(x) = log2(x) / log2(10).////// Requirements:/// - All from "log2".////// Caveats:/// - All from "log2".////// @param x The unsigned 60.18-decimal fixed-point number for which to calculate the common logarithm./// @return result The common logarithm as an unsigned 60.18-decimal fixed-point number.functionlog10(uint256 x) internalpurereturns (uint256 result) {
if (x < SCALE) {
revert PRBMathUD60x18__LogInputTooSmall(x);
}
// Note that the "mul" in this block is the assembly multiplication operation, not the "mul" function defined// in this contract.// prettier-ignoreassembly {
switch x
case1 { result :=mul(SCALE, sub(0, 18)) }
case10 { result :=mul(SCALE, sub(1, 18)) }
case100 { result :=mul(SCALE, sub(2, 18)) }
case1000 { result :=mul(SCALE, sub(3, 18)) }
case10000 { result :=mul(SCALE, sub(4, 18)) }
case100000 { result :=mul(SCALE, sub(5, 18)) }
case1000000 { result :=mul(SCALE, sub(6, 18)) }
case10000000 { result :=mul(SCALE, sub(7, 18)) }
case100000000 { result :=mul(SCALE, sub(8, 18)) }
case1000000000 { result :=mul(SCALE, sub(9, 18)) }
case10000000000 { result :=mul(SCALE, sub(10, 18)) }
case100000000000 { result :=mul(SCALE, sub(11, 18)) }
case1000000000000 { result :=mul(SCALE, sub(12, 18)) }
case10000000000000 { result :=mul(SCALE, sub(13, 18)) }
case100000000000000 { result :=mul(SCALE, sub(14, 18)) }
case1000000000000000 { result :=mul(SCALE, sub(15, 18)) }
case10000000000000000 { result :=mul(SCALE, sub(16, 18)) }
case100000000000000000 { result :=mul(SCALE, sub(17, 18)) }
case1000000000000000000 { result :=0 }
case10000000000000000000 { result := SCALE }
case100000000000000000000 { result :=mul(SCALE, 2) }
case1000000000000000000000 { result :=mul(SCALE, 3) }
case10000000000000000000000 { result :=mul(SCALE, 4) }
case100000000000000000000000 { result :=mul(SCALE, 5) }
case1000000000000000000000000 { result :=mul(SCALE, 6) }
case10000000000000000000000000 { result :=mul(SCALE, 7) }
case100000000000000000000000000 { result :=mul(SCALE, 8) }
case1000000000000000000000000000 { result :=mul(SCALE, 9) }
case10000000000000000000000000000 { result :=mul(SCALE, 10) }
case100000000000000000000000000000 { result :=mul(SCALE, 11) }
case1000000000000000000000000000000 { result :=mul(SCALE, 12) }
case10000000000000000000000000000000 { result :=mul(SCALE, 13) }
case100000000000000000000000000000000 { result :=mul(SCALE, 14) }
case1000000000000000000000000000000000 { result :=mul(SCALE, 15) }
case10000000000000000000000000000000000 { result :=mul(SCALE, 16) }
case100000000000000000000000000000000000 { result :=mul(SCALE, 17) }
case1000000000000000000000000000000000000 { result :=mul(SCALE, 18) }
case10000000000000000000000000000000000000 { result :=mul(SCALE, 19) }
case100000000000000000000000000000000000000 { result :=mul(SCALE, 20) }
case1000000000000000000000000000000000000000 { result :=mul(SCALE, 21) }
case10000000000000000000000000000000000000000 { result :=mul(SCALE, 22) }
case100000000000000000000000000000000000000000 { result :=mul(SCALE, 23) }
case1000000000000000000000000000000000000000000 { result :=mul(SCALE, 24) }
case10000000000000000000000000000000000000000000 { result :=mul(SCALE, 25) }
case100000000000000000000000000000000000000000000 { result :=mul(SCALE, 26) }
case1000000000000000000000000000000000000000000000 { result :=mul(SCALE, 27) }
case10000000000000000000000000000000000000000000000 { result :=mul(SCALE, 28) }
case100000000000000000000000000000000000000000000000 { result :=mul(SCALE, 29) }
case1000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 30) }
case10000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 31) }
case100000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 32) }
case1000000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 33) }
case10000000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 34) }
case100000000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 35) }
case1000000000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 36) }
case10000000000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 37) }
case100000000000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 38) }
case1000000000000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 39) }
case10000000000000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 40) }
case100000000000000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 41) }
case1000000000000000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 42) }
case10000000000000000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 43) }
case100000000000000000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 44) }
case1000000000000000000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 45) }
case10000000000000000000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 46) }
case100000000000000000000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 47) }
case1000000000000000000000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 48) }
case10000000000000000000000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 49) }
case100000000000000000000000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 50) }
case1000000000000000000000000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 51) }
case10000000000000000000000000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 52) }
case100000000000000000000000000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 53) }
case1000000000000000000000000000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 54) }
case10000000000000000000000000000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 55) }
case100000000000000000000000000000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 56) }
case1000000000000000000000000000000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 57) }
case10000000000000000000000000000000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 58) }
case100000000000000000000000000000000000000000000000000000000000000000000000000000 { result :=mul(SCALE, 59) }
default {
result := MAX_UD60x18
}
}
if (result == MAX_UD60x18) {
// Do the fixed-point division inline to save gas. The denominator is log2(10).unchecked {
result = (log2(x) * SCALE) /3_321928094887362347;
}
}
}
/// @notice Calculates the binary logarithm of x.////// @dev Based on the iterative approximation algorithm./// https://en.wikipedia.org/wiki/Binary_logarithm#Iterative_approximation////// Requirements:/// - x must be greater than or equal to SCALE, otherwise the result would be negative.////// Caveats:/// - The results are nor perfectly accurate to the last decimal, due to the lossy precision of the iterative approximation.////// @param x The unsigned 60.18-decimal fixed-point number for which to calculate the binary logarithm./// @return result The binary logarithm as an unsigned 60.18-decimal fixed-point number.functionlog2(uint256 x) internalpurereturns (uint256 result) {
if (x < SCALE) {
revert PRBMathUD60x18__LogInputTooSmall(x);
}
unchecked {
// Calculate the integer part of the logarithm and add it to the result and finally calculate y = x * 2^(-n).uint256 n = PRBMath.mostSignificantBit(x / SCALE);
// The integer part of the logarithm as an unsigned 60.18-decimal fixed-point number. The operation can't overflow// because n is maximum 255 and SCALE is 1e18.
result = n * SCALE;
// This is y = x * 2^(-n).uint256 y = x >> n;
// If y = 1, the fractional part is zero.if (y == SCALE) {
return result;
}
// Calculate the fractional part via the iterative approximation.// The "delta >>= 1" part is equivalent to "delta /= 2", but shifting bits is faster.for (uint256 delta = HALF_SCALE; delta >0; delta >>=1) {
y = (y * y) / SCALE;
// Is y^2 > 2 and so in the range [2,4)?if (y >=2* SCALE) {
// Add the 2^(-m) factor to the logarithm.
result += delta;
// Corresponds to z/2 on Wikipedia.
y >>=1;
}
}
}
}
/// @notice Multiplies two unsigned 60.18-decimal fixed-point numbers together, returning a new unsigned 60.18-decimal/// fixed-point number./// @dev See the documentation for the "PRBMath.mulDivFixedPoint" function./// @param x The multiplicand as an unsigned 60.18-decimal fixed-point number./// @param y The multiplier as an unsigned 60.18-decimal fixed-point number./// @return result The product as an unsigned 60.18-decimal fixed-point number.functionmul(uint256 x, uint256 y) internalpurereturns (uint256 result) {
result = PRBMath.mulDivFixedPoint(x, y);
}
/// @notice Returns PI as an unsigned 60.18-decimal fixed-point number.functionpi() internalpurereturns (uint256 result) {
result =3_141592653589793238;
}
/// @notice Raises x to the power of y.////// @dev Based on the insight that x^y = 2^(log2(x) * y).////// Requirements:/// - All from "exp2", "log2" and "mul".////// Caveats:/// - All from "exp2", "log2" and "mul"./// - Assumes 0^0 is 1.////// @param x Number to raise to given power y, as an unsigned 60.18-decimal fixed-point number./// @param y Exponent to raise x to, as an unsigned 60.18-decimal fixed-point number./// @return result x raised to power y, as an unsigned 60.18-decimal fixed-point number.functionpow(uint256 x, uint256 y) internalpurereturns (uint256 result) {
if (x ==0) {
result = y ==0 ? SCALE : uint256(0);
} else {
result = exp2(mul(log2(x), y));
}
}
/// @notice Raises x (unsigned 60.18-decimal fixed-point number) to the power of y (basic unsigned integer) using the/// famous algorithm "exponentiation by squaring".////// @dev See https://en.wikipedia.org/wiki/Exponentiation_by_squaring////// Requirements:/// - The result must fit within MAX_UD60x18.////// Caveats:/// - All from "mul"./// - Assumes 0^0 is 1.////// @param x The base as an unsigned 60.18-decimal fixed-point number./// @param y The exponent as an uint256./// @return result The result as an unsigned 60.18-decimal fixed-point number.functionpowu(uint256 x, uint256 y) internalpurereturns (uint256 result) {
// Calculate the first iteration of the loop in advance.
result = y &1>0 ? x : SCALE;
// Equivalent to "for(y /= 2; y > 0; y /= 2)" but faster.for (y >>=1; y >0; y >>=1) {
x = PRBMath.mulDivFixedPoint(x, x);
// Equivalent to "y % 2 == 1" but faster.if (y &1>0) {
result = PRBMath.mulDivFixedPoint(result, x);
}
}
}
/// @notice Returns 1 as an unsigned 60.18-decimal fixed-point number.functionscale() internalpurereturns (uint256 result) {
result = SCALE;
}
/// @notice Calculates the square root of x, rounding down./// @dev Uses the Babylonian method https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method.////// Requirements:/// - x must be less than MAX_UD60x18 / SCALE.////// @param x The unsigned 60.18-decimal fixed-point number for which to calculate the square root./// @return result The result as an unsigned 60.18-decimal fixed-point .functionsqrt(uint256 x) internalpurereturns (uint256 result) {
unchecked {
if (x > MAX_UD60x18 / SCALE) {
revert PRBMathUD60x18__SqrtOverflow(x);
}
// Multiply x by the SCALE to account for the factor of SCALE that is picked up when multiplying two unsigned// 60.18-decimal fixed-point numbers together (in this case, those two numbers are both the square root).
result = PRBMath.sqrt(x * SCALE);
}
}
/// @notice Converts a unsigned 60.18-decimal fixed-point number to basic integer form, rounding down in the process./// @param x The unsigned 60.18-decimal fixed-point number to convert./// @return result The same number in basic integer form.functiontoUint(uint256 x) internalpurereturns (uint256 result) {
unchecked {
result = x / SCALE;
}
}
}
Contract Source Code
File 78 of 103: Position.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;// @title Position// @dev Stuct for positions//// borrowing fees for position require only a borrowingFactor to track// an example on how this works is if the global cumulativeBorrowingFactor is 10020%// a position would be opened with borrowingFactor as 10020%// after some time, if the cumulativeBorrowingFactor is updated to 10025% the position would// owe 5% of the position size as borrowing fees// the total pending borrowing fees of all positions is factored into the calculation of the pool value for LPs// when a position is increased or decreased, the pending borrowing fees for the position is deducted from the position's// collateral and transferred into the LP pool//// the same borrowing fee factor tracking cannot be applied for funding fees as those calculations consider pending funding fees// based on the fiat value of the position sizes//// for example, if the price of the longToken is $2000 and a long position owes $200 in funding fees, the opposing short position// claims the funding fees of 0.1 longToken ($200), if the price of the longToken changes to $4000 later, the long position would// only owe 0.05 longToken ($200)// this would result in differences between the amounts deducted and amounts paid out, for this reason, the actual token amounts// to be deducted and to be paid out need to be tracked instead//// for funding fees, there are four values to consider:// 1. long positions with market.longToken as collateral// 2. long positions with market.shortToken as collateral// 3. short positions with market.longToken as collateral// 4. short positions with market.shortToken as collaterallibraryPosition{
// @dev there is a limit on the number of fields a struct can have when being passed// or returned as a memory variable which can cause "Stack too deep" errors// use sub-structs to avoid this issue// @param addresses address values// @param numbers number values// @param flags boolean valuesstructProps {
Addresses addresses;
Numbers numbers;
Flags flags;
}
// @param account the position's account// @param market the position's market// @param collateralToken the position's collateralTokenstructAddresses {
address account;
address market;
address collateralToken;
}
// @param sizeInUsd the position's size in USD// @param sizeInTokens the position's size in tokens// @param collateralAmount the amount of collateralToken for collateral// @param borrowingFactor the position's borrowing factor// @param fundingFeeAmountPerSize the position's funding fee per size// @param longTokenClaimableFundingAmountPerSize the position's claimable funding amount per size// for the market.longToken// @param shortTokenClaimableFundingAmountPerSize the position's claimable funding amount per size// for the market.shortToken// @param increasedAtTime the time at which this position was increased// @param decreasedAtTime the time at which this position was decreasedstructNumbers {
uint256 sizeInUsd;
uint256 sizeInTokens;
uint256 collateralAmount;
uint256 borrowingFactor;
uint256 fundingFeeAmountPerSize;
uint256 longTokenClaimableFundingAmountPerSize;
uint256 shortTokenClaimableFundingAmountPerSize;
uint256 increasedAtTime;
uint256 decreasedAtTime;
}
// @param isLong whether the position is a long or shortstructFlags {
bool isLong;
}
functionaccount(Props memory props) internalpurereturns (address) {
return props.addresses.account;
}
functionsetAccount(Props memory props, address value) internalpure{
props.addresses.account = value;
}
functionmarket(Props memory props) internalpurereturns (address) {
return props.addresses.market;
}
functionsetMarket(Props memory props, address value) internalpure{
props.addresses.market = value;
}
functioncollateralToken(Props memory props) internalpurereturns (address) {
return props.addresses.collateralToken;
}
functionsetCollateralToken(Props memory props, address value) internalpure{
props.addresses.collateralToken = value;
}
functionsizeInUsd(Props memory props) internalpurereturns (uint256) {
return props.numbers.sizeInUsd;
}
functionsetSizeInUsd(Props memory props, uint256 value) internalpure{
props.numbers.sizeInUsd = value;
}
functionsizeInTokens(Props memory props) internalpurereturns (uint256) {
return props.numbers.sizeInTokens;
}
functionsetSizeInTokens(Props memory props, uint256 value) internalpure{
props.numbers.sizeInTokens = value;
}
functioncollateralAmount(Props memory props) internalpurereturns (uint256) {
return props.numbers.collateralAmount;
}
functionsetCollateralAmount(Props memory props, uint256 value) internalpure{
props.numbers.collateralAmount = value;
}
functionborrowingFactor(Props memory props) internalpurereturns (uint256) {
return props.numbers.borrowingFactor;
}
functionsetBorrowingFactor(Props memory props, uint256 value) internalpure{
props.numbers.borrowingFactor = value;
}
functionfundingFeeAmountPerSize(Props memory props) internalpurereturns (uint256) {
return props.numbers.fundingFeeAmountPerSize;
}
functionsetFundingFeeAmountPerSize(Props memory props, uint256 value) internalpure{
props.numbers.fundingFeeAmountPerSize = value;
}
functionlongTokenClaimableFundingAmountPerSize(Props memory props) internalpurereturns (uint256) {
return props.numbers.longTokenClaimableFundingAmountPerSize;
}
functionsetLongTokenClaimableFundingAmountPerSize(Props memory props, uint256 value) internalpure{
props.numbers.longTokenClaimableFundingAmountPerSize = value;
}
functionshortTokenClaimableFundingAmountPerSize(Props memory props) internalpurereturns (uint256) {
return props.numbers.shortTokenClaimableFundingAmountPerSize;
}
functionsetShortTokenClaimableFundingAmountPerSize(Props memory props, uint256 value) internalpure{
props.numbers.shortTokenClaimableFundingAmountPerSize = value;
}
functionincreasedAtTime(Props memory props) internalpurereturns (uint256) {
return props.numbers.increasedAtTime;
}
functionsetIncreasedAtTime(Props memory props, uint256 value) internalpure{
props.numbers.increasedAtTime = value;
}
functiondecreasedAtTime(Props memory props) internalpurereturns (uint256) {
return props.numbers.decreasedAtTime;
}
functionsetDecreasedAtTime(Props memory props, uint256 value) internalpure{
props.numbers.decreasedAtTime = value;
}
functionisLong(Props memory props) internalpurereturns (bool) {
return props.flags.isLong;
}
functionsetIsLong(Props memory props, bool value) internalpure{
props.flags.isLong = value;
}
// @dev get the key for a position// @param account the position's account// @param market the position's market// @param collateralToken the position's collateralToken// @param isLong whether the position is long or short// @return the position keyfunctiongetPositionKey(address _account, address _market, address _collateralToken, bool _isLong) internalpurereturns (bytes32) {
bytes32 _key =keccak256(abi.encode(_account, _market, _collateralToken, _isLong));
return _key;
}
}
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"@openzeppelin/contracts/utils/math/SignedMath.sol";
import"../market/MarketUtils.sol";
import"../utils/Precision.sol";
import"../utils/Calc.sol";
import"./PricingUtils.sol";
import"../referral/IReferralStorage.sol";
import"../referral/ReferralUtils.sol";
// @title PositionPricingUtils// @dev Library for position pricing functionslibraryPositionPricingUtils{
usingSignedMathforint256;
usingSafeCastforuint256;
usingSafeCastforint256;
usingPositionforPosition.Props;
usingPriceforPrice.Props;
usingEventUtilsforEventUtils.AddressItems;
usingEventUtilsforEventUtils.UintItems;
usingEventUtilsforEventUtils.IntItems;
usingEventUtilsforEventUtils.BoolItems;
usingEventUtilsforEventUtils.Bytes32Items;
usingEventUtilsforEventUtils.BytesItems;
usingEventUtilsforEventUtils.StringItems;
structGetPositionFeesParams {
DataStore dataStore;
IReferralStorage referralStorage;
Position.Props position;
Price.Props collateralTokenPrice;
bool forPositiveImpact;
address longToken;
address shortToken;
uint256 sizeDeltaUsd;
address uiFeeReceiver;
bool isLiquidation;
}
// @dev GetPriceImpactUsdParams struct used in getPriceImpactUsd to avoid stack// too deep errors// @param dataStore DataStore// @param market the market to check// @param usdDelta the change in position size in USD// @param isLong whether the position is long or shortstructGetPriceImpactUsdParams {
DataStore dataStore;
Market.Props market;
int256 usdDelta;
bool isLong;
}
// @dev OpenInterestParams struct to contain open interest values// @param longOpenInterest the amount of long open interest// @param shortOpenInterest the amount of short open interest// @param nextLongOpenInterest the updated amount of long open interest// @param nextShortOpenInterest the updated amount of short open intereststructOpenInterestParams {
uint256 longOpenInterest;
uint256 shortOpenInterest;
uint256 nextLongOpenInterest;
uint256 nextShortOpenInterest;
}
// @dev PositionFees struct to contain fee values// @param feeReceiverAmount the amount for the fee receiver// @param feeAmountForPool the amount of fees for the pool// @param positionFeeAmountForPool the position fee amount for the pool// @param positionFeeAmount the fee amount for increasing / decreasing the position// @param borrowingFeeAmount the borrowing fee amount// @param totalCostAmount the total cost amount in tokensstructPositionFees {
PositionReferralFees referral;
PositionProFees pro;
PositionFundingFees funding;
PositionBorrowingFees borrowing;
PositionUiFees ui;
PositionLiquidationFees liquidation;
Price.Props collateralTokenPrice;
uint256 positionFeeFactor;
uint256 protocolFeeAmount;
uint256 positionFeeReceiverFactor;
uint256 feeReceiverAmount;
uint256 feeAmountForPool;
uint256 positionFeeAmountForPool;
uint256 positionFeeAmount;
uint256 totalCostAmountExcludingFunding;
uint256 totalCostAmount;
uint256 totalDiscountAmount;
}
structPositionProFees {
uint256 traderTier;
uint256 traderDiscountFactor;
uint256 traderDiscountAmount;
}
structPositionLiquidationFees {
uint256 liquidationFeeUsd;
uint256 liquidationFeeAmount;
uint256 liquidationFeeReceiverFactor;
uint256 liquidationFeeAmountForFeeReceiver;
}
// @param affiliate the referral affiliate of the trader// @param traderDiscountAmount the discount amount for the trader// @param affiliateRewardAmount the affiliate reward amountstructPositionReferralFees {
bytes32 referralCode;
address affiliate;
address trader;
uint256 totalRebateFactor;
uint256 affiliateRewardFactor;
uint256 adjustedAffiliateRewardFactor;
uint256 traderDiscountFactor;
uint256 totalRebateAmount;
uint256 traderDiscountAmount;
uint256 affiliateRewardAmount;
}
structPositionBorrowingFees {
uint256 borrowingFeeUsd;
uint256 borrowingFeeAmount;
uint256 borrowingFeeReceiverFactor;
uint256 borrowingFeeAmountForFeeReceiver;
}
// @param fundingFeeAmount the position's funding fee amount// @param claimableLongTokenAmount the negative funding fee in long token that is claimable// @param claimableShortTokenAmount the negative funding fee in short token that is claimable// @param latestLongTokenFundingAmountPerSize the latest long token funding// amount per size for the market// @param latestShortTokenFundingAmountPerSize the latest short token funding// amount per size for the marketstructPositionFundingFees {
uint256 fundingFeeAmount;
uint256 claimableLongTokenAmount;
uint256 claimableShortTokenAmount;
uint256 latestFundingFeeAmountPerSize;
uint256 latestLongTokenClaimableFundingAmountPerSize;
uint256 latestShortTokenClaimableFundingAmountPerSize;
}
structPositionUiFees {
address uiFeeReceiver;
uint256 uiFeeReceiverFactor;
uint256 uiFeeAmount;
}
// @dev get the price impact in USD for a position increase / decrease// @param params GetPriceImpactUsdParamsfunctiongetPriceImpactUsd(GetPriceImpactUsdParams memory params) internalviewreturns (int256) {
OpenInterestParams memory openInterestParams = getNextOpenInterest(params);
int256 priceImpactUsd = _getPriceImpactUsd(params.dataStore, params.market.marketToken, openInterestParams);
// the virtual price impact calculation is skipped if the price impact// is positive since the action is helping to balance the pool//// in case two virtual pools are unbalanced in a different direction// e.g. pool0 has more longs than shorts while pool1 has less longs// than shorts// not skipping the virtual price impact calculation would lead to// a negative price impact for any trade on either pools and would// disincentivise the balancing of poolsif (priceImpactUsd >=0) { return priceImpactUsd; }
(bool hasVirtualInventory, int256 virtualInventory) = MarketUtils.getVirtualInventoryForPositions(params.dataStore, params.market.indexToken);
if (!hasVirtualInventory) { return priceImpactUsd; }
OpenInterestParams memory openInterestParamsForVirtualInventory = getNextOpenInterestForVirtualInventory(params, virtualInventory);
int256 priceImpactUsdForVirtualInventory = _getPriceImpactUsd(params.dataStore, params.market.marketToken, openInterestParamsForVirtualInventory);
return priceImpactUsdForVirtualInventory < priceImpactUsd ? priceImpactUsdForVirtualInventory : priceImpactUsd;
}
// @dev get the price impact in USD for a position increase / decrease// @param dataStore DataStore// @param market the trading market// @param openInterestParams OpenInterestParamsfunction_getPriceImpactUsd(DataStore dataStore, address market, OpenInterestParams memory openInterestParams) internalviewreturns (int256) {
uint256 initialDiffUsd = Calc.diff(openInterestParams.longOpenInterest, openInterestParams.shortOpenInterest);
uint256 nextDiffUsd = Calc.diff(openInterestParams.nextLongOpenInterest, openInterestParams.nextShortOpenInterest);
// check whether an improvement in balance comes from causing the balance to switch sides// for example, if there is $2000 of ETH and $1000 of USDC in the pool// adding $1999 USDC into the pool will reduce absolute balance from $1000 to $999 but it does not// help rebalance the pool much, the isSameSideRebalance value helps avoid gaming using this casebool isSameSideRebalance = openInterestParams.longOpenInterest <= openInterestParams.shortOpenInterest == openInterestParams.nextLongOpenInterest <= openInterestParams.nextShortOpenInterest;
uint256 impactExponentFactor = dataStore.getUint(Keys.positionImpactExponentFactorKey(market));
if (isSameSideRebalance) {
bool hasPositiveImpact = nextDiffUsd < initialDiffUsd;
uint256 impactFactor = MarketUtils.getAdjustedPositionImpactFactor(dataStore, market, hasPositiveImpact);
return PricingUtils.getPriceImpactUsdForSameSideRebalance(
initialDiffUsd,
nextDiffUsd,
impactFactor,
impactExponentFactor
);
} else {
(uint256 positiveImpactFactor, uint256 negativeImpactFactor) = MarketUtils.getAdjustedPositionImpactFactors(dataStore, market);
return PricingUtils.getPriceImpactUsdForCrossoverRebalance(
initialDiffUsd,
nextDiffUsd,
positiveImpactFactor,
negativeImpactFactor,
impactExponentFactor
);
}
}
// @dev get the next open interest values// @param params GetPriceImpactUsdParams// @return OpenInterestParamsfunctiongetNextOpenInterest(
GetPriceImpactUsdParams memory params
) internalviewreturns (OpenInterestParams memory) {
uint256 longOpenInterest = MarketUtils.getOpenInterest(
params.dataStore,
params.market,
true
);
uint256 shortOpenInterest = MarketUtils.getOpenInterest(
params.dataStore,
params.market,
false
);
return getNextOpenInterestParams(params, longOpenInterest, shortOpenInterest);
}
functiongetNextOpenInterestForVirtualInventory(
GetPriceImpactUsdParams memory params,
int256 virtualInventory
) internalpurereturns (OpenInterestParams memory) {
uint256 longOpenInterest;
uint256 shortOpenInterest;
// if virtualInventory is more than zero it means that// tokens were virtually sold to the pool, so set shortOpenInterest// to the virtualInventory value// if virtualInventory is less than zero it means that// tokens were virtually bought from the pool, so set longOpenInterest// to the virtualInventory valueif (virtualInventory >0) {
shortOpenInterest = virtualInventory.toUint256();
} else {
longOpenInterest = (-virtualInventory).toUint256();
}
// the virtual long and short open interest is adjusted by the usdDelta// to prevent an underflow in getNextOpenInterestParams// price impact depends on the change in USD balance, so offsetting both// values equally should not change the price impact calculationif (params.usdDelta <0) {
uint256 offset = (-params.usdDelta).toUint256();
longOpenInterest += offset;
shortOpenInterest += offset;
}
return getNextOpenInterestParams(params, longOpenInterest, shortOpenInterest);
}
functiongetNextOpenInterestParams(
GetPriceImpactUsdParams memory params,
uint256 longOpenInterest,
uint256 shortOpenInterest
) internalpurereturns (OpenInterestParams memory) {
uint256 nextLongOpenInterest = longOpenInterest;
uint256 nextShortOpenInterest = shortOpenInterest;
if (params.isLong) {
if (params.usdDelta <0&& (-params.usdDelta).toUint256() > longOpenInterest) {
revert Errors.UsdDeltaExceedsLongOpenInterest(params.usdDelta, longOpenInterest);
}
nextLongOpenInterest = Calc.sumReturnUint256(longOpenInterest, params.usdDelta);
} else {
if (params.usdDelta <0&& (-params.usdDelta).toUint256() > shortOpenInterest) {
revert Errors.UsdDeltaExceedsShortOpenInterest(params.usdDelta, shortOpenInterest);
}
nextShortOpenInterest = Calc.sumReturnUint256(shortOpenInterest, params.usdDelta);
}
OpenInterestParams memory openInterestParams = OpenInterestParams(
longOpenInterest,
shortOpenInterest,
nextLongOpenInterest,
nextShortOpenInterest
);
return openInterestParams;
}
// @dev get position fees// @param dataStore DataStore// @param referralStorage IReferralStorage// @param position the position values// @param collateralTokenPrice the price of the position's collateralToken// @param longToken the long token of the market// @param shortToken the short token of the market// @param sizeDeltaUsd the change in position size// @return PositionFeesfunctiongetPositionFees(
GetPositionFeesParams memory params
) internalviewreturns (PositionFees memory) {
PositionFees memory fees = getPositionFeesAfterReferral(
params.dataStore,
params.referralStorage,
params.collateralTokenPrice,
params.forPositiveImpact,
params.position.account(),
params.position.market(),
params.sizeDeltaUsd
);
uint256 borrowingFeeUsd = MarketUtils.getBorrowingFees(params.dataStore, params.position);
fees.borrowing = getBorrowingFees(
params.dataStore,
params.collateralTokenPrice,
borrowingFeeUsd
);
if (params.isLiquidation) {
fees.liquidation = getLiquidationFees(params.dataStore, params.position.market(), params.sizeDeltaUsd, params.collateralTokenPrice);
}
fees.feeAmountForPool =
fees.positionFeeAmountForPool +
fees.borrowing.borrowingFeeAmount -
fees.borrowing.borrowingFeeAmountForFeeReceiver +
fees.liquidation.liquidationFeeAmount -
fees.liquidation.liquidationFeeAmountForFeeReceiver;
fees.feeReceiverAmount +=
fees.borrowing.borrowingFeeAmountForFeeReceiver +
fees.liquidation.liquidationFeeAmountForFeeReceiver;
fees.funding.latestFundingFeeAmountPerSize = MarketUtils.getFundingFeeAmountPerSize(
params.dataStore,
params.position.market(),
params.position.collateralToken(),
params.position.isLong()
);
fees.funding.latestLongTokenClaimableFundingAmountPerSize = MarketUtils.getClaimableFundingAmountPerSize(
params.dataStore,
params.position.market(),
params.longToken,
params.position.isLong()
);
fees.funding.latestShortTokenClaimableFundingAmountPerSize = MarketUtils.getClaimableFundingAmountPerSize(
params.dataStore,
params.position.market(),
params.shortToken,
params.position.isLong()
);
fees.funding = getFundingFees(
fees.funding,
params.position
);
fees.ui = getUiFees(
params.dataStore,
params.collateralTokenPrice,
params.sizeDeltaUsd,
params.uiFeeReceiver
);
fees.totalCostAmountExcludingFunding =
fees.positionFeeAmount
+ fees.borrowing.borrowingFeeAmount
+ fees.liquidation.liquidationFeeAmount
+ fees.ui.uiFeeAmount
- fees.totalDiscountAmount;
fees.totalCostAmount =
fees.totalCostAmountExcludingFunding
+ fees.funding.fundingFeeAmount;
return fees;
}
functiongetBorrowingFees(
DataStore dataStore,
Price.Props memory collateralTokenPrice,
uint256 borrowingFeeUsd
) internalviewreturns (PositionBorrowingFees memory) {
PositionBorrowingFees memory borrowingFees;
borrowingFees.borrowingFeeUsd = borrowingFeeUsd;
borrowingFees.borrowingFeeAmount = borrowingFeeUsd / collateralTokenPrice.min;
borrowingFees.borrowingFeeReceiverFactor = dataStore.getUint(Keys.BORROWING_FEE_RECEIVER_FACTOR);
borrowingFees.borrowingFeeAmountForFeeReceiver = Precision.applyFactor(borrowingFees.borrowingFeeAmount, borrowingFees.borrowingFeeReceiverFactor);
return borrowingFees;
}
functiongetFundingFees(
PositionFundingFees memory fundingFees,
Position.Props memory position
) internalpurereturns (PositionFundingFees memory) {
fundingFees.fundingFeeAmount = MarketUtils.getFundingAmount(
fundingFees.latestFundingFeeAmountPerSize,
position.fundingFeeAmountPerSize(),
position.sizeInUsd(),
true// roundUpMagnitude
);
fundingFees.claimableLongTokenAmount = MarketUtils.getFundingAmount(
fundingFees.latestLongTokenClaimableFundingAmountPerSize,
position.longTokenClaimableFundingAmountPerSize(),
position.sizeInUsd(),
false// roundUpMagnitude
);
fundingFees.claimableShortTokenAmount = MarketUtils.getFundingAmount(
fundingFees.latestShortTokenClaimableFundingAmountPerSize,
position.shortTokenClaimableFundingAmountPerSize(),
position.sizeInUsd(),
false// roundUpMagnitude
);
return fundingFees;
}
functiongetUiFees(
DataStore dataStore,
Price.Props memory collateralTokenPrice,
uint256 sizeDeltaUsd,
address uiFeeReceiver
) internalviewreturns (PositionUiFees memory) {
PositionUiFees memory uiFees;
if (uiFeeReceiver ==address(0)) {
return uiFees;
}
uiFees.uiFeeReceiver = uiFeeReceiver;
uiFees.uiFeeReceiverFactor = MarketUtils.getUiFeeFactor(dataStore, uiFeeReceiver);
uiFees.uiFeeAmount = Precision.applyFactor(sizeDeltaUsd, uiFees.uiFeeReceiverFactor) / collateralTokenPrice.min;
return uiFees;
}
// @dev get position fees after applying referral rebates / discounts// @param dataStore DataStore// @param referralStorage IReferralStorage// @param collateralTokenPrice the price of the position's collateralToken// @param the position's account// @param market the position's market// @param sizeDeltaUsd the change in position size// @return (affiliate, traderDiscountAmount, affiliateRewardAmount, feeReceiverAmount, positionFeeAmountForPool)functiongetPositionFeesAfterReferral(
DataStore dataStore,
IReferralStorage referralStorage,
Price.Props memory collateralTokenPrice,
bool forPositiveImpact,
address account,
address market,
uint256 sizeDeltaUsd
) internalviewreturns (PositionFees memory) {
PositionFees memory fees;
fees.collateralTokenPrice = collateralTokenPrice;
fees.referral.trader = account;
uint256 minAffiliateRewardFactor;
(
fees.referral.referralCode,
fees.referral.affiliate,
fees.referral.affiliateRewardFactor,
fees.referral.traderDiscountFactor,
minAffiliateRewardFactor
) = ReferralUtils.getReferralInfo(dataStore, referralStorage, account);
// note that since it is possible to incur both positive and negative price impact values// and the negative price impact factor may be larger than the positive impact factor// it is possible for the balance to be improved overall but for the price impact to still be negative// in this case the fee factor for the negative price impact would be charged// a user could split the order into two, to incur a smaller fee, reducing the fee through this should not be a large issue
fees.positionFeeFactor = dataStore.getUint(Keys.positionFeeFactorKey(market, forPositiveImpact));
fees.positionFeeAmount = Precision.applyFactor(sizeDeltaUsd, fees.positionFeeFactor) / collateralTokenPrice.min;
// pro tiers are provided as a flexible option to allow for custom criteria based discounts,// the exact criteria and usage of this feature should be decided by the DAO
fees.pro.traderTier = dataStore.getUint(Keys.proTraderTierKey(account));
if (fees.pro.traderTier >0) {
fees.pro.traderDiscountFactor = dataStore.getUint(Keys.proDiscountFactorKey(fees.pro.traderTier));
if (fees.pro.traderDiscountFactor >0) {
fees.pro.traderDiscountAmount = Precision.applyFactor(fees.positionFeeAmount, fees.pro.traderDiscountFactor);
}
}
// if pro discount is higher than referral discount then affiliate reward is capped at (total referral rebate - pro discount)// but can not be lower than configured min affiliate reward//// example 1:// referral code is 10% affiliate reward and 10% trader discount, pro discount is 5%// min affiliate reward 5%, total referral rebate is 20%, affiliate reward cap is max of (20% - 5%, 5%) = 15%// trader gets 10% discount, affiliate reward is capped at 15%, affiliate gets full 10% reward// protocol gets 80%//// example 2:// referral code is 10% affiliate reward and 10% trader discount, pro discount is 13%// min affiliate reward 5%, total referral rebate is 20%, affiliate reward cap is max of (20% - 13%, 5%) = 7%// trader gets 13% discount, affiliate reward is capped at 7%, affiliate gets capped 7% reward// protocol gets 80%//// example 3:// referral code is 10% affiliate reward and 10% trader discount, pro discount is 18%// min affiliate reward 5%, total referral rebate is 20%, affiliate reward cap is max of (20% - 18%, 5%) = 5%// trader gets 18% discount, affiliate reward is capped at 5%, affiliate gets capped 5% reward// protocol gets 77%//// example 4:// referral code is 10% affiliate reward and 10% trader discount, pro discount is 25%// min affiliate reward 5%, total referral rebate is 20%, affiliate reward cap is max of (20% - 25%, 5%) = 5%// trader gets 25% discount, affiliate reward is capped at 5%, affiliate gets capped 5% reward// protocol gets 70%if (fees.referral.referralCode !=bytes32(0)) {
fees.referral.adjustedAffiliateRewardFactor = fees.referral.affiliateRewardFactor;
fees.referral.totalRebateFactor = fees.referral.affiliateRewardFactor + fees.referral.traderDiscountFactor;
// if pro discount is higher than referral discount then affiliate reward should be capped// at max of (min affiliate reward, total referral rebate - pro discount)if (fees.pro.traderDiscountFactor > fees.referral.traderDiscountFactor) {
fees.referral.adjustedAffiliateRewardFactor = fees.pro.traderDiscountFactor > fees.referral.totalRebateFactor
? minAffiliateRewardFactor
: fees.referral.totalRebateFactor - fees.pro.traderDiscountFactor;
if (fees.referral.adjustedAffiliateRewardFactor < minAffiliateRewardFactor) {
fees.referral.adjustedAffiliateRewardFactor = minAffiliateRewardFactor;
}
}
fees.referral.affiliateRewardAmount = Precision.applyFactor(fees.positionFeeAmount, fees.referral.adjustedAffiliateRewardFactor);
fees.referral.traderDiscountAmount = Precision.applyFactor(fees.positionFeeAmount, fees.referral.traderDiscountFactor);
fees.referral.totalRebateAmount = fees.referral.affiliateRewardAmount + fees.referral.traderDiscountAmount;
}
fees.totalDiscountAmount = fees.pro.traderDiscountAmount > fees.referral.traderDiscountAmount
? fees.pro.traderDiscountAmount
: fees.referral.traderDiscountAmount;
fees.protocolFeeAmount = fees.positionFeeAmount - fees.referral.affiliateRewardAmount - fees.totalDiscountAmount;
fees.positionFeeReceiverFactor = dataStore.getUint(Keys.POSITION_FEE_RECEIVER_FACTOR);
fees.feeReceiverAmount = Precision.applyFactor(fees.protocolFeeAmount, fees.positionFeeReceiverFactor);
fees.positionFeeAmountForPool = fees.protocolFeeAmount - fees.feeReceiverAmount;
return fees;
}
functiongetLiquidationFees(DataStore dataStore, address market, uint256 sizeInUsd, Price.Props memory collateralTokenPrice) internalviewreturns (PositionLiquidationFees memory) {
PositionLiquidationFees memory liquidationFees;
uint256 liquidationFeeFactor = dataStore.getUint(Keys.liquidationFeeFactorKey(market));
if (liquidationFeeFactor ==0) {
return liquidationFees;
}
liquidationFees.liquidationFeeUsd = Precision.applyFactor(sizeInUsd, liquidationFeeFactor);
liquidationFees.liquidationFeeAmount = Calc.roundUpDivision(liquidationFees.liquidationFeeUsd, collateralTokenPrice.min);
liquidationFees.liquidationFeeReceiverFactor = dataStore.getUint(Keys.LIQUIDATION_FEE_RECEIVER_FACTOR);
liquidationFees.liquidationFeeAmountForFeeReceiver = Precision.applyFactor(liquidationFees.liquidationFeeAmount, liquidationFees.liquidationFeeReceiverFactor);
return liquidationFees;
}
}
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"@openzeppelin/contracts/utils/math/SafeCast.sol";
import"../utils/Precision.sol";
import"./Position.sol";
import"../data/DataStore.sol";
import"../data/Keys.sol";
import"../pricing/PositionPricingUtils.sol";
import"../order/BaseOrderUtils.sol";
// @title PositionUtils// @dev Library for position functionslibraryPositionUtils{
usingSafeCastforuint256;
usingSafeCastforint256;
usingPriceforPrice.Props;
usingPositionforPosition.Props;
usingOrderforOrder.Props;
// @dev UpdatePositionParams struct used in increasePosition and decreasePosition// to avoid stack too deep errors//// @param contracts BaseOrderUtils.ExecuteOrderParamsContracts// @param market the values of the trading market// @param order the decrease position order// @param orderKey the key of the order// @param position the order's position// @param positionKey the key of the order's positionstructUpdatePositionParams {
BaseOrderUtils.ExecuteOrderParamsContracts contracts;
Market.Props market;
Order.Props order;
bytes32 orderKey;
Position.Props position;
bytes32 positionKey;
Order.SecondaryOrderType secondaryOrderType;
}
// @param dataStore DataStore// @param eventEmitter EventEmitter// @param oracle Oracle// @param referralStorage IReferralStoragestructUpdatePositionParamsContracts {
DataStore dataStore;
EventEmitter eventEmitter;
Oracle oracle;
SwapHandler swapHandler;
IReferralStorage referralStorage;
}
structWillPositionCollateralBeSufficientValues {
uint256 positionSizeInUsd;
uint256 positionCollateralAmount;
int256 realizedPnlUsd;
int256 openInterestDelta;
}
structDecreasePositionCollateralValuesOutput {
address outputToken;
uint256 outputAmount;
address secondaryOutputToken;
uint256 secondaryOutputAmount;
}
// @dev ProcessCollateralValues struct used to contain the values in processCollateral// @param executionPrice the order execution price// @param remainingCollateralAmount the remaining collateral amount of the position// @param positionPnlUsd the pnl of the position in USD// @param sizeDeltaInTokens the change in position size in tokens// @param priceImpactAmount the price impact in tokens// @param priceImpactDiffUsd the price impact difference in USD// @param pendingCollateralDeduction the pending collateral deduction// @param output DecreasePositionCollateralValuesOutputstructDecreasePositionCollateralValues {
uint256 executionPrice;
uint256 remainingCollateralAmount;
int256 basePnlUsd;
int256 uncappedBasePnlUsd;
uint256 sizeDeltaInTokens;
int256 priceImpactUsd;
uint256 priceImpactDiffUsd;
DecreasePositionCollateralValuesOutput output;
}
// @dev DecreasePositionCache struct used in decreasePosition to// avoid stack too deep errors// @param prices the prices of the tokens in the market// @param pnlToken the token that the pnl for the user is in, for long positions// this is the market.longToken, for short positions this is the market.shortToken// @param pnlTokenPrice the price of the pnlToken// @param initialCollateralAmount the initial collateral amount// @param nextPositionSizeInUsd the new position size in USD// @param nextPositionBorrowingFactor the new position borrowing factorstructDecreasePositionCache {
MarketUtils.MarketPrices prices;
int256 estimatedPositionPnlUsd;
int256 estimatedRealizedPnlUsd;
int256 estimatedRemainingPnlUsd;
address pnlToken;
Price.Props pnlTokenPrice;
Price.Props collateralTokenPrice;
uint256 initialCollateralAmount;
uint256 nextPositionSizeInUsd;
uint256 nextPositionBorrowingFactor;
}
structGetPositionPnlUsdCache {
int256 positionValue;
int256 totalPositionPnl;
int256 uncappedTotalPositionPnl;
address pnlToken;
uint256 poolTokenAmount;
uint256 poolTokenPrice;
uint256 poolTokenUsd;
int256 poolPnl;
int256 cappedPoolPnl;
uint256 sizeDeltaInTokens;
int256 positionPnlUsd;
int256 uncappedPositionPnlUsd;
}
structIsPositionLiquidatableInfo {
int256 remainingCollateralUsd;
int256 minCollateralUsd;
int256 minCollateralUsdForLeverage;
}
// @dev IsPositionLiquidatableCache struct used in isPositionLiquidatable// to avoid stack too deep errors// @param positionPnlUsd the position's pnl in USD// @param minCollateralFactor the min collateral factor// @param collateralTokenPrice the collateral token price// @param collateralUsd the position's collateral in USD// @param usdDeltaForPriceImpact the usdDelta value for the price impact calculation// @param priceImpactUsd the price impact of closing the position in USDstructIsPositionLiquidatableCache {
int256 positionPnlUsd;
uint256 minCollateralFactor;
Price.Props collateralTokenPrice;
uint256 collateralUsd;
int256 usdDeltaForPriceImpact;
int256 priceImpactUsd;
bool hasPositiveImpact;
}
structGetExecutionPriceForDecreaseCache {
int256 priceImpactUsd;
uint256 priceImpactDiffUsd;
uint256 executionPrice;
}
// @dev get the position pnl in USD//// for long positions, pnl is calculated as:// (position.sizeInTokens * indexTokenPrice) - position.sizeInUsd// if position.sizeInTokens is larger for long positions, the position will have// larger profits and smaller losses for the same changes in token price//// for short positions, pnl is calculated as:// position.sizeInUsd - (position.sizeInTokens * indexTokenPrice)// if position.sizeInTokens is smaller for long positions, the position will have// larger profits and smaller losses for the same changes in token price//// @param position the position values// @param sizeDeltaUsd the change in position size// @param indexTokenPrice the price of the index token//// @return (positionPnlUsd, uncappedPositionPnlUsd, sizeDeltaInTokens)functiongetPositionPnlUsd(
DataStore dataStore,
Market.Props memory market,
MarketUtils.MarketPrices memory prices,
Position.Props memory position,
uint256 sizeDeltaUsd
) publicviewreturns (int256, int256, uint256) {
GetPositionPnlUsdCache memory cache;
uint256 executionPrice = prices.indexTokenPrice.pickPriceForPnl(position.isLong(), false);
// position.sizeInUsd is the cost of the tokens, positionValue is the current worth of the tokens
cache.positionValue = (position.sizeInTokens() * executionPrice).toInt256();
cache.totalPositionPnl = position.isLong() ? cache.positionValue - position.sizeInUsd().toInt256() : position.sizeInUsd().toInt256() - cache.positionValue;
cache.uncappedTotalPositionPnl = cache.totalPositionPnl;
if (cache.totalPositionPnl >0) {
cache.pnlToken = position.isLong() ? market.longToken : market.shortToken;
cache.poolTokenAmount = MarketUtils.getPoolAmount(dataStore, market, cache.pnlToken);
cache.poolTokenPrice = position.isLong() ? prices.longTokenPrice.min : prices.shortTokenPrice.min;
cache.poolTokenUsd = cache.poolTokenAmount * cache.poolTokenPrice;
cache.poolPnl = MarketUtils.getPnl(
dataStore,
market,
prices.indexTokenPrice,
position.isLong(),
true
);
cache.cappedPoolPnl = MarketUtils.getCappedPnl(
dataStore,
market.marketToken,
position.isLong(),
cache.poolPnl,
cache.poolTokenUsd,
Keys.MAX_PNL_FACTOR_FOR_TRADERS
);
if (cache.cappedPoolPnl != cache.poolPnl && cache.cappedPoolPnl >0&& cache.poolPnl >0) {
cache.totalPositionPnl = Precision.mulDiv(cache.totalPositionPnl.toUint256(), cache.cappedPoolPnl, cache.poolPnl.toUint256());
}
}
if (position.sizeInUsd() == sizeDeltaUsd) {
cache.sizeDeltaInTokens = position.sizeInTokens();
} else {
if (position.isLong()) {
cache.sizeDeltaInTokens = Calc.roundUpDivision(position.sizeInTokens() * sizeDeltaUsd, position.sizeInUsd());
} else {
cache.sizeDeltaInTokens = position.sizeInTokens() * sizeDeltaUsd / position.sizeInUsd();
}
}
cache.positionPnlUsd = Precision.mulDiv(cache.totalPositionPnl, cache.sizeDeltaInTokens, position.sizeInTokens());
cache.uncappedPositionPnlUsd = Precision.mulDiv(cache.uncappedTotalPositionPnl, cache.sizeDeltaInTokens, position.sizeInTokens());
return (cache.positionPnlUsd, cache.uncappedPositionPnlUsd, cache.sizeDeltaInTokens);
}
// @dev validate that a position is not empty// @param position the position valuesfunctionvalidateNonEmptyPosition(Position.Props memory position) internalpure{
if (position.sizeInUsd() ==0&& position.sizeInTokens() ==0&& position.collateralAmount() ==0) {
revert Errors.EmptyPosition();
}
}
// @dev check if a position is valid// @param dataStore DataStore// @param referralStorage IReferralStorage// @param position the position values// @param market the market values// @param prices the prices of the tokens in the market// @param shouldValidateMinCollateralUsd whether min collateral usd needs to be validated// validation is skipped for decrease position to prevent reverts in case the order size// is just slightly smaller than the position size// in decrease position, the remaining collateral is estimated at the start, and the order// size is updated to match the position size if the remaining collateral will be less than// the min collateral usd// since this is an estimate, there may be edge cases where there is a small remaining position size// and small amount of collateral remaining// validation is skipped for this case as it is preferred for the order to be executed// since the small amount of collateral remaining only impacts the potential payment of liquidation// keepersfunctionvalidatePosition(
DataStore dataStore,
IReferralStorage referralStorage,
Position.Props memory position,
Market.Props memory market,
MarketUtils.MarketPrices memory prices,
bool shouldValidateMinPositionSize,
bool shouldValidateMinCollateralUsd
) publicview{
if (position.sizeInUsd() ==0|| position.sizeInTokens() ==0) {
revert Errors.InvalidPositionSizeValues(position.sizeInUsd(), position.sizeInTokens());
}
MarketUtils.validateEnabledMarket(dataStore, market.marketToken);
MarketUtils.validateMarketCollateralToken(market, position.collateralToken());
if (shouldValidateMinPositionSize) {
uint256 minPositionSizeUsd = dataStore.getUint(Keys.MIN_POSITION_SIZE_USD);
if (position.sizeInUsd() < minPositionSizeUsd) {
revert Errors.MinPositionSize(position.sizeInUsd(), minPositionSizeUsd);
}
}
(bool isLiquidatable, stringmemory reason, IsPositionLiquidatableInfo memory info) = isPositionLiquidatable(
dataStore,
referralStorage,
position,
market,
prices,
shouldValidateMinCollateralUsd
);
if (isLiquidatable) {
revert Errors.LiquidatablePosition(
reason,
info.remainingCollateralUsd,
info.minCollateralUsd,
info.minCollateralUsdForLeverage
);
}
}
// @dev check if a position is liquidatable// @param dataStore DataStore// @param referralStorage IReferralStorage// @param position the position values// @param market the market values// @param prices the prices of the tokens in the marketfunctionisPositionLiquidatable(
DataStore dataStore,
IReferralStorage referralStorage,
Position.Props memory position,
Market.Props memory market,
MarketUtils.MarketPrices memory prices,
bool shouldValidateMinCollateralUsd
) publicviewreturns (bool, stringmemory, IsPositionLiquidatableInfo memory) {
IsPositionLiquidatableCache memory cache;
IsPositionLiquidatableInfo memory info;
(cache.positionPnlUsd, /* int256 uncappedBasePnlUsd */, /* uint256 sizeDeltaInTokens */) = getPositionPnlUsd(
dataStore,
market,
prices,
position,
position.sizeInUsd()
);
cache.collateralTokenPrice = MarketUtils.getCachedTokenPrice(
position.collateralToken(),
market,
prices
);
cache.collateralUsd = position.collateralAmount() * cache.collateralTokenPrice.min;
// calculate the usdDeltaForPriceImpact for fully closing the position
cache.usdDeltaForPriceImpact =-position.sizeInUsd().toInt256();
cache.priceImpactUsd = PositionPricingUtils.getPriceImpactUsd(
PositionPricingUtils.GetPriceImpactUsdParams(
dataStore,
market,
cache.usdDeltaForPriceImpact,
position.isLong()
)
);
cache.hasPositiveImpact = cache.priceImpactUsd >0;
// even if there is a large positive price impact, positions that would be liquidated// if the positive price impact is reduced should not be allowed to be created// as they would be easily liquidated if the price impact changes// cap the priceImpactUsd to zero to prevent these positions from being createdif (cache.priceImpactUsd >=0) {
cache.priceImpactUsd =0;
} else {
uint256 maxPriceImpactFactor = MarketUtils.getMaxPositionImpactFactorForLiquidations(
dataStore,
market.marketToken
);
// if there is a large build up of open interest and a sudden large price movement// it may result in a large imbalance between longs and shorts// this could result in very large price impact temporarily// cap the max negative price impact to prevent cascading liquidationsint256 maxNegativePriceImpactUsd =-Precision.applyFactor(position.sizeInUsd(), maxPriceImpactFactor).toInt256();
if (cache.priceImpactUsd < maxNegativePriceImpactUsd) {
cache.priceImpactUsd = maxNegativePriceImpactUsd;
}
}
PositionPricingUtils.GetPositionFeesParams memory getPositionFeesParams = PositionPricingUtils.GetPositionFeesParams(
dataStore, // dataStore
referralStorage, // referralStorage
position, // position
cache.collateralTokenPrice, //collateralTokenPrice
cache.hasPositiveImpact, // forPositiveImpact
market.longToken, // longToken
market.shortToken, // shortToken
position.sizeInUsd(), // sizeDeltaUsdaddress(0), // uiFeeReceiver// should not account for liquidation fees to determine if position should be liquidatedfalse// isLiquidation
);
PositionPricingUtils.PositionFees memory fees = PositionPricingUtils.getPositionFees(getPositionFeesParams);
// the totalCostAmount is in tokens, use collateralTokenPrice.min to calculate the cost in USD// since in PositionPricingUtils.getPositionFees the totalCostAmount in tokens was calculated// using collateralTokenPrice.minuint256 collateralCostUsd = fees.totalCostAmount * cache.collateralTokenPrice.min;
// the position's pnl is counted as collateral for the liquidation check// as a position in profit should not be liquidated if the pnl is sufficient// to cover the position's fees
info.remainingCollateralUsd =
cache.collateralUsd.toInt256()
+ cache.positionPnlUsd
+ cache.priceImpactUsd
- collateralCostUsd.toInt256();
cache.minCollateralFactor = MarketUtils.getMinCollateralFactor(dataStore, market.marketToken);
// validate if (remaining collateral) / position.size is less than the min collateral factor (max leverage exceeded)// this validation includes the position fee to be paid when closing the position// i.e. if the position does not have sufficient collateral after closing fees it is considered a liquidatable position
info.minCollateralUsdForLeverage = Precision.applyFactor(position.sizeInUsd(), cache.minCollateralFactor).toInt256();
if (shouldValidateMinCollateralUsd) {
info.minCollateralUsd = dataStore.getUint(Keys.MIN_COLLATERAL_USD).toInt256();
if (info.remainingCollateralUsd < info.minCollateralUsd) {
return (true, "min collateral", info);
}
}
if (info.remainingCollateralUsd <=0) {
return (true, "< 0", info);
}
if (info.remainingCollateralUsd < info.minCollateralUsdForLeverage) {
return (true, "min collateral for leverage", info);
}
return (false, "", info);
}
// fees and price impact are not included for the willPositionCollateralBeSufficient validation// this is because this validation is meant to guard against a specific scenario of price impact// gaming//// price impact could be gamed by opening high leverage positions, if the price impact// that should be charged is higher than the amount of collateral in the position// then a user could pay less price impact than what is required, and there is a risk that// price manipulation could be profitable if the price impact cost is less than it should be//// this check should be sufficient even without factoring in fees as fees should have a minimal impact// it may be possible that funding or borrowing fees are accumulated and need to be deducted which could// lead to a user paying less price impact than they should, however gaming of this form should be difficult// since the funding and borrowing fees would still add up for the user's cost//// another possibility would be if a user opens a large amount of both long and short positions, and// funding fees are paid from one side to the other, but since most of the open interest is owned by the// user the user earns most of the paid cost, in this scenario the borrowing fees should still be significant// since some time would be required for the funding fees to accumulate//// fees and price impact are validated in the validatePosition checkfunctionwillPositionCollateralBeSufficient(
DataStore dataStore,
Market.Props memory market,
MarketUtils.MarketPrices memory prices,
address collateralToken,
bool isLong,
WillPositionCollateralBeSufficientValues memory values
) publicviewreturns (bool, int256) {
Price.Props memory collateralTokenPrice = MarketUtils.getCachedTokenPrice(
collateralToken,
market,
prices
);
int256 remainingCollateralUsd = values.positionCollateralAmount.toInt256() * collateralTokenPrice.min.toInt256();
// deduct realized pnl if it is negative since this would be paid from// the position's collateralif (values.realizedPnlUsd <0) {
remainingCollateralUsd = remainingCollateralUsd + values.realizedPnlUsd;
}
if (remainingCollateralUsd <0) {
return (false, remainingCollateralUsd);
}
// the min collateral factor will increase as the open interest for a market increases// this may lead to previously created limit increase orders not being executable//// the position's pnl is not factored into the remainingCollateralUsd value, since// factoring in a positive pnl may allow the user to manipulate price and bypass this check// it may be useful to factor in a negative pnl for this check, this can be added if requireduint256 minCollateralFactor = MarketUtils.getMinCollateralFactorForOpenInterest(
dataStore,
market,
values.openInterestDelta,
isLong
);
uint256 minCollateralFactorForMarket = MarketUtils.getMinCollateralFactor(dataStore, market.marketToken);
// use the minCollateralFactor for the market if it is largerif (minCollateralFactorForMarket > minCollateralFactor) {
minCollateralFactor = minCollateralFactorForMarket;
}
int256 minCollateralUsdForLeverage = Precision.applyFactor(values.positionSizeInUsd, minCollateralFactor).toInt256();
bool willBeSufficient = remainingCollateralUsd >= minCollateralUsdForLeverage;
return (willBeSufficient, remainingCollateralUsd);
}
functionupdateFundingAndBorrowingState(
DataStore dataStore,
EventEmitter eventEmitter,
Market.Props memory market,
MarketUtils.MarketPrices memory prices
) external{
// update the funding amount per size for the market
MarketUtils.updateFundingState(
dataStore,
eventEmitter,
market,
prices
);
// update the cumulative borrowing factor for longs
MarketUtils.updateCumulativeBorrowingFactor(
dataStore,
eventEmitter,
market,
prices,
true// isLong
);
// update the cumulative borrowing factor for shorts
MarketUtils.updateCumulativeBorrowingFactor(
dataStore,
eventEmitter,
market,
prices,
false// isLong
);
}
functionupdateTotalBorrowing(
PositionUtils.UpdatePositionParams memory params,
uint256 nextPositionSizeInUsd,
uint256 nextPositionBorrowingFactor
) internal{
MarketUtils.updateTotalBorrowing(
params.contracts.dataStore, // dataStore
params.market.marketToken, // market
params.position.isLong(), // isLong
params.position.sizeInUsd(), // prevPositionSizeInUsd
params.position.borrowingFactor(), // prevPositionBorrowingFactor
nextPositionSizeInUsd, // nextPositionSizeInUsd
nextPositionBorrowingFactor // nextPositionBorrowingFactor
);
}
// the order.receiver is meant to allow the output of an order to be// received by an address that is different from the position.account// address// for funding fees, the funds are still credited to the owner// of the position indicated by order.accountfunctionincrementClaimableFundingAmount(
PositionUtils.UpdatePositionParams memory params,
PositionPricingUtils.PositionFees memory fees
) internal{
// if the position has negative funding fees, distribute it to allow it to be claimableif (fees.funding.claimableLongTokenAmount >0) {
MarketUtils.incrementClaimableFundingAmount(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.market.marketToken,
params.market.longToken,
params.order.account(),
fees.funding.claimableLongTokenAmount
);
}
if (fees.funding.claimableShortTokenAmount >0) {
MarketUtils.incrementClaimableFundingAmount(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.market.marketToken,
params.market.shortToken,
params.order.account(),
fees.funding.claimableShortTokenAmount
);
}
}
functionupdateOpenInterest(
PositionUtils.UpdatePositionParams memory params,
int256 sizeDeltaUsd,
int256 sizeDeltaInTokens
) internal{
if (sizeDeltaUsd !=0) {
MarketUtils.applyDeltaToOpenInterest(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.market,
params.position.collateralToken(),
params.position.isLong(),
sizeDeltaUsd
);
MarketUtils.applyDeltaToOpenInterestInTokens(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.position.market(),
params.position.collateralToken(),
params.position.isLong(),
sizeDeltaInTokens
);
}
}
functionhandleReferral(
PositionUtils.UpdatePositionParams memory params,
PositionPricingUtils.PositionFees memory fees
) internal{
ReferralUtils.incrementAffiliateReward(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.position.market(),
params.position.collateralToken(),
fees.referral.affiliate,
fees.referral.affiliateRewardAmount
);
}
// returns priceImpactUsd, priceImpactAmount, sizeDeltaInTokens, executionPricefunctiongetExecutionPriceForIncrease(
UpdatePositionParams memory params,
Price.Props memory indexTokenPrice
) externalviewreturns (int256, int256, uint256, uint256) {
// note that the executionPrice is not validated against the order.acceptablePrice value// if the sizeDeltaUsd is zero// for limit orders the order.triggerPrice should still have been validatedif (params.order.sizeDeltaUsd() ==0) {
// increase order:// - long: use the larger price// - short: use the smaller pricereturn (0, 0, 0, indexTokenPrice.pickPrice(params.position.isLong()));
}
int256 priceImpactUsd = PositionPricingUtils.getPriceImpactUsd(
PositionPricingUtils.GetPriceImpactUsdParams(
params.contracts.dataStore,
params.market,
params.order.sizeDeltaUsd().toInt256(),
params.order.isLong()
)
);
// cap priceImpactUsd based on the amount available in the position impact pool
priceImpactUsd = MarketUtils.getCappedPositionImpactUsd(
params.contracts.dataStore,
params.market.marketToken,
indexTokenPrice,
priceImpactUsd,
params.order.sizeDeltaUsd()
);
// for long positions//// if price impact is positive, the sizeDeltaInTokens would be increased by the priceImpactAmount// the priceImpactAmount should be minimized//// if price impact is negative, the sizeDeltaInTokens would be decreased by the priceImpactAmount// the priceImpactAmount should be maximized// for short positions//// if price impact is positive, the sizeDeltaInTokens would be decreased by the priceImpactAmount// the priceImpactAmount should be minimized//// if price impact is negative, the sizeDeltaInTokens would be increased by the priceImpactAmount// the priceImpactAmount should be maximizedint256 priceImpactAmount;
if (priceImpactUsd >0) {
// use indexTokenPrice.max and round down to minimize the priceImpactAmount
priceImpactAmount = priceImpactUsd / indexTokenPrice.max.toInt256();
} else {
// use indexTokenPrice.min and round up to maximize the priceImpactAmount
priceImpactAmount = Calc.roundUpMagnitudeDivision(priceImpactUsd, indexTokenPrice.min);
}
uint256 baseSizeDeltaInTokens;
if (params.position.isLong()) {
// round the number of tokens for long positions down
baseSizeDeltaInTokens = params.order.sizeDeltaUsd() / indexTokenPrice.max;
} else {
// round the number of tokens for short positions up
baseSizeDeltaInTokens = Calc.roundUpDivision(params.order.sizeDeltaUsd(), indexTokenPrice.min);
}
int256 sizeDeltaInTokens;
if (params.position.isLong()) {
sizeDeltaInTokens = baseSizeDeltaInTokens.toInt256() + priceImpactAmount;
} else {
sizeDeltaInTokens = baseSizeDeltaInTokens.toInt256() - priceImpactAmount;
}
if (sizeDeltaInTokens <0) {
revert Errors.PriceImpactLargerThanOrderSize(priceImpactUsd, params.order.sizeDeltaUsd());
}
// using increase of long positions as an example// if price is $2000, sizeDeltaUsd is $5000, priceImpactUsd is -$1000// priceImpactAmount = -1000 / 2000 = -0.5// baseSizeDeltaInTokens = 5000 / 2000 = 2.5// sizeDeltaInTokens = 2.5 - 0.5 = 2// executionPrice = 5000 / 2 = $2500uint256 executionPrice = BaseOrderUtils.getExecutionPriceForIncrease(
params.order.sizeDeltaUsd(),
sizeDeltaInTokens.toUint256(),
params.order.acceptablePrice(),
params.position.isLong()
);
return (priceImpactUsd, priceImpactAmount, sizeDeltaInTokens.toUint256(), executionPrice);
}
// returns priceImpactUsd, priceImpactDiffUsd, executionPricefunctiongetExecutionPriceForDecrease(
UpdatePositionParams memory params,
Price.Props memory indexTokenPrice
) externalviewreturns (int256, uint256, uint256) {
uint256 sizeDeltaUsd = params.order.sizeDeltaUsd();
// note that the executionPrice is not validated against the order.acceptablePrice value// if the sizeDeltaUsd is zero// for limit orders the order.triggerPrice should still have been validatedif (sizeDeltaUsd ==0) {
// decrease order:// - long: use the smaller price// - short: use the larger pricereturn (0, 0, indexTokenPrice.pickPrice(!params.position.isLong()));
}
GetExecutionPriceForDecreaseCache memory cache;
cache.priceImpactUsd = PositionPricingUtils.getPriceImpactUsd(
PositionPricingUtils.GetPriceImpactUsdParams(
params.contracts.dataStore,
params.market,
-sizeDeltaUsd.toInt256(),
params.order.isLong()
)
);
// cap priceImpactUsd based on the amount available in the position impact pool
cache.priceImpactUsd = MarketUtils.getCappedPositionImpactUsd(
params.contracts.dataStore,
params.market.marketToken,
indexTokenPrice,
cache.priceImpactUsd,
sizeDeltaUsd
);
if (cache.priceImpactUsd <0) {
uint256 maxPriceImpactFactor = MarketUtils.getMaxPositionImpactFactor(
params.contracts.dataStore,
params.market.marketToken,
false
);
// convert the max price impact to the min negative value// e.g. if sizeDeltaUsd is 10,000 and maxPriceImpactFactor is 2%// then minPriceImpactUsd = -200int256 minPriceImpactUsd =-Precision.applyFactor(sizeDeltaUsd, maxPriceImpactFactor).toInt256();
// cap priceImpactUsd to the min negative value and store the difference in priceImpactDiffUsd// e.g. if priceImpactUsd is -500 and minPriceImpactUsd is -200// then set priceImpactDiffUsd to -200 - -500 = 300// set priceImpactUsd to -200if (cache.priceImpactUsd < minPriceImpactUsd) {
cache.priceImpactDiffUsd = (minPriceImpactUsd - cache.priceImpactUsd).toUint256();
cache.priceImpactUsd = minPriceImpactUsd;
}
}
// the executionPrice is calculated after the price impact is capped// so the output amount directly received by the user may not match// the executionPrice, the difference would be stored as a// claimable amount
cache.executionPrice = BaseOrderUtils.getExecutionPriceForDecrease(
indexTokenPrice,
params.position.sizeInUsd(),
params.position.sizeInTokens(),
sizeDeltaUsd,
cache.priceImpactUsd,
params.order.acceptablePrice(),
params.position.isLong()
);
return (cache.priceImpactUsd, cache.priceImpactDiffUsd, cache.executionPrice);
}
}
Contract Source Code
File 83 of 103: Precision.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;// there is a known issue with prb-math v3.x releases// https://github.com/PaulRBerg/prb-math/issues/178// due to this, either prb-math v2.x or v4.x versions should be used insteadimport"prb-math/contracts/PRBMathUD60x18.sol";
import"@openzeppelin/contracts/utils/math/SafeCast.sol";
import"@openzeppelin/contracts/utils/math/SignedMath.sol";
import"@openzeppelin/contracts/utils/math/Math.sol";
/**
* @title Precision
* @dev Library for precision values and conversions
*/libraryPrecision{
usingSafeCastforuint256;
usingSignedMathforint256;
uint256publicconstant FLOAT_PRECISION =10**30;
uint256publicconstant FLOAT_PRECISION_SQRT =10**15;
uint256publicconstant WEI_PRECISION =10**18;
uint256publicconstant BASIS_POINTS_DIVISOR =10000;
uint256publicconstant FLOAT_TO_WEI_DIVISOR =10**12;
/**
* Applies the given factor to the given value and returns the result.
*
* @param value The value to apply the factor to.
* @param factor The factor to apply.
* @return The result of applying the factor to the value.
*/functionapplyFactor(uint256 value, uint256 factor) internalpurereturns (uint256) {
return mulDiv(value, factor, FLOAT_PRECISION);
}
/**
* Applies the given factor to the given value and returns the result.
*
* @param value The value to apply the factor to.
* @param factor The factor to apply.
* @return The result of applying the factor to the value.
*/functionapplyFactor(uint256 value, int256 factor) internalpurereturns (int256) {
return mulDiv(value, factor, FLOAT_PRECISION);
}
functionapplyFactor(uint256 value, int256 factor, bool roundUpMagnitude) internalpurereturns (int256) {
return mulDiv(value, factor, FLOAT_PRECISION, roundUpMagnitude);
}
functionmulDiv(uint256 value, uint256 numerator, uint256 denominator) internalpurereturns (uint256) {
return Math.mulDiv(value, numerator, denominator);
}
functionmulDiv(int256 value, uint256 numerator, uint256 denominator) internalpurereturns (int256) {
return mulDiv(numerator, value, denominator);
}
functionmulDiv(uint256 value, int256 numerator, uint256 denominator) internalpurereturns (int256) {
uint256 result = mulDiv(value, numerator.abs(), denominator);
return numerator >0 ? result.toInt256() : -result.toInt256();
}
functionmulDiv(uint256 value, int256 numerator, uint256 denominator, bool roundUpMagnitude) internalpurereturns (int256) {
uint256 result = mulDiv(value, numerator.abs(), denominator, roundUpMagnitude);
return numerator >0 ? result.toInt256() : -result.toInt256();
}
functionmulDiv(uint256 value, uint256 numerator, uint256 denominator, bool roundUpMagnitude) internalpurereturns (uint256) {
if (roundUpMagnitude) {
return Math.mulDiv(value, numerator, denominator, Math.Rounding.Up);
}
return Math.mulDiv(value, numerator, denominator);
}
functionapplyExponentFactor(uint256 floatValue,
uint256 exponentFactor
) internalpurereturns (uint256) {
// `PRBMathUD60x18.pow` doesn't work for `x` less than oneif (floatValue < FLOAT_PRECISION) {
return0;
}
if (exponentFactor == FLOAT_PRECISION) {
return floatValue;
}
// `PRBMathUD60x18.pow` accepts 2 fixed point numbers 60x18// we need to convert float (30 decimals) to 60x18 (18 decimals) and then back to 30 decimalsuint256 weiValue = PRBMathUD60x18.pow(
floatToWei(floatValue),
floatToWei(exponentFactor)
);
return weiToFloat(weiValue);
}
functiontoFactor(uint256 value, uint256 divisor, bool roundUpMagnitude) internalpurereturns (uint256) {
if (value ==0) { return0; }
if (roundUpMagnitude) {
return Math.mulDiv(value, FLOAT_PRECISION, divisor, Math.Rounding.Up);
}
return Math.mulDiv(value, FLOAT_PRECISION, divisor);
}
functiontoFactor(uint256 value, uint256 divisor) internalpurereturns (uint256) {
return toFactor(value, divisor, false);
}
functiontoFactor(int256 value, uint256 divisor) internalpurereturns (int256) {
uint256 result = toFactor(value.abs(), divisor);
return value >0 ? result.toInt256() : -result.toInt256();
}
/**
* Converts the given value from float to wei.
*
* @param value The value to convert.
* @return The converted value in wei.
*/functionfloatToWei(uint256 value) internalpurereturns (uint256) {
return value / FLOAT_TO_WEI_DIVISOR;
}
/**
* Converts the given value from wei to float.
*
* @param value The value to convert.
* @return The converted value in float.
*/functionweiToFloat(uint256 value) internalpurereturns (uint256) {
return value * FLOAT_TO_WEI_DIVISOR;
}
/**
* Converts the given number of basis points to float.
*
* @param basisPoints The number of basis points to convert.
* @return The converted value in float.
*/functionbasisPointsToFloat(uint256 basisPoints) internalpurereturns (uint256) {
return basisPoints * FLOAT_PRECISION / BASIS_POINTS_DIVISOR;
}
}
Contract Source Code
File 84 of 103: Price.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;// @title Price// @dev Struct for priceslibraryPrice{
// @param min the min price// @param max the max pricestructProps {
uint256 min;
uint256 max;
}
// @dev check if a price is empty// @param props Props// @return whether a price is emptyfunctionisEmpty(Props memory props) internalpurereturns (bool) {
return props.min==0|| props.max==0;
}
// @dev get the average of the min and max values// @param props Props// @return the average of the min and max valuesfunctionmidPrice(Props memory props) internalpurereturns (uint256) {
return (props.max+ props.min) /2;
}
// @dev pick either the min or max value// @param props Props// @param maximize whether to pick the min or max value// @return either the min or max valuefunctionpickPrice(Props memory props, bool maximize) internalpurereturns (uint256) {
return maximize ? props.max : props.min;
}
// @dev pick the min or max price depending on whether it is for a long or short position// and whether the pending pnl should be maximized or not// @param props Props// @param isLong whether it is for a long or short position// @param maximize whether the pnl should be maximized or not// @return the min or max pricefunctionpickPriceForPnl(Props memory props, bool isLong, bool maximize) internalpurereturns (uint256) {
// for long positions, pick the larger price to maximize pnl// for short positions, pick the smaller price to maximize pnlif (isLong) {
return maximize ? props.max : props.min;
}
return maximize ? props.min : props.max;
}
}
Contract Source Code
File 85 of 103: PricingUtils.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"../utils/Calc.sol";
import"../utils/Precision.sol";
// @title PricingUtils// @dev Library for pricing functions//// Price impact is calculated as://// ```// (initial imbalance) ^ (price impact exponent) * (price impact factor / 2) - (next imbalance) ^ (price impact exponent) * (price impact factor / 2)// ```//// For spot actions (deposits, withdrawals, swaps), imbalance is calculated as the// difference in the worth of the long tokens and short tokens.//// For example://// - A pool has 10 long tokens, each long token is worth $5000// - The pool also has 50,000 short tokens, each short token is worth $1// - The `price impact exponent` is set to 2 and `price impact factor` is set// to `0.01 / 50,000`// - The pool is equally balanced with $50,000 of long tokens and $50,000 of// short tokens// - If a user deposits 10 long tokens, the pool would now have $100,000 of long// tokens and $50,000 of short tokens// - The change in imbalance would be from $0 to -$50,000// - There would be negative price impact charged on the user's deposit,// calculated as `0 ^ 2 * (0.01 / 50,000) - 50,000 ^ 2 * (0.01 / 50,000) => -$500`// - If the user now withdraws 5 long tokens, the balance would change// from -$50,000 to -$25,000, a net change of +$25,000// - There would be a positive price impact rebated to the user in the form of// additional long tokens, calculated as `50,000 ^ 2 * (0.01 / 50,000) - 25,000 ^ 2 * (0.01 / 50,000) => $375`//// For position actions (increase / decrease position), imbalance is calculated// as the difference in the long and short open interest.//// `price impact exponents` and `price impact factors` are configured per market// and can differ for spot and position actions.//// The purpose of the price impact is to help reduce the risk of price manipulation,// since the contracts use an oracle price which would be an average or median price// of multiple reference exchanges. Without a price impact, it may be profitable to// manipulate the prices on reference exchanges while executing orders on the contracts.//// This risk will also be present if the positive and negative price impact values// are similar, for that reason the positive price impact should be set to a low// value in times of volatility or irregular price movements.libraryPricingUtils{
// @dev get the price impact USD if there is no crossover in balance// a crossover in balance is for example if the long open interest is larger// than the short open interest, and a short position is opened such that the// short open interest becomes larger than the long open interest// @param initialDiffUsd the initial difference in USD// @param nextDiffUsd the next difference in USD// @param impactFactor the impact factor// @param impactExponentFactor the impact exponent factorfunctiongetPriceImpactUsdForSameSideRebalance(uint256 initialDiffUsd,
uint256 nextDiffUsd,
uint256 impactFactor,
uint256 impactExponentFactor
) internalpurereturns (int256) {
bool hasPositiveImpact = nextDiffUsd < initialDiffUsd;
uint256 deltaDiffUsd = Calc.diff(
applyImpactFactor(initialDiffUsd, impactFactor, impactExponentFactor),
applyImpactFactor(nextDiffUsd, impactFactor, impactExponentFactor)
);
int256 priceImpactUsd = Calc.toSigned(deltaDiffUsd, hasPositiveImpact);
return priceImpactUsd;
}
// @dev get the price impact USD if there is a crossover in balance// a crossover in balance is for example if the long open interest is larger// than the short open interest, and a short position is opened such that the// short open interest becomes larger than the long open interest// @param initialDiffUsd the initial difference in USD// @param nextDiffUsd the next difference in USD// @param hasPositiveImpact whether there is a positive impact on balance// @param impactFactor the impact factor// @param impactExponentFactor the impact exponent factorfunctiongetPriceImpactUsdForCrossoverRebalance(uint256 initialDiffUsd,
uint256 nextDiffUsd,
uint256 positiveImpactFactor,
uint256 negativeImpactFactor,
uint256 impactExponentFactor
) internalpurereturns (int256) {
uint256 positiveImpactUsd = applyImpactFactor(initialDiffUsd, positiveImpactFactor, impactExponentFactor);
uint256 negativeImpactUsd = applyImpactFactor(nextDiffUsd, negativeImpactFactor, impactExponentFactor);
uint256 deltaDiffUsd = Calc.diff(positiveImpactUsd, negativeImpactUsd);
int256 priceImpactUsd = Calc.toSigned(deltaDiffUsd, positiveImpactUsd > negativeImpactUsd);
return priceImpactUsd;
}
// @dev apply the impact factor calculation to a USD diff value// @param diffUsd the difference in USD// @param impactFactor the impact factor// @param impactExponentFactor the impact exponent factorfunctionapplyImpactFactor(uint256 diffUsd,
uint256 impactFactor,
uint256 impactExponentFactor
) internalpurereturns (uint256) {
uint256 exponentValue = Precision.applyExponentFactor(diffUsd, impactExponentFactor);
return Precision.applyFactor(exponentValue, impactFactor);
}
}
Contract Source Code
File 86 of 103: ReentrancyGuard.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)pragmasolidity ^0.8.0;/**
* @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].
*/abstractcontractReentrancyGuard{
// 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.uint256privateconstant _NOT_ENTERED =1;
uint256privateconstant _ENTERED =2;
uint256private _status;
constructor() {
_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 making it call a
* `private` function that does the actual work.
*/modifiernonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function_nonReentrantBefore() private{
// On the first call to nonReentrant, _status will be _NOT_ENTEREDrequire(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
}
function_nonReentrantAfter() private{
// By storing the original value once again, a refund is triggered (see// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/function_reentrancyGuardEntered() internalviewreturns (bool) {
return _status == _ENTERED;
}
}
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"./RoleStore.sol";
/**
* @title RoleModule
* @dev Contract for role validation functions
*/contractRoleModule{
RoleStore publicimmutable roleStore;
/**
* @dev Constructor that initializes the role store for this contract.
*
* @param _roleStore The contract instance to use as the role store.
*/constructor(RoleStore _roleStore) {
roleStore = _roleStore;
}
/**
* @dev Only allows the contract's own address to call the function.
*/modifieronlySelf() {
if (msg.sender!=address(this)) {
revert Errors.Unauthorized(msg.sender, "SELF");
}
_;
}
/**
* @dev Only allows addresses with the TIMELOCK_MULTISIG role to call the function.
*/modifieronlyTimelockMultisig() {
_validateRole(Role.TIMELOCK_MULTISIG, "TIMELOCK_MULTISIG");
_;
}
/**
* @dev Only allows addresses with the TIMELOCK_ADMIN role to call the function.
*/modifieronlyTimelockAdmin() {
_validateRole(Role.TIMELOCK_ADMIN, "TIMELOCK_ADMIN");
_;
}
/**
* @dev Only allows addresses with the CONFIG_KEEPER role to call the function.
*/modifieronlyConfigKeeper() {
_validateRole(Role.CONFIG_KEEPER, "CONFIG_KEEPER");
_;
}
/**
* @dev Only allows addresses with the LIMITED_CONFIG_KEEPER role to call the function.
*/modifieronlyLimitedConfigKeeper() {
_validateRole(Role.LIMITED_CONFIG_KEEPER, "LIMITED_CONFIG_KEEPER");
_;
}
/**
* @dev Only allows addresses with the CONTROLLER role to call the function.
*/modifieronlyController() {
_validateRole(Role.CONTROLLER, "CONTROLLER");
_;
}
/**
* @dev Only allows addresses with the GOV_TOKEN_CONTROLLER role to call the function.
*/modifieronlyGovTokenController() {
_validateRole(Role.GOV_TOKEN_CONTROLLER, "GOV_TOKEN_CONTROLLER");
_;
}
/**
* @dev Only allows addresses with the ROUTER_PLUGIN role to call the function.
*/modifieronlyRouterPlugin() {
_validateRole(Role.ROUTER_PLUGIN, "ROUTER_PLUGIN");
_;
}
/**
* @dev Only allows addresses with the MARKET_KEEPER role to call the function.
*/modifieronlyMarketKeeper() {
_validateRole(Role.MARKET_KEEPER, "MARKET_KEEPER");
_;
}
/**
* @dev Only allows addresses with the FEE_KEEPER role to call the function.
*/modifieronlyFeeKeeper() {
_validateRole(Role.FEE_KEEPER, "FEE_KEEPER");
_;
}
/**
* @dev Only allows addresses with the FEE_DISTRIBUTION_KEEPER role to call the function.
*/modifieronlyFeeDistributionKeeper() {
_validateRole(Role.FEE_DISTRIBUTION_KEEPER, "FEE_DISTRIBUTION_KEEPER");
_;
}
/**
* @dev Only allows addresses with the ORDER_KEEPER role to call the function.
*/modifieronlyOrderKeeper() {
_validateRole(Role.ORDER_KEEPER, "ORDER_KEEPER");
_;
}
/**
* @dev Only allows addresses with the PRICING_KEEPER role to call the function.
*/modifieronlyPricingKeeper() {
_validateRole(Role.PRICING_KEEPER, "PRICING_KEEPER");
_;
}
/**
* @dev Only allows addresses with the LIQUIDATION_KEEPER role to call the function.
*/modifieronlyLiquidationKeeper() {
_validateRole(Role.LIQUIDATION_KEEPER, "LIQUIDATION_KEEPER");
_;
}
/**
* @dev Only allows addresses with the ADL_KEEPER role to call the function.
*/modifieronlyAdlKeeper() {
_validateRole(Role.ADL_KEEPER, "ADL_KEEPER");
_;
}
/**
* @dev Only allows addresses with the CONTRIBUTOR_KEEPER role to call the function.
*/modifieronlyContributorKeeper() {
_validateRole(Role.CONTRIBUTOR_KEEPER, "CONTRIBUTOR_KEEPER");
_;
}
/**
* @dev Only allows addresses with the CONTRIBUTOR_DISTRIBUTOR role to call the function.
*/modifieronlyContributorDistributor() {
_validateRole(Role.CONTRIBUTOR_DISTRIBUTOR, "CONTRIBUTOR_DISTRIBUTOR");
_;
}
/**
* @dev Validates that the caller has the specified role.
*
* If the caller does not have the specified role, the transaction is reverted.
*
* @param role The key of the role to validate.
* @param roleName The name of the role to validate.
*/function_validateRole(bytes32 role, stringmemory roleName) internalview{
if (!roleStore.hasRole(msg.sender, role)) {
revert Errors.Unauthorized(msg.sender, roleName);
}
}
}
Contract Source Code
File 91 of 103: RoleStore.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import"../utils/EnumerableValues.sol";
import"./Role.sol";
import"../error/Errors.sol";
/**
* @title RoleStore
* @dev Stores roles and their members.
*/contractRoleStore{
usingEnumerableSetforEnumerableSet.AddressSet;
usingEnumerableSetforEnumerableSet.Bytes32Set;
usingEnumerableValuesforEnumerableSet.AddressSet;
usingEnumerableValuesforEnumerableSet.Bytes32Set;
EnumerableSet.Bytes32Set internal roles;
mapping(bytes32=> EnumerableSet.AddressSet) internal roleMembers;
// checking if an account has a role is a frequently used function// roleCache helps to save gas by offering a more efficient lookup// vs calling roleMembers[key].contains(account)mapping(address=>mapping (bytes32=>bool)) roleCache;
modifieronlyRoleAdmin() {
if (!hasRole(msg.sender, Role.ROLE_ADMIN)) {
revert Errors.Unauthorized(msg.sender, "ROLE_ADMIN");
}
_;
}
constructor() {
_grantRole(msg.sender, Role.ROLE_ADMIN);
}
/**
* @dev Grants the specified role to the given account.
*
* @param account The address of the account.
* @param roleKey The key of the role to grant.
*/functiongrantRole(address account, bytes32 roleKey) externalonlyRoleAdmin{
_grantRole(account, roleKey);
}
/**
* @dev Revokes the specified role from the given account.
*
* @param account The address of the account.
* @param roleKey The key of the role to revoke.
*/functionrevokeRole(address account, bytes32 roleKey) externalonlyRoleAdmin{
_revokeRole(account, roleKey);
}
/**
* @dev Returns true if the given account has the specified role.
*
* @param account The address of the account.
* @param roleKey The key of the role.
* @return True if the account has the role, false otherwise.
*/functionhasRole(address account, bytes32 roleKey) publicviewreturns (bool) {
return roleCache[account][roleKey];
}
/**
* @dev Returns the number of roles stored in the contract.
*
* @return The number of roles.
*/functiongetRoleCount() externalviewreturns (uint256) {
return roles.length();
}
/**
* @dev Returns the keys of the roles stored in the contract.
*
* @param start The starting index of the range of roles to return.
* @param end The ending index of the range of roles to return.
* @return The keys of the roles.
*/functiongetRoles(uint256 start, uint256 end) externalviewreturns (bytes32[] memory) {
return roles.valuesAt(start, end);
}
/**
* @dev Returns the number of members of the specified role.
*
* @param roleKey The key of the role.
* @return The number of members of the role.
*/functiongetRoleMemberCount(bytes32 roleKey) externalviewreturns (uint256) {
return roleMembers[roleKey].length();
}
/**
* @dev Returns the members of the specified role.
*
* @param roleKey The key of the role.
* @param start the start index, the value for this index will be included.
* @param end the end index, the value for this index will not be included.
* @return The members of the role.
*/functiongetRoleMembers(bytes32 roleKey, uint256 start, uint256 end) externalviewreturns (address[] memory) {
return roleMembers[roleKey].valuesAt(start, end);
}
function_grantRole(address account, bytes32 roleKey) internal{
roles.add(roleKey);
roleMembers[roleKey].add(account);
roleCache[account][roleKey] =true;
}
function_revokeRole(address account, bytes32 roleKey) internal{
roleMembers[roleKey].remove(account);
roleCache[account][roleKey] =false;
if (roleMembers[roleKey].length() ==0) {
if (roleKey == Role.ROLE_ADMIN) {
revert Errors.ThereMustBeAtLeastOneRoleAdmin();
}
if (roleKey == Role.TIMELOCK_MULTISIG) {
revert Errors.ThereMustBeAtLeastOneTimelockMultiSig();
}
}
}
}
Contract Source Code
File 92 of 103: SafeCast.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SafeCast.sol)// This file was procedurally generated from scripts/generate/templates/SafeCast.js.pragmasolidity ^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.
*/librarySafeCast{
/**
* @dev Returns the downcasted uint248 from uint256, reverting on
* overflow (when the input is greater than largest uint248).
*
* Counterpart to Solidity's `uint248` operator.
*
* Requirements:
*
* - input must fit into 248 bits
*
* _Available since v4.7._
*/functiontoUint248(uint256 value) internalpurereturns (uint248) {
require(value <=type(uint248).max, "SafeCast: value doesn't fit in 248 bits");
returnuint248(value);
}
/**
* @dev Returns the downcasted uint240 from uint256, reverting on
* overflow (when the input is greater than largest uint240).
*
* Counterpart to Solidity's `uint240` operator.
*
* Requirements:
*
* - input must fit into 240 bits
*
* _Available since v4.7._
*/functiontoUint240(uint256 value) internalpurereturns (uint240) {
require(value <=type(uint240).max, "SafeCast: value doesn't fit in 240 bits");
returnuint240(value);
}
/**
* @dev Returns the downcasted uint232 from uint256, reverting on
* overflow (when the input is greater than largest uint232).
*
* Counterpart to Solidity's `uint232` operator.
*
* Requirements:
*
* - input must fit into 232 bits
*
* _Available since v4.7._
*/functiontoUint232(uint256 value) internalpurereturns (uint232) {
require(value <=type(uint232).max, "SafeCast: value doesn't fit in 232 bits");
returnuint232(value);
}
/**
* @dev Returns the downcasted uint224 from uint256, reverting on
* overflow (when the input is greater than largest uint224).
*
* Counterpart to Solidity's `uint224` operator.
*
* Requirements:
*
* - input must fit into 224 bits
*
* _Available since v4.2._
*/functiontoUint224(uint256 value) internalpurereturns (uint224) {
require(value <=type(uint224).max, "SafeCast: value doesn't fit in 224 bits");
returnuint224(value);
}
/**
* @dev Returns the downcasted uint216 from uint256, reverting on
* overflow (when the input is greater than largest uint216).
*
* Counterpart to Solidity's `uint216` operator.
*
* Requirements:
*
* - input must fit into 216 bits
*
* _Available since v4.7._
*/functiontoUint216(uint256 value) internalpurereturns (uint216) {
require(value <=type(uint216).max, "SafeCast: value doesn't fit in 216 bits");
returnuint216(value);
}
/**
* @dev Returns the downcasted uint208 from uint256, reverting on
* overflow (when the input is greater than largest uint208).
*
* Counterpart to Solidity's `uint208` operator.
*
* Requirements:
*
* - input must fit into 208 bits
*
* _Available since v4.7._
*/functiontoUint208(uint256 value) internalpurereturns (uint208) {
require(value <=type(uint208).max, "SafeCast: value doesn't fit in 208 bits");
returnuint208(value);
}
/**
* @dev Returns the downcasted uint200 from uint256, reverting on
* overflow (when the input is greater than largest uint200).
*
* Counterpart to Solidity's `uint200` operator.
*
* Requirements:
*
* - input must fit into 200 bits
*
* _Available since v4.7._
*/functiontoUint200(uint256 value) internalpurereturns (uint200) {
require(value <=type(uint200).max, "SafeCast: value doesn't fit in 200 bits");
returnuint200(value);
}
/**
* @dev Returns the downcasted uint192 from uint256, reverting on
* overflow (when the input is greater than largest uint192).
*
* Counterpart to Solidity's `uint192` operator.
*
* Requirements:
*
* - input must fit into 192 bits
*
* _Available since v4.7._
*/functiontoUint192(uint256 value) internalpurereturns (uint192) {
require(value <=type(uint192).max, "SafeCast: value doesn't fit in 192 bits");
returnuint192(value);
}
/**
* @dev Returns the downcasted uint184 from uint256, reverting on
* overflow (when the input is greater than largest uint184).
*
* Counterpart to Solidity's `uint184` operator.
*
* Requirements:
*
* - input must fit into 184 bits
*
* _Available since v4.7._
*/functiontoUint184(uint256 value) internalpurereturns (uint184) {
require(value <=type(uint184).max, "SafeCast: value doesn't fit in 184 bits");
returnuint184(value);
}
/**
* @dev Returns the downcasted uint176 from uint256, reverting on
* overflow (when the input is greater than largest uint176).
*
* Counterpart to Solidity's `uint176` operator.
*
* Requirements:
*
* - input must fit into 176 bits
*
* _Available since v4.7._
*/functiontoUint176(uint256 value) internalpurereturns (uint176) {
require(value <=type(uint176).max, "SafeCast: value doesn't fit in 176 bits");
returnuint176(value);
}
/**
* @dev Returns the downcasted uint168 from uint256, reverting on
* overflow (when the input is greater than largest uint168).
*
* Counterpart to Solidity's `uint168` operator.
*
* Requirements:
*
* - input must fit into 168 bits
*
* _Available since v4.7._
*/functiontoUint168(uint256 value) internalpurereturns (uint168) {
require(value <=type(uint168).max, "SafeCast: value doesn't fit in 168 bits");
returnuint168(value);
}
/**
* @dev Returns the downcasted uint160 from uint256, reverting on
* overflow (when the input is greater than largest uint160).
*
* Counterpart to Solidity's `uint160` operator.
*
* Requirements:
*
* - input must fit into 160 bits
*
* _Available since v4.7._
*/functiontoUint160(uint256 value) internalpurereturns (uint160) {
require(value <=type(uint160).max, "SafeCast: value doesn't fit in 160 bits");
returnuint160(value);
}
/**
* @dev Returns the downcasted uint152 from uint256, reverting on
* overflow (when the input is greater than largest uint152).
*
* Counterpart to Solidity's `uint152` operator.
*
* Requirements:
*
* - input must fit into 152 bits
*
* _Available since v4.7._
*/functiontoUint152(uint256 value) internalpurereturns (uint152) {
require(value <=type(uint152).max, "SafeCast: value doesn't fit in 152 bits");
returnuint152(value);
}
/**
* @dev Returns the downcasted uint144 from uint256, reverting on
* overflow (when the input is greater than largest uint144).
*
* Counterpart to Solidity's `uint144` operator.
*
* Requirements:
*
* - input must fit into 144 bits
*
* _Available since v4.7._
*/functiontoUint144(uint256 value) internalpurereturns (uint144) {
require(value <=type(uint144).max, "SafeCast: value doesn't fit in 144 bits");
returnuint144(value);
}
/**
* @dev Returns the downcasted uint136 from uint256, reverting on
* overflow (when the input is greater than largest uint136).
*
* Counterpart to Solidity's `uint136` operator.
*
* Requirements:
*
* - input must fit into 136 bits
*
* _Available since v4.7._
*/functiontoUint136(uint256 value) internalpurereturns (uint136) {
require(value <=type(uint136).max, "SafeCast: value doesn't fit in 136 bits");
returnuint136(value);
}
/**
* @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
*
* _Available since v2.5._
*/functiontoUint128(uint256 value) internalpurereturns (uint128) {
require(value <=type(uint128).max, "SafeCast: value doesn't fit in 128 bits");
returnuint128(value);
}
/**
* @dev Returns the downcasted uint120 from uint256, reverting on
* overflow (when the input is greater than largest uint120).
*
* Counterpart to Solidity's `uint120` operator.
*
* Requirements:
*
* - input must fit into 120 bits
*
* _Available since v4.7._
*/functiontoUint120(uint256 value) internalpurereturns (uint120) {
require(value <=type(uint120).max, "SafeCast: value doesn't fit in 120 bits");
returnuint120(value);
}
/**
* @dev Returns the downcasted uint112 from uint256, reverting on
* overflow (when the input is greater than largest uint112).
*
* Counterpart to Solidity's `uint112` operator.
*
* Requirements:
*
* - input must fit into 112 bits
*
* _Available since v4.7._
*/functiontoUint112(uint256 value) internalpurereturns (uint112) {
require(value <=type(uint112).max, "SafeCast: value doesn't fit in 112 bits");
returnuint112(value);
}
/**
* @dev Returns the downcasted uint104 from uint256, reverting on
* overflow (when the input is greater than largest uint104).
*
* Counterpart to Solidity's `uint104` operator.
*
* Requirements:
*
* - input must fit into 104 bits
*
* _Available since v4.7._
*/functiontoUint104(uint256 value) internalpurereturns (uint104) {
require(value <=type(uint104).max, "SafeCast: value doesn't fit in 104 bits");
returnuint104(value);
}
/**
* @dev Returns the downcasted uint96 from uint256, reverting on
* overflow (when the input is greater than largest uint96).
*
* Counterpart to Solidity's `uint96` operator.
*
* Requirements:
*
* - input must fit into 96 bits
*
* _Available since v4.2._
*/functiontoUint96(uint256 value) internalpurereturns (uint96) {
require(value <=type(uint96).max, "SafeCast: value doesn't fit in 96 bits");
returnuint96(value);
}
/**
* @dev Returns the downcasted uint88 from uint256, reverting on
* overflow (when the input is greater than largest uint88).
*
* Counterpart to Solidity's `uint88` operator.
*
* Requirements:
*
* - input must fit into 88 bits
*
* _Available since v4.7._
*/functiontoUint88(uint256 value) internalpurereturns (uint88) {
require(value <=type(uint88).max, "SafeCast: value doesn't fit in 88 bits");
returnuint88(value);
}
/**
* @dev Returns the downcasted uint80 from uint256, reverting on
* overflow (when the input is greater than largest uint80).
*
* Counterpart to Solidity's `uint80` operator.
*
* Requirements:
*
* - input must fit into 80 bits
*
* _Available since v4.7._
*/functiontoUint80(uint256 value) internalpurereturns (uint80) {
require(value <=type(uint80).max, "SafeCast: value doesn't fit in 80 bits");
returnuint80(value);
}
/**
* @dev Returns the downcasted uint72 from uint256, reverting on
* overflow (when the input is greater than largest uint72).
*
* Counterpart to Solidity's `uint72` operator.
*
* Requirements:
*
* - input must fit into 72 bits
*
* _Available since v4.7._
*/functiontoUint72(uint256 value) internalpurereturns (uint72) {
require(value <=type(uint72).max, "SafeCast: value doesn't fit in 72 bits");
returnuint72(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
*
* _Available since v2.5._
*/functiontoUint64(uint256 value) internalpurereturns (uint64) {
require(value <=type(uint64).max, "SafeCast: value doesn't fit in 64 bits");
returnuint64(value);
}
/**
* @dev Returns the downcasted uint56 from uint256, reverting on
* overflow (when the input is greater than largest uint56).
*
* Counterpart to Solidity's `uint56` operator.
*
* Requirements:
*
* - input must fit into 56 bits
*
* _Available since v4.7._
*/functiontoUint56(uint256 value) internalpurereturns (uint56) {
require(value <=type(uint56).max, "SafeCast: value doesn't fit in 56 bits");
returnuint56(value);
}
/**
* @dev Returns the downcasted uint48 from uint256, reverting on
* overflow (when the input is greater than largest uint48).
*
* Counterpart to Solidity's `uint48` operator.
*
* Requirements:
*
* - input must fit into 48 bits
*
* _Available since v4.7._
*/functiontoUint48(uint256 value) internalpurereturns (uint48) {
require(value <=type(uint48).max, "SafeCast: value doesn't fit in 48 bits");
returnuint48(value);
}
/**
* @dev Returns the downcasted uint40 from uint256, reverting on
* overflow (when the input is greater than largest uint40).
*
* Counterpart to Solidity's `uint40` operator.
*
* Requirements:
*
* - input must fit into 40 bits
*
* _Available since v4.7._
*/functiontoUint40(uint256 value) internalpurereturns (uint40) {
require(value <=type(uint40).max, "SafeCast: value doesn't fit in 40 bits");
returnuint40(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
*
* _Available since v2.5._
*/functiontoUint32(uint256 value) internalpurereturns (uint32) {
require(value <=type(uint32).max, "SafeCast: value doesn't fit in 32 bits");
returnuint32(value);
}
/**
* @dev Returns the downcasted uint24 from uint256, reverting on
* overflow (when the input is greater than largest uint24).
*
* Counterpart to Solidity's `uint24` operator.
*
* Requirements:
*
* - input must fit into 24 bits
*
* _Available since v4.7._
*/functiontoUint24(uint256 value) internalpurereturns (uint24) {
require(value <=type(uint24).max, "SafeCast: value doesn't fit in 24 bits");
returnuint24(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
*
* _Available since v2.5._
*/functiontoUint16(uint256 value) internalpurereturns (uint16) {
require(value <=type(uint16).max, "SafeCast: value doesn't fit in 16 bits");
returnuint16(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
*
* _Available since v2.5._
*/functiontoUint8(uint256 value) internalpurereturns (uint8) {
require(value <=type(uint8).max, "SafeCast: value doesn't fit in 8 bits");
returnuint8(value);
}
/**
* @dev Converts a signed int256 into an unsigned uint256.
*
* Requirements:
*
* - input must be greater than or equal to 0.
*
* _Available since v3.0._
*/functiontoUint256(int256 value) internalpurereturns (uint256) {
require(value >=0, "SafeCast: value must be positive");
returnuint256(value);
}
/**
* @dev Returns the downcasted int248 from int256, reverting on
* overflow (when the input is less than smallest int248 or
* greater than largest int248).
*
* Counterpart to Solidity's `int248` operator.
*
* Requirements:
*
* - input must fit into 248 bits
*
* _Available since v4.7._
*/functiontoInt248(int256 value) internalpurereturns (int248 downcasted) {
downcasted =int248(value);
require(downcasted == value, "SafeCast: value doesn't fit in 248 bits");
}
/**
* @dev Returns the downcasted int240 from int256, reverting on
* overflow (when the input is less than smallest int240 or
* greater than largest int240).
*
* Counterpart to Solidity's `int240` operator.
*
* Requirements:
*
* - input must fit into 240 bits
*
* _Available since v4.7._
*/functiontoInt240(int256 value) internalpurereturns (int240 downcasted) {
downcasted =int240(value);
require(downcasted == value, "SafeCast: value doesn't fit in 240 bits");
}
/**
* @dev Returns the downcasted int232 from int256, reverting on
* overflow (when the input is less than smallest int232 or
* greater than largest int232).
*
* Counterpart to Solidity's `int232` operator.
*
* Requirements:
*
* - input must fit into 232 bits
*
* _Available since v4.7._
*/functiontoInt232(int256 value) internalpurereturns (int232 downcasted) {
downcasted =int232(value);
require(downcasted == value, "SafeCast: value doesn't fit in 232 bits");
}
/**
* @dev Returns the downcasted int224 from int256, reverting on
* overflow (when the input is less than smallest int224 or
* greater than largest int224).
*
* Counterpart to Solidity's `int224` operator.
*
* Requirements:
*
* - input must fit into 224 bits
*
* _Available since v4.7._
*/functiontoInt224(int256 value) internalpurereturns (int224 downcasted) {
downcasted =int224(value);
require(downcasted == value, "SafeCast: value doesn't fit in 224 bits");
}
/**
* @dev Returns the downcasted int216 from int256, reverting on
* overflow (when the input is less than smallest int216 or
* greater than largest int216).
*
* Counterpart to Solidity's `int216` operator.
*
* Requirements:
*
* - input must fit into 216 bits
*
* _Available since v4.7._
*/functiontoInt216(int256 value) internalpurereturns (int216 downcasted) {
downcasted =int216(value);
require(downcasted == value, "SafeCast: value doesn't fit in 216 bits");
}
/**
* @dev Returns the downcasted int208 from int256, reverting on
* overflow (when the input is less than smallest int208 or
* greater than largest int208).
*
* Counterpart to Solidity's `int208` operator.
*
* Requirements:
*
* - input must fit into 208 bits
*
* _Available since v4.7._
*/functiontoInt208(int256 value) internalpurereturns (int208 downcasted) {
downcasted =int208(value);
require(downcasted == value, "SafeCast: value doesn't fit in 208 bits");
}
/**
* @dev Returns the downcasted int200 from int256, reverting on
* overflow (when the input is less than smallest int200 or
* greater than largest int200).
*
* Counterpart to Solidity's `int200` operator.
*
* Requirements:
*
* - input must fit into 200 bits
*
* _Available since v4.7._
*/functiontoInt200(int256 value) internalpurereturns (int200 downcasted) {
downcasted =int200(value);
require(downcasted == value, "SafeCast: value doesn't fit in 200 bits");
}
/**
* @dev Returns the downcasted int192 from int256, reverting on
* overflow (when the input is less than smallest int192 or
* greater than largest int192).
*
* Counterpart to Solidity's `int192` operator.
*
* Requirements:
*
* - input must fit into 192 bits
*
* _Available since v4.7._
*/functiontoInt192(int256 value) internalpurereturns (int192 downcasted) {
downcasted =int192(value);
require(downcasted == value, "SafeCast: value doesn't fit in 192 bits");
}
/**
* @dev Returns the downcasted int184 from int256, reverting on
* overflow (when the input is less than smallest int184 or
* greater than largest int184).
*
* Counterpart to Solidity's `int184` operator.
*
* Requirements:
*
* - input must fit into 184 bits
*
* _Available since v4.7._
*/functiontoInt184(int256 value) internalpurereturns (int184 downcasted) {
downcasted =int184(value);
require(downcasted == value, "SafeCast: value doesn't fit in 184 bits");
}
/**
* @dev Returns the downcasted int176 from int256, reverting on
* overflow (when the input is less than smallest int176 or
* greater than largest int176).
*
* Counterpart to Solidity's `int176` operator.
*
* Requirements:
*
* - input must fit into 176 bits
*
* _Available since v4.7._
*/functiontoInt176(int256 value) internalpurereturns (int176 downcasted) {
downcasted =int176(value);
require(downcasted == value, "SafeCast: value doesn't fit in 176 bits");
}
/**
* @dev Returns the downcasted int168 from int256, reverting on
* overflow (when the input is less than smallest int168 or
* greater than largest int168).
*
* Counterpart to Solidity's `int168` operator.
*
* Requirements:
*
* - input must fit into 168 bits
*
* _Available since v4.7._
*/functiontoInt168(int256 value) internalpurereturns (int168 downcasted) {
downcasted =int168(value);
require(downcasted == value, "SafeCast: value doesn't fit in 168 bits");
}
/**
* @dev Returns the downcasted int160 from int256, reverting on
* overflow (when the input is less than smallest int160 or
* greater than largest int160).
*
* Counterpart to Solidity's `int160` operator.
*
* Requirements:
*
* - input must fit into 160 bits
*
* _Available since v4.7._
*/functiontoInt160(int256 value) internalpurereturns (int160 downcasted) {
downcasted =int160(value);
require(downcasted == value, "SafeCast: value doesn't fit in 160 bits");
}
/**
* @dev Returns the downcasted int152 from int256, reverting on
* overflow (when the input is less than smallest int152 or
* greater than largest int152).
*
* Counterpart to Solidity's `int152` operator.
*
* Requirements:
*
* - input must fit into 152 bits
*
* _Available since v4.7._
*/functiontoInt152(int256 value) internalpurereturns (int152 downcasted) {
downcasted =int152(value);
require(downcasted == value, "SafeCast: value doesn't fit in 152 bits");
}
/**
* @dev Returns the downcasted int144 from int256, reverting on
* overflow (when the input is less than smallest int144 or
* greater than largest int144).
*
* Counterpart to Solidity's `int144` operator.
*
* Requirements:
*
* - input must fit into 144 bits
*
* _Available since v4.7._
*/functiontoInt144(int256 value) internalpurereturns (int144 downcasted) {
downcasted =int144(value);
require(downcasted == value, "SafeCast: value doesn't fit in 144 bits");
}
/**
* @dev Returns the downcasted int136 from int256, reverting on
* overflow (when the input is less than smallest int136 or
* greater than largest int136).
*
* Counterpart to Solidity's `int136` operator.
*
* Requirements:
*
* - input must fit into 136 bits
*
* _Available since v4.7._
*/functiontoInt136(int256 value) internalpurereturns (int136 downcasted) {
downcasted =int136(value);
require(downcasted == value, "SafeCast: value doesn't fit in 136 bits");
}
/**
* @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._
*/functiontoInt128(int256 value) internalpurereturns (int128 downcasted) {
downcasted =int128(value);
require(downcasted == value, "SafeCast: value doesn't fit in 128 bits");
}
/**
* @dev Returns the downcasted int120 from int256, reverting on
* overflow (when the input is less than smallest int120 or
* greater than largest int120).
*
* Counterpart to Solidity's `int120` operator.
*
* Requirements:
*
* - input must fit into 120 bits
*
* _Available since v4.7._
*/functiontoInt120(int256 value) internalpurereturns (int120 downcasted) {
downcasted =int120(value);
require(downcasted == value, "SafeCast: value doesn't fit in 120 bits");
}
/**
* @dev Returns the downcasted int112 from int256, reverting on
* overflow (when the input is less than smallest int112 or
* greater than largest int112).
*
* Counterpart to Solidity's `int112` operator.
*
* Requirements:
*
* - input must fit into 112 bits
*
* _Available since v4.7._
*/functiontoInt112(int256 value) internalpurereturns (int112 downcasted) {
downcasted =int112(value);
require(downcasted == value, "SafeCast: value doesn't fit in 112 bits");
}
/**
* @dev Returns the downcasted int104 from int256, reverting on
* overflow (when the input is less than smallest int104 or
* greater than largest int104).
*
* Counterpart to Solidity's `int104` operator.
*
* Requirements:
*
* - input must fit into 104 bits
*
* _Available since v4.7._
*/functiontoInt104(int256 value) internalpurereturns (int104 downcasted) {
downcasted =int104(value);
require(downcasted == value, "SafeCast: value doesn't fit in 104 bits");
}
/**
* @dev Returns the downcasted int96 from int256, reverting on
* overflow (when the input is less than smallest int96 or
* greater than largest int96).
*
* Counterpart to Solidity's `int96` operator.
*
* Requirements:
*
* - input must fit into 96 bits
*
* _Available since v4.7._
*/functiontoInt96(int256 value) internalpurereturns (int96 downcasted) {
downcasted =int96(value);
require(downcasted == value, "SafeCast: value doesn't fit in 96 bits");
}
/**
* @dev Returns the downcasted int88 from int256, reverting on
* overflow (when the input is less than smallest int88 or
* greater than largest int88).
*
* Counterpart to Solidity's `int88` operator.
*
* Requirements:
*
* - input must fit into 88 bits
*
* _Available since v4.7._
*/functiontoInt88(int256 value) internalpurereturns (int88 downcasted) {
downcasted =int88(value);
require(downcasted == value, "SafeCast: value doesn't fit in 88 bits");
}
/**
* @dev Returns the downcasted int80 from int256, reverting on
* overflow (when the input is less than smallest int80 or
* greater than largest int80).
*
* Counterpart to Solidity's `int80` operator.
*
* Requirements:
*
* - input must fit into 80 bits
*
* _Available since v4.7._
*/functiontoInt80(int256 value) internalpurereturns (int80 downcasted) {
downcasted =int80(value);
require(downcasted == value, "SafeCast: value doesn't fit in 80 bits");
}
/**
* @dev Returns the downcasted int72 from int256, reverting on
* overflow (when the input is less than smallest int72 or
* greater than largest int72).
*
* Counterpart to Solidity's `int72` operator.
*
* Requirements:
*
* - input must fit into 72 bits
*
* _Available since v4.7._
*/functiontoInt72(int256 value) internalpurereturns (int72 downcasted) {
downcasted =int72(value);
require(downcasted == value, "SafeCast: value doesn't fit in 72 bits");
}
/**
* @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._
*/functiontoInt64(int256 value) internalpurereturns (int64 downcasted) {
downcasted =int64(value);
require(downcasted == value, "SafeCast: value doesn't fit in 64 bits");
}
/**
* @dev Returns the downcasted int56 from int256, reverting on
* overflow (when the input is less than smallest int56 or
* greater than largest int56).
*
* Counterpart to Solidity's `int56` operator.
*
* Requirements:
*
* - input must fit into 56 bits
*
* _Available since v4.7._
*/functiontoInt56(int256 value) internalpurereturns (int56 downcasted) {
downcasted =int56(value);
require(downcasted == value, "SafeCast: value doesn't fit in 56 bits");
}
/**
* @dev Returns the downcasted int48 from int256, reverting on
* overflow (when the input is less than smallest int48 or
* greater than largest int48).
*
* Counterpart to Solidity's `int48` operator.
*
* Requirements:
*
* - input must fit into 48 bits
*
* _Available since v4.7._
*/functiontoInt48(int256 value) internalpurereturns (int48 downcasted) {
downcasted =int48(value);
require(downcasted == value, "SafeCast: value doesn't fit in 48 bits");
}
/**
* @dev Returns the downcasted int40 from int256, reverting on
* overflow (when the input is less than smallest int40 or
* greater than largest int40).
*
* Counterpart to Solidity's `int40` operator.
*
* Requirements:
*
* - input must fit into 40 bits
*
* _Available since v4.7._
*/functiontoInt40(int256 value) internalpurereturns (int40 downcasted) {
downcasted =int40(value);
require(downcasted == value, "SafeCast: value doesn't fit in 40 bits");
}
/**
* @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._
*/functiontoInt32(int256 value) internalpurereturns (int32 downcasted) {
downcasted =int32(value);
require(downcasted == value, "SafeCast: value doesn't fit in 32 bits");
}
/**
* @dev Returns the downcasted int24 from int256, reverting on
* overflow (when the input is less than smallest int24 or
* greater than largest int24).
*
* Counterpart to Solidity's `int24` operator.
*
* Requirements:
*
* - input must fit into 24 bits
*
* _Available since v4.7._
*/functiontoInt24(int256 value) internalpurereturns (int24 downcasted) {
downcasted =int24(value);
require(downcasted == value, "SafeCast: value doesn't fit in 24 bits");
}
/**
* @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._
*/functiontoInt16(int256 value) internalpurereturns (int16 downcasted) {
downcasted =int16(value);
require(downcasted == value, "SafeCast: value doesn't fit in 16 bits");
}
/**
* @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._
*/functiontoInt8(int256 value) internalpurereturns (int8 downcasted) {
downcasted =int8(value);
require(downcasted == value, "SafeCast: value doesn't fit in 8 bits");
}
/**
* @dev Converts an unsigned uint256 into a signed int256.
*
* Requirements:
*
* - input must be less than or equal to maxInt256.
*
* _Available since v3.0._
*/functiontoInt256(uint256 value) internalpurereturns (int256) {
// Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positiverequire(value <=uint256(type(int256).max), "SafeCast: value doesn't fit in an int256");
returnint256(value);
}
}
Contract Source Code
File 93 of 103: SafeERC20.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)pragmasolidity ^0.8.0;import"../IERC20.sol";
import"../extensions/IERC20Permit.sol";
import"../../../utils/Address.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.
*/librarySafeERC20{
usingAddressforaddress;
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/functionsafeTransfer(IERC20 token, address to, uint256 value) internal{
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/functionsafeTransferFrom(IERC20 token, addressfrom, 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.
*/functionsafeApprove(IERC20 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'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));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/functionsafeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal{
uint256 oldAllowance = token.allowance(address(this), spender);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/functionsafeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal{
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/functionforceApprove(IERC20 token, address spender, uint256 value) internal{
bytesmemory approvalCall =abi.encodeWithSelector(token.approve.selector, spender, value);
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
* Revert on invalid signature.
*/functionsafePermit(
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");
}
/**
* @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(IERC20 token, bytesmemory 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.bytesmemory returndata =address(token).functionCall(data, "SafeERC20: low-level call failed");
require(returndata.length==0||abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
/**
* @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).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/function_callOptionalReturnBool(IERC20 token, bytesmemory data) privatereturns (bool) {
// 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 cannot use {Address-functionCall} here since this should return false// and not revert is the subcall reverts.
(bool success, bytesmemory returndata) =address(token).call(data);
return
success && (returndata.length==0||abi.decode(returndata, (bool))) && Address.isContract(address(token));
}
}
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)pragmasolidity ^0.8.0;/**
* @dev Standard signed math utilities missing in the Solidity language.
*/librarySignedMath{
/**
* @dev Returns the largest of two signed numbers.
*/functionmax(int256 a, int256 b) internalpurereturns (int256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two signed numbers.
*/functionmin(int256 a, int256 b) internalpurereturns (int256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two signed numbers without overflow.
* The result is rounded towards zero.
*/functionaverage(int256 a, int256 b) internalpurereturns (int256) {
// Formula from the book "Hacker's Delight"int256 x = (a & b) + ((a ^ b) >>1);
return x + (int256(uint256(x) >>255) & (a ^ b));
}
/**
* @dev Returns the absolute unsigned value of a signed value.
*/functionabs(int256 n) internalpurereturns (uint256) {
unchecked {
// must be unchecked in order to support `n = type(int256).min`returnuint256(n >=0 ? n : -n);
}
}
}
Contract Source Code
File 96 of 103: StrictBank.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"@openzeppelin/contracts/token/ERC20/IERC20.sol";
import"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import"./Bank.sol";
// @title StrictBank// @dev a stricter version of Bank//// the Bank contract does not have functions to validate the amount of tokens// transferred in// the Bank contract will mainly assume that safeTransferFrom calls work correctly// and that tokens were transferred into it if there was no revert//// the StrictBank contract keeps track of its internal token balance// and uses recordTransferIn to compare its change in balance and return// the amount of tokens receivedcontractStrictBankisBank{
usingSafeERC20forIERC20;
// used to record token balances to evaluate amounts transferred inmapping (address=>uint256) public tokenBalances;
constructor(RoleStore _roleStore, DataStore _dataStore) Bank(_roleStore, _dataStore) {}
// @dev records a token transfer into the contract// @param token the token to record the transfer for// @return the amount of tokens transferred infunctionrecordTransferIn(address token) externalonlyControllerreturns (uint256) {
return _recordTransferIn(token);
}
// @dev this can be used to update the tokenBalances in case of token burns// or similar balance changes// the prevBalance is not validated to be more than the nextBalance as this// could allow someone to block this call by transferring into the contract// @param token the token to record the burn for// @return the new balancefunctionsyncTokenBalance(address token) externalonlyControllerreturns (uint256) {
uint256 nextBalance = IERC20(token).balanceOf(address(this));
tokenBalances[token] = nextBalance;
return nextBalance;
}
// @dev records a token transfer into the contract// @param token the token to record the transfer for// @return the amount of tokens transferred infunction_recordTransferIn(address token) internalreturns (uint256) {
uint256 prevBalance = tokenBalances[token];
uint256 nextBalance = IERC20(token).balanceOf(address(this));
tokenBalances[token] = nextBalance;
return nextBalance - prevBalance;
}
// @dev update the internal balance after tokens have been transferred out// this is called from the Bank contract// @param token the token that was transferred outfunction_afterTransferOut(address token) internaloverride{
tokenBalances[token] = IERC20(token).balanceOf(address(this));
}
}
Contract Source Code
File 97 of 103: SwapHandler.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"@openzeppelin/contracts/security/ReentrancyGuard.sol";
import"../role/RoleModule.sol";
import"./SwapUtils.sol";
/**
* @title SwapHandler
* @dev A contract to help with swap functions
*/contractSwapHandlerisReentrancyGuard, RoleModule{
constructor(RoleStore _roleStore) RoleModule(_roleStore) {}
/**
* @dev perform a swap based on the given params
* @param params SwapUtils.SwapParams
* @return (outputToken, outputAmount)
*/functionswap(
SwapUtils.SwapParams memory params
)
externalnonReentrantonlyControllerreturns (address, uint256)
{
return SwapUtils.swap(params);
}
}
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"@openzeppelin/contracts/utils/math/SignedMath.sol";
import"../market/MarketUtils.sol";
import"../utils/Precision.sol";
import"../utils/Calc.sol";
import"./PricingUtils.sol";
import"./ISwapPricingUtils.sol";
// @title SwapPricingUtils// @dev Library for pricing functionslibrarySwapPricingUtils{
usingSignedMathforint256;
usingSafeCastforuint256;
usingSafeCastforint256;
usingEventUtilsforEventUtils.AddressItems;
usingEventUtilsforEventUtils.UintItems;
usingEventUtilsforEventUtils.IntItems;
usingEventUtilsforEventUtils.BoolItems;
usingEventUtilsforEventUtils.Bytes32Items;
usingEventUtilsforEventUtils.BytesItems;
usingEventUtilsforEventUtils.StringItems;
// @dev GetPriceImpactUsdParams struct used in getPriceImpactUsd to// avoid stack too deep errors// @param dataStore DataStore// @param market the market to check// @param tokenA the token to check balance for// @param tokenB the token to check balance for// @param priceForTokenA the price for tokenA// @param priceForTokenB the price for tokenB// @param usdDeltaForTokenA the USD change in amount of tokenA// @param usdDeltaForTokenB the USD change in amount of tokenBstructGetPriceImpactUsdParams {
DataStore dataStore;
Market.Props market;
address tokenA;
address tokenB;
uint256 priceForTokenA;
uint256 priceForTokenB;
int256 usdDeltaForTokenA;
int256 usdDeltaForTokenB;
bool includeVirtualInventoryImpact;
}
structEmitSwapInfoParams {
bytes32 orderKey;
address market;
address receiver;
address tokenIn;
address tokenOut;
uint256 tokenInPrice;
uint256 tokenOutPrice;
uint256 amountIn;
uint256 amountInAfterFees;
uint256 amountOut;
int256 priceImpactUsd;
int256 priceImpactAmount;
int256 tokenInPriceImpactAmount;
}
// @dev PoolParams struct to contain pool values// @param poolUsdForTokenA the USD value of tokenA in the pool// @param poolUsdForTokenB the USD value of tokenB in the pool// @param nextPoolUsdForTokenA the next USD value of tokenA in the pool// @param nextPoolUsdForTokenB the next USD value of tokenB in the poolstructPoolParams {
uint256 poolUsdForTokenA;
uint256 poolUsdForTokenB;
uint256 nextPoolUsdForTokenA;
uint256 nextPoolUsdForTokenB;
}
// @dev SwapFees struct to contain swap fee values// @param feeReceiverAmount the fee amount for the fee receiver// @param feeAmountForPool the fee amount for the pool// @param amountAfterFees the output amount after feesstructSwapFees {
uint256 feeReceiverAmount;
uint256 feeAmountForPool;
uint256 amountAfterFees;
address uiFeeReceiver;
uint256 uiFeeReceiverFactor;
uint256 uiFeeAmount;
}
// @dev get the price impact in USD//// note that there will be some difference between the pool amounts used for// calculating the price impact and fees vs the actual pool amounts after the// swap is done, since the pool amounts will be increased / decreased by an amount// after factoring in the calculated price impact and fees//// since the calculations are based on the real-time prices values of the tokens// if a token price increases, the pool will incentivise swapping out more of that token// this is useful if prices are ranging, if prices are strongly directional, the pool may// be selling tokens as the token price increases//// @param params GetPriceImpactUsdParams//// @return the price impact in USDfunctiongetPriceImpactUsd(GetPriceImpactUsdParams memory params) externalviewreturns (int256) {
PoolParams memory poolParams = getNextPoolAmountsUsd(params);
int256 priceImpactUsd = _getPriceImpactUsd(params.dataStore, params.market, poolParams);
// the virtual price impact calculation is skipped if the price impact// is positive since the action is helping to balance the pool//// in case two virtual pools are unbalanced in a different direction// e.g. pool0 has more WNT than USDC while pool1 has less WNT// than USDT// not skipping the virtual price impact calculation would lead to// a negative price impact for any trade on either pools and would// disincentivise the balancing of poolsif (priceImpactUsd >=0) { return priceImpactUsd; }
if (!params.includeVirtualInventoryImpact) {
return priceImpactUsd;
}
// note that the virtual pool for the long token / short token may be different across pools// e.g. ETH/USDC, ETH/USDT would have USDC and USDT as the short tokens// the short token amount is multiplied by the price of the token in the current pool, e.g. if the swap// is for the ETH/USDC pool, the combined USDC and USDT short token amounts is multiplied by the price of// USDC to calculate the price impact, this should be reasonable most of the time unless there is a// large depeg of one of the tokens, in which case it may be necessary to remove that market from being a virtual// market, removal of virtual markets may lead to incorrect virtual token accounting, the feature to correct for// this can be added if needed
(bool hasVirtualInventory, uint256 virtualPoolAmountForLongToken, uint256 virtualPoolAmountForShortToken) = MarketUtils.getVirtualInventoryForSwaps(
params.dataStore,
params.market.marketToken
);
if (!hasVirtualInventory) {
return priceImpactUsd;
}
uint256 virtualPoolAmountForTokenA;
uint256 virtualPoolAmountForTokenB;
if (params.tokenA == params.market.longToken) {
virtualPoolAmountForTokenA = virtualPoolAmountForLongToken;
virtualPoolAmountForTokenB = virtualPoolAmountForShortToken;
} else {
virtualPoolAmountForTokenA = virtualPoolAmountForShortToken;
virtualPoolAmountForTokenB = virtualPoolAmountForLongToken;
}
PoolParams memory poolParamsForVirtualInventory = getNextPoolAmountsParams(
params,
virtualPoolAmountForTokenA,
virtualPoolAmountForTokenB
);
int256 priceImpactUsdForVirtualInventory = _getPriceImpactUsd(params.dataStore, params.market, poolParamsForVirtualInventory);
return priceImpactUsdForVirtualInventory < priceImpactUsd ? priceImpactUsdForVirtualInventory : priceImpactUsd;
}
// @dev get the price impact in USD// @param dataStore DataStore// @param market the trading market// @param poolParams PoolParams// @return the price impact in USDfunction_getPriceImpactUsd(DataStore dataStore, Market.Props memory market, PoolParams memory poolParams) internalviewreturns (int256) {
uint256 initialDiffUsd = Calc.diff(poolParams.poolUsdForTokenA, poolParams.poolUsdForTokenB);
uint256 nextDiffUsd = Calc.diff(poolParams.nextPoolUsdForTokenA, poolParams.nextPoolUsdForTokenB);
// check whether an improvement in balance comes from causing the balance to switch sides// for example, if there is $2000 of ETH and $1000 of USDC in the pool// adding $1999 USDC into the pool will reduce absolute balance from $1000 to $999 but it does not// help rebalance the pool much, the isSameSideRebalance value helps avoid gaming using this casebool isSameSideRebalance = (poolParams.poolUsdForTokenA <= poolParams.poolUsdForTokenB) == (poolParams.nextPoolUsdForTokenA <= poolParams.nextPoolUsdForTokenB);
uint256 impactExponentFactor = dataStore.getUint(Keys.swapImpactExponentFactorKey(market.marketToken));
if (isSameSideRebalance) {
bool hasPositiveImpact = nextDiffUsd < initialDiffUsd;
uint256 impactFactor = MarketUtils.getAdjustedSwapImpactFactor(dataStore, market.marketToken, hasPositiveImpact);
return PricingUtils.getPriceImpactUsdForSameSideRebalance(
initialDiffUsd,
nextDiffUsd,
impactFactor,
impactExponentFactor
);
} else {
(uint256 positiveImpactFactor, uint256 negativeImpactFactor) = MarketUtils.getAdjustedSwapImpactFactors(dataStore, market.marketToken);
return PricingUtils.getPriceImpactUsdForCrossoverRebalance(
initialDiffUsd,
nextDiffUsd,
positiveImpactFactor,
negativeImpactFactor,
impactExponentFactor
);
}
}
// @dev get the next pool amounts in USD// @param params GetPriceImpactUsdParams// @return PoolParamsfunctiongetNextPoolAmountsUsd(
GetPriceImpactUsdParams memory params
) internalviewreturns (PoolParams memory) {
uint256 poolAmountForTokenA = MarketUtils.getPoolAmount(params.dataStore, params.market, params.tokenA);
uint256 poolAmountForTokenB = MarketUtils.getPoolAmount(params.dataStore, params.market, params.tokenB);
return getNextPoolAmountsParams(
params,
poolAmountForTokenA,
poolAmountForTokenB
);
}
functiongetNextPoolAmountsParams(
GetPriceImpactUsdParams memory params,
uint256 poolAmountForTokenA,
uint256 poolAmountForTokenB
) internalpurereturns (PoolParams memory) {
uint256 poolUsdForTokenA = poolAmountForTokenA * params.priceForTokenA;
uint256 poolUsdForTokenB = poolAmountForTokenB * params.priceForTokenB;
if (params.usdDeltaForTokenA <0&& (-params.usdDeltaForTokenA).toUint256() > poolUsdForTokenA) {
revert Errors.UsdDeltaExceedsPoolValue(params.usdDeltaForTokenA, poolUsdForTokenA);
}
if (params.usdDeltaForTokenB <0&& (-params.usdDeltaForTokenB).toUint256() > poolUsdForTokenB) {
revert Errors.UsdDeltaExceedsPoolValue(params.usdDeltaForTokenB, poolUsdForTokenB);
}
uint256 nextPoolUsdForTokenA = Calc.sumReturnUint256(poolUsdForTokenA, params.usdDeltaForTokenA);
uint256 nextPoolUsdForTokenB = Calc.sumReturnUint256(poolUsdForTokenB, params.usdDeltaForTokenB);
PoolParams memory poolParams = PoolParams(
poolUsdForTokenA,
poolUsdForTokenB,
nextPoolUsdForTokenA,
nextPoolUsdForTokenB
);
return poolParams;
}
// @dev get the swap fees// @param dataStore DataStore// @param marketToken the address of the market token// @param amount the total swap fee amountfunctiongetSwapFees(
DataStore dataStore,
address marketToken,
uint256 amount,
bool forPositiveImpact,
address uiFeeReceiver,
ISwapPricingUtils.SwapPricingType swapPricingType
) internalviewreturns (SwapFees memory) {
SwapFees memory fees;
// note that since it is possible to incur both positive and negative price impact values// and the negative price impact factor may be larger than the positive impact factor// it is possible for the balance to be improved overall but for the price impact to still be negative// in this case the fee factor for the negative price impact would be charged// a user could split the order into two, to incur a smaller fee, reducing the fee through this should not be a large issueuint256 feeFactor;
if (swapPricingType == ISwapPricingUtils.SwapPricingType.Swap) {
feeFactor = dataStore.getUint(Keys.swapFeeFactorKey(marketToken, forPositiveImpact));
} elseif (swapPricingType == ISwapPricingUtils.SwapPricingType.Shift) {
// empty branch as feeFactor is already zero
} elseif (swapPricingType == ISwapPricingUtils.SwapPricingType.Atomic) {
feeFactor = dataStore.getUint(Keys.atomicSwapFeeFactorKey(marketToken));
} elseif (swapPricingType == ISwapPricingUtils.SwapPricingType.Deposit) {
feeFactor = dataStore.getUint(Keys.depositFeeFactorKey(marketToken, forPositiveImpact));
} elseif (swapPricingType == ISwapPricingUtils.SwapPricingType.Withdrawal) {
feeFactor = dataStore.getUint(Keys.withdrawalFeeFactorKey(marketToken, forPositiveImpact));
}
uint256 swapFeeReceiverFactor = dataStore.getUint(Keys.SWAP_FEE_RECEIVER_FACTOR);
uint256 feeAmount = Precision.applyFactor(amount, feeFactor);
fees.feeReceiverAmount = Precision.applyFactor(feeAmount, swapFeeReceiverFactor);
fees.feeAmountForPool = feeAmount - fees.feeReceiverAmount;
fees.uiFeeReceiver = uiFeeReceiver;
fees.uiFeeReceiverFactor = MarketUtils.getUiFeeFactor(dataStore, uiFeeReceiver);
fees.uiFeeAmount = Precision.applyFactor(amount, fees.uiFeeReceiverFactor);
fees.amountAfterFees = amount - feeAmount - fees.uiFeeAmount;
return fees;
}
// note that the priceImpactUsd may not be entirely accurate since it is the// base calculation and the actual price impact may be capped by the available// amount in the swap impact poolfunctionemitSwapInfo(
EventEmitter eventEmitter,
EmitSwapInfoParams memory params
) internal{
EventUtils.EventLogData memory eventData;
eventData.bytes32Items.initItems(1);
eventData.bytes32Items.setItem(0, "orderKey", params.orderKey);
eventData.addressItems.initItems(4);
eventData.addressItems.setItem(0, "market", params.market);
eventData.addressItems.setItem(1, "receiver", params.receiver);
eventData.addressItems.setItem(2, "tokenIn", params.tokenIn);
eventData.addressItems.setItem(3, "tokenOut", params.tokenOut);
eventData.uintItems.initItems(5);
eventData.uintItems.setItem(0, "tokenInPrice", params.tokenInPrice);
eventData.uintItems.setItem(1, "tokenOutPrice", params.tokenOutPrice);
eventData.uintItems.setItem(2, "amountIn", params.amountIn);
// note that amountInAfterFees includes negative price impact
eventData.uintItems.setItem(3, "amountInAfterFees", params.amountInAfterFees);
eventData.uintItems.setItem(4, "amountOut", params.amountOut);
eventData.intItems.initItems(3);
eventData.intItems.setItem(0, "priceImpactUsd", params.priceImpactUsd);
eventData.intItems.setItem(1, "priceImpactAmount", params.priceImpactAmount);
eventData.intItems.setItem(2, "tokenInPriceImpactAmount", params.tokenInPriceImpactAmount);
eventEmitter.emitEventLog1(
"SwapInfo",
Cast.toBytes32(params.market),
eventData
);
}
functionemitSwapFeesCollected(
EventEmitter eventEmitter,
bytes32 tradeKey,
address market,
address token,
uint256 tokenPrice,
bytes32 swapFeeType,
SwapFees memory fees
) external{
EventUtils.EventLogData memory eventData;
eventData.bytes32Items.initItems(2);
eventData.bytes32Items.setItem(0, "tradeKey", tradeKey);
eventData.bytes32Items.setItem(1, "swapFeeType", swapFeeType);
eventData.addressItems.initItems(3);
eventData.addressItems.setItem(0, "uiFeeReceiver", fees.uiFeeReceiver);
eventData.addressItems.setItem(1, "market", market);
eventData.addressItems.setItem(2, "token", token);
eventData.uintItems.initItems(6);
eventData.uintItems.setItem(0, "tokenPrice", tokenPrice);
eventData.uintItems.setItem(1, "feeReceiverAmount", fees.feeReceiverAmount);
eventData.uintItems.setItem(2, "feeAmountForPool", fees.feeAmountForPool);
eventData.uintItems.setItem(3, "amountAfterFees", fees.amountAfterFees);
eventData.uintItems.setItem(4, "uiFeeReceiverFactor", fees.uiFeeReceiverFactor);
eventData.uintItems.setItem(5, "uiFeeAmount", fees.uiFeeAmount);
eventEmitter.emitEventLog1(
"SwapFeesCollected",
Cast.toBytes32(market),
eventData
);
}
}
Contract Source Code
File 100 of 103: SwapUtils.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"../data/DataStore.sol";
import"../event/EventEmitter.sol";
import"../oracle/Oracle.sol";
import"../pricing/SwapPricingUtils.sol";
import"../fee/FeeUtils.sol";
/**
* @title SwapUtils
* @dev Library for swap functions
*/librarySwapUtils{
usingSafeCastforuint256;
usingSafeCastforint256;
usingPriceforPrice.Props;
usingEventUtilsforEventUtils.AddressItems;
usingEventUtilsforEventUtils.UintItems;
usingEventUtilsforEventUtils.IntItems;
usingEventUtilsforEventUtils.BoolItems;
usingEventUtilsforEventUtils.Bytes32Items;
usingEventUtilsforEventUtils.BytesItems;
usingEventUtilsforEventUtils.StringItems;
/**
* @param dataStore The contract that provides access to data stored on-chain.
* @param eventEmitter The contract that emits events.
* @param oracle The contract that provides access to price data from oracles.
* @param bank The contract providing the funds for the swap.
* @param key An identifying key for the swap.
* @param tokenIn The address of the token that is being swapped.
* @param amountIn The amount of the token that is being swapped.
* @param swapPathMarkets An array of market properties, specifying the markets in which the swap should be executed.
* @param minOutputAmount The minimum amount of tokens that should be received as part of the swap.
* @param receiver The address to which the swapped tokens should be sent.
* @param uiFeeReceiver The address of the ui fee receiver.
* @param shouldUnwrapNativeToken A boolean indicating whether the received tokens should be unwrapped from the wrapped native token (WNT) if they are wrapped.
*/structSwapParams {
DataStore dataStore;
EventEmitter eventEmitter;
Oracle oracle;
Bank bank;
bytes32 key;
address tokenIn;
uint256 amountIn;
Market.Props[] swapPathMarkets;
uint256 minOutputAmount;
address receiver;
address uiFeeReceiver;
bool shouldUnwrapNativeToken;
}
/**
* @param market The market in which the swap should be executed.
* @param tokenIn The address of the token that is being swapped.
* @param amountIn The amount of the token that is being swapped.
* @param receiver The address to which the swapped tokens should be sent.
* @param shouldUnwrapNativeToken A boolean indicating whether the received tokens should be unwrapped from the wrapped native token (WNT) if they are wrapped.
*/struct_SwapParams {
Market.Props market;
address tokenIn;
uint256 amountIn;
address receiver;
bool shouldUnwrapNativeToken;
}
/**
* @param tokenOut The address of the token that is being received as part of the swap.
* @param tokenInPrice The price of the token that is being swapped.
* @param tokenOutPrice The price of the token that is being received as part of the swap.
* @param amountIn The amount of the token that is being swapped.
* @param amountOut The amount of the token that is being received as part of the swap.
* @param poolAmountOut The total amount of the token that is being received by all users in the swap pool.
*/structSwapCache {
address tokenOut;
Price.Props tokenInPrice;
Price.Props tokenOutPrice;
uint256 amountIn;
uint256 amountInAfterFees;
uint256 amountOut;
uint256 poolAmountOut;
int256 priceImpactUsd;
int256 priceImpactAmount;
uint256 cappedDiffUsd;
int256 tokenInPriceImpactAmount;
}
eventSwapReverted(string reason, bytes reasonBytes);
/**
* @dev Swaps a given amount of a given token for another token based on a
* specified swap path.
* @param params The parameters for the swap.
* @return A tuple containing the address of the token that was received as
* part of the swap and the amount of the received token.
*/functionswap(SwapParams memory params) externalreturns (address, uint256) {
if (params.amountIn ==0) {
return (params.tokenIn, params.amountIn);
}
if (params.swapPathMarkets.length==0) {
if (params.amountIn < params.minOutputAmount) {
revert Errors.InsufficientOutputAmount(params.amountIn, params.minOutputAmount);
}
if (address(params.bank) != params.receiver) {
params.bank.transferOut(
params.tokenIn,
params.receiver,
params.amountIn,
params.shouldUnwrapNativeToken
);
}
return (params.tokenIn, params.amountIn);
}
if (address(params.bank) != params.swapPathMarkets[0].marketToken) {
params.bank.transferOut(
params.tokenIn,
params.swapPathMarkets[0].marketToken,
params.amountIn,
false
);
}
address tokenOut = params.tokenIn;
uint256 outputAmount = params.amountIn;
for (uint256 i; i < params.swapPathMarkets.length; i++) {
Market.Props memory market = params.swapPathMarkets[i];
bool flagExists = params.dataStore.getBool(Keys.swapPathMarketFlagKey(market.marketToken));
if (flagExists) {
revert Errors.DuplicatedMarketInSwapPath(market.marketToken);
}
params.dataStore.setBool(Keys.swapPathMarketFlagKey(market.marketToken), true);
uint256 nextIndex = i +1;
address receiver;
if (nextIndex < params.swapPathMarkets.length) {
receiver = params.swapPathMarkets[nextIndex].marketToken;
} else {
receiver = params.receiver;
}
_SwapParams memory _params = _SwapParams(
market,
tokenOut,
outputAmount,
receiver,
i == params.swapPathMarkets.length-1 ? params.shouldUnwrapNativeToken : false// only convert ETH on the last swap if needed
);
(tokenOut, outputAmount) = _swap(params, _params);
}
for (uint256 i; i < params.swapPathMarkets.length; i++) {
Market.Props memory market = params.swapPathMarkets[i];
params.dataStore.setBool(Keys.swapPathMarketFlagKey(market.marketToken), false);
}
if (outputAmount < params.minOutputAmount) {
revert Errors.InsufficientSwapOutputAmount(outputAmount, params.minOutputAmount);
}
return (tokenOut, outputAmount);
}
functionvalidateSwapOutputToken(
DataStore dataStore,
address[] memory swapPath,
address inputToken,
address expectedOutputToken
) internalview{
address outputToken = getOutputToken(dataStore, swapPath, inputToken);
if (outputToken != expectedOutputToken) {
revert Errors.InvalidSwapOutputToken(outputToken, expectedOutputToken);
}
}
functiongetOutputToken(
DataStore dataStore,
address[] memory swapPath,
address inputToken
) internalviewreturns (address) {
address outputToken = inputToken;
Market.Props[] memory markets = MarketUtils.getSwapPathMarkets(dataStore, swapPath);
uint256 marketCount = markets.length;
for (uint256 i; i < marketCount; i++) {
Market.Props memory market = markets[i];
outputToken = MarketUtils.getOppositeToken(outputToken, market);
}
return outputToken;
}
/**
* Performs a swap on a single market.
*
* @param params The parameters for the swap.
* @param _params The parameters for the swap on this specific market.
* @return The token and amount that was swapped.
*/function_swap(SwapParams memory params, _SwapParams memory _params) internalreturns (address, uint256) {
SwapCache memory cache;
if (_params.tokenIn != _params.market.longToken && _params.tokenIn != _params.market.shortToken) {
revert Errors.InvalidTokenIn(_params.tokenIn, _params.market.marketToken);
}
MarketUtils.validateSwapMarket(params.dataStore, _params.market);
cache.tokenOut = MarketUtils.getOppositeToken(_params.tokenIn, _params.market);
cache.tokenInPrice = params.oracle.getPrimaryPrice(_params.tokenIn);
cache.tokenOutPrice = params.oracle.getPrimaryPrice(cache.tokenOut);
// note that this may not be entirely accurate since the effect of the// swap fees are not accounted for
cache.priceImpactUsd = SwapPricingUtils.getPriceImpactUsd(
SwapPricingUtils.GetPriceImpactUsdParams(
params.dataStore,
_params.market,
_params.tokenIn,
cache.tokenOut,
cache.tokenInPrice.midPrice(),
cache.tokenOutPrice.midPrice(),
(_params.amountIn * cache.tokenInPrice.midPrice()).toInt256(),
-(_params.amountIn * cache.tokenInPrice.midPrice()).toInt256(),
true// includeVirtualInventoryImpact
)
);
SwapPricingUtils.SwapFees memory fees = SwapPricingUtils.getSwapFees(
params.dataStore,
_params.market.marketToken,
_params.amountIn,
cache.priceImpactUsd >0, // forPositiveImpact
params.uiFeeReceiver,
ISwapPricingUtils.SwapPricingType.Swap
);
FeeUtils.incrementClaimableFeeAmount(
params.dataStore,
params.eventEmitter,
_params.market.marketToken,
_params.tokenIn,
fees.feeReceiverAmount,
Keys.SWAP_FEE_TYPE
);
FeeUtils.incrementClaimableUiFeeAmount(
params.dataStore,
params.eventEmitter,
params.uiFeeReceiver,
_params.market.marketToken,
_params.tokenIn,
fees.uiFeeAmount,
Keys.UI_SWAP_FEE_TYPE
);
if (cache.priceImpactUsd >0) {
// when there is a positive price impact factor, additional tokens from the swap impact pool// are withdrawn for the user// for example, if 50,000 USDC is swapped out and there is a positive price impact// an additional 100 USDC may be sent to the user// the swap impact pool is decreased by the used amount
cache.amountIn = fees.amountAfterFees;
(cache.priceImpactAmount, cache.cappedDiffUsd) = MarketUtils.applySwapImpactWithCap(
params.dataStore,
params.eventEmitter,
_params.market.marketToken,
cache.tokenOut,
cache.tokenOutPrice,
cache.priceImpactUsd
);
// if the positive price impact was capped, use the tokenIn swap// impact pool to pay for the positive price impactif (cache.cappedDiffUsd !=0) {
(cache.tokenInPriceImpactAmount, /* uint256 cappedDiffUsd */) = MarketUtils.applySwapImpactWithCap(
params.dataStore,
params.eventEmitter,
_params.market.marketToken,
_params.tokenIn,
cache.tokenInPrice,
cache.cappedDiffUsd.toInt256()
);
// this additional amountIn is already in the Market// it is subtracted from the swap impact pool amount// and the market pool amount is increased by the updated// amountIn below
cache.amountIn += cache.tokenInPriceImpactAmount.toUint256();
}
// round amountOut down
cache.amountOut = cache.amountIn * cache.tokenInPrice.min/ cache.tokenOutPrice.max;
cache.poolAmountOut = cache.amountOut;
// the below amount is subtracted from the swap impact pool instead of the market pool amount
cache.amountOut += cache.priceImpactAmount.toUint256();
} else {
// when there is a negative price impact factor,// less of the input amount is sent to the pool// for example, if 10 ETH is swapped in and there is a negative price impact// only 9.995 ETH may be swapped in// the remaining 0.005 ETH will be stored in the swap impact pool
(cache.priceImpactAmount, /* uint256 cappedDiffUsd */) = MarketUtils.applySwapImpactWithCap(
params.dataStore,
params.eventEmitter,
_params.market.marketToken,
_params.tokenIn,
cache.tokenInPrice,
cache.priceImpactUsd
);
if (fees.amountAfterFees <= (-cache.priceImpactAmount).toUint256()) {
revert Errors.SwapPriceImpactExceedsAmountIn(fees.amountAfterFees, cache.priceImpactAmount);
}
cache.amountIn = fees.amountAfterFees - (-cache.priceImpactAmount).toUint256();
cache.amountOut = cache.amountIn * cache.tokenInPrice.min/ cache.tokenOutPrice.max;
cache.poolAmountOut = cache.amountOut;
}
// the amountOut value includes the positive price impact amountif (_params.receiver != _params.market.marketToken) {
MarketToken(payable(_params.market.marketToken)).transferOut(
cache.tokenOut,
_params.receiver,
cache.amountOut,
_params.shouldUnwrapNativeToken
);
}
MarketUtils.applyDeltaToPoolAmount(
params.dataStore,
params.eventEmitter,
_params.market,
_params.tokenIn,
(cache.amountIn + fees.feeAmountForPool).toInt256()
);
// the poolAmountOut excludes the positive price impact amount// as that is deducted from the swap impact pool instead
MarketUtils.applyDeltaToPoolAmount(
params.dataStore,
params.eventEmitter,
_params.market,
cache.tokenOut,
-cache.poolAmountOut.toInt256()
);
MarketUtils.MarketPrices memory prices = MarketUtils.MarketPrices(
params.oracle.getPrimaryPrice(_params.market.indexToken),
_params.tokenIn == _params.market.longToken ? cache.tokenInPrice : cache.tokenOutPrice,
_params.tokenIn == _params.market.shortToken ? cache.tokenInPrice : cache.tokenOutPrice
);
MarketUtils.validatePoolAmount(
params.dataStore,
_params.market,
_params.tokenIn
);
// for single token markets cache.tokenOut will always equal _params.market.longToken// so only the reserve for longs will be validated// swaps should be disabled for single token markets so this should not be an issue
MarketUtils.validateReserve(
params.dataStore,
_params.market,
prices,
cache.tokenOut == _params.market.longToken
);
MarketUtils.validateMaxPnl(
params.dataStore,
_params.market,
prices,
_params.tokenIn == _params.market.longToken ? Keys.MAX_PNL_FACTOR_FOR_DEPOSITS : Keys.MAX_PNL_FACTOR_FOR_WITHDRAWALS,
cache.tokenOut == _params.market.shortToken ? Keys.MAX_PNL_FACTOR_FOR_WITHDRAWALS : Keys.MAX_PNL_FACTOR_FOR_DEPOSITS
);
SwapPricingUtils.EmitSwapInfoParams memory emitSwapInfoParams;
emitSwapInfoParams.orderKey = params.key;
emitSwapInfoParams.market = _params.market.marketToken;
emitSwapInfoParams.receiver = _params.receiver;
emitSwapInfoParams.tokenIn = _params.tokenIn;
emitSwapInfoParams.tokenOut = cache.tokenOut;
emitSwapInfoParams.tokenInPrice = cache.tokenInPrice.min;
emitSwapInfoParams.tokenOutPrice = cache.tokenOutPrice.max;
emitSwapInfoParams.amountIn = _params.amountIn;
emitSwapInfoParams.amountInAfterFees = fees.amountAfterFees;
emitSwapInfoParams.amountOut = cache.amountOut;
emitSwapInfoParams.priceImpactUsd = cache.priceImpactUsd;
emitSwapInfoParams.priceImpactAmount = cache.priceImpactAmount;
emitSwapInfoParams.tokenInPriceImpactAmount = cache.tokenInPriceImpactAmount;
SwapPricingUtils.emitSwapInfo(
params.eventEmitter,
emitSwapInfoParams
);
SwapPricingUtils.emitSwapFeesCollected(
params.eventEmitter,
params.key,
_params.market.marketToken,
_params.tokenIn,
cache.tokenInPrice.min,
Keys.SWAP_FEE_TYPE,
fees
);
return (cache.tokenOut, cache.amountOut);
}
}
Contract Source Code
File 101 of 103: TokenUtils.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity ^0.8.0;import"@openzeppelin/contracts/utils/Address.sol";
import"@openzeppelin/contracts/token/ERC20/IERC20.sol";
import"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import"../data/DataStore.sol";
import"../data/Keys.sol";
import"../error/ErrorUtils.sol";
import"../utils/AccountUtils.sol";
import"./IWNT.sol";
/**
* @title TokenUtils
* @dev Library for token functions, helps with transferring of tokens and
* native token functions
*/libraryTokenUtils{
usingAddressforaddress;
usingSafeERC20forIERC20;
eventTokenTransferReverted(string reason, bytes returndata);
eventNativeTokenTransferReverted(string reason);
/**
* @dev Returns the address of the WNT token.
* @param dataStore DataStore contract instance where the address of the WNT token is stored.
* @return The address of the WNT token.
*/functionwnt(DataStore dataStore) internalviewreturns (address) {
return dataStore.getAddress(Keys.WNT);
}
/**
* @dev Transfers the specified amount of `token` from the caller to `receiver`.
* limit the amount of gas forwarded so that a user cannot intentionally
* construct a token call that would consume all gas and prevent necessary
* actions like request cancellation from being executed
*
* @param dataStore The data store that contains the `tokenTransferGasLimit` for the specified `token`.
* @param token The address of the ERC20 token that is being transferred.
* @param receiver The address of the recipient of the `token` transfer.
* @param amount The amount of `token` to transfer.
*/functiontransfer(
DataStore dataStore,
address token,
address receiver,
uint256 amount
) internal{
if (amount ==0) { return; }
AccountUtils.validateReceiver(receiver);
uint256 gasLimit = dataStore.getUint(Keys.tokenTransferGasLimit(token));
if (gasLimit ==0) {
revert Errors.EmptyTokenTranferGasLimit(token);
}
(bool success0, /* bytes memory returndata */) = nonRevertingTransferWithGasLimit(
IERC20(token),
receiver,
amount,
gasLimit
);
if (success0) { return; }
address holdingAddress = dataStore.getAddress(Keys.HOLDING_ADDRESS);
if (holdingAddress ==address(0)) {
revert Errors.EmptyHoldingAddress();
}
// in case transfers to the receiver fail due to blacklisting or other reasons// send the tokens to a holding address to avoid possible gaming through reverting// transfers
(bool success1, bytesmemory returndata) = nonRevertingTransferWithGasLimit(
IERC20(token),
holdingAddress,
amount,
gasLimit
);
if (success1) { return; }
(stringmemory reason, /* bool hasRevertMessage */) = ErrorUtils.getRevertMessage(returndata);
emit TokenTransferReverted(reason, returndata);
// throw custom errors to prevent spoofing of errors// this is necessary because contracts like DepositHandler, WithdrawalHandler, OrderHandler// do not cancel requests for specific errorsrevert Errors.TokenTransferError(token, receiver, amount);
}
functionsendNativeToken(
DataStore dataStore,
address receiver,
uint256 amount
) internal{
if (amount ==0) { return; }
AccountUtils.validateReceiver(receiver);
uint256 gasLimit = dataStore.getUint(Keys.NATIVE_TOKEN_TRANSFER_GAS_LIMIT);
bool success;
// use an assembly call to avoid loading large data into memory// input mem[in…(in+insize)]// output area mem[out…(out+outsize))]assembly {
success :=call(
gasLimit, // gas limit
receiver, // receiver
amount, // value0, // in0, // insize0, // out0// outsize
)
}
if (success) { return; }
// if the transfer failed, re-wrap the token and send it to the receiver
depositAndSendWrappedNativeToken(
dataStore,
receiver,
amount
);
}
/**
* Deposits the specified amount of native token and sends the specified
* amount of wrapped native token to the specified receiver address.
*
* @param dataStore the data store to use for storing and retrieving data
* @param receiver the address of the recipient of the wrapped native token transfer
* @param amount the amount of native token to deposit and the amount of wrapped native token to send
*/functiondepositAndSendWrappedNativeToken(
DataStore dataStore,
address receiver,
uint256 amount
) internal{
if (amount ==0) { return; }
AccountUtils.validateReceiver(receiver);
address _wnt = wnt(dataStore);
IWNT(_wnt).deposit{value: amount}();
transfer(
dataStore,
_wnt,
receiver,
amount
);
}
/**
* @dev Withdraws the specified amount of wrapped native token and sends the
* corresponding amount of native token to the specified receiver address.
*
* limit the amount of gas forwarded so that a user cannot intentionally
* construct a token call that would consume all gas and prevent necessary
* actions like request cancellation from being executed
*
* @param dataStore the data store to use for storing and retrieving data
* @param _wnt the address of the WNT contract to withdraw the wrapped native token from
* @param receiver the address of the recipient of the native token transfer
* @param amount the amount of wrapped native token to withdraw and the amount of native token to send
*/functionwithdrawAndSendNativeToken(
DataStore dataStore,
address _wnt,
address receiver,
uint256 amount
) internal{
if (amount ==0) { return; }
AccountUtils.validateReceiver(receiver);
IWNT(_wnt).withdraw(amount);
uint256 gasLimit = dataStore.getUint(Keys.NATIVE_TOKEN_TRANSFER_GAS_LIMIT);
bool success;
// use an assembly call to avoid loading large data into memory// input mem[in…(in+insize)]// output area mem[out…(out+outsize))]assembly {
success :=call(
gasLimit, // gas limit
receiver, // receiver
amount, // value0, // in0, // insize0, // out0// outsize
)
}
if (success) { return; }
// if the transfer failed, re-wrap the token and send it to the receiver
depositAndSendWrappedNativeToken(
dataStore,
receiver,
amount
);
}
/**
* @dev Transfers the specified amount of ERC20 token to the specified receiver
* address, with a gas limit to prevent the transfer from consuming all available gas.
* adapted from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/utils/SafeERC20.sol
*
* @param token the ERC20 contract to transfer the tokens from
* @param to the address of the recipient of the token transfer
* @param amount the amount of tokens to transfer
* @param gasLimit the maximum amount of gas that the token transfer can consume
* @return a tuple containing a boolean indicating the success or failure of the
* token transfer, and a bytes value containing the return data from the token transfer
*/functionnonRevertingTransferWithGasLimit(
IERC20 token,
address to,
uint256 amount,
uint256 gasLimit
) internalreturns (bool, bytesmemory) {
bytesmemory data =abi.encodeWithSelector(token.transfer.selector, to, amount);
(bool success, bytesmemory returndata) =address(token).call{ gas: gasLimit }(data);
if (success) {
if (returndata.length==0) {
// only check isContract if the call was successful and the return data is empty// otherwise we already know that it was a contractif (!address(token).isContract()) {
return (false, "Call to non-contract");
}
}
// some tokens do not revert on a failed transfer, they will return a boolean instead// validate that the returned boolean is true, otherwise indicate that the token transfer failedif (returndata.length>0&&!abi.decode(returndata, (bool))) {
return (false, returndata);
}
// transfers on some tokens do not return a boolean value, they will just revert if a transfer fails// for these tokens, if success is true then the transfer should have completedreturn (true, returndata);
}
return (false, returndata);
}
}