1pub use crate::action::{
2 Action, AddKeyAction, CreateAccountAction, DeleteAccountAction, DeleteKeyAction,
3 DeployContractAction, FunctionCallAction, StakeAction, TransferAction,
4};
5use crate::errors::{InvalidTxError, TxExecutionError};
6use crate::hash::{CryptoHash, hash};
7use crate::merkle::MerklePath;
8use crate::profile_data_v3::ProfileDataV3;
9use crate::types::{AccountId, Balance, Gas, Nonce};
10use borsh::{BorshDeserialize, BorshSerialize};
11use near_crypto::{PublicKey, Signature};
12use near_fmt::{AbbrBytes, Slice};
13use near_parameters::RuntimeConfig;
14use near_primitives_core::serialize::{from_base64, to_base64};
15use near_primitives_core::types::Compute;
16use near_schema_checker_lib::ProtocolSchema;
17#[cfg(feature = "schemars")]
18use schemars::json_schema;
19use serde::de::Error as DecodeError;
20use serde::ser::Error as EncodeError;
21use std::borrow::Borrow;
22use std::fmt;
23use std::hash::{Hash, Hasher};
24use std::io::{Error, ErrorKind, Read, Write};
25
26pub type LogEntry = String;
27
28#[derive(
29 BorshSerialize, BorshDeserialize, serde::Serialize, PartialEq, Eq, Debug, Clone, ProtocolSchema,
30)]
31pub struct TransactionV0 {
32 pub signer_id: AccountId,
34 pub public_key: PublicKey,
37 pub nonce: Nonce,
40 pub receiver_id: AccountId,
42 pub block_hash: CryptoHash,
44 pub actions: Vec<Action>,
46}
47
48#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone, ProtocolSchema)]
49pub struct TransactionV1 {
50 pub signer_id: AccountId,
52 pub public_key: PublicKey,
55 pub nonce: Nonce,
58 pub receiver_id: AccountId,
60 pub block_hash: CryptoHash,
62 pub actions: Vec<Action>,
64 pub priority_fee: u64,
66}
67
68impl Transaction {
69 pub fn get_hash_and_size(&self) -> (CryptoHash, u64) {
71 let bytes = borsh::to_vec(&self).expect("Failed to deserialize");
72 (hash(&bytes), bytes.len() as u64)
73 }
74}
75
76#[derive(Eq, PartialEq, Debug, Clone)]
77pub enum Transaction {
78 V0(TransactionV0),
79 V1(TransactionV1),
80}
81
82impl Transaction {
83 pub fn signer_id(&self) -> &AccountId {
84 match self {
85 Transaction::V0(tx) => &tx.signer_id,
86 Transaction::V1(tx) => &tx.signer_id,
87 }
88 }
89
90 pub fn receiver_id(&self) -> &AccountId {
91 match self {
92 Transaction::V0(tx) => &tx.receiver_id,
93 Transaction::V1(tx) => &tx.receiver_id,
94 }
95 }
96
97 pub fn public_key(&self) -> &PublicKey {
98 match self {
99 Transaction::V0(tx) => &tx.public_key,
100 Transaction::V1(tx) => &tx.public_key,
101 }
102 }
103
104 pub fn nonce(&self) -> Nonce {
105 match self {
106 Transaction::V0(tx) => tx.nonce,
107 Transaction::V1(tx) => tx.nonce,
108 }
109 }
110
111 pub fn actions(&self) -> &[Action] {
112 match self {
113 Transaction::V0(tx) => &tx.actions,
114 Transaction::V1(tx) => &tx.actions,
115 }
116 }
117
118 pub fn take_actions(self) -> Vec<Action> {
119 match self {
120 Transaction::V0(tx) => tx.actions,
121 Transaction::V1(tx) => tx.actions,
122 }
123 }
124
125 pub fn block_hash(&self) -> &CryptoHash {
126 match self {
127 Transaction::V0(tx) => &tx.block_hash,
128 Transaction::V1(tx) => &tx.block_hash,
129 }
130 }
131
132 pub fn priority_fee(&self) -> Option<u64> {
133 match self {
134 Transaction::V0(_) => None,
135 Transaction::V1(tx) => Some(tx.priority_fee),
136 }
137 }
138}
139
140impl BorshSerialize for Transaction {
141 fn serialize<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
142 match self {
143 Transaction::V0(tx) => tx.serialize(writer)?,
144 Transaction::V1(tx) => {
145 BorshSerialize::serialize(&1_u8, writer)?;
146 tx.serialize(writer)?;
147 }
148 }
149 Ok(())
150 }
151}
152
153impl BorshDeserialize for Transaction {
154 fn deserialize_reader<R: Read>(reader: &mut R) -> std::io::Result<Self> {
157 let u1 = u8::deserialize_reader(reader)?;
158 let u2 = u8::deserialize_reader(reader)?;
159 let u3 = u8::deserialize_reader(reader)?;
160 let u4 = u8::deserialize_reader(reader)?;
161 let read_signer_id = |buf: [u8; 4], reader: &mut R| -> std::io::Result<AccountId> {
169 let str_len = u32::from_le_bytes(buf);
170 let mut str_vec = Vec::with_capacity(str_len as usize);
171 for _ in 0..str_len {
172 str_vec.push(u8::deserialize_reader(reader)?);
173 }
174 AccountId::try_from(String::from_utf8(str_vec).map_err(|_| {
175 Error::new(ErrorKind::InvalidData, "Failed to parse AccountId from bytes")
176 })?)
177 .map_err(|e| Error::new(ErrorKind::InvalidData, e.to_string()))
178 };
179
180 if u2 == 0 {
181 let signer_id = read_signer_id([u1, u2, u3, u4], reader)?;
182 let public_key = PublicKey::deserialize_reader(reader)?;
183 let nonce = Nonce::deserialize_reader(reader)?;
184 let receiver_id = AccountId::deserialize_reader(reader)?;
185 let block_hash = CryptoHash::deserialize_reader(reader)?;
186 let actions = Vec::<Action>::deserialize_reader(reader)?;
187 Ok(Transaction::V0(TransactionV0 {
188 signer_id,
189 public_key,
190 nonce,
191 receiver_id,
192 block_hash,
193 actions,
194 }))
195 } else {
196 let u5 = u8::deserialize_reader(reader)?;
197 let signer_id = read_signer_id([u2, u3, u4, u5], reader)?;
198 let public_key = PublicKey::deserialize_reader(reader)?;
199 let nonce = Nonce::deserialize_reader(reader)?;
200 let receiver_id = AccountId::deserialize_reader(reader)?;
201 let block_hash = CryptoHash::deserialize_reader(reader)?;
202 let actions = Vec::<Action>::deserialize_reader(reader)?;
203 let priority_fee = u64::deserialize_reader(reader)?;
204 Ok(Transaction::V1(TransactionV1 {
205 signer_id,
206 public_key,
207 nonce,
208 receiver_id,
209 block_hash,
210 actions,
211 priority_fee,
212 }))
213 }
214 }
215}
216
217#[derive(Clone, Debug, PartialEq, Eq)]
222pub struct ValidatedTransaction(SignedTransaction);
223
224impl ValidatedTransaction {
225 #[allow(clippy::result_large_err)]
226 pub fn new(
227 config: &RuntimeConfig,
228 signed_tx: SignedTransaction,
229 ) -> Result<Self, (InvalidTxError, SignedTransaction)> {
230 if matches!(signed_tx.transaction, Transaction::V1(_)) {
232 return Err((InvalidTxError::InvalidTransactionVersion, signed_tx));
233 }
234 let tx_size = signed_tx.get_size();
235 let max_tx_size = config.wasm_config.limit_config.max_transaction_size;
236 if tx_size > max_tx_size {
237 return Err((
238 InvalidTxError::TransactionSizeExceeded { size: tx_size, limit: max_tx_size },
239 signed_tx,
240 ));
241 }
242
243 if signed_tx
244 .signature
245 .verify(signed_tx.get_hash().as_ref(), signed_tx.transaction.public_key())
246 {
247 Ok(Self(signed_tx))
248 } else {
249 Err((InvalidTxError::InvalidSignature, signed_tx))
250 }
251 }
252
253 pub fn new_for_test(signed_tx: SignedTransaction) -> Self {
255 Self(signed_tx)
256 }
257
258 #[allow(clippy::result_large_err)]
267 pub fn new_list(
268 config: &RuntimeConfig,
269 signed_txs: impl IntoIterator<Item = SignedTransaction>,
270 ) -> Result<Vec<ValidatedTransaction>, (InvalidTxError, SignedTransaction)> {
271 let mut validated_txs = vec![];
272 for signed_tx in signed_txs {
273 validated_txs.push(ValidatedTransaction::new(&config, signed_tx)?);
274 }
275 Ok(validated_txs)
276 }
277
278 pub fn to_signed_tx(&self) -> &SignedTransaction {
279 &self.0
280 }
281
282 pub fn into_signed_tx(self) -> SignedTransaction {
283 self.0
284 }
285
286 pub fn to_tx(&self) -> &Transaction {
287 &self.0.transaction
288 }
289
290 pub fn get_hash(&self) -> CryptoHash {
293 self.0.get_hash()
294 }
295
296 pub fn to_hash(&self) -> ValidatedTransactionHash {
298 ValidatedTransactionHash(self.get_hash())
299 }
300
301 pub fn get_size(&self) -> u64 {
302 self.0.get_size()
303 }
304
305 pub fn signer_id(&self) -> &AccountId {
306 self.to_tx().signer_id()
307 }
308
309 pub fn receiver_id(&self) -> &AccountId {
310 self.to_tx().receiver_id()
311 }
312
313 pub fn nonce(&self) -> Nonce {
314 self.to_tx().nonce()
315 }
316
317 pub fn public_key(&self) -> &PublicKey {
318 self.to_tx().public_key()
319 }
320
321 pub fn actions(&self) -> &[Action] {
322 self.to_tx().actions()
323 }
324}
325
326pub struct ValidatedTransactionHash(CryptoHash);
330
331impl ValidatedTransactionHash {
332 pub fn get_hash(&self) -> CryptoHash {
333 self.0
334 }
335}
336
337#[derive(BorshSerialize, BorshDeserialize, Eq, Debug, Clone, ProtocolSchema)]
338#[borsh(init=init)]
339pub struct SignedTransaction {
340 pub transaction: Transaction,
341 pub signature: Signature,
342 #[borsh(skip)]
343 hash: CryptoHash,
344 #[borsh(skip)]
345 size: u64,
346}
347
348impl SignedTransaction {
349 pub fn new(signature: Signature, transaction: Transaction) -> Self {
350 let mut signed_tx =
351 Self { signature, transaction, hash: CryptoHash::default(), size: u64::default() };
352 signed_tx.init();
353 signed_tx
354 }
355
356 pub fn init(&mut self) {
357 let (hash, size) = self.transaction.get_hash_and_size();
358 self.hash = hash;
359 self.size = size;
360 }
361
362 pub fn get_hash(&self) -> CryptoHash {
363 self.hash
364 }
365
366 pub fn get_size(&self) -> u64 {
367 self.size
368 }
369}
370
371impl Hash for SignedTransaction {
372 fn hash<H: Hasher>(&self, state: &mut H) {
373 self.hash.hash(state)
374 }
375}
376
377impl PartialEq for SignedTransaction {
378 fn eq(&self, other: &SignedTransaction) -> bool {
379 self.hash == other.hash && self.signature == other.signature
380 }
381}
382
383impl Borrow<CryptoHash> for SignedTransaction {
384 fn borrow(&self) -> &CryptoHash {
385 &self.hash
386 }
387}
388
389impl serde::Serialize for SignedTransaction {
390 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
391 where
392 S: serde::Serializer,
393 {
394 let signed_tx_borsh = borsh::to_vec(self).map_err(|err| {
395 S::Error::custom(&format!("the value could not be borsh encoded due to: {}", err))
396 })?;
397 let signed_tx_base64 = to_base64(&signed_tx_borsh);
398 serializer.serialize_str(&signed_tx_base64)
399 }
400}
401
402impl<'de> serde::Deserialize<'de> for SignedTransaction {
403 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
404 where
405 D: serde::Deserializer<'de>,
406 {
407 let signed_tx_base64 = <String as serde::Deserialize>::deserialize(deserializer)?;
408 let signed_tx_borsh = from_base64(&signed_tx_base64).map_err(|err| {
409 D::Error::custom(&format!("the value could not decoded from base64 due to: {}", err))
410 })?;
411 borsh::from_slice::<Self>(&signed_tx_borsh).map_err(|err| {
412 D::Error::custom(&format!("the value could not decoded from borsh due to: {}", err))
413 })
414 }
415}
416
417#[cfg(feature = "schemars")]
418impl schemars::JsonSchema for SignedTransaction {
419 fn schema_name() -> std::borrow::Cow<'static, str> {
420 "SignedTransaction".to_string().into()
421 }
422
423 fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
424 json_schema!({
425 "type": "string",
426 "format": "byte"
427 })
428 }
429}
430
431#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Clone, Default, ProtocolSchema)]
433pub enum ExecutionStatus {
434 #[default]
436 Unknown,
437 Failure(TxExecutionError),
439 SuccessValue(Vec<u8>),
441 SuccessReceiptId(CryptoHash),
444}
445
446impl fmt::Debug for ExecutionStatus {
447 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
448 match self {
449 ExecutionStatus::Unknown => f.write_str("Unknown"),
450 ExecutionStatus::Failure(e) => f.write_fmt(format_args!("Failure({})", e)),
451 ExecutionStatus::SuccessValue(v) => {
452 f.write_fmt(format_args!("SuccessValue({})", AbbrBytes(v)))
453 }
454 ExecutionStatus::SuccessReceiptId(receipt_id) => {
455 f.write_fmt(format_args!("SuccessReceiptId({})", receipt_id))
456 }
457 }
458 }
459}
460
461#[derive(BorshSerialize, BorshDeserialize, PartialEq, Clone)]
463pub struct PartialExecutionOutcome {
464 pub receipt_ids: Vec<CryptoHash>,
465 pub gas_burnt: Gas,
466 pub tokens_burnt: Balance,
467 pub executor_id: AccountId,
468 pub status: PartialExecutionStatus,
469}
470
471impl From<&ExecutionOutcome> for PartialExecutionOutcome {
472 fn from(outcome: &ExecutionOutcome) -> Self {
473 Self {
474 receipt_ids: outcome.receipt_ids.clone(),
475 gas_burnt: outcome.gas_burnt,
476 tokens_burnt: outcome.tokens_burnt,
477 executor_id: outcome.executor_id.clone(),
478 status: outcome.status.clone().into(),
479 }
480 }
481}
482
483#[derive(BorshSerialize, BorshDeserialize, PartialEq, Clone)]
485pub enum PartialExecutionStatus {
486 Unknown,
487 Failure,
488 SuccessValue(Vec<u8>),
489 SuccessReceiptId(CryptoHash),
490}
491
492impl From<ExecutionStatus> for PartialExecutionStatus {
493 fn from(status: ExecutionStatus) -> PartialExecutionStatus {
494 match status {
495 ExecutionStatus::Unknown => PartialExecutionStatus::Unknown,
496 ExecutionStatus::Failure(_) => PartialExecutionStatus::Failure,
497 ExecutionStatus::SuccessValue(value) => PartialExecutionStatus::SuccessValue(value),
498 ExecutionStatus::SuccessReceiptId(id) => PartialExecutionStatus::SuccessReceiptId(id),
499 }
500 }
501}
502
503#[derive(
505 BorshSerialize,
506 BorshDeserialize,
507 PartialEq,
508 Clone,
509 smart_default::SmartDefault,
510 Eq,
511 ProtocolSchema,
512)]
513pub struct ExecutionOutcome {
514 pub logs: Vec<LogEntry>,
516 pub receipt_ids: Vec<CryptoHash>,
518 pub gas_burnt: Gas,
520 #[borsh(skip)]
526 pub compute_usage: Option<Compute>,
527 pub tokens_burnt: Balance,
531 #[default("test".parse().unwrap())]
534 pub executor_id: AccountId,
535 pub status: ExecutionStatus,
539 pub metadata: ExecutionMetadata,
541}
542
543#[derive(
544 BorshSerialize, BorshDeserialize, PartialEq, Clone, Eq, Debug, Default, ProtocolSchema,
545)]
546pub enum ExecutionMetadata {
547 #[default]
549 V1,
550 V2(crate::profile_data_v2::ProfileDataV2),
552 V3(Box<ProfileDataV3>),
554}
555
556impl fmt::Debug for ExecutionOutcome {
557 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
558 f.debug_struct("ExecutionOutcome")
559 .field("logs", &Slice(&self.logs))
560 .field("receipt_ids", &Slice(&self.receipt_ids))
561 .field("burnt_gas", &self.gas_burnt)
562 .field("compute_usage", &self.compute_usage.unwrap_or_default())
563 .field("tokens_burnt", &self.tokens_burnt)
564 .field("status", &self.status)
565 .field("metadata", &self.metadata)
566 .finish()
567 }
568}
569
570#[derive(
574 PartialEq, Clone, Default, Debug, BorshSerialize, BorshDeserialize, Eq, ProtocolSchema,
575)]
576pub struct ExecutionOutcomeWithId {
577 pub id: CryptoHash,
579 pub outcome: ExecutionOutcome,
581}
582
583impl ExecutionOutcomeWithId {
584 pub fn to_hashes(&self) -> Vec<CryptoHash> {
585 let mut result = Vec::with_capacity(2 + self.outcome.logs.len());
586 result.push(self.id);
587 result.push(CryptoHash::hash_borsh(PartialExecutionOutcome::from(&self.outcome)));
588 result.extend(self.outcome.logs.iter().map(|log| hash(log.as_bytes())));
589 result
590 }
591}
592
593#[derive(
595 PartialEq, Clone, Default, Debug, BorshSerialize, BorshDeserialize, Eq, ProtocolSchema,
596)]
597pub struct ExecutionOutcomeWithIdAndProof {
598 pub proof: MerklePath,
599 pub block_hash: CryptoHash,
600 pub outcome_with_id: ExecutionOutcomeWithId,
602}
603
604impl ExecutionOutcomeWithIdAndProof {
605 pub fn id(&self) -> &CryptoHash {
606 &self.outcome_with_id.id
607 }
608}
609
610pub fn verify_transaction_signature(
611 transaction: &SignedTransaction,
612 public_keys: &[PublicKey],
613) -> bool {
614 let hash = transaction.get_hash();
615 let hash = hash.as_ref();
616 public_keys.iter().any(|key| transaction.signature.verify(hash, key))
617}
618
619#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, ProtocolSchema)]
621pub struct ExecutionOutcomeWithProof {
622 pub proof: MerklePath,
623 pub outcome: ExecutionOutcome,
624}
625#[cfg(test)]
626mod tests {
627 use super::*;
628 use crate::account::{AccessKey, AccessKeyPermission, FunctionCallPermission};
629 use borsh::BorshDeserialize;
630 use near_crypto::{InMemorySigner, KeyType, Signature, Signer};
631
632 #[test]
633 fn test_verify_transaction() {
634 let signer: Signer =
635 InMemorySigner::from_random("test".parse().unwrap(), KeyType::ED25519).into();
636 let transaction = Transaction::V0(TransactionV0 {
637 signer_id: "test".parse().unwrap(),
638 public_key: signer.public_key(),
639 nonce: 0,
640 receiver_id: "test".parse().unwrap(),
641 block_hash: Default::default(),
642 actions: vec![],
643 })
644 .sign(&signer);
645 let wrong_public_key = PublicKey::from_seed(KeyType::ED25519, "wrong");
646 let valid_keys = vec![signer.public_key(), wrong_public_key.clone()];
647 assert!(verify_transaction_signature(&transaction, &valid_keys));
648
649 let invalid_keys = vec![wrong_public_key];
650 assert!(!verify_transaction_signature(&transaction, &invalid_keys));
651
652 let bytes = borsh::to_vec(&transaction).unwrap();
653 let decoded_tx = SignedTransaction::try_from_slice(&bytes).unwrap();
654 assert!(verify_transaction_signature(&decoded_tx, &valid_keys));
655 }
656
657 fn create_transaction_v0() -> TransactionV0 {
658 let public_key: PublicKey = "22skMptHjFWNyuEWY22ftn2AbLPSYpmYwGJRGwpNHbTV".parse().unwrap();
659 TransactionV0 {
660 signer_id: "test.near".parse().unwrap(),
661 public_key: public_key.clone(),
662 nonce: 1,
663 receiver_id: "123".parse().unwrap(),
664 block_hash: Default::default(),
665 actions: vec![
666 Action::CreateAccount(CreateAccountAction {}),
667 Action::DeployContract(DeployContractAction { code: vec![1, 2, 3] }),
668 Action::FunctionCall(Box::new(FunctionCallAction {
669 method_name: "qqq".to_string(),
670 args: vec![1, 2, 3],
671 gas: 1_000,
672 deposit: 1_000_000,
673 })),
674 Action::Transfer(TransferAction { deposit: 123 }),
675 Action::Stake(Box::new(StakeAction {
676 public_key: public_key.clone(),
677 stake: 1_000_000,
678 })),
679 Action::AddKey(Box::new(AddKeyAction {
680 public_key: public_key.clone(),
681 access_key: AccessKey {
682 nonce: 0,
683 permission: AccessKeyPermission::FunctionCall(FunctionCallPermission {
684 allowance: None,
685 receiver_id: "zzz".parse().unwrap(),
686 method_names: vec!["www".to_string()],
687 }),
688 },
689 })),
690 Action::DeleteKey(Box::new(DeleteKeyAction { public_key })),
691 Action::DeleteAccount(DeleteAccountAction {
692 beneficiary_id: "123".parse().unwrap(),
693 }),
694 ],
695 }
696 }
697
698 fn create_transaction_v1() -> TransactionV1 {
699 let public_key: PublicKey = "22skMptHjFWNyuEWY22ftn2AbLPSYpmYwGJRGwpNHbTV".parse().unwrap();
700 TransactionV1 {
701 signer_id: "test.near".parse().unwrap(),
702 public_key: public_key.clone(),
703 nonce: 1,
704 receiver_id: "123".parse().unwrap(),
705 block_hash: Default::default(),
706 actions: vec![
707 Action::CreateAccount(CreateAccountAction {}),
708 Action::DeployContract(DeployContractAction { code: vec![1, 2, 3] }),
709 Action::FunctionCall(Box::new(FunctionCallAction {
710 method_name: "qqq".to_string(),
711 args: vec![1, 2, 3],
712 gas: 1_000,
713 deposit: 1_000_000,
714 })),
715 Action::Transfer(TransferAction { deposit: 123 }),
716 Action::Stake(Box::new(StakeAction {
717 public_key: public_key.clone(),
718 stake: 1_000_000,
719 })),
720 Action::AddKey(Box::new(AddKeyAction {
721 public_key: public_key.clone(),
722 access_key: AccessKey {
723 nonce: 0,
724 permission: AccessKeyPermission::FunctionCall(FunctionCallPermission {
725 allowance: None,
726 receiver_id: "zzz".parse().unwrap(),
727 method_names: vec!["www".to_string()],
728 }),
729 },
730 })),
731 Action::DeleteKey(Box::new(DeleteKeyAction { public_key })),
732 Action::DeleteAccount(DeleteAccountAction {
733 beneficiary_id: "123".parse().unwrap(),
734 }),
735 ],
736 priority_fee: 1,
737 }
738 }
739
740 #[test]
743 fn test_serialize_transaction() {
744 let transaction = Transaction::V0(create_transaction_v0());
745 let signed_tx = SignedTransaction::new(Signature::empty(KeyType::ED25519), transaction);
746 let new_signed_tx =
747 SignedTransaction::try_from_slice(&borsh::to_vec(&signed_tx).unwrap()).unwrap();
748
749 assert_eq!(
750 new_signed_tx.get_hash().to_string(),
751 "4GXvjMFN6wSxnU9jEVT8HbXP5Yk6yELX9faRSKp6n9fX"
752 );
753 }
754
755 #[test]
756 fn test_serialize_transaction_versions() {
757 let transaction_v0 = Transaction::V0(create_transaction_v0());
758 let serialized_tx_v0 = borsh::to_vec(&transaction_v0).unwrap();
759 let deserialized_tx_v0 = Transaction::try_from_slice(&serialized_tx_v0).unwrap();
760 assert_eq!(transaction_v0, deserialized_tx_v0);
761
762 let transaction_v1 = Transaction::V1(create_transaction_v1());
763 let serialized_tx_v1 = borsh::to_vec(&transaction_v1).unwrap();
764 let deserialized_tx_v1 = Transaction::try_from_slice(&serialized_tx_v1).unwrap();
765 assert_eq!(transaction_v1, deserialized_tx_v1);
766 }
767
768 #[test]
769 fn test_outcome_to_hashes() {
770 let outcome = ExecutionOutcome {
771 status: ExecutionStatus::SuccessValue(vec![123]),
772 logs: vec!["123".to_string(), "321".to_string()],
773 receipt_ids: vec![],
774 gas_burnt: 123,
775 compute_usage: Some(456),
776 tokens_burnt: 1234000,
777 executor_id: "alice".parse().unwrap(),
778 metadata: ExecutionMetadata::V1,
779 };
780 let id = CryptoHash([42u8; 32]);
781 let outcome = ExecutionOutcomeWithId { id, outcome };
782 assert_eq!(
783 vec![
784 id,
785 "5JQs5ekQqKudMmYejuccbtEu1bzhQPXa92Zm4HdV64dQ".parse().unwrap(),
786 hash("123".as_bytes()),
787 hash("321".as_bytes()),
788 ],
789 outcome.to_hashes()
790 );
791 }
792}