// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.13; import "@equilibria/root/number/types/UFixed18.sol"; import "@equilibria/perennial-oracle/contracts/interfaces/IOracleProvider.sol"; import "./Position.sol"; import "../IProduct.sol"; /// @dev PrePosition type struct PrePosition { /// @dev Oracle version at which the new position delta was recorded uint256 oracleVersion; /// @dev Size of position to open at oracle version Position openPosition; /// @dev Size of position to close at oracle version Position closePosition; } using PrePositionLib for PrePosition global; /** * @title PrePositionLib * @notice Library that manages a pre-settlement position delta. * @dev PrePositions track the currently awaiting-settlement deltas to a settled Position. These are * Primarily necessary to introduce lag into the settlement system such that oracle lag cannot be * gamed to a user's advantage. When a user opens or closes a new position, it sits as a PrePosition * for one oracle version until it's settle into the Position, making it then effective. PrePositions * are automatically settled at the correct oracle version even if a flywheel call doesn't happen until * several version into the future by using the historical version lookups in the corresponding "Versioned" * global state types. */ library PrePositionLib { /** * @notice Returns whether there is no pending-settlement position delta * @param self The struct to operate on * @return Whether the pending-settlement position delta is empty */ function isEmpty(PrePosition memory self) internal pure returns (bool) { return self.openPosition.isEmpty() && self.closePosition.isEmpty(); } /** * @notice Increments the maker side of the open position delta * @param self The struct to operate on * @param currentVersion The current oracle version index * @param amount The position amount to open */ function openMake(PrePosition storage self, uint256 currentVersion, UFixed18 amount) internal { self.openPosition.maker = self.openPosition.maker.add(amount); self.oracleVersion = currentVersion; } /** * @notice Increments the maker side of the close position delta * @param self The struct to operate on * @param currentVersion The current oracle version index * @param amount The maker position amount to close */ function closeMake(PrePosition storage self, uint256 currentVersion, UFixed18 amount) internal { self.closePosition.maker = self.closePosition.maker.add(amount); self.oracleVersion = currentVersion; } /** * @notice Increments the taker side of the open position delta * @param self The struct to operate on * @param currentVersion The current oracle version index * @param amount The taker position amount to open */ function openTake(PrePosition storage self, uint256 currentVersion, UFixed18 amount) internal { self.openPosition.taker = self.openPosition.taker.add(amount); self.oracleVersion = currentVersion; } /** * @notice Increments the taker side of the close position delta * @param self The struct to operate on * @param currentVersion The current oracle version index * @param amount The taker position amount to close */ function closeTake(PrePosition storage self, uint256 currentVersion, UFixed18 amount) internal { self.closePosition.taker = self.closePosition.taker.add(amount); self.oracleVersion = currentVersion; } /** * @notice Returns whether the the pending position delta can be settled at version `toOracleVersion` * @dev Pending-settlement positions deltas can be settled (1) oracle version after they are recorded * @param self The struct to operate on * @param toOracleVersion The potential oracle version to settle * @return Whether the position delta can be settled */ function canSettle( PrePosition memory self, IOracleProvider.OracleVersion memory toOracleVersion ) internal pure returns (bool) { return !isEmpty(self) && toOracleVersion.version > self.oracleVersion; } /** * @notice Computes the fee incurred for opening or closing the pending-settlement position * @dev Must be called from a valid product to get the proper fee amounts * @param self The struct to operate on * @param latestOracleVersion The oracle version at which position was modified * @return The maker / taker fee incurred */ function computeFee( PrePosition memory self, IOracleProvider.OracleVersion memory latestOracleVersion ) internal view returns (Position memory) { Position memory positionDelta = self.openPosition.add(self.closePosition); (UFixed18 makerNotional, UFixed18 takerNotional) = ( Fixed18Lib.from(positionDelta.maker).mul(latestOracleVersion.price).abs(), Fixed18Lib.from(positionDelta.taker).mul(latestOracleVersion.price).abs() ); IProduct product = IProduct(address(this)); return Position(makerNotional.mul(product.makerFee()), takerNotional.mul(product.takerFee())); } /** * @notice Computes the next oracle version to settle * @dev - If there is no pending-settlement position delta, returns the current oracle version * - Otherwise returns the oracle version at which the pending-settlement position delta can be first settled * * Corresponds to point (b) in the Position settlement flow * @param self The struct to operate on * @param currentVersion The current oracle version index * @return Next oracle version to settle */ function settleVersion(PrePosition storage self, uint256 currentVersion) internal view returns (uint256) { uint256 _oracleVersion = self.oracleVersion; return _oracleVersion == 0 ? currentVersion : _oracleVersion + 1; } }