wasm_client_solana/methods/
simulate_transaction.rs

1use serde::Deserialize;
2use serde::Deserializer;
3use serde::Serialize;
4use serde::ser::SerializeTuple;
5use solana_transaction::versioned::VersionedTransaction;
6use solana_transaction_error::TransactionError;
7
8use super::Context;
9use crate::deserialize_and_decode;
10use crate::impl_http_method;
11use crate::rpc_config::RpcSimulateTransactionConfig;
12use crate::rpc_config::serialize_and_encode;
13use crate::solana_account_decoder::UiAccount;
14use crate::solana_transaction_status::UiTransactionEncoding;
15use crate::solana_transaction_status::UiTransactionReturnData;
16
17#[derive(Debug, PartialEq, Eq)]
18pub struct SimulateTransactionRequest {
19	pub transaction: VersionedTransaction,
20	pub config: Option<RpcSimulateTransactionConfig>,
21}
22
23impl Serialize for SimulateTransactionRequest {
24	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
25	where
26		S: serde::Serializer,
27	{
28		let encoding = match self.config {
29			Some(ref c) => c.encoding.unwrap_or(UiTransactionEncoding::Base64),
30			None => UiTransactionEncoding::Base64,
31		};
32
33		let serialized_encoded =
34			serialize_and_encode::<VersionedTransaction>(&self.transaction, encoding).unwrap();
35
36		let tuple = if let Some(config) = &self.config {
37			let mut tuple = serializer.serialize_tuple(2)?;
38			tuple.serialize_element(&serialized_encoded)?;
39			tuple.serialize_element(&config)?;
40			tuple
41		} else {
42			let mut tuple = serializer.serialize_tuple(1)?;
43			tuple.serialize_element(&serialized_encoded)?;
44			tuple
45		};
46
47		tuple.end()
48	}
49}
50
51impl<'de> Deserialize<'de> for SimulateTransactionRequest {
52	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
53	where
54		D: Deserializer<'de>,
55	{
56		#[derive(Deserialize)]
57		#[serde(rename = "SimulateTransactionRequest")]
58		struct Inner(String, Option<RpcSimulateTransactionConfig>);
59
60		let inner = Inner::deserialize(deserializer)?;
61		let encoding = match inner.1 {
62			Some(ref config) => config.encoding.unwrap_or(UiTransactionEncoding::Base64),
63			None => UiTransactionEncoding::Base64,
64		};
65
66		let transaction =
67			deserialize_and_decode::<VersionedTransaction>(&inner.0, encoding).unwrap();
68
69		Ok(SimulateTransactionRequest {
70			transaction,
71			config: inner.1,
72		})
73	}
74}
75
76impl_http_method!(SimulateTransactionRequest, "simulateTransaction");
77
78impl SimulateTransactionRequest {
79	pub fn new(transaction: VersionedTransaction) -> Self {
80		Self {
81			transaction,
82			config: Some(RpcSimulateTransactionConfig {
83				encoding: Some(UiTransactionEncoding::Base64),
84				replace_recent_blockhash: Some(true),
85				..Default::default()
86			}),
87		}
88	}
89
90	pub fn new_with_config(
91		transaction: VersionedTransaction,
92		config: RpcSimulateTransactionConfig,
93	) -> Self {
94		Self {
95			transaction,
96			config: Some(config),
97		}
98	}
99}
100
101#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
102#[serde(rename_all = "camelCase")]
103pub struct SimulateTransactionResponseValue {
104	pub err: Option<TransactionError>,
105	pub logs: Option<Vec<String>>,
106	pub accounts: Option<Vec<Option<UiAccount>>>,
107	pub units_consumed: Option<u64>,
108	pub return_data: Option<UiTransactionReturnData>,
109}
110
111#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
112pub struct SimulateTransactionResponse {
113	pub context: Context,
114	pub value: SimulateTransactionResponseValue,
115}
116
117#[cfg(test)]
118mod tests {
119	use assert2::check;
120	use base64::Engine;
121	use base64::prelude::BASE64_STANDARD;
122	use solana_pubkey::pubkey;
123
124	use super::*;
125	use crate::ClientRequest;
126	use crate::ClientResponse;
127	use crate::methods::HttpMethod;
128	use crate::solana_transaction_status::UiReturnDataEncoding;
129
130	#[test]
131	fn request() {
132		let tx = bincode::deserialize(&BASE64_STANDARD.decode("AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDArczbMia1tLmq7zz4DinMNN0pJ1JtLdqIJPUw3YrGCzYAMHBsgN27lcgB6H2WQvFgyZuJYHa46puOQo9yQ8CVQbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCp20C7Wj2aiuk5TReAXo+VTVg8QTHjs0UjNMMKCvpzZ+ABAgEBARU=").unwrap()).unwrap();
133		let request = ClientRequest::builder()
134			.method(SimulateTransactionRequest::NAME)
135			.id(1)
136			.params(SimulateTransactionRequest::new_with_config(
137				tx,
138				RpcSimulateTransactionConfig {
139					encoding: Some(UiTransactionEncoding::Base64),
140					..Default::default()
141				},
142			))
143			.build();
144
145		insta::assert_compact_json_snapshot!(request, @r###"
146  {
147    "jsonrpc": "2.0",
148    "id": 1,
149    "method": "simulateTransaction",
150    "params": [
151      "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDArczbMia1tLmq7zz4DinMNN0pJ1JtLdqIJPUw3YrGCzYAMHBsgN27lcgB6H2WQvFgyZuJYHa46puOQo9yQ8CVQbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCp20C7Wj2aiuk5TReAXo+VTVg8QTHjs0UjNMMKCvpzZ+ABAgEBARU=",
152      {
153        "encoding": "base64",
154        "sigVerify": false
155      }
156    ]
157  }
158  "###);
159	}
160
161	#[test]
162	fn response() {
163		let raw_json = r#"{"jsonrpc":"2.0","result":{"context":{"slot":218},"value":{"err":null,"accounts":null,"logs":["Program 83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri invoke [1]","Program 83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri consumed 2366 of 1400000 compute units","Program return: 83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri KgAAAAAAAAA=","Program 83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri success"],"returnData":{"data":["Kg==","base64"],"programId":"83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri"},"unitsConsumed":2366}},"id":1}"#;
164
165		let response: ClientResponse<SimulateTransactionResponse> =
166			serde_json::from_str(raw_json).unwrap();
167
168		check!(response.id == 1);
169		check!(response.jsonrpc == "2.0");
170		check!(response.result.context.slot == 218);
171		check!(
172			response.result.value
173				== SimulateTransactionResponseValue {
174					accounts: None,
175					err: None,
176					logs: Some(vec![
177						"Program 83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri invoke [1]"
178							.to_string(),
179						"Program 83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri consumed 2366 of \
180						 1400000 compute units"
181							.to_string(),
182						"Program return: 83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri KgAAAAAAAAA="
183							.to_string(),
184						"Program 83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri success".to_string()
185					]),
186					return_data: Some(UiTransactionReturnData {
187						program_id: pubkey!("83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri"),
188						data: ("Kg==".to_string(), UiReturnDataEncoding::Base64)
189					}),
190					units_consumed: Some(2366)
191				}
192		);
193	}
194}