manifest/
lib.rs

1//! Manifest is a limit order book exchange on the Solana blockchain.
2//!
3
4pub mod logs;
5pub mod program;
6pub mod quantities;
7pub mod state;
8pub mod utils;
9pub mod validation;
10
11#[cfg(feature = "certora")]
12pub mod certora;
13
14use hypertree::trace;
15use program::{
16    batch_update::process_batch_update, claim_seat::process_claim_seat,
17    create_market::process_create_market, deposit::process_deposit,
18    expand_market::process_expand_market, global_add_trader::process_global_add_trader,
19    global_clean::process_global_clean, global_create::process_global_create,
20    global_deposit::process_global_deposit, global_evict::process_global_evict,
21    global_withdraw::process_global_withdraw, process_swap, withdraw::process_withdraw,
22    ManifestInstruction,
23};
24use solana_program::{
25    account_info::AccountInfo, declare_id, entrypoint::ProgramResult, program_error::ProgramError,
26    pubkey::Pubkey,
27};
28
29#[cfg(not(feature = "no-entrypoint"))]
30use solana_security_txt::security_txt;
31
32#[cfg(not(feature = "no-entrypoint"))]
33security_txt! {
34    name: "manifest",
35    project_url: "https://manifest.trade",
36    contacts: "email:britt@cks.systems",
37    policy: "",
38    preferred_languages: "en",
39    source_code: "https://github.com/CKS-Systems/manifest",
40    auditors: ""
41}
42
43// Overview of some economic disincentive security assumptions. There are
44// multiple ways to spam and cause hassle for honest traders. The strategy to
45// combat the different ways largely relies on economic assumptions that by
46// making it prohibitively expensive to grief honest traders.
47//
48// Denial of service through many small orders.
49// Because each regular order requires funds to be placed on the market as well
50// as possibly rent to expand an account, an attacker would have some associated
51// cost. Particularly important is the cost of growing the market. If this
52// happens, honest actors should simply create a new market and the rent that
53// the attacker posted is irretrievable, thus making the attack only a temporary
54// nuissance. If the market is full and no new seats can be claimed, the same
55// mitigation applies.
56//
57// CU exhaustion
58// Clients are expected to manage CU estimates on their own. There should not be
59// a way to make a stuck market because an honest actor can reduce their size
60// and take orders in their way. The gas cost to place the orders is nearly the
61// same as to match through them, and the cleaner gets the reward of plus EV
62// trades.
63//
64// Global order spam
65// If a bad actor were to place many orders that were not backed across many
66// markets, there is an added gas as well as input accounts cost for honest
67// actors cancelling or matching them. To combat this, there is a gas prepayment
68// of 5_000 lamports per order. Because of this, if there is ever multiple
69// unbacked or expired global orders, it is now profitable for a 3rd party to
70// run a cleanup job and claim the gas prepayment.
71//
72// Global seat squatting
73// Because global accounts cannot be thrown away and started anew like markets,
74// there needs to be a gating to who can use the bytes in the account. Also,
75// there is a good reason to keep the account size small as it costs more CU to
76// allow large accounts. To address this, there is a cap on the number of
77// traders who can have a seat on a global account. To prevent squatting and
78// guarantee that they are actually used, there is a mechanism for eviction. To
79// evict someone and free up a seat, the evictor needs to deposit more than the
80// lowest depositor to evict them. The evictee gets their tokens back, but their
81// global orders are now unbacked and could have their gas prepayment claimed.
82// This is a risk that global traders take when they leave themselves vulnerable
83// to be evicted by keeping a low balance. There is a concern of a flash loan
84// being able to evict many seats in one transaction. This however is protected
85// by solana account limits because each evictee gets their tokens withdrawn, so
86// an account is needed per eviction, and that will quickly run into the solana
87// transaction limit before an attacker is able to clear a substantial number of
88// seats in one transaction.
89
90declare_id!("MNFSTqtC93rEfYHB6hF82sKdZpUDFWkViLByLd1k1Ms");
91
92#[cfg(not(feature = "no-entrypoint"))]
93solana_program::entrypoint!(process_instruction);
94
95pub fn process_instruction(
96    program_id: &Pubkey,
97    accounts: &[AccountInfo],
98    instruction_data: &[u8],
99) -> ProgramResult {
100    let (tag, data) = instruction_data
101        .split_first()
102        .ok_or(ProgramError::InvalidInstructionData)?;
103
104    let instruction: ManifestInstruction =
105        ManifestInstruction::try_from(*tag).or(Err(ProgramError::InvalidInstructionData))?;
106
107    trace!("Instruction: {:?}", instruction);
108
109    match instruction {
110        ManifestInstruction::CreateMarket => {
111            process_create_market(program_id, accounts, data)?;
112        }
113        ManifestInstruction::ClaimSeat => {
114            process_claim_seat(program_id, accounts, data)?;
115        }
116        ManifestInstruction::Deposit => {
117            process_deposit(program_id, accounts, data)?;
118        }
119        ManifestInstruction::Withdraw => {
120            process_withdraw(program_id, accounts, data)?;
121        }
122        ManifestInstruction::Swap => {
123            process_swap(program_id, accounts, data)?;
124        }
125        ManifestInstruction::SwapV2 => {
126            process_swap(program_id, accounts, data)?;
127        }
128        ManifestInstruction::Expand => {
129            process_expand_market(program_id, accounts, data)?;
130        }
131        ManifestInstruction::BatchUpdate => {
132            process_batch_update(program_id, accounts, data)?;
133        }
134        ManifestInstruction::GlobalCreate => {
135            process_global_create(program_id, accounts, data)?;
136        }
137        ManifestInstruction::GlobalAddTrader => {
138            process_global_add_trader(program_id, accounts, data)?;
139        }
140        ManifestInstruction::GlobalDeposit => {
141            process_global_deposit(program_id, accounts, data)?;
142        }
143        ManifestInstruction::GlobalWithdraw => {
144            process_global_withdraw(program_id, accounts, data)?;
145        }
146        ManifestInstruction::GlobalEvict => {
147            process_global_evict(program_id, accounts, data)?;
148        }
149        ManifestInstruction::GlobalClean => {
150            process_global_clean(program_id, accounts, data)?;
151        }
152    }
153
154    Ok(())
155}