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