casper_types/transaction/
transaction_target.rs

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        /// The amount of motes to transfer before code is executed.
41        ///
42        /// This is for protection against phishing attack where a malicious session code drains
43        /// the balance of the caller account. The amount stated here is the maximum amount
44        /// that can be transferred from the caller account to the session account.
45        transferred_value: u64,
46        /// The seed for the session code that is used for an installer.
47        seed: Option<[u8; 32]>,
48    },
49}
50
51impl TransactionRuntimeParams {
52    /// Returns the contract runtime tag.
53    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/// The execution target of a [`crate::Transaction`].
173#[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    /// The execution target is a native operation (e.g. a transfer).
183    Native,
184    /// The execution target is a stored entity or package.
185    Stored {
186        /// The identifier of the stored execution target.
187        id: TransactionInvocationTarget,
188        /// The execution runtime to use.
189        runtime: TransactionRuntimeParams,
190    },
191    /// The execution target is the included module bytes, i.e. compiled Wasm.
192    Session {
193        /// Flag determining if the Wasm is an install/upgrade.
194        is_install_upgrade: bool,
195        /// The compiled Wasm.
196        module_bytes: Bytes,
197        /// The execution runtime to use.
198        runtime: TransactionRuntimeParams,
199    },
200}
201
202impl TransactionTarget {
203    /// Returns a new `TransactionTarget::Native`.
204    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    /// Returns a `hash_addr` for a targeted contract, if known.
236    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    /// Returns the invocation target, if any.
245    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    /// Returns a random `TransactionTarget`.
253    #[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    /// Returns `true` if the transaction target is [`Session`].
276    ///
277    /// [`Session`]: TransactionTarget::Session
278    #[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}