Skip to content

Commit

Permalink
MarketUpdated event (#1697)
Browse files Browse the repository at this point in the history
* Add tracking code to runtime

* Add market size and market skew to

* Update test asserting the event

* Add MarketUpdated event.

Note that for an event to be emitted from a library, it needs to be defined in two places:
- The library itself, and
- The interface of the contract that is using the library.

* We now hit a size limit AsyncOrderModule
- Creating a AsyncOrderViewModule

* Dont forget to update cannon files

* Add currentFundingRate and currentFundingVelocity to MarketUpdated event

* Revert IAsyncOrderViewModule

* Use AsyncOrderSettlementModule like spot market

* Let call site emit MarketUpdated event to avoid emitting events from library
  • Loading branch information
0xjocke authored Jul 5, 2023
1 parent 9fa0cde commit df26ebd
Show file tree
Hide file tree
Showing 11 changed files with 378 additions and 209 deletions.
5 changes: 5 additions & 0 deletions markets/perps-market/cannonfile.test.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ artifact = "PerpsMarketFactoryModule"
[contract.AsyncOrderModule]
artifact = "AsyncOrderModule"

[contract.AsyncOrderSettlementModule]
artifact = "AsyncOrderSettlementModule"

[contract.AtomicOrderModule]
artifact = "AtomicOrderModule"

Expand Down Expand Up @@ -73,6 +76,7 @@ contracts = [
"PerpsMarketModule",
"AtomicOrderModule",
"AsyncOrderModule",
"AsyncOrderSettlementModule",
"FeatureFlagModule",
"LimitOrderModule",
"LiquidationModule",
Expand All @@ -85,6 +89,7 @@ depends = [
"contract.PerpsMarketFactoryModule",
"contract.AtomicOrderModule",
"contract.AsyncOrderModule",
"contract.AsyncOrderSettlementModule",
"contract.PerpsAccountModule",
"contract.PerpsMarketModule",
"contract.FeatureFlagModule",
Expand Down
5 changes: 5 additions & 0 deletions markets/perps-market/cannonfile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ artifact = "PerpsMarketFactoryModule"
[contract.AsyncOrderModule]
artifact = "AsyncOrderModule"

[contract.AsyncOrderSettlementModule]
artifact = "AsyncOrderSettlementModule"

[contract.AtomicOrderModule]
artifact = "AtomicOrderModule"

Expand Down Expand Up @@ -73,6 +76,7 @@ contracts = [
"PerpsMarketModule",
"AtomicOrderModule",
"AsyncOrderModule",
"AsyncOrderSettlementModule",
"FeatureFlagModule",
"LimitOrderModule",
"LiquidationModule",
Expand All @@ -85,6 +89,7 @@ depends = [
"contract.PerpsMarketFactoryModule",
"contract.AtomicOrderModule",
"contract.AsyncOrderModule",
"contract.AsyncOrderSettlementModule",
"contract.PerpsAccountModule",
"contract.PerpsMarketModule",
"contract.FeatureFlagModule",
Expand Down
36 changes: 2 additions & 34 deletions markets/perps-market/contracts/interfaces/IAsyncOrderModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,6 @@ interface IAsyncOrderModule {
address sender
);

event OrderSettled(
uint128 indexed marketId,
uint128 indexed accountId,
uint256 fillPrice,
int256 accountPnlRealized,
int128 newSize,
uint256 collectedFees,
uint256 settelementReward,
bytes32 indexed trackingCode,
address settler
);

event OrderCanceled(
uint128 indexed marketId,
uint128 indexed accountId,
Expand All @@ -40,35 +28,15 @@ interface IAsyncOrderModule {
);

error OrderAlreadyCommitted(uint128 marketId, uint128 accountId);
error SettlementStrategyNotFound(SettlementStrategy.Type strategyType);
error OffchainLookup(
address sender,
string[] urls,
bytes callData,
bytes4 callbackFunction,
bytes extraData
);

function commitOrder(
AsyncOrder.OrderCommitmentRequest memory commitment
) external returns (AsyncOrder.Data memory retOrder, uint fees);

function cancelOrder(uint128 marketId, uint128 accountId) external;

// only used due to stack too deep during settlement
struct SettleOrderRuntime {
uint128 marketId;
uint128 accountId;
int128 newPositionSize;
int256 pnl;
uint256 pnlUint;
uint256 amountToDeposit;
uint256 settlementReward;
bytes32 trackingCode;
}

function getOrder(
uint128 marketId,
uint128 accountId
) external returns (AsyncOrder.Data memory);

function cancelOrder(uint128 marketId, uint128 accountId) external;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.11 <0.9.0;
import {SettlementStrategy} from "../storage/SettlementStrategy.sol";

interface IAsyncOrderSettlementModule {
error SettlementStrategyNotFound(SettlementStrategy.Type strategyType);
error OffchainLookup(
address sender,
string[] urls,
bytes callData,
bytes4 callbackFunction,
bytes extraData
);

event OrderSettled(
uint128 indexed marketId,
uint128 indexed accountId,
uint256 fillPrice,
int256 accountPnlRealized,
int128 newSize,
uint256 collectedFees,
uint256 settelementReward,
bytes32 indexed trackingCode,
address settler
);

// only used due to stack too deep during settlement
struct SettleOrderRuntime {
uint128 marketId;
uint128 accountId;
int128 newPositionSize;
int256 pnl;
uint256 pnlUint;
uint256 amountToDeposit;
uint256 settlementReward;
bytes32 trackingCode;
}

function settle(uint128 marketId, uint128 accountId) external view;

function settlePythOrder(bytes calldata result, bytes calldata extraData) external payable;
}
13 changes: 13 additions & 0 deletions markets/perps-market/contracts/interfaces/IMarketEvents.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.11 <0.9.0;

interface IMarketEvents {
event MarketUpdated(
uint128 marketId,
int256 skew,
uint256 size,
int256 sizeDelta,
int256 currentFundingRate,
int256 currentFundingVelocity
);
}
167 changes: 0 additions & 167 deletions markets/perps-market/contracts/modules/AsyncOrderModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ contract AsyncOrderModule is IAsyncOrderModule {
using SafeCastU256 for uint256;
using SafeCastI256 for int256;

int256 public constant PRECISION = 18;

function commitOrder(
AsyncOrder.OrderCommitmentRequest memory commitment
) external override returns (AsyncOrder.Data memory retOrder, uint fees) {
Expand Down Expand Up @@ -86,46 +84,6 @@ contract AsyncOrderModule is IAsyncOrderModule {
return (order, feesAccrued);
}

function settle(uint128 marketId, uint128 accountId) external view {
GlobalPerpsMarket.load().checkLiquidation(accountId);
(
AsyncOrder.Data storage order,
SettlementStrategy.Data storage settlementStrategy
) = _performOrderValidityChecks(marketId, accountId);

_settleOffchain(order, settlementStrategy);
}

function settlePythOrder(bytes calldata result, bytes calldata extraData) external payable {
(uint128 marketId, uint128 asyncOrderId) = abi.decode(extraData, (uint128, uint128));
(
AsyncOrder.Data storage order,
SettlementStrategy.Data storage settlementStrategy
) = _performOrderValidityChecks(marketId, asyncOrderId);

bytes32[] memory priceIds = new bytes32[](1);
priceIds[0] = settlementStrategy.feedId;

bytes[] memory updateData = new bytes[](1);
updateData[0] = result;

IPythVerifier.PriceFeed[] memory priceFeeds = IPythVerifier(
settlementStrategy.priceVerificationContract
).parsePriceFeedUpdates{value: msg.value}(
updateData,
priceIds,
order.settlementTime.to64(),
(order.settlementTime + settlementStrategy.priceWindowDuration).to64()
);

IPythVerifier.PriceFeed memory pythData = priceFeeds[0];
uint offchainPrice = _getScaledPrice(pythData.price.price, pythData.price.expo).toUint();

settlementStrategy.checkPriceDeviation(offchainPrice, PerpsPrice.getCurrentPrice(marketId));

_settleOrder(offchainPrice, order, settlementStrategy);
}

function getOrder(
uint128 marketId,
uint128 accountId
Expand All @@ -143,129 +101,4 @@ contract AsyncOrderModule is IAsyncOrderModule {
order.reset();
emit OrderCanceled(marketId, accountId, order.settlementTime, order.acceptablePrice);
}

function _settleOffchain(
AsyncOrder.Data storage asyncOrder,
SettlementStrategy.Data storage settlementStrategy
) private view returns (uint, int256, uint256) {
string[] memory urls = new string[](1);
urls[0] = settlementStrategy.url;

bytes4 selector;
if (settlementStrategy.strategyType == SettlementStrategy.Type.PYTH) {
selector = AsyncOrderModule.settlePythOrder.selector;
} else {
revert SettlementStrategyNotFound(settlementStrategy.strategyType);
}

// see EIP-3668: https://eips.ethereum.org/EIPS/eip-3668
revert OffchainLookup(
address(this),
urls,
abi.encodePacked(settlementStrategy.feedId, _getTimeInBytes(asyncOrder.settlementTime)),
selector,
abi.encode(asyncOrder.marketId, asyncOrder.accountId) // extraData that gets sent to callback for validation
);
}

function _settleOrder(
uint256 price,
AsyncOrder.Data storage asyncOrder,
SettlementStrategy.Data storage settlementStrategy
) private {
SettleOrderRuntime memory runtime;

runtime.accountId = asyncOrder.accountId;
runtime.marketId = asyncOrder.marketId;

// check if account is flagged
GlobalPerpsMarket.load().checkLiquidation(runtime.accountId);
(
Position.Data memory newPosition,
uint totalFees,
uint fillPrice,
Position.Data storage oldPosition
) = asyncOrder.validateOrder(settlementStrategy, price);

runtime.newPositionSize = newPosition.size;

PerpsMarketFactory.Data storage factory = PerpsMarketFactory.load();
PerpsAccount.Data storage perpsAccount = PerpsAccount.load(runtime.accountId);
// use fill price to calculate realized pnl
(runtime.pnl, , , ) = oldPosition.getPnl(fillPrice);

runtime.pnlUint = MathUtil.abs(runtime.pnl);
if (runtime.pnl > 0) {
factory.synthetix.withdrawMarketUsd(runtime.marketId, address(this), runtime.pnlUint);
perpsAccount.addCollateralAmount(SNX_USD_MARKET_ID, runtime.pnlUint);
} else if (runtime.pnl < 0) {
perpsAccount.deductFromAccount(runtime.pnlUint);
runtime.amountToDeposit = runtime.pnlUint;
// all gets deposited below with fees
}

// after pnl is realized, update position
PerpsMarket.loadValid(runtime.marketId).updatePositionData(runtime.accountId, newPosition);

perpsAccount.updatePositionMarkets(runtime.marketId, runtime.newPositionSize);
perpsAccount.deductFromAccount(totalFees);

runtime.settlementReward = settlementStrategy.settlementReward;
runtime.amountToDeposit += totalFees - runtime.settlementReward;
if (runtime.settlementReward > 0) {
// pay keeper
factory.usdToken.transfer(msg.sender, runtime.settlementReward);
}

if (runtime.amountToDeposit > 0) {
// deposit into market manager
factory.depositToMarketManager(runtime.marketId, runtime.amountToDeposit);
}

// exctracted from asyncOrder before order is reset
bytes32 trackingCode = asyncOrder.trackingCode;

asyncOrder.reset();

// emit event
emit OrderSettled(
runtime.marketId,
runtime.accountId,
fillPrice,
runtime.pnl,
runtime.newPositionSize,
totalFees,
runtime.settlementReward,
trackingCode,
msg.sender
);
}

function _performOrderValidityChecks(
uint128 marketId,
uint128 accountId
) private view returns (AsyncOrder.Data storage, SettlementStrategy.Data storage) {
AsyncOrder.Data storage order = PerpsMarket.loadValid(marketId).asyncOrders[accountId];
SettlementStrategy.Data storage settlementStrategy = PerpsMarketConfiguration
.load(marketId)
.settlementStrategies[order.settlementStrategyId];

order.checkValidity();
order.checkWithinSettlementWindow(settlementStrategy);

return (order, settlementStrategy);
}

function _getTimeInBytes(uint256 settlementTime) private pure returns (bytes8) {
bytes32 settlementTimeBytes = bytes32(abi.encode(settlementTime));

// get last 8 bytes
return bytes8(settlementTimeBytes << 192);
}

// borrowed from PythNode.sol
function _getScaledPrice(int64 price, int32 expo) private pure returns (int256) {
int256 factor = PRECISION + expo;
return factor > 0 ? price.upscale(factor.toUint()) : price.downscale((-factor).toUint());
}
}
Loading

0 comments on commit df26ebd

Please sign in to comment.