simulator_client/
builders.rs1use std::collections::{BTreeMap, BTreeSet};
2
3use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
4use bon::Builder;
5use simulator_api::{
6 AccountData, AccountModifications, AgentParams, BinaryEncoding, ContinueParams,
7 CreateBacktestSessionRequest, CreateBacktestSessionRequestV1, CreateSessionParams,
8};
9use solana_address::Address;
10use solana_client::rpc_client::SerializableTransaction;
11use thiserror::Error;
12
13use crate::BacktestClientError;
14
15#[derive(Debug, Error)]
17pub enum SerializeEncodeError {
18 #[error("bincode serialization error: {0}")]
19 Bincode(#[from] bincode::error::EncodeError),
20}
21
22fn serialize_to_base64(value: &impl serde::Serialize) -> Result<String, SerializeEncodeError> {
23 let bytes = bincode::serde::encode_to_vec(
24 value,
25 bincode::config::standard()
26 .with_fixed_int_encoding()
27 .with_little_endian(),
28 )?;
29 Ok(BASE64.encode(&bytes))
30}
31
32#[derive(Debug, Clone, Builder)]
37pub struct CreateSession {
38 pub start_slot: u64,
39
40 pub end_slot: Option<u64>,
41
42 pub slot_count: Option<u64>,
43
44 #[builder(default)]
45 pub signer_filter: BTreeSet<Address>,
46
47 #[builder(default)]
48 pub preload_programs: BTreeSet<Address>,
49
50 #[builder(default)]
51 pub preload_account_bundles: Vec<String>,
52
53 #[builder(default)]
55 pub send_summary: bool,
56
57 #[builder(default)]
59 pub parallel: bool,
60
61 pub capacity_wait_timeout_secs: Option<u16>,
62
63 pub disconnect_timeout_secs: Option<u16>,
64
65 pub extra_compute_units: Option<u32>,
67
68 #[builder(default)]
69 pub agents: Vec<AgentParams>,
70}
71
72impl CreateSession {
73 pub fn add_signer_filter(mut self, address: Address) -> Self {
75 self.signer_filter.insert(address);
76 self
77 }
78
79 pub fn add_preload_program(mut self, address: Address) -> Self {
81 self.preload_programs.insert(address);
82 self
83 }
84
85 pub fn into_params(self) -> Result<CreateSessionParams, BacktestClientError> {
87 let end_slot = match (self.end_slot, self.slot_count) {
88 (Some(_), Some(_)) => {
89 return Err(BacktestClientError::InvalidParams {
90 message: "CreateSession: set only one of end_slot or slot_count".to_string(),
91 });
92 }
93 (Some(end_slot), None) => end_slot,
94 (None, Some(slot_count)) => {
95 self.start_slot.checked_add(slot_count).ok_or_else(|| {
96 BacktestClientError::InvalidParams {
97 message: "CreateSession: start_slot + slot_count overflow".to_string(),
98 }
99 })?
100 }
101 (None, None) => {
102 return Err(BacktestClientError::InvalidParams {
103 message: "CreateSession: must set end_slot or slot_count".to_string(),
104 });
105 }
106 };
107
108 if end_slot < self.start_slot {
109 return Err(BacktestClientError::InvalidParams {
110 message: format!(
111 "CreateSession: end_slot ({end_slot}) must be >= start_slot ({})",
112 self.start_slot
113 ),
114 });
115 }
116
117 Ok(CreateSessionParams {
118 start_slot: self.start_slot,
119 end_slot,
120 signer_filter: self.signer_filter,
121 preload_programs: self.preload_programs,
122 preload_account_bundles: self.preload_account_bundles,
123 send_summary: self.send_summary,
124 capacity_wait_timeout_secs: self.capacity_wait_timeout_secs,
125 disconnect_timeout_secs: self.disconnect_timeout_secs,
126 extra_compute_units: self.extra_compute_units,
127 agents: self.agents,
128 })
129 }
130
131 pub fn into_request(self) -> Result<CreateBacktestSessionRequest, BacktestClientError> {
133 let parallel = self.parallel;
134 let request = self.into_params()?;
135 if parallel {
136 Ok(CreateBacktestSessionRequestV1 { request, parallel }.into())
137 } else {
138 Ok(request.into())
139 }
140 }
141}
142
143#[derive(Debug, Builder)]
147pub struct Continue {
148 #[builder(default = ContinueParams::default_advance_count())]
149 pub advance_count: u64,
150
151 #[builder(default)]
152 pub transactions: Vec<String>,
153
154 #[builder(default)]
155 pub modify_accounts: BTreeMap<Address, AccountData>,
156}
157
158impl Continue {
159 pub fn push_transaction_base64(mut self, data: impl Into<String>) -> Self {
161 self.transactions.push(data.into());
162 self
163 }
164
165 pub fn push_transaction_bytes(mut self, bytes: &[u8]) -> Self {
167 self.transactions.push(BinaryEncoding::Base64.encode(bytes));
168 self
169 }
170
171 pub fn push_transaction(
173 mut self,
174 transaction: &impl SerializableTransaction,
175 ) -> Result<Self, SerializeEncodeError> {
176 self.transactions.push(serialize_to_base64(&transaction)?);
177 Ok(self)
178 }
179
180 pub fn modify_account(mut self, address: Address, account: AccountData) -> Self {
182 self.modify_accounts.insert(address, account);
183 self
184 }
185
186 pub fn into_params(self) -> ContinueParams {
188 ContinueParams {
189 advance_count: self.advance_count,
190 transactions: self.transactions,
191 modify_account_states: AccountModifications(self.modify_accounts),
192 }
193 }
194}