1use alloc::vec::Vec;
2use core::fmt::{self, Debug, Display, Formatter};
3
4use super::{serialization::CalltableSerializationEnvelope, TransactionInvocationTarget};
5#[cfg(any(feature = "testing", test))]
6use crate::testing::TestRng;
7use crate::{
8 bytesrepr::{
9 Bytes,
10 Error::{self, Formatting},
11 FromBytes, ToBytes,
12 },
13 transaction::serialization::CalltableSerializationEnvelopeBuilder,
14 ContractRuntimeTag, HashAddr,
15};
16#[cfg(feature = "datasize")]
17use datasize::DataSize;
18#[cfg(any(feature = "testing", test))]
19use rand::{Rng, RngCore};
20#[cfg(feature = "json-schema")]
21use schemars::JsonSchema;
22use serde::{Deserialize, Serialize};
23
24const VM_CASPER_V1_TAG: u8 = 0;
25const VM_CASPER_V2_TAG: u8 = 1;
26const TRANSFERRED_VALUE_INDEX: u16 = 1;
27const SEED_VALUE_INDEX: u16 = 2;
28
29#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, Debug)]
30#[cfg_attr(feature = "datasize", derive(DataSize))]
31#[cfg_attr(
32 feature = "json-schema",
33 derive(JsonSchema),
34 schemars(description = "Session params of a TransactionTarget.")
35)]
36#[serde(deny_unknown_fields)]
37pub enum TransactionRuntimeParams {
38 VmCasperV1,
39 VmCasperV2 {
40 transferred_value: u64,
46 seed: Option<[u8; 32]>,
48 },
49}
50
51impl TransactionRuntimeParams {
52 pub fn contract_runtime_tag(&self) -> ContractRuntimeTag {
54 match self {
55 TransactionRuntimeParams::VmCasperV1 => ContractRuntimeTag::VmCasperV1,
56 TransactionRuntimeParams::VmCasperV2 { .. } => ContractRuntimeTag::VmCasperV2,
57 }
58 }
59
60 pub fn seed(&self) -> Option<[u8; 32]> {
61 match self {
62 TransactionRuntimeParams::VmCasperV1 => None,
63 TransactionRuntimeParams::VmCasperV2 { seed, .. } => *seed,
64 }
65 }
66
67 pub fn serialized_field_lengths(&self) -> Vec<usize> {
68 match self {
69 TransactionRuntimeParams::VmCasperV1 => vec![crate::bytesrepr::U8_SERIALIZED_LENGTH],
70 TransactionRuntimeParams::VmCasperV2 {
71 transferred_value,
72 seed,
73 } => {
74 vec![
75 crate::bytesrepr::U8_SERIALIZED_LENGTH,
76 transferred_value.serialized_length(),
77 seed.serialized_length(),
78 ]
79 }
80 }
81 }
82}
83
84impl ToBytes for TransactionRuntimeParams {
85 fn to_bytes(&self) -> Result<Vec<u8>, Error> {
86 match self {
87 TransactionRuntimeParams::VmCasperV1 => {
88 CalltableSerializationEnvelopeBuilder::new(self.serialized_field_lengths())?
89 .add_field(TAG_FIELD_INDEX, &VM_CASPER_V1_TAG)?
90 .binary_payload_bytes()
91 }
92 TransactionRuntimeParams::VmCasperV2 {
93 transferred_value,
94 seed,
95 } => CalltableSerializationEnvelopeBuilder::new(self.serialized_field_lengths())?
96 .add_field(TAG_FIELD_INDEX, &VM_CASPER_V2_TAG)?
97 .add_field(TRANSFERRED_VALUE_INDEX, transferred_value)?
98 .add_field(SEED_VALUE_INDEX, seed)?
99 .binary_payload_bytes(),
100 }
101 }
102
103 fn serialized_length(&self) -> usize {
104 match self {
105 TransactionRuntimeParams::VmCasperV1 => {
106 CalltableSerializationEnvelope::estimate_size(vec![
107 crate::bytesrepr::U8_SERIALIZED_LENGTH,
108 ])
109 }
110 TransactionRuntimeParams::VmCasperV2 {
111 transferred_value,
112 seed,
113 } => CalltableSerializationEnvelope::estimate_size(vec![
114 crate::bytesrepr::U8_SERIALIZED_LENGTH,
115 transferred_value.serialized_length(),
116 seed.serialized_length(),
117 ]),
118 }
119 }
120}
121
122impl FromBytes for TransactionRuntimeParams {
123 fn from_bytes(bytes: &[u8]) -> Result<(TransactionRuntimeParams, &[u8]), Error> {
124 let (binary_payload, remainder) = CalltableSerializationEnvelope::from_bytes(3, bytes)?;
125 let window = binary_payload.start_consuming()?.ok_or(Formatting)?;
126 window.verify_index(TAG_FIELD_INDEX)?;
127 let (tag, window) = window.deserialize_and_maybe_next::<u8>()?;
128 let to_ret = match tag {
129 VM_CASPER_V1_TAG => {
130 if window.is_some() {
131 return Err(Formatting);
132 }
133 Ok(TransactionRuntimeParams::VmCasperV1)
134 }
135 VM_CASPER_V2_TAG => {
136 let window = window.ok_or(Formatting)?;
137 window.verify_index(TRANSFERRED_VALUE_INDEX)?;
138 let (transferred_value, window) = window.deserialize_and_maybe_next::<u64>()?;
139 let window = window.ok_or(Formatting)?;
140 window.verify_index(SEED_VALUE_INDEX)?;
141 let (seed, window) = window.deserialize_and_maybe_next::<Option<[u8; 32]>>()?;
142 if window.is_some() {
143 return Err(Formatting);
144 }
145 Ok(TransactionRuntimeParams::VmCasperV2 {
146 transferred_value,
147 seed,
148 })
149 }
150 _ => Err(Formatting),
151 };
152 to_ret.map(|endpoint| (endpoint, remainder))
153 }
154}
155
156impl Display for TransactionRuntimeParams {
157 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
158 match self {
159 TransactionRuntimeParams::VmCasperV1 => write!(formatter, "vm-casper-v1"),
160 TransactionRuntimeParams::VmCasperV2 {
161 transferred_value,
162 seed,
163 } => write!(
164 formatter,
165 "vm-casper-v2 {{ transferred_value: {}, seed: {:?} }}",
166 transferred_value, seed
167 ),
168 }
169 }
170}
171
172#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize)]
174#[cfg_attr(feature = "datasize", derive(DataSize))]
175#[cfg_attr(
176 feature = "json-schema",
177 derive(JsonSchema),
178 schemars(description = "Execution target of a Transaction.")
179)]
180#[serde(deny_unknown_fields)]
181pub enum TransactionTarget {
182 Native,
184 Stored {
186 id: TransactionInvocationTarget,
188 runtime: TransactionRuntimeParams,
190 },
191 Session {
193 is_install_upgrade: bool,
195 module_bytes: Bytes,
197 runtime: TransactionRuntimeParams,
199 },
200}
201
202impl TransactionTarget {
203 pub fn new_native() -> Self {
205 TransactionTarget::Native
206 }
207
208 fn serialized_field_lengths(&self) -> Vec<usize> {
209 match self {
210 TransactionTarget::Native => {
211 vec![crate::bytesrepr::U8_SERIALIZED_LENGTH]
212 }
213 TransactionTarget::Stored { id, runtime } => {
214 vec![
215 crate::bytesrepr::U8_SERIALIZED_LENGTH,
216 id.serialized_length(),
217 runtime.serialized_length(),
218 ]
219 }
220 TransactionTarget::Session {
221 is_install_upgrade,
222 module_bytes,
223 runtime,
224 } => {
225 vec![
226 crate::bytesrepr::U8_SERIALIZED_LENGTH,
227 is_install_upgrade.serialized_length(),
228 runtime.serialized_length(),
229 module_bytes.serialized_length(),
230 ]
231 }
232 }
233 }
234
235 pub fn contract_hash_addr(&self) -> Option<HashAddr> {
237 if let Some(invocation_target) = self.invocation_target() {
238 invocation_target.contract_by_hash()
239 } else {
240 None
241 }
242 }
243
244 pub fn invocation_target(&self) -> Option<TransactionInvocationTarget> {
246 match self {
247 TransactionTarget::Native | TransactionTarget::Session { .. } => None,
248 TransactionTarget::Stored { id, .. } => Some(id.clone()),
249 }
250 }
251
252 #[cfg(any(feature = "testing", test))]
254 pub fn random(rng: &mut TestRng) -> Self {
255 match rng.gen_range(0..3) {
256 0 => TransactionTarget::Native,
257 1 => TransactionTarget::Stored {
258 id: TransactionInvocationTarget::random(rng),
259 runtime: TransactionRuntimeParams::VmCasperV1,
260 },
261 2 => {
262 let mut buffer = vec![0u8; rng.gen_range(0..100)];
263 rng.fill_bytes(buffer.as_mut());
264 let is_install_upgrade = rng.gen();
265 TransactionTarget::Session {
266 is_install_upgrade,
267 module_bytes: Bytes::from(buffer),
268 runtime: TransactionRuntimeParams::VmCasperV1,
269 }
270 }
271 _ => unreachable!(),
272 }
273 }
274
275 #[must_use]
279 pub fn is_session(&self) -> bool {
280 matches!(self, Self::Session { .. })
281 }
282}
283
284const TAG_FIELD_INDEX: u16 = 0;
285
286const NATIVE_VARIANT: u8 = 0;
287
288const STORED_VARIANT: u8 = 1;
289const STORED_ID_INDEX: u16 = 1;
290const STORED_RUNTIME_INDEX: u16 = 2;
291
292const SESSION_VARIANT: u8 = 2;
293const SESSION_IS_INSTALL_INDEX: u16 = 1;
294const SESSION_RUNTIME_INDEX: u16 = 2;
295const SESSION_MODULE_BYTES_INDEX: u16 = 3;
296
297impl ToBytes for TransactionTarget {
298 fn to_bytes(&self) -> Result<Vec<u8>, Error> {
299 match self {
300 TransactionTarget::Native => {
301 CalltableSerializationEnvelopeBuilder::new(self.serialized_field_lengths())?
302 .add_field(TAG_FIELD_INDEX, &NATIVE_VARIANT)?
303 .binary_payload_bytes()
304 }
305 TransactionTarget::Stored { id, runtime } => {
306 CalltableSerializationEnvelopeBuilder::new(self.serialized_field_lengths())?
307 .add_field(TAG_FIELD_INDEX, &STORED_VARIANT)?
308 .add_field(STORED_ID_INDEX, &id)?
309 .add_field(STORED_RUNTIME_INDEX, &runtime)?
310 .binary_payload_bytes()
311 }
312 TransactionTarget::Session {
313 is_install_upgrade,
314 module_bytes,
315 runtime,
316 } => CalltableSerializationEnvelopeBuilder::new(self.serialized_field_lengths())?
317 .add_field(TAG_FIELD_INDEX, &SESSION_VARIANT)?
318 .add_field(SESSION_IS_INSTALL_INDEX, &is_install_upgrade)?
319 .add_field(SESSION_RUNTIME_INDEX, &runtime)?
320 .add_field(SESSION_MODULE_BYTES_INDEX, &module_bytes)?
321 .binary_payload_bytes(),
322 }
323 }
324
325 fn serialized_length(&self) -> usize {
326 CalltableSerializationEnvelope::estimate_size(self.serialized_field_lengths())
327 }
328}
329
330impl FromBytes for TransactionTarget {
331 fn from_bytes(bytes: &[u8]) -> Result<(TransactionTarget, &[u8]), Error> {
332 let (binary_payload, remainder) = CalltableSerializationEnvelope::from_bytes(6, bytes)?;
333 let window = binary_payload.start_consuming()?.ok_or(Formatting)?;
334 window.verify_index(TAG_FIELD_INDEX)?;
335 let (tag, window) = window.deserialize_and_maybe_next::<u8>()?;
336 let to_ret = match tag {
337 NATIVE_VARIANT => {
338 if window.is_some() {
339 return Err(Formatting);
340 }
341 Ok(TransactionTarget::Native)
342 }
343 STORED_VARIANT => {
344 let window = window.ok_or(Formatting)?;
345 window.verify_index(STORED_ID_INDEX)?;
346 let (id, window) =
347 window.deserialize_and_maybe_next::<TransactionInvocationTarget>()?;
348 let window = window.ok_or(Formatting)?;
349 window.verify_index(STORED_RUNTIME_INDEX)?;
350 let (runtime, window) =
351 window.deserialize_and_maybe_next::<TransactionRuntimeParams>()?;
352 if window.is_some() {
353 return Err(Formatting);
354 }
355 Ok(TransactionTarget::Stored { id, runtime })
356 }
357 SESSION_VARIANT => {
358 let window = window.ok_or(Formatting)?;
359 window.verify_index(SESSION_IS_INSTALL_INDEX)?;
360 let (is_install_upgrade, window) = window.deserialize_and_maybe_next::<bool>()?;
361 let window = window.ok_or(Formatting)?;
362 window.verify_index(SESSION_RUNTIME_INDEX)?;
363 let (runtime, window) =
364 window.deserialize_and_maybe_next::<TransactionRuntimeParams>()?;
365 let window = window.ok_or(Formatting)?;
366 window.verify_index(SESSION_MODULE_BYTES_INDEX)?;
367 let (module_bytes, window) = window.deserialize_and_maybe_next::<Bytes>()?;
368
369 if window.is_some() {
370 return Err(Formatting);
371 }
372 Ok(TransactionTarget::Session {
373 is_install_upgrade,
374 module_bytes,
375 runtime,
376 })
377 }
378 _ => Err(Formatting),
379 };
380 to_ret.map(|endpoint| (endpoint, remainder))
381 }
382}
383
384impl Display for TransactionTarget {
385 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
386 match self {
387 TransactionTarget::Native => write!(formatter, "native"),
388 TransactionTarget::Stored { id, runtime } => {
389 write!(formatter, "stored({}, {})", id, runtime,)
390 }
391 TransactionTarget::Session {
392 is_install_upgrade,
393 module_bytes,
394 runtime,
395 } => write!(
396 formatter,
397 "session({} module bytes, runtime: {}, is_install_upgrade: {})",
398 module_bytes.len(),
399 runtime,
400 is_install_upgrade,
401 ),
402 }
403 }
404}
405
406impl Debug for TransactionTarget {
407 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
408 match self {
409 TransactionTarget::Native => formatter.debug_struct("Native").finish(),
410 TransactionTarget::Stored { id, runtime } => formatter
411 .debug_struct("Stored")
412 .field("id", id)
413 .field("runtime", runtime)
414 .finish(),
415 TransactionTarget::Session {
416 is_install_upgrade,
417 module_bytes,
418 runtime,
419 } => {
420 struct BytesLen(usize);
421 impl Debug for BytesLen {
422 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
423 write!(formatter, "{} bytes", self.0)
424 }
425 }
426
427 formatter
428 .debug_struct("Session")
429 .field("module_bytes", &BytesLen(module_bytes.len()))
430 .field("is_install_upgrade", is_install_upgrade)
431 .field("runtime", runtime)
432 .finish()
433 }
434 }
435 }
436}
437
438#[cfg(test)]
439mod tests {
440 use crate::{bytesrepr, gens::transaction_target_arb};
441 use proptest::prelude::*;
442
443 proptest! {
444 #[test]
445 fn generative_bytesrepr_roundtrip(val in transaction_target_arb()) {
446 bytesrepr::test_serialization_roundtrip(&val);
447 }
448 }
449}