Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add chainlink datastreams settlement strategy for perps market #2326

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions markets/perps-market/cannonfile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ artifact = "PerpsMarketFactoryModule"
[contract.AsyncOrderModule]
artifact = "AsyncOrderModule"

[contract.AsyncOrderSettlementChainlinkModule]
artifact = "AsyncOrderSettlementChainlinkModule"

[contract.AsyncOrderSettlementPythModule]
artifact = "AsyncOrderSettlementPythModule"

Expand Down Expand Up @@ -85,6 +88,7 @@ contracts = [
"PerpsAccountModule",
"PerpsMarketModule",
"AsyncOrderModule",
"AsyncOrderSettlementChainlinkModule",
"AsyncOrderSettlementPythModule",
"AsyncOrderCancelModule",
"FeatureFlagModule",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.11 <0.9.0;

import {Position} from "../storage/Position.sol";
import {MarketUpdate} from "../storage/MarketUpdate.sol";

interface IAsyncOrderSettlementChainlinkModule {
/**
* @notice Gets fired when a new order is settled.
* @param marketId Id of the market used for the trade.
* @param accountId Id of the account used for the trade.
* @param fillPrice Price at which the order was settled.
* @param pnl Pnl of the previous closed position.
* @param accruedFunding Accrued funding of the previous closed position.
* @param sizeDelta Size delta from order.
* @param newSize New size of the position after settlement.
* @param totalFees Amount of fees collected by the protocol.
* @param referralFees Amount of fees collected by the referrer.
* @param collectedFees Amount of fees collected by fee collector.
* @param settlementReward reward to sender for settling order.
* @param trackingCode Optional code for integrator tracking purposes.
* @param settler address of the settler of the order.
*/
event OrderSettled(
uint128 indexed marketId,
uint128 indexed accountId,
uint256 fillPrice,
int256 pnl,
int256 accruedFunding,
int128 sizeDelta,
int128 newSize,
uint256 totalFees,
uint256 referralFees,
uint256 collectedFees,
uint256 settlementReward,
bytes32 indexed trackingCode,
address settler
);

/**
* @notice Gets fired after order settles and includes the interest charged to the account.
* @param accountId Id of the account used for the trade.
* @param interest interest charges
*/
event InterestCharged(uint128 indexed accountId, uint256 interest);

// only used due to stack too deep during settlement
struct SettleOrderRuntime {
uint128 marketId;
uint128 accountId;
int128 sizeDelta;
int256 pnl;
uint256 chargedInterest;
int256 accruedFunding;
uint256 settlementReward;
uint256 fillPrice;
uint256 totalFees;
uint256 referralFees;
uint256 feeCollectorFees;
Position.Data newPosition;
MarketUpdate.Data updateData;
uint256 synthDeductionIterator;
uint128[] deductedSynthIds;
uint256[] deductedAmount;
int256 chargedAmount;
uint256 newAccountDebt;
}

/**
* @notice Settles an offchain order using the offchain retrieved data from pyth.
* @param accountId The account id to settle the order
*/
function settleOrder(uint128 accountId) external;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.11 <0.9.0;

interface IChainlinkDatastreamsERC7412 {
error OracleDataRequired(address oracleContract, bytes oracleQuery, uint256 feeRequired);

function getPriceForTimestamp(
bytes32 feedId,
uint32 forTimestamp
) external view returns (int192);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.11 <0.9.0;

import {ERC2771Context} from "@synthetixio/core-contracts/contracts/utils/ERC2771Context.sol";
import {FeatureFlag} from "@synthetixio/core-modules/contracts/storage/FeatureFlag.sol";
import {IAsyncOrderSettlementChainlinkModule} from "../interfaces/IAsyncOrderSettlementChainlinkModule.sol";
import {PerpsAccount, SNX_USD_MARKET_ID} from "../storage/PerpsAccount.sol";
import {MathUtil} from "../utils/MathUtil.sol";
import {Flags} from "../utils/Flags.sol";
import {PerpsMarket} from "../storage/PerpsMarket.sol";
import {AsyncOrder} from "../storage/AsyncOrder.sol";
import {Position} from "../storage/Position.sol";
import {GlobalPerpsMarket} from "../storage/GlobalPerpsMarket.sol";
import {SettlementStrategy} from "../storage/SettlementStrategy.sol";
import {PerpsMarketFactory} from "../storage/PerpsMarketFactory.sol";
import {GlobalPerpsMarketConfiguration} from "../storage/GlobalPerpsMarketConfiguration.sol";
import {IMarketEvents} from "../interfaces/IMarketEvents.sol";
import {IAccountEvents} from "../interfaces/IAccountEvents.sol";
import {KeeperCosts} from "../storage/KeeperCosts.sol";
import {IChainlinkDatastreamsERC7412} from "../interfaces/external/IChainlinkDatastreamsERC7412.sol";
import {SafeCastU256, SafeCastI256} from "@synthetixio/core-contracts/contracts/utils/SafeCast.sol";

/**
* @title Module for settling async orders using chainlink datastreams as price feed.
* @dev See IAsyncOrderSettlementChainlinkModule.
*/
contract AsyncOrderSettlementChainlinkModule is
IAsyncOrderSettlementChainlinkModule,
IMarketEvents,
IAccountEvents
{
using SafeCastI256 for int256;
using SafeCastU256 for uint256;
using PerpsAccount for PerpsAccount.Data;
using PerpsMarket for PerpsMarket.Data;
using AsyncOrder for AsyncOrder.Data;
using PerpsMarketFactory for PerpsMarketFactory.Data;
using GlobalPerpsMarket for GlobalPerpsMarket.Data;
using GlobalPerpsMarketConfiguration for GlobalPerpsMarketConfiguration.Data;
using Position for Position.Data;
using KeeperCosts for KeeperCosts.Data;

/**
* @inheritdoc IAsyncOrderSettlementChainlinkModule
*/
function settleOrder(uint128 accountId) external {
FeatureFlag.ensureAccessToFeature(Flags.PERPS_SYSTEM);

(
AsyncOrder.Data storage asyncOrder,
SettlementStrategy.Data storage settlementStrategy
) = AsyncOrder.loadValid(accountId);

int256 offchainPrice = IChainlinkDatastreamsERC7412(
settlementStrategy.priceVerificationContract
).getPriceForTimestamp(
settlementStrategy.feedId,
(asyncOrder.commitmentTime + settlementStrategy.commitmentPriceDelay).to32()
);

_settleOrder(offchainPrice.toUint(), asyncOrder, settlementStrategy);
}

/**
* @notice Settles an offchain order
* @param price provided by offchain oracle
* @param asyncOrder to be validated and settled
* @param settlementStrategy used to validate order and calculate settlement reward
*/
function _settleOrder(
uint256 price,
AsyncOrder.Data storage asyncOrder,
SettlementStrategy.Data storage settlementStrategy
) private {
/// @dev runtime stores order settlement data; circumvents stack limitations
SettleOrderRuntime memory runtime;

runtime.accountId = asyncOrder.request.accountId;
runtime.marketId = asyncOrder.request.marketId;
runtime.sizeDelta = asyncOrder.request.sizeDelta;

GlobalPerpsMarket.load().checkLiquidation(runtime.accountId);

Position.Data storage oldPosition;

// validate order request can be settled; call reverts if not
(runtime.newPosition, runtime.totalFees, runtime.fillPrice, oldPosition) = asyncOrder
.validateRequest(settlementStrategy, price);

// validate final fill price is acceptable relative to price specified by trader
asyncOrder.validateAcceptablePrice(runtime.fillPrice);

PerpsMarketFactory.Data storage factory = PerpsMarketFactory.load();
PerpsAccount.Data storage perpsAccount = PerpsAccount.load(runtime.accountId);

// use actual fill price to calculate realized pnl
(runtime.pnl, , runtime.chargedInterest, runtime.accruedFunding, , ) = oldPosition.getPnl(
runtime.fillPrice
);

runtime.chargedAmount = runtime.pnl - runtime.totalFees.toInt();
perpsAccount.charge(runtime.chargedAmount);

emit AccountCharged(runtime.accountId, runtime.chargedAmount, perpsAccount.debt);

// only update position state after pnl has been realized
runtime.updateData = PerpsMarket.loadValid(runtime.marketId).updatePositionData(
runtime.accountId,
runtime.newPosition
);
perpsAccount.updateOpenPositions(runtime.marketId, runtime.newPosition.size);

emit MarketUpdated(
runtime.updateData.marketId,
price,
runtime.updateData.skew,
runtime.updateData.size,
runtime.sizeDelta,
runtime.updateData.currentFundingRate,
runtime.updateData.currentFundingVelocity,
runtime.updateData.interestRate
);

runtime.settlementReward = AsyncOrder.settlementRewardCost(settlementStrategy);

// if settlement reward is non-zero, pay keeper
if (runtime.settlementReward > 0) {
factory.withdrawMarketUsd(ERC2771Context._msgSender(), runtime.settlementReward);
}

{
// order fees are total fees minus settlement reward
uint256 orderFees = runtime.totalFees - runtime.settlementReward;
GlobalPerpsMarketConfiguration.Data storage s = GlobalPerpsMarketConfiguration.load();
s.collectFees(orderFees, asyncOrder.request.referrer, factory);
}

// trader can now commit a new order
asyncOrder.reset();

/// @dev two events emitted to avoid stack limitations
emit InterestCharged(runtime.accountId, runtime.chargedInterest);

emit OrderSettled(
runtime.marketId,
runtime.accountId,
runtime.fillPrice,
runtime.pnl,
runtime.accruedFunding,
runtime.sizeDelta,
runtime.newPosition.size,
runtime.totalFees,
runtime.referralFees,
runtime.feeCollectorFees,
runtime.settlementReward,
asyncOrder.request.trackingCode,
ERC2771Context._msgSender()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ library SettlementStrategy {
}

enum Type {
PYTH
PYTH,
CHAINLINK
}
}
6 changes: 4 additions & 2 deletions markets/perps-market/storage.dump.json
Original file line number Diff line number Diff line change
Expand Up @@ -1244,7 +1244,8 @@
"type": "enum",
"name": "strategyType",
"members": [
"PYTH"
"PYTH",
"CHAINLINK"
]
},
{
Expand Down Expand Up @@ -1513,7 +1514,8 @@
"type": "enum",
"name": "strategyType",
"members": [
"PYTH"
"PYTH",
"CHAINLINK"
],
"size": 1,
"slot": "0",
Expand Down