Skip to content
Closed
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
22 changes: 22 additions & 0 deletions crates/af-iperps/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ module perpetuals::errors {
const InvalidExpirationTimestamp: u64 = 23;
/// Stop order gas cost provided is not enough
const NotEnoughGasForStopOrder: u64 = 24;
/// TWAP order gas cost provided is not enough
const NotEnoughGasForTWAPOrder: u64 = 25;
/// Invalid account trying to perform an action on a StopOrderTicket
const InvalidAccountForStopOrder: u64 = 26;
/// Invalid executor trying to execute the StopOrderTicket
Expand Down Expand Up @@ -126,6 +128,26 @@ module perpetuals::errors {
const NoOpenInterestToSocializeBadDebt: u64 = 40;
/// Bad debt amount is greater than max allowed threshold
const BadDebtAboveThreshold: u64 = 41;
/// TWAP order is past its allowed start or end execution timestamp
const TWAPOrderTicketExpired: u64 = 43;
/// Amount executed in one TWAP execution is outside the allowed range
const TWAPOrderAmountUncertaintyViolated: u64 = 44;
/// Current timestamp is too early for the next TWAP execution
const TWAPOrderExecutionGapViolated: u64 = 45;
/// The TWAP order has already been fully executed
const TWAPOrderFullyExecuted: u64 = 46;
/// TWAP order is being executed after the retry deadline has passed
const TWAPOrderExecutedAfterRetryTime: u64 = 47;
/// TWAP order is not in a terminal state required for finalization
const TWAPOrderCannotBeFinalized: u64 = 48;
/// Invalid account trying to perform an action on a TWAPOrderTicket
const TWAPOrderInvalidAccount: u64 = 49;
/// Invalid executor trying to perform an action on a TWAPOrderTicket
const TWAPOrderInvalidExecutor: u64 = 50;
/// TWAP order is being edited while it is being executed
const TWAPOrderCannotEditExecutingOrder: u64 = 51;
/// Invalid split between execution gas pool and finalization gas
const TWAPOrderInvalidGasSplit: u64 = 52;

// Market ---------------------------------------------------------------

Expand Down
7 changes: 7 additions & 0 deletions crates/af-iperps/src/event_instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ event_instance!(EventInstance {
AddedIntegratorConfig<Otw>,
AllocatedCollateral,
CanceledOrder,
CanceledTWAPOrderTicket<Otw>,
ClosedMarket,
ClosedPositionAtSettlementPrices,
CreatedAccount<Otw>,
Expand All @@ -71,24 +72,30 @@ event_instance!(EventInstance {
CreatedPosition,
CreatedPositionFeesProposal,
CreatedStopOrderTicket<Otw>,
CreatedTWAPOrderTicket<Otw>,
DeallocatedCollateral,
DeletedMarginRatiosProposal,
DeletedPositionFeesProposal,
DeletedStopOrderTicket<Otw>,
DeletedTWAPOrderTicket<Otw>,
DepositedCollateral<Otw>,
DonatedToInsuranceFund,
EditedStopOrderTicketDetails<Otw>,
EditedStopOrderTicketExecutors<Otw>,
EditedTWAPOrderTicketDetails<Otw>,
EditedTWAPOrderTicketExecutors<Otw>,
ExecutedStopOrderTicket<Otw>,
FilledMakerOrder,
FilledMakerOrders,
FilledTakerOrder,
FinalizedTWAPOrderTicket<Otw>,
LiquidatedPosition,
PaidIntegratorFees<Otw>,
PausedMarket,
PerformedADL,
PerformedLiquidation,
PostedOrder,
ProcessedTWAPOrderTicket<Otw>,
RegisteredCollateralInfo<Otw>,
RegisteredMarketInfo<Otw>,
RejectedPositionFeesProposal,
Expand Down
123 changes: 123 additions & 0 deletions crates/af-iperps/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@ pub mod order_helpers;
pub mod order_id;
#[cfg(feature = "stop-orders")]
pub mod stop_order_helpers;
#[cfg(feature = "stop-orders")]
pub mod twap_order_helpers;

pub use self::market::{MarketParams, MarketState};
pub use self::orderbook::Order;
pub use self::position::Position;
pub use self::twap_orders::TWAPOrderDetails;

// Convenient aliases since these types will never exist onchain with a type argument other than an
// OTW.
Expand All @@ -35,6 +38,8 @@ pub type Account = self::account::Account<Otw>;
pub type AccountTypeTag = self::account::AccountTypeTag<Otw>;
pub type StopOrderTicket = self::stop_orders::StopOrderTicket<Otw>;
pub type StopOrderTicketTypetag = self::stop_orders::StopOrderTicketTypeTag<Otw>;
pub type TWAPOrderTicket = self::twap_orders::TWAPOrderTicket<Otw>;
pub type TWAPOrderTicketTypetag = self::twap_orders::TWAPOrderTicketTypeTag<Otw>;
pub type ClearingHouse = self::clearing_house::ClearingHouse<Otw>;
pub type ClearingHouseTypeTag = self::clearing_house::ClearingHouseTypeTag<Otw>;
pub type Vault = self::clearing_house::Vault<Otw>;
Expand Down Expand Up @@ -276,6 +281,73 @@ sui_pkg_sdk!(perpetuals {
}
}

module twap_orders {
/// The details to be hashed for the `encrypted_details` argument of
/// `create_twap_order_ticket`.
struct TWAPOrderDetails has drop {
/// Exclusive deadline for the first valid TWAP execution attempt.
first_run_expire_timestamp: Option<u64>,
/// Exclusive deadline for any TWAP execution attempt.
expire_timestamp: Option<u64>,
/// Expected time between two consecutive valid TWAP execution attempts.
execution_gap_ms: u64,
/// Maximum amount by which a valid attempt may happen earlier than
/// `execution_gap_ms`.
execution_time_uncertainty_ms: u64,
/// Amount of chunks the TWAP order is split into.
chunks_amount: u64,
/// Maximum size of the final fresh tail, expressed in basis points of
/// one execution amount, that may be merged into the previous chunk.
small_tail_merge_threshold_bps: u64,
/// Maximum additional delay after the nominal execution gap before the TWAP
/// becomes spoiled.
time_for_retry_ms: u64,
/// Maximum deviation allowed between the caller-requested amount and
/// one execution amount, expressed in basis points.
amount_uncertainty_bps: u64,
/// Maximum allowed amount for one execution after backlog adjustments,
/// expressed in basis points of the total order size.
max_one_execution_amount_bps: u64,
side: bool,
size: u64,
max_slippage_bps: u64,
reduce_only: bool,
salt: vector<u8>
}

/// Object that allows off-chain executors to process a TWAP order in multiple
/// executions until it is finalized or canceled.
struct TWAPOrderTicket<!phantom T> has key, store {
id: UID,
/// Clearing house for which the TWAP order is placed.
clearing_house_id: ID,
/// Addresses allowed to execute the order on behalf of the user.
executors: vector<address>,
/// Gas coin that must be provided by the user to cover the whole TWAP
/// lifecycle.
gas: Balance<SUI>,
/// Total gas budget for the entire TWAP execution.
gas_execution_budget: u64,
/// User account id.
account_id: u64,
/// Hash of the off-chain order details. See `TWAPOrderDetails`.
encrypted_details: vector<u8>,
/// Amount of the order that has already been executed.
processed_amount: u64,
/// Amount of the TWAP target that has already been scheduled into
/// sub-orders.
scheduled_amount: u64,
/// Timestamp of the last valid execution attempt.
last_attempt_timestamp_ms: u64,
/// Timestamp anchoring spoilage checks.
retry_anchor_timestamp_ms: u64,
/// Timestamp of the last successful fill.
last_execution_timestamp_ms: u64,
/// Portion of `gas_execution_budget` already paid out.
paid_execution_gas: u64,
}
}

module events {
struct CreatedAccount<!phantom T> has copy, drop {
account_obj_id: ID,
Expand Down Expand Up @@ -602,6 +674,57 @@ sui_pkg_sdk!(perpetuals {
executors: vector<address>
}

struct CreatedTWAPOrderTicket<!phantom T> has copy, drop {
ticket_id: ID,
account_id: u64,
executors: vector<address>,
gas: u64,
encrypted_details: vector<u8>
}

struct ProcessedTWAPOrderTicket<!phantom T> has copy, drop {
ticket_id: ID,
account_id: u64,
execution_amount: u64,
filled_amount: u64,
remainder: u64,
processed_amount: u64,
last_execution_timestamp_ms: u64,
}

struct FinalizedTWAPOrderTicket<!phantom T> has copy, drop {
ticket_id: ID,
account_id: u64,
executor: address,
deallocated_collateral: u64,
}

struct CanceledTWAPOrderTicket<!phantom T> has copy, drop {
ticket_id: ID,
account_id: u64,
sender: address,
deallocated_collateral: u64,
partial_fill: bool,
}

struct DeletedTWAPOrderTicket<!phantom T> has copy, drop {
ticket_id: ID,
account_id: u64,
executor: address
}

struct EditedTWAPOrderTicketDetails<!phantom T> has copy, drop {
ticket_id: ID,
account_id: u64,
encrypted_details: vector<u8>
}

struct EditedTWAPOrderTicketExecutors<!phantom T> has copy, drop {
ticket_id: ID,
account_id: u64,
executors: vector<address>
}

struct CreatedMarginRatiosProposal has copy, drop {
ch_id: ID,
margin_ratio_initial: IFixed,
Expand Down
66 changes: 66 additions & 0 deletions crates/af-iperps/src/twap_order_helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//! Helpers for TWAP orders.

use fastcrypto::hash::{Blake2b256, HashFunction};
use serde::Serialize;

pub trait TWAPOrderTicketDetails {
/// Pure transaction input to use when calling `create_twap_order_ticket`.
fn encrypted_details(&self) -> Result<Vec<u8>, sui_sdk_types::bcs::Error>
where
Self: Serialize,
{
Ok(Blake2b256::digest(sui_sdk_types::bcs::ToBcs::to_bcs(&self)?).to_vec())
}
}

/// The details to be hashed for the `encrypted_details` argument of
/// `create_twap_order_ticket`.
#[derive(Debug, Serialize)]
pub struct TWAPDetails {
pub first_run_expire_timestamp: Option<u64>,
pub expire_timestamp: Option<u64>,
pub execution_gap_ms: u64,
pub execution_time_uncertainty_ms: u64,
pub chunks_amount: u64,
pub small_tail_merge_threshold_bps: u64,
pub time_for_retry_ms: u64,
pub amount_uncertainty_bps: u64,
pub max_one_execution_amount_bps: u64,
pub side: bool,
pub size: u64,
pub max_slippage_bps: u64,
pub reduce_only: bool,
pub salt: Vec<u8>,
}

impl TWAPOrderTicketDetails for TWAPDetails {}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn encrypted_details_hashes_bcs_payload_including_salt() {
let details = TWAPDetails {
first_run_expire_timestamp: Some(1),
expire_timestamp: Some(2),
execution_gap_ms: 3,
execution_time_uncertainty_ms: 4,
chunks_amount: 5,
small_tail_merge_threshold_bps: 6,
time_for_retry_ms: 7,
amount_uncertainty_bps: 8,
max_one_execution_amount_bps: 9,
side: true,
size: 10,
max_slippage_bps: 11,
reduce_only: false,
salt: vec![12; 32],
};

let expected = Blake2b256::digest(sui_sdk_types::bcs::ToBcs::to_bcs(&details).unwrap());
let actual = details.encrypted_details().unwrap();

assert_eq!(actual, expected.to_vec());
}
}
Loading