Skip to main content

bulk_client/transaction/
actions.rs

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/// Meta data for an action
11#[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    // Market = ordinal(0)
23    #[serde(rename = "m")]
24    MarketOrder(MarketOrder),
25    // Limit = ordinal(1)
26    #[serde(rename = "l")]
27    LimitOrder(LimitOrder),
28    // Modify = ordinal(2)
29    #[serde(rename = "mod")]
30    ModifyOrder(ModifyOrder),
31    // Cancel = ordinal(3)
32    #[serde(rename = "cx")]
33    Cancel(CancelOrder),
34    // CancelAll = ordinal(4)
35    #[serde(rename = "cxa")]
36    CancelAll(CancelAll),
37    // Stop = ordinal(5)
38    #[serde(rename = "st")]
39    Stop(StopOrTP),
40    // TakeProfit = ordinal(6)
41    #[serde(rename = "tp")]
42    TakeProfit(StopOrTP),
43    // Range = ordinal(7)
44    #[serde(rename = "rng")]
45    Range(Range),
46    // Trigger = ordinal(8)
47    #[serde(rename = "trig")]
48    Trigger(Trigger),
49    // Trailing = ordinal(9)
50    #[serde(rename = "trl")]
51    Trailing(Trailing),
52    // OnFill = ordinal(10)
53    #[serde(rename = "of")]
54    OnFill(OnFill),
55
56    // Price = ordinal(11)
57    #[serde(rename = "px")]
58    Price(Price),
59    // Corrs = ordinal(12)
60    #[serde(rename = "corrs")]
61    Corrs(Matrix),
62    // PythOracle = ordinal(13)
63    #[serde(rename = "o")]
64    PythOracle(PythOracle),
65    // Beacon = ordinal(14)
66    #[serde(rename = "beacon")]
67    Beacon(Beacon),
68    // Join = ordinal(15)
69    #[serde(rename = "join")]
70    Join(Join),
71
72    // Faucet = ordinal(16)
73    Faucet(Faucet),
74    // AgentWallet = ordinal(17)
75    AgentWalletCreation(AgentWalletCreation),
76    // UpdateUserSettings = ordinal(18)
77    UpdateUserSettings(UpdateUserSettings),
78
79    // WhitelistFaucet = ordinal(19)
80    WhitelistFaucet(WhitelistFaucet),
81
82    // AddMarket = ordinal(20)
83    AddMarket(AddMarket),
84    // ConfigFairPrice = ordinal(21)
85    ConfigFairPrice(OpaqueAction),
86    // ConfigVolatility = ordinal(22)
87    ConfigVolatility(OpaqueAction),
88    // ConfigSecurity = ordinal(23)
89    ConfigSecurity(OpaqueAction),
90    // ConfigRegime = ordinal(24)
91    ConfigRegime(OpaqueAction),
92    // ConfigRisk = ordinal(25)
93    ConfigRisk(OpaqueAction),
94    // ConfigFeePolicy = ordinal(26)
95    #[serde(rename = "cfgf")]
96    ConfigFeePolicy(OpaqueAction),
97
98    // CreateSubAccount = ordinal(27)
99    CreateSubAccount(CreateSubAccount),
100    // RemoveSubAccount = ordinal(28)
101    RemoveSubAccount(RemoveSubAccount),
102    // Transfer = ordinal(29)
103    Transfer(Transfer),
104    // CreateMultisig = ordinal(30)
105    CreateMultisig(CreateMultisig),
106
107    // MultisigPropose = ordinal(31)
108    #[serde(rename = "msp")]
109    MultisigPropose(MultisigPropose),
110    // MultisigApprove = ordinal(32)
111    #[serde(rename = "msa")]
112    MultisigApprove(MultisigApprove),
113    // MultisigReject = ordinal(33)
114    #[serde(rename = "msr")]
115    MultisigReject(MultisigReject),
116    // MultisigCancel = ordinal(34)
117    #[serde(rename = "msc")]
118    MultisigCancel(MultisigCancel),
119    // MultisigExecute = ordinal(35)
120    #[serde(rename = "mse")]
121    MultisigExecute(MultisigExecute),
122    // UpdateMultisigPolicy = ordinal(36)
123    #[serde(rename = "msu")]
124    UpdateMultisigPolicy(UpdateMultisigPolicy),
125
126    // RenameSubAccount = ordinal(37)
127    #[serde(rename = "rsa", alias = "renameSubAccount")]
128    RenameSubAccount(RenameSubAccount),
129    // UpdateValidatorSet = ordinal(39)
130    #[serde(rename = "uvs")]
131    UpdateValidatorSet(UpdateValidatorSet),
132
133    // UpdateRiskConfig = ordinal(40)
134    #[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    /// Get account associated with action
193    pub fn account(&self) -> &Pubkey {
194        dispatch!(self, x => &x.meta.account)
195    }
196
197    /// Get nonce associated with action
198    pub fn nonce(&self) -> u64 {
199        dispatch!(self, x => x.meta.nonce)
200    }
201
202    /// Get nonce associated with action
203    pub fn seqno(&self) -> u32 {
204        dispatch!(self, x => x.meta.seqno)
205    }
206
207    /// Get or compute hash of action
208    pub fn hash(&mut self) -> Hash {
209        use sha2::Digest;
210
211        // Single dispatch: cache check + extract raw pointer to meta
212        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        // Read meta fields through pointer — no live borrows of self
220        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        // Immutable borrow of self released here
229        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        // Write result — no active borrows of self
235        unsafe {
236            (*meta_ptr).hash = Some(hash);
237        }
238        hash
239    }
240
241    /// Link tx and action meta information in each action
242    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}