1use serde::{Deserialize, Serialize};
2use solana_hash::Hash;
3use solana_keypair::Pubkey;
4use crate::msgs::{AddMarket, AgentWalletCreation, Beacon, CancelAll, CancelOrder, Faucet, Join, LimitOrder, MarketOrder, Matrix, ModifyOrder, OpaqueAction, Price, PythOracle, UpdateUserSettings, UpdateValidatorSet, WhitelistFaucet};
5use crate::msgs::conditional::{OnFill, Range, StopOrTP, Trailing, Trigger};
6use crate::msgs::multisig::{CreateMultisig, MultisigApprove, MultisigCancel, MultisigExecute, MultisigPropose, MultisigReject, UpdateMultisigPolicy};
7use crate::msgs::risk::RiskConfigChange;
8use crate::msgs::subaccounts::{CreateSubAccount, RemoveSubAccount, RenameSubAccount, Transfer};
9
10#[derive(Clone, Copy, Debug, Default)]
12pub struct ActionMeta {
13 pub account: Pubkey,
14 pub nonce: u64,
15 pub seqno: u32,
16 pub hash: Option<Hash>,
17}
18
19#[derive(Clone, Debug, Serialize, Deserialize)]
20#[serde(rename_all = "camelCase")]
21pub enum Action {
22 #[serde(rename = "m")]
24 MarketOrder(MarketOrder),
25 #[serde(rename = "l")]
27 LimitOrder(LimitOrder),
28 #[serde(rename = "mod")]
30 ModifyOrder(ModifyOrder),
31 #[serde(rename = "cx")]
33 Cancel(CancelOrder),
34 #[serde(rename = "cxa")]
36 CancelAll(CancelAll),
37 #[serde(rename = "st")]
39 Stop(StopOrTP),
40 #[serde(rename = "tp")]
42 TakeProfit(StopOrTP),
43 #[serde(rename = "rng")]
45 Range(Range),
46 #[serde(rename = "trig")]
48 Trigger(Trigger),
49 #[serde(rename = "trl")]
51 Trailing(Trailing),
52 #[serde(rename = "of")]
54 OnFill(OnFill),
55
56 #[serde(rename = "px")]
58 Price(Price),
59 #[serde(rename = "corrs")]
61 Corrs(Matrix),
62 #[serde(rename = "o")]
64 PythOracle(PythOracle),
65 #[serde(rename = "beacon")]
67 Beacon(Beacon),
68 #[serde(rename = "join")]
70 Join(Join),
71
72 Faucet(Faucet),
74 AgentWalletCreation(AgentWalletCreation),
76 UpdateUserSettings(UpdateUserSettings),
78
79 WhitelistFaucet(WhitelistFaucet),
81
82 AddMarket(AddMarket),
84 ConfigFairPrice(OpaqueAction),
86 ConfigVolatility(OpaqueAction),
88 ConfigSecurity(OpaqueAction),
90 ConfigRegime(OpaqueAction),
92 ConfigRisk(OpaqueAction),
94 #[serde(rename = "cfgf")]
96 ConfigFeePolicy(OpaqueAction),
97
98 CreateSubAccount(CreateSubAccount),
100 RemoveSubAccount(RemoveSubAccount),
102 Transfer(Transfer),
104 CreateMultisig(CreateMultisig),
106
107 #[serde(rename = "msp")]
109 MultisigPropose(MultisigPropose),
110 #[serde(rename = "msa")]
112 MultisigApprove(MultisigApprove),
113 #[serde(rename = "msr")]
115 MultisigReject(MultisigReject),
116 #[serde(rename = "msc")]
118 MultisigCancel(MultisigCancel),
119 #[serde(rename = "mse")]
121 MultisigExecute(MultisigExecute),
122 #[serde(rename = "msu")]
124 UpdateMultisigPolicy(UpdateMultisigPolicy),
125
126 #[serde(rename = "rsa", alias = "renameSubAccount")]
128 RenameSubAccount(RenameSubAccount),
129 #[serde(rename = "uvs")]
131 UpdateValidatorSet(UpdateValidatorSet),
132
133 #[serde(rename = "risk")]
135 UpdateRiskConfig(RiskConfigChange),
136}
137
138macro_rules! dispatch {
139 ($self:expr, $x:ident => $body:expr) => {
140 match $self {
141 Action::MarketOrder($x) => $body,
142 Action::LimitOrder($x) => $body,
143 Action::ModifyOrder($x) => $body,
144 Action::Cancel($x) => $body,
145 Action::CancelAll($x) => $body,
146 Action::Stop($x) => $body,
147 Action::TakeProfit($x) => $body,
148 Action::Range($x) => $body,
149 Action::Trigger($x) => $body,
150 Action::Trailing($x) => $body,
151 Action::OnFill($x) => $body,
152 Action::Price($x) => $body,
153 Action::Corrs($x) => $body,
154 Action::PythOracle($x) => $body,
155 Action::Beacon($x) => $body,
156 Action::Join($x) => $body,
157
158 Action::Faucet($x) => $body,
159 Action::AgentWalletCreation($x) => $body,
160 Action::UpdateUserSettings($x) => $body,
161 Action::WhitelistFaucet($x) => $body,
162
163 Action::AddMarket($x) => $body,
164 Action::ConfigFairPrice($x) => $body,
165 Action::ConfigVolatility($x) => $body,
166 Action::ConfigSecurity($x) => $body,
167 Action::ConfigRegime($x) => $body,
168 Action::ConfigRisk($x) => $body,
169 Action::ConfigFeePolicy($x) => $body,
170
171 Action::CreateSubAccount($x) => $body,
172 Action::RemoveSubAccount($x) => $body,
173 Action::Transfer($x) => $body,
174
175 Action::CreateMultisig($x) => $body,
176 Action::MultisigPropose($x) => $body,
177 Action::MultisigApprove($x) => $body,
178 Action::MultisigReject($x) => $body,
179 Action::MultisigCancel($x) => $body,
180 Action::MultisigExecute($x) => $body,
181 Action::UpdateMultisigPolicy($x) => $body,
182
183 Action::RenameSubAccount($x) => $body,
184 Action::UpdateValidatorSet($x) => $body,
185
186 Action::UpdateRiskConfig($x) => $body,
187 }
188 };
189}
190
191impl Action {
192 pub fn account(&self) -> &Pubkey {
194 dispatch!(self, x => &x.meta.account)
195 }
196
197 pub fn nonce(&self) -> u64 {
199 dispatch!(self, x => x.meta.nonce)
200 }
201
202 pub fn seqno(&self) -> u32 {
204 dispatch!(self, x => x.meta.seqno)
205 }
206
207 pub fn hash(&mut self) -> Hash {
209 use sha2::Digest;
210
211 let meta_ptr: *mut ActionMeta = dispatch!(self, x => {
213 if let Some(h) = x.meta.hash {
214 return h;
215 }
216 &mut x.meta as *mut ActionMeta
217 });
218
219 let (seqno, account, nonce) = unsafe {
221 let m = &*meta_ptr;
222 (m.seqno, m.account, m.nonce)
223 };
224
225 let mut hasher = sha2::Sha256::new();
226 hasher.update(&seqno.to_le_bytes());
227 bincode::serialize_into(&mut hasher, &*self).expect("serialization failed");
228 hasher.update(account.as_ref());
230 hasher.update(&nonce.to_le_bytes());
231
232 let hash = Hash::from(Into::<[u8; 32]>::into(hasher.finalize()));
233
234 unsafe {
236 (*meta_ptr).hash = Some(hash);
237 }
238 hash
239 }
240
241 pub fn link(&mut self, meta: ActionMeta) {
243 dispatch!(self, x => {
244 x.meta = meta;
245 })
246 }
247}
248
249
250impl From<MarketOrder> for Action {
251 fn from(o: MarketOrder) -> Self {
252 Action::MarketOrder(o)
253 }
254}
255
256impl From<LimitOrder> for Action {
257 fn from(o: LimitOrder) -> Self {
258 Action::LimitOrder(o)
259 }
260}
261
262impl From<ModifyOrder> for Action {
263 fn from(o: ModifyOrder) -> Self {
264 Action::ModifyOrder(o)
265 }
266}
267
268impl From<CancelAll> for Action {
269 fn from(o: CancelAll) -> Self {
270 Action::CancelAll(o)
271 }
272}
273
274impl From<CancelOrder> for Action {
275 fn from(o: CancelOrder) -> Self {
276 Action::Cancel(o)
277 }
278}
279
280impl From<Price> for Action {
281 fn from(o: Price) -> Self {
282 Action::Price(o)
283 }
284}
285
286impl From<PythOracle> for Action {
287 fn from(o: PythOracle) -> Self {
288 Action::PythOracle(o)
289 }
290}
291
292impl From<Faucet> for Action {
293 fn from(o: Faucet) -> Self {
294 Action::Faucet(o)
295 }
296}
297
298impl From<AgentWalletCreation> for Action {
299 fn from(o: AgentWalletCreation) -> Self {
300 Action::AgentWalletCreation(o)
301 }
302}
303
304impl From<UpdateUserSettings> for Action {
305 fn from(o: UpdateUserSettings) -> Self {
306 Action::UpdateUserSettings(o)
307 }
308}
309
310impl From<WhitelistFaucet> for Action {
311 fn from(o: WhitelistFaucet) -> Self {
312 Action::WhitelistFaucet(o)
313 }
314}
315
316
317
318#[cfg(test)]
319mod tests {
320 use std::sync::Arc;
321 use crate::common::tif::TimeInForce;
322 use super::*;
323
324 #[test]
325 fn test_limit_hash() {
326 let limit = LimitOrder {
327 symbol: Arc::from("BTC-USD"),
328 is_buy: true,
329 price: 100000.0,
330 size: 1.0,
331 tif: TimeInForce::ALO,
332 reduce_only: false,
333 iso: false,
334 meta: ActionMeta {
335 account: Default::default(),
336 nonce: 1_776_128_000_000_000_000,
337 seqno: 0,
338 hash: None,
339 },
340 };
341
342 let mut action = Action::LimitOrder(limit);
343 let hash = action.hash();
344
345 assert_eq!(
346 hash.to_string(),
347 "9BreqftLa7ZAsYLkvJDRBRxiSukzGoTfbQNMBWWkUAUJ"
348 );
349 }
350}