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}