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, WhitelistFaucet};
5use crate::msgs::conditional::{OnFill, Range, StopOrTP, Trailing, Trigger};
6use crate::msgs::multisig::{CreateMultisig, MultisigApprove, MultisigCancel, MultisigExecute, MultisigPropose, MultisigReject, UpdateMultisigPolicy};
7use crate::msgs::subaccounts::{CreateSubAccount, RemoveSubAccount, Transfer};
8
9/// Meta data for an action
10#[derive(Clone, Copy, Debug, Default)]
11pub struct ActionMeta {
12    pub account: Pubkey,
13    pub nonce: u64,
14    pub seqno: u32,
15    pub hash: Option<Hash>,
16}
17
18#[derive(Clone, Debug, Serialize, Deserialize)]
19#[serde(rename_all = "camelCase")]
20pub enum Action {
21    // Market = ordinal(0)
22    #[serde(rename = "m")]
23    MarketOrder(MarketOrder),
24    // Limit = ordinal(1)
25    #[serde(rename = "l")]
26    LimitOrder(LimitOrder),
27    // Modify = ordinal(2)
28    #[serde(rename = "mod")]
29    ModifyOrder(ModifyOrder),
30    // Cancel = ordinal(3)
31    #[serde(rename = "cx")]
32    Cancel(CancelOrder),
33    // CancelAll = ordinal(4)
34    #[serde(rename = "cxa")]
35    CancelAll(CancelAll),
36    // Stop = ordinal(5)
37    #[serde(rename = "st")]
38    Stop(StopOrTP),
39    // TakeProfit = ordinal(6)
40    #[serde(rename = "tp")]
41    TakeProfit(StopOrTP),
42    // Range = ordinal(7)
43    #[serde(rename = "rng")]
44    Range(Range),
45    // Trigger = ordinal(8)
46    #[serde(rename = "trig")]
47    Trigger(Trigger),
48    // Trailing = ordinal(9)
49    #[serde(rename = "trl")]
50    Trailing(Trailing),
51    // OnFill = ordinal(10)
52    #[serde(rename = "of")]
53    OnFill(OnFill),
54
55    // Price = ordinal(11)
56    #[serde(rename = "px")]
57    Price(Price),
58    // Corrs = ordinal(12)
59    #[serde(rename = "corrs")]
60    Corrs(Matrix),
61    // PythOracle = ordinal(13)
62    #[serde(rename = "o")]
63    PythOracle(PythOracle),
64    // Beacon = ordinal(14)
65    #[serde(rename = "beacon")]
66    Beacon(Beacon),
67    // Join = ordinal(15)
68    #[serde(rename = "join")]
69    Join(Join),
70
71    // Faucet = ordinal(16)
72    Faucet(Faucet),
73    // AgentWallet = ordinal(17)
74    AgentWalletCreation(AgentWalletCreation),
75    // UpdateUserSettings = ordinal(18)
76    UpdateUserSettings(UpdateUserSettings),
77
78    // WhitelistFaucet = ordinal(19)
79    WhitelistFaucet(WhitelistFaucet),
80
81    // AddMarket = ordinal(20)
82    AddMarket(AddMarket),
83    // ConfigFairPrice = ordinal(21)
84    ConfigFairPrice(OpaqueAction),
85    // ConfigVolatility = ordinal(22)
86    ConfigVolatility(OpaqueAction),
87    // ConfigSecurity = ordinal(23)
88    ConfigSecurity(OpaqueAction),
89    // ConfigRegime = ordinal(24)
90    ConfigRegime(OpaqueAction),
91    // ConfigRisk = ordinal(25)
92    ConfigRisk(OpaqueAction),
93    // ConfigFeePolicy = ordinal(26)
94    #[serde(rename = "cfgf")]
95    ConfigFeePolicy(OpaqueAction),
96
97    // CreateSubAccount = ordinal(27)
98    CreateSubAccount(CreateSubAccount),
99    // RemoveSubAccount = ordinal(28)
100    RemoveSubAccount(RemoveSubAccount),
101    // Transfer = ordinal(29)
102    Transfer(Transfer),
103    // CreateMultisig = ordinal(30)
104    CreateMultisig(CreateMultisig),
105
106    // MultisigPropose = ordinal(31)
107    #[serde(rename = "msp")]
108    MultisigPropose(MultisigPropose),
109    // MultisigApprove = ordinal(32)
110    #[serde(rename = "msa")]
111    MultisigApprove(MultisigApprove),
112    // MultisigReject = ordinal(33)
113    #[serde(rename = "msr")]
114    MultisigReject(MultisigReject),
115    // MultisigCancel = ordinal(34)
116    #[serde(rename = "msc")]
117    MultisigCancel(MultisigCancel),
118    // MultisigExecute = ordinal(35)
119    #[serde(rename = "mse")]
120    MultisigExecute(MultisigExecute),
121    // UpdateMultisigPolicy = ordinal(36)
122    #[serde(rename = "msu")]
123    UpdateMultisigPolicy(UpdateMultisigPolicy),
124}
125
126macro_rules! dispatch {
127    ($self:expr, $x:ident => $body:expr) => {
128        match $self {
129            Action::MarketOrder($x) => $body,
130            Action::LimitOrder($x) => $body,
131            Action::ModifyOrder($x) => $body,
132            Action::Cancel($x) => $body,
133            Action::CancelAll($x) => $body,
134            Action::Stop($x) => $body,
135            Action::TakeProfit($x) => $body,
136            Action::Range($x) => $body,
137            Action::Trigger($x) => $body,
138            Action::Trailing($x) => $body,
139            Action::OnFill($x) => $body,
140            Action::Price($x) => $body,
141            Action::Corrs($x) => $body,
142            Action::PythOracle($x) => $body,
143            Action::Beacon($x) => $body,
144            Action::Join($x) => $body,
145
146            Action::Faucet($x) => $body,
147            Action::AgentWalletCreation($x) => $body,
148            Action::UpdateUserSettings($x) => $body,
149            Action::WhitelistFaucet($x) => $body,
150
151            Action::AddMarket($x) => $body,
152            Action::ConfigFairPrice($x) => $body,
153            Action::ConfigVolatility($x) => $body,
154            Action::ConfigSecurity($x) => $body,
155            Action::ConfigRegime($x) => $body,
156            Action::ConfigRisk($x) => $body,
157            Action::ConfigFeePolicy($x) => $body,
158
159            Action::CreateSubAccount($x) => $body,
160            Action::RemoveSubAccount($x) => $body,
161            Action::Transfer($x) => $body,
162
163            Action::CreateMultisig($x) => $body,
164            Action::MultisigPropose($x) => $body,
165            Action::MultisigApprove($x) => $body,
166            Action::MultisigReject($x) => $body,
167            Action::MultisigCancel($x) => $body,
168            Action::MultisigExecute($x) => $body,
169            Action::UpdateMultisigPolicy($x) => $body,
170        }
171    };
172}
173
174impl Action {
175    /// Get account associated with action
176    pub fn account(&self) -> &Pubkey {
177        dispatch!(self, x => &x.meta.account)
178    }
179
180    /// Get nonce associated with action
181    pub fn nonce(&self) -> u64 {
182        dispatch!(self, x => x.meta.nonce)
183    }
184
185    /// Get nonce associated with action
186    pub fn seqno(&self) -> u32 {
187        dispatch!(self, x => x.meta.seqno)
188    }
189
190    /// Get or compute hash of action
191    pub fn hash(&mut self) -> Hash {
192        use sha2::Digest;
193
194        // Single dispatch: cache check + extract raw pointer to meta
195        let meta_ptr: *mut ActionMeta = dispatch!(self, x => {
196            if let Some(h) = x.meta.hash {
197                return h;
198            }
199            &mut x.meta as *mut ActionMeta
200        });
201
202        // Read meta fields through pointer — no live borrows of self
203        let (seqno, account, nonce) = unsafe {
204            let m = &*meta_ptr;
205            (m.seqno, m.account, m.nonce)
206        };
207
208        let mut hasher = sha2::Sha256::new();
209        hasher.update(&seqno.to_le_bytes());
210        bincode::serialize_into(&mut hasher, &*self).expect("serialization failed");
211        // Immutable borrow of self released here
212        hasher.update(account.as_ref());
213        hasher.update(&nonce.to_le_bytes());
214
215        let hash = Hash::from(Into::<[u8; 32]>::into(hasher.finalize()));
216
217        // Write result — no active borrows of self
218        unsafe {
219            (*meta_ptr).hash = Some(hash);
220        }
221        hash
222    }
223
224    /// Link tx and action meta information in each action
225    pub fn link(&mut self, meta: ActionMeta) {
226        dispatch!(self, x => {
227            x.meta = meta;
228        })
229    }
230}
231
232
233impl From<MarketOrder> for Action {
234    fn from(o: MarketOrder) -> Self {
235        Action::MarketOrder(o)
236    }
237}
238
239impl From<LimitOrder> for Action {
240    fn from(o: LimitOrder) -> Self {
241        Action::LimitOrder(o)
242    }
243}
244
245impl From<ModifyOrder> for Action {
246    fn from(o: ModifyOrder) -> Self {
247        Action::ModifyOrder(o)
248    }
249}
250
251impl From<CancelAll> for Action {
252    fn from(o: CancelAll) -> Self {
253        Action::CancelAll(o)
254    }
255}
256
257impl From<CancelOrder> for Action {
258    fn from(o: CancelOrder) -> Self {
259        Action::Cancel(o)
260    }
261}
262
263impl From<Price> for Action {
264    fn from(o: Price) -> Self {
265        Action::Price(o)
266    }
267}
268
269impl From<PythOracle> for Action {
270    fn from(o: PythOracle) -> Self {
271        Action::PythOracle(o)
272    }
273}
274
275impl From<Faucet> for Action {
276    fn from(o: Faucet) -> Self {
277        Action::Faucet(o)
278    }
279}
280
281impl From<AgentWalletCreation> for Action {
282    fn from(o: AgentWalletCreation) -> Self {
283        Action::AgentWalletCreation(o)
284    }
285}
286
287impl From<UpdateUserSettings> for Action {
288    fn from(o: UpdateUserSettings) -> Self {
289        Action::UpdateUserSettings(o)
290    }
291}
292
293impl From<WhitelistFaucet> for Action {
294    fn from(o: WhitelistFaucet) -> Self {
295        Action::WhitelistFaucet(o)
296    }
297}
298
299
300
301#[cfg(test)]
302mod tests {
303    use std::sync::Arc;
304    use crate::common::tif::TimeInForce;
305    use super::*;
306
307    #[test]
308    fn test_limit_hash() {
309        let limit = LimitOrder {
310            symbol: Arc::from("BTC-USD"),
311            is_buy: true,
312            price: 100000.0,
313            size: 1.0,
314            tif: TimeInForce::ALO,
315            reduce_only: false,
316            iso: false,
317            meta: ActionMeta {
318                account: Default::default(),
319                nonce: 1_776_128_000_000_000_000,
320                seqno: 0,
321                hash: None,
322            },
323        };
324
325        let mut action = Action::LimitOrder(limit);
326        let hash = action.hash();
327
328        assert_eq!(
329            hash.to_string(),
330            "9BreqftLa7ZAsYLkvJDRBRxiSukzGoTfbQNMBWWkUAUJ"
331        );
332    }
333}