1use std::collections::BTreeMap;
4
5use base64::{Engine as _, engine::general_purpose::STANDARD};
6use serde::Deserialize;
7use serde_with::{base64::Base64, serde_as};
8
9use super::block_reference::TxExecutionStatus;
10use super::error::{ActionError, TxExecutionError};
11use super::{AccountId, CryptoHash, Gas, NearToken, PublicKey, Signature};
12
13pub const STORAGE_AMOUNT_PER_BYTE: u128 = 10_000_000_000_000_000_000; #[derive(Debug, Clone, Deserialize)]
32pub struct AccountView {
33 pub amount: NearToken,
35 pub locked: NearToken,
37 pub code_hash: CryptoHash,
39 pub storage_usage: u64,
41 #[serde(default)]
43 pub storage_paid_at: u64,
44 #[serde(default)]
46 pub global_contract_hash: Option<CryptoHash>,
47 #[serde(default)]
49 pub global_contract_account_id: Option<AccountId>,
50 pub block_height: u64,
52 pub block_hash: CryptoHash,
54}
55
56impl AccountView {
57 fn storage_required(&self) -> NearToken {
59 let yocto = STORAGE_AMOUNT_PER_BYTE.saturating_mul(self.storage_usage as u128);
60 NearToken::from_yoctonear(yocto)
61 }
62
63 pub fn available(&self) -> NearToken {
72 let storage_required = self.storage_required();
73
74 if self.locked >= storage_required {
76 return self.amount;
77 }
78
79 let reserved_for_storage = storage_required.saturating_sub(self.locked);
81 self.amount.saturating_sub(reserved_for_storage)
82 }
83
84 pub fn storage_cost(&self) -> NearToken {
88 let storage_required = self.storage_required();
89
90 if self.locked >= storage_required {
91 NearToken::ZERO
92 } else {
93 storage_required.saturating_sub(self.locked)
94 }
95 }
96
97 pub fn has_contract(&self) -> bool {
99 !self.code_hash.is_zero()
100 }
101}
102
103#[derive(Debug, Clone)]
105pub struct AccountBalance {
106 pub total: NearToken,
108 pub available: NearToken,
110 pub locked: NearToken,
112 pub storage_cost: NearToken,
114 pub storage_usage: u64,
116}
117
118impl std::fmt::Display for AccountBalance {
119 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120 write!(f, "{}", self.available)
121 }
122}
123
124impl From<AccountView> for AccountBalance {
125 fn from(view: AccountView) -> Self {
126 Self {
127 total: view.amount,
128 available: view.available(),
129 locked: view.locked,
130 storage_cost: view.storage_cost(),
131 storage_usage: view.storage_usage,
132 }
133 }
134}
135
136#[derive(Debug, Clone, Deserialize)]
138pub struct AccessKeyView {
139 pub nonce: u64,
141 pub permission: AccessKeyPermissionView,
143 pub block_height: u64,
145 pub block_hash: CryptoHash,
147}
148
149#[derive(Debug, Clone, Deserialize)]
151pub struct AccessKeyDetails {
152 pub nonce: u64,
154 pub permission: AccessKeyPermissionView,
156}
157
158#[derive(Debug, Clone, Deserialize)]
160#[serde(rename_all = "PascalCase")]
161pub enum AccessKeyPermissionView {
162 FullAccess,
164 FunctionCall {
166 allowance: Option<NearToken>,
168 receiver_id: AccountId,
170 method_names: Vec<String>,
172 },
173 GasKeyFunctionCall {
175 balance: NearToken,
177 num_nonces: u16,
179 allowance: Option<NearToken>,
181 receiver_id: AccountId,
183 method_names: Vec<String>,
185 },
186 GasKeyFullAccess {
188 balance: NearToken,
190 num_nonces: u16,
192 },
193}
194
195#[derive(Debug, Clone, Deserialize)]
197pub struct AccessKeyListView {
198 pub keys: Vec<AccessKeyInfoView>,
200 pub block_height: u64,
202 pub block_hash: CryptoHash,
204}
205
206#[derive(Debug, Clone, Deserialize)]
208pub struct AccessKeyInfoView {
209 pub public_key: PublicKey,
211 pub access_key: AccessKeyDetails,
213}
214
215#[derive(Debug, Clone, Deserialize)]
221pub struct BlockView {
222 pub author: AccountId,
224 pub header: BlockHeaderView,
226 pub chunks: Vec<ChunkHeaderView>,
228}
229
230#[derive(Debug, Clone, Deserialize)]
232pub struct BlockHeaderView {
233 pub height: u64,
235 #[serde(default)]
237 pub prev_height: Option<u64>,
238 pub hash: CryptoHash,
240 pub prev_hash: CryptoHash,
242 pub prev_state_root: CryptoHash,
244 pub chunk_receipts_root: CryptoHash,
246 pub chunk_headers_root: CryptoHash,
248 pub chunk_tx_root: CryptoHash,
250 pub outcome_root: CryptoHash,
252 pub chunks_included: u64,
254 pub challenges_root: CryptoHash,
256 pub timestamp: u64,
258 pub timestamp_nanosec: String,
260 pub random_value: CryptoHash,
262 #[serde(default)]
264 pub validator_proposals: Vec<ValidatorStakeView>,
265 #[serde(default)]
267 pub chunk_mask: Vec<bool>,
268 pub gas_price: NearToken,
270 #[serde(default)]
272 pub block_ordinal: Option<u64>,
273 pub total_supply: NearToken,
275 #[serde(default)]
277 pub challenges_result: Vec<SlashedValidator>,
278 pub last_final_block: CryptoHash,
280 pub last_ds_final_block: CryptoHash,
282 pub epoch_id: CryptoHash,
284 pub next_epoch_id: CryptoHash,
286 pub next_bp_hash: CryptoHash,
288 pub block_merkle_root: CryptoHash,
290 #[serde(default)]
292 pub epoch_sync_data_hash: Option<CryptoHash>,
293 #[serde(default)]
295 pub block_body_hash: Option<CryptoHash>,
296 #[serde(default)]
298 pub approvals: Vec<Option<Signature>>,
299 pub signature: Signature,
301 pub latest_protocol_version: u32,
303 #[serde(default)]
305 pub rent_paid: Option<NearToken>,
306 #[serde(default)]
308 pub validator_reward: Option<NearToken>,
309 #[serde(default)]
311 pub chunk_endorsements: Option<Vec<Vec<u8>>>,
312 #[serde(default)]
314 pub shard_split: Option<(u64, AccountId)>,
315}
316
317#[derive(Debug, Clone, Deserialize)]
321#[serde(untagged)]
322pub enum ValidatorStakeView {
323 V1(ValidatorStakeViewV1),
325}
326
327#[derive(Debug, Clone, Deserialize)]
329pub struct ValidatorStakeViewV1 {
330 pub account_id: AccountId,
332 pub public_key: PublicKey,
334 pub stake: NearToken,
336}
337
338impl ValidatorStakeView {
339 pub fn into_v1(self) -> ValidatorStakeViewV1 {
341 match self {
342 Self::V1(v) => v,
343 }
344 }
345
346 pub fn account_id(&self) -> &AccountId {
348 match self {
349 Self::V1(v) => &v.account_id,
350 }
351 }
352
353 pub fn stake(&self) -> NearToken {
355 match self {
356 Self::V1(v) => v.stake,
357 }
358 }
359}
360
361#[derive(Debug, Clone, Deserialize)]
363pub struct SlashedValidator {
364 pub account_id: AccountId,
366 pub is_double_sign: bool,
368}
369
370#[derive(Debug, Clone, Deserialize)]
372pub struct ChunkHeaderView {
373 pub chunk_hash: CryptoHash,
375 pub prev_block_hash: CryptoHash,
377 pub outcome_root: CryptoHash,
379 pub prev_state_root: CryptoHash,
381 pub encoded_merkle_root: CryptoHash,
383 pub encoded_length: u64,
385 pub height_created: u64,
387 pub height_included: u64,
389 pub shard_id: u64,
391 pub gas_used: u64,
393 pub gas_limit: u64,
395 pub validator_reward: NearToken,
397 pub balance_burnt: NearToken,
399 pub outgoing_receipts_root: CryptoHash,
401 pub tx_root: CryptoHash,
403 #[serde(default)]
405 pub validator_proposals: Vec<ValidatorStakeView>,
406 #[serde(default)]
408 pub congestion_info: Option<CongestionInfoView>,
409 #[serde(default)]
411 pub bandwidth_requests: Option<BandwidthRequests>,
412 #[serde(default)]
414 pub rent_paid: Option<NearToken>,
415 #[serde(default)]
421 pub proposed_split: Option<Option<TrieSplit>>,
422 pub signature: Signature,
424}
425
426#[derive(Debug, Clone, Deserialize)]
428pub enum BandwidthRequests {
429 V1(BandwidthRequestsV1),
431}
432
433#[derive(Debug, Clone, Deserialize)]
435pub struct BandwidthRequestsV1 {
436 pub requests: Vec<BandwidthRequest>,
438}
439
440#[derive(Debug, Clone, Deserialize)]
442pub struct BandwidthRequest {
443 pub to_shard: u16,
445 pub requested_values_bitmap: BandwidthRequestBitmap,
447}
448
449#[derive(Debug, Clone, Deserialize)]
451pub struct BandwidthRequestBitmap {
452 pub data: [u8; 5],
454}
455
456#[derive(Debug, Clone, Deserialize)]
458pub struct TrieSplit {
459 pub boundary_account: AccountId,
461 pub left_memory: u64,
463 pub right_memory: u64,
465}
466
467#[derive(Debug, Clone, Deserialize)]
469pub struct CongestionInfoView {
470 #[serde(default, deserialize_with = "dec_format")]
472 pub delayed_receipts_gas: u128,
473 #[serde(default, deserialize_with = "dec_format")]
475 pub buffered_receipts_gas: u128,
476 #[serde(default)]
478 pub receipt_bytes: u64,
479 #[serde(default)]
481 pub allowed_shard: u16,
482}
483
484fn dec_format<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<u128, D::Error> {
486 #[derive(Deserialize)]
487 #[serde(untagged)]
488 enum StringOrNum {
489 String(String),
490 Num(u128),
491 }
492 match StringOrNum::deserialize(deserializer)? {
493 StringOrNum::String(s) => s.parse().map_err(serde::de::Error::custom),
494 StringOrNum::Num(n) => Ok(n),
495 }
496}
497
498fn gas_dec_format<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Gas, D::Error> {
500 #[derive(Deserialize)]
501 #[serde(untagged)]
502 enum StringOrNum {
503 String(String),
504 Num(u64),
505 }
506 let raw = match StringOrNum::deserialize(deserializer)? {
507 StringOrNum::String(s) => s.parse::<u64>().map_err(serde::de::Error::custom)?,
508 StringOrNum::Num(n) => n,
509 };
510 Ok(Gas::from_gas(raw))
511}
512
513#[derive(Debug, Clone, Deserialize)]
515pub struct GasPrice {
516 pub gas_price: NearToken,
518}
519
520impl GasPrice {
521 pub fn as_u128(&self) -> u128 {
523 self.gas_price.as_yoctonear()
524 }
525}
526
527#[derive(Debug, Clone, Default, Deserialize)]
536pub enum FinalExecutionStatus {
537 #[default]
539 NotStarted,
540 Started,
542 Failure(TxExecutionError),
544 SuccessValue(String),
546}
547
548#[derive(Debug, Clone)]
575pub struct SendTxResponse {
576 pub transaction_hash: CryptoHash,
578 pub sender_id: AccountId,
580}
581
582#[doc(hidden)]
588#[derive(Debug, Clone, Deserialize)]
589pub struct RawTransactionResponse {
590 #[serde(skip)]
593 pub transaction_hash: CryptoHash,
594 pub final_execution_status: TxExecutionStatus,
596 #[serde(flatten)]
598 pub outcome: Option<FinalExecutionOutcome>,
599}
600
601#[derive(Debug, Clone, Deserialize)]
611pub struct FinalExecutionOutcome {
612 pub status: FinalExecutionStatus,
614 pub transaction: TransactionView,
616 pub transaction_outcome: ExecutionOutcomeWithId,
618 pub receipts_outcome: Vec<ExecutionOutcomeWithId>,
620 #[serde(default)]
622 pub receipts: Vec<Receipt>,
623}
624
625impl FinalExecutionOutcome {
626 pub fn is_success(&self) -> bool {
628 matches!(&self.status, FinalExecutionStatus::SuccessValue(_))
629 }
630
631 pub fn is_failure(&self) -> bool {
633 matches!(&self.status, FinalExecutionStatus::Failure(_))
634 }
635
636 pub fn failure_message(&self) -> Option<String> {
638 match &self.status {
639 FinalExecutionStatus::Failure(err) => Some(err.to_string()),
640 _ => None,
641 }
642 }
643
644 pub fn failure_error(&self) -> Option<&TxExecutionError> {
646 match &self.status {
647 FinalExecutionStatus::Failure(err) => Some(err),
648 _ => None,
649 }
650 }
651
652 pub fn transaction_hash(&self) -> &CryptoHash {
654 &self.transaction_outcome.id
655 }
656
657 pub fn total_gas_used(&self) -> Gas {
659 let tx_gas = self.transaction_outcome.outcome.gas_burnt.as_gas();
660 let receipt_gas: u64 = self
661 .receipts_outcome
662 .iter()
663 .map(|r| r.outcome.gas_burnt.as_gas())
664 .sum();
665 Gas::from_gas(tx_gas + receipt_gas)
666 }
667
668 pub fn result(&self) -> Result<Vec<u8>, crate::error::Error> {
681 match &self.status {
682 FinalExecutionStatus::Failure(TxExecutionError::InvalidTxError(e)) => {
683 Err(crate::error::Error::InvalidTx(Box::new(e.clone())))
684 }
685 FinalExecutionStatus::Failure(TxExecutionError::ActionError(e)) => Err(
686 crate::error::Error::InvalidTransaction(format!("Action error: {e}")),
687 ),
688 FinalExecutionStatus::SuccessValue(s) => STANDARD.decode(s).map_err(|e| {
689 crate::error::Error::InvalidTransaction(format!(
690 "Failed to decode base64 SuccessValue: {e}"
691 ))
692 }),
693 other => Err(crate::error::Error::InvalidTransaction(format!(
694 "Transaction status is {:?}, expected SuccessValue",
695 other,
696 ))),
697 }
698 }
699
700 pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T, crate::error::Error> {
705 let bytes = self.result()?;
706 serde_json::from_slice(&bytes).map_err(crate::error::Error::from)
707 }
708}
709
710#[derive(Debug, Clone)]
722pub enum ExecutionStatus {
723 Unknown,
725 Failure(ActionError),
727 SuccessValue(Vec<u8>),
729 SuccessReceiptId(CryptoHash),
731}
732
733impl<'de> serde::Deserialize<'de> for ExecutionStatus {
734 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
735 #[serde_as]
737 #[derive(Deserialize)]
738 enum Raw {
739 Unknown,
740 Failure(TxExecutionError),
741 SuccessValue(#[serde_as(as = "Base64")] Vec<u8>),
742 SuccessReceiptId(CryptoHash),
743 }
744
745 match Raw::deserialize(deserializer)? {
746 Raw::Unknown => Ok(Self::Unknown),
747 Raw::Failure(TxExecutionError::ActionError(e)) => Ok(Self::Failure(e)),
748 Raw::Failure(TxExecutionError::InvalidTxError(e)) => Err(serde::de::Error::custom(
749 format!("unexpected InvalidTxError in receipt execution status: {e}"),
750 )),
751 Raw::SuccessValue(v) => Ok(Self::SuccessValue(v)),
752 Raw::SuccessReceiptId(h) => Ok(Self::SuccessReceiptId(h)),
753 }
754 }
755}
756
757#[derive(Debug, Clone, Deserialize)]
759pub struct TransactionView {
760 pub signer_id: AccountId,
762 pub public_key: PublicKey,
764 pub nonce: u64,
766 pub receiver_id: AccountId,
768 pub hash: CryptoHash,
770 #[serde(default)]
772 pub actions: Vec<ActionView>,
773 pub signature: Signature,
775 #[serde(default)]
777 pub priority_fee: Option<u64>,
778 #[serde(default)]
780 pub nonce_index: Option<u16>,
781}
782
783#[derive(Deserialize)]
792#[serde(untagged)]
793enum GlobalContractIdCompat {
794 CodeHash { hash: CryptoHash },
795 AccountId { account_id: AccountId },
796 DeprecatedCodeHash(CryptoHash),
797 DeprecatedAccountId(AccountId),
798}
799
800#[derive(Debug, Clone, Deserialize)]
806#[serde(from = "GlobalContractIdCompat")]
807pub enum GlobalContractIdentifierView {
808 CodeHash(CryptoHash),
810 AccountId(AccountId),
812}
813
814impl From<GlobalContractIdCompat> for GlobalContractIdentifierView {
815 fn from(compat: GlobalContractIdCompat) -> Self {
816 match compat {
817 GlobalContractIdCompat::CodeHash { hash }
818 | GlobalContractIdCompat::DeprecatedCodeHash(hash) => Self::CodeHash(hash),
819 GlobalContractIdCompat::AccountId { account_id }
820 | GlobalContractIdCompat::DeprecatedAccountId(account_id) => {
821 Self::AccountId(account_id)
822 }
823 }
824 }
825}
826
827impl std::fmt::Display for GlobalContractIdentifierView {
828 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
829 match self {
830 Self::CodeHash(hash) => write!(f, "hash({hash})"),
831 Self::AccountId(account_id) => write!(f, "{account_id}"),
832 }
833 }
834}
835
836#[derive(Debug, Clone, Deserialize)]
842pub struct DelegateActionView {
843 pub sender_id: AccountId,
845 pub receiver_id: AccountId,
847 pub actions: Vec<ActionView>,
849 pub nonce: u64,
851 pub max_block_height: u64,
853 pub public_key: PublicKey,
855}
856
857#[derive(Debug, Clone, Deserialize)]
859#[serde(rename_all = "PascalCase")]
860pub enum ActionView {
861 CreateAccount,
862 DeployContract {
863 code: String, },
865 FunctionCall {
866 method_name: String,
867 args: String, gas: Gas,
869 deposit: NearToken,
870 },
871 Transfer {
872 deposit: NearToken,
873 },
874 Stake {
875 stake: NearToken,
876 public_key: PublicKey,
877 },
878 AddKey {
879 public_key: PublicKey,
880 access_key: AccessKeyDetails,
881 },
882 DeleteKey {
883 public_key: PublicKey,
884 },
885 DeleteAccount {
886 beneficiary_id: AccountId,
887 },
888 Delegate {
889 delegate_action: DelegateActionView,
890 signature: Signature,
891 },
892 #[serde(rename = "DeployGlobalContract")]
893 DeployGlobalContract {
894 code: String,
895 },
896 #[serde(rename = "DeployGlobalContractByAccountId")]
897 DeployGlobalContractByAccountId {
898 code: String,
899 },
900 #[serde(rename = "UseGlobalContract")]
901 UseGlobalContract {
902 code_hash: CryptoHash,
903 },
904 #[serde(rename = "UseGlobalContractByAccountId")]
905 UseGlobalContractByAccountId {
906 account_id: AccountId,
907 },
908 #[serde(rename = "DeterministicStateInit")]
909 DeterministicStateInit {
910 code: GlobalContractIdentifierView,
911 #[serde(default)]
912 data: BTreeMap<String, String>,
913 deposit: NearToken,
914 },
915 TransferToGasKey {
916 public_key: PublicKey,
917 deposit: NearToken,
918 },
919 WithdrawFromGasKey {
920 public_key: PublicKey,
921 amount: NearToken,
922 },
923}
924
925#[derive(Debug, Clone, Deserialize)]
927pub struct MerklePathItem {
928 pub hash: CryptoHash,
930 pub direction: MerkleDirection,
932}
933
934#[derive(Debug, Clone, Deserialize)]
936pub enum MerkleDirection {
937 Left,
938 Right,
939}
940
941#[derive(Debug, Clone, Deserialize)]
943pub struct ExecutionOutcomeWithId {
944 pub id: CryptoHash,
946 pub outcome: ExecutionOutcome,
948 #[serde(default)]
950 pub proof: Vec<MerklePathItem>,
951 pub block_hash: CryptoHash,
953}
954
955#[derive(Debug, Clone, Deserialize)]
957pub struct ExecutionOutcome {
958 pub executor_id: AccountId,
960 pub gas_burnt: Gas,
962 pub tokens_burnt: NearToken,
964 pub logs: Vec<String>,
966 pub receipt_ids: Vec<CryptoHash>,
968 pub status: ExecutionStatus,
970 #[serde(default)]
972 pub metadata: Option<ExecutionMetadata>,
973}
974
975#[derive(Debug, Clone, Deserialize)]
977pub struct ExecutionMetadata {
978 pub version: u32,
980 #[serde(default)]
982 pub gas_profile: Option<Vec<GasProfileEntry>>,
983}
984
985#[derive(Debug, Clone, Deserialize)]
987pub struct GasProfileEntry {
988 pub cost_category: String,
990 pub cost: String,
992 #[serde(deserialize_with = "gas_dec_format")]
994 pub gas_used: Gas,
995}
996
997#[derive(Debug, Clone, Deserialize)]
999pub struct ViewFunctionResult {
1000 pub result: Vec<u8>,
1002 pub logs: Vec<String>,
1004 pub block_height: u64,
1006 pub block_hash: CryptoHash,
1008}
1009
1010impl ViewFunctionResult {
1011 pub fn bytes(&self) -> &[u8] {
1013 &self.result
1014 }
1015
1016 pub fn as_string(&self) -> Result<String, std::string::FromUtf8Error> {
1018 String::from_utf8(self.result.clone())
1019 }
1020
1021 pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T, serde_json::Error> {
1030 serde_json::from_slice(&self.result)
1031 }
1032
1033 pub fn borsh<T: borsh::BorshDeserialize>(&self) -> Result<T, borsh::io::Error> {
1044 borsh::from_slice(&self.result)
1045 }
1046}
1047
1048#[derive(Debug, Clone, Deserialize)]
1054pub struct Receipt {
1055 pub predecessor_id: AccountId,
1057 pub receiver_id: AccountId,
1059 pub receipt_id: CryptoHash,
1061 pub receipt: ReceiptContent,
1063 #[serde(default)]
1065 pub priority: Option<u64>,
1066}
1067
1068#[derive(Debug, Clone, Deserialize)]
1070pub enum ReceiptContent {
1071 Action(ActionReceiptData),
1073 Data(DataReceiptData),
1075 GlobalContractDistribution {
1077 id: GlobalContractIdentifierView,
1079 target_shard: u64,
1081 #[serde(default)]
1083 already_delivered_shards: Vec<u64>,
1084 code: String,
1086 #[serde(default)]
1088 nonce: Option<u64>,
1089 },
1090}
1091
1092#[derive(Debug, Clone, Deserialize)]
1094pub struct DataReceiverView {
1095 pub data_id: CryptoHash,
1097 pub receiver_id: AccountId,
1099}
1100
1101#[derive(Debug, Clone, Deserialize)]
1103pub struct ActionReceiptData {
1104 pub signer_id: AccountId,
1106 pub signer_public_key: PublicKey,
1108 pub gas_price: NearToken,
1110 #[serde(default)]
1112 pub output_data_receivers: Vec<DataReceiverView>,
1113 #[serde(default)]
1115 pub input_data_ids: Vec<CryptoHash>,
1116 pub actions: Vec<ActionView>,
1118 #[serde(default)]
1120 pub is_promise_yield: Option<bool>,
1121}
1122
1123#[derive(Debug, Clone, Deserialize)]
1125pub struct DataReceiptData {
1126 pub data_id: CryptoHash,
1128 #[serde(default)]
1130 pub data: Option<String>,
1131}
1132
1133#[derive(Debug, Clone, Deserialize)]
1139pub struct StatusResponse {
1140 pub protocol_version: u32,
1142 pub latest_protocol_version: u32,
1144 pub chain_id: String,
1146 pub genesis_hash: CryptoHash,
1148 #[serde(default)]
1150 pub rpc_addr: Option<String>,
1151 #[serde(default)]
1153 pub node_public_key: Option<String>,
1154 #[serde(default)]
1156 pub node_key: Option<String>,
1157 #[serde(default)]
1159 pub validator_account_id: Option<AccountId>,
1160 #[serde(default)]
1162 pub validator_public_key: Option<PublicKey>,
1163 #[serde(default)]
1165 pub validators: Vec<ValidatorInfo>,
1166 pub sync_info: SyncInfo,
1168 pub version: NodeVersion,
1170 #[serde(default)]
1172 pub uptime_sec: Option<u64>,
1173}
1174
1175#[derive(Debug, Clone, Deserialize)]
1177pub struct ValidatorInfo {
1178 pub account_id: AccountId,
1180}
1181
1182#[derive(Debug, Clone, Deserialize)]
1184pub struct SyncInfo {
1185 pub latest_block_hash: CryptoHash,
1187 pub latest_block_height: u64,
1189 #[serde(default)]
1191 pub latest_state_root: Option<CryptoHash>,
1192 pub latest_block_time: String,
1194 pub syncing: bool,
1196 #[serde(default)]
1198 pub earliest_block_hash: Option<CryptoHash>,
1199 #[serde(default)]
1201 pub earliest_block_height: Option<u64>,
1202 #[serde(default)]
1204 pub earliest_block_time: Option<String>,
1205 #[serde(default)]
1207 pub epoch_id: Option<CryptoHash>,
1208 #[serde(default)]
1210 pub epoch_start_height: Option<u64>,
1211}
1212
1213#[derive(Debug, Clone, Deserialize)]
1215pub struct NodeVersion {
1216 pub version: String,
1218 pub build: String,
1220 #[serde(default)]
1222 pub commit: Option<String>,
1223 #[serde(default)]
1225 pub rustc_version: Option<String>,
1226}
1227
1228#[cfg(test)]
1229mod tests {
1230 use super::*;
1231
1232 fn make_account_view(amount: u128, locked: u128, storage_usage: u64) -> AccountView {
1233 AccountView {
1234 amount: NearToken::from_yoctonear(amount),
1235 locked: NearToken::from_yoctonear(locked),
1236 code_hash: CryptoHash::default(),
1237 storage_usage,
1238 storage_paid_at: 0,
1239 global_contract_hash: None,
1240 global_contract_account_id: None,
1241 block_height: 0,
1242 block_hash: CryptoHash::default(),
1243 }
1244 }
1245
1246 #[test]
1247 fn test_available_balance_no_stake_no_storage() {
1248 let view = make_account_view(1_000_000_000_000_000_000_000_000, 0, 0); assert_eq!(view.available(), view.amount);
1251 }
1252
1253 #[test]
1254 fn test_available_balance_with_storage_no_stake() {
1255 let amount = 1_000_000_000_000_000_000_000_000u128; let storage_usage = 1000u64;
1260 let storage_cost = STORAGE_AMOUNT_PER_BYTE * storage_usage as u128; let view = make_account_view(amount, 0, storage_usage);
1263 let expected = NearToken::from_yoctonear(amount - storage_cost);
1264 assert_eq!(view.available(), expected);
1265 }
1266
1267 #[test]
1268 fn test_available_balance_stake_covers_storage() {
1269 let amount = 1_000_000_000_000_000_000_000_000u128; let locked = 1_000_000_000_000_000_000_000_000u128; let storage_usage = 1000u64;
1275
1276 let view = make_account_view(amount, locked, storage_usage);
1277 assert_eq!(view.available(), view.amount);
1279 }
1280
1281 #[test]
1282 fn test_available_balance_stake_partially_covers_storage() {
1283 let amount = 1_000_000_000_000_000_000_000_000u128; let locked = 5_000_000_000_000_000_000_000u128; let storage_usage = 1000u64;
1291 let storage_cost = STORAGE_AMOUNT_PER_BYTE * storage_usage as u128; let reserved = storage_cost - locked; let view = make_account_view(amount, locked, storage_usage);
1295 let expected = NearToken::from_yoctonear(amount - reserved);
1296 assert_eq!(view.available(), expected);
1297 }
1298
1299 #[test]
1300 fn test_storage_cost_calculation() {
1301 let storage_usage = 1000u64;
1302 let view = make_account_view(1_000_000_000_000_000_000_000_000, 0, storage_usage);
1303
1304 let expected_cost = STORAGE_AMOUNT_PER_BYTE * storage_usage as u128;
1305 assert_eq!(
1306 view.storage_cost(),
1307 NearToken::from_yoctonear(expected_cost)
1308 );
1309 }
1310
1311 #[test]
1312 fn test_storage_cost_zero_when_stake_covers() {
1313 let locked = 1_000_000_000_000_000_000_000_000u128; let view = make_account_view(1_000_000_000_000_000_000_000_000, locked, 1000);
1316
1317 assert_eq!(view.storage_cost(), NearToken::ZERO);
1318 }
1319
1320 #[test]
1321 fn test_account_balance_from_view() {
1322 let amount = 1_000_000_000_000_000_000_000_000u128; let locked = 500_000_000_000_000_000_000_000u128; let storage_usage = 1000u64;
1325
1326 let view = make_account_view(amount, locked, storage_usage);
1327 let balance = AccountBalance::from(view.clone());
1328
1329 assert_eq!(balance.total, view.amount);
1330 assert_eq!(balance.available, view.available());
1331 assert_eq!(balance.locked, view.locked);
1332 assert_eq!(balance.storage_cost, view.storage_cost());
1333 assert_eq!(balance.storage_usage, storage_usage);
1334 }
1335
1336 fn make_view_result(result: Vec<u8>) -> ViewFunctionResult {
1341 ViewFunctionResult {
1342 result,
1343 logs: vec![],
1344 block_height: 12345,
1345 block_hash: CryptoHash::default(),
1346 }
1347 }
1348
1349 #[test]
1350 fn test_view_function_result_bytes() {
1351 let data = vec![1, 2, 3, 4, 5];
1352 let result = make_view_result(data.clone());
1353 assert_eq!(result.bytes(), &data[..]);
1354 }
1355
1356 #[test]
1357 fn test_view_function_result_as_string() {
1358 let result = make_view_result(b"hello world".to_vec());
1359 assert_eq!(result.as_string().unwrap(), "hello world");
1360 }
1361
1362 #[test]
1363 fn test_view_function_result_json() {
1364 let result = make_view_result(b"42".to_vec());
1365 let value: u64 = result.json().unwrap();
1366 assert_eq!(value, 42);
1367 }
1368
1369 #[test]
1370 fn test_view_function_result_json_object() {
1371 let result = make_view_result(b"{\"count\":123}".to_vec());
1372 let value: serde_json::Value = result.json().unwrap();
1373 assert_eq!(value["count"], 123);
1374 }
1375
1376 #[test]
1377 fn test_view_function_result_borsh() {
1378 let original: u64 = 42;
1380 let encoded = borsh::to_vec(&original).unwrap();
1381 let result = make_view_result(encoded);
1382
1383 let decoded: u64 = result.borsh().unwrap();
1384 assert_eq!(decoded, original);
1385 }
1386
1387 #[test]
1388 fn test_view_function_result_borsh_struct() {
1389 #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, PartialEq, Debug)]
1390 struct TestStruct {
1391 value: u64,
1392 name: String,
1393 }
1394
1395 let original = TestStruct {
1396 value: 123,
1397 name: "test".to_string(),
1398 };
1399 let encoded = borsh::to_vec(&original).unwrap();
1400 let result = make_view_result(encoded);
1401
1402 let decoded: TestStruct = result.borsh().unwrap();
1403 assert_eq!(decoded, original);
1404 }
1405
1406 #[test]
1407 fn test_view_function_result_borsh_error() {
1408 let result = make_view_result(vec![1, 2, 3]);
1410 let decoded: Result<u64, _> = result.borsh();
1411 assert!(decoded.is_err());
1412 }
1413
1414 #[test]
1419 fn test_gas_profile_entry_string_gas_used() {
1420 let json = serde_json::json!({
1421 "cost_category": "WASM_HOST_COST",
1422 "cost": "BASE",
1423 "gas_used": "123456789"
1424 });
1425 let entry: GasProfileEntry = serde_json::from_value(json).unwrap();
1426 assert_eq!(entry.gas_used.as_gas(), 123456789);
1427 }
1428
1429 #[test]
1430 fn test_gas_profile_entry_numeric_gas_used() {
1431 let json = serde_json::json!({
1432 "cost_category": "ACTION_COST",
1433 "cost": "FUNCTION_CALL",
1434 "gas_used": 999000000
1435 });
1436 let entry: GasProfileEntry = serde_json::from_value(json).unwrap();
1437 assert_eq!(entry.gas_used.as_gas(), 999000000);
1438 }
1439
1440 #[test]
1441 fn test_gas_key_function_call_deserialization() {
1442 let json = serde_json::json!({
1443 "GasKeyFunctionCall": {
1444 "balance": "1000000000000000000000000",
1445 "num_nonces": 5,
1446 "allowance": "500000000000000000000000",
1447 "receiver_id": "app.near",
1448 "method_names": ["call_method"]
1449 }
1450 });
1451 let perm: AccessKeyPermissionView = serde_json::from_value(json).unwrap();
1452 assert!(matches!(
1453 perm,
1454 AccessKeyPermissionView::GasKeyFunctionCall { .. }
1455 ));
1456 }
1457
1458 #[test]
1459 fn test_gas_key_full_access_deserialization() {
1460 let json = serde_json::json!({
1461 "GasKeyFullAccess": {
1462 "balance": "1000000000000000000000000",
1463 "num_nonces": 10
1464 }
1465 });
1466 let perm: AccessKeyPermissionView = serde_json::from_value(json).unwrap();
1467 assert!(matches!(
1468 perm,
1469 AccessKeyPermissionView::GasKeyFullAccess { .. }
1470 ));
1471 }
1472
1473 #[test]
1474 fn test_transfer_to_gas_key_action_view_deserialization() {
1475 let json = serde_json::json!({
1476 "TransferToGasKey": {
1477 "public_key": "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp",
1478 "deposit": "1000000000000000000000000"
1479 }
1480 });
1481 let action: ActionView = serde_json::from_value(json).unwrap();
1482 assert!(matches!(action, ActionView::TransferToGasKey { .. }));
1483 }
1484
1485 #[test]
1486 fn test_delegate_action_view_deserialization() {
1487 let json = serde_json::json!({
1488 "Delegate": {
1489 "delegate_action": {
1490 "sender_id": "alice.near",
1491 "receiver_id": "contract.near",
1492 "actions": [
1493 {"FunctionCall": {
1494 "method_name": "do_something",
1495 "args": "e30=",
1496 "gas": 30000000000000_u64,
1497 "deposit": "0"
1498 }}
1499 ],
1500 "nonce": 42,
1501 "max_block_height": 100000,
1502 "public_key": "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp"
1503 },
1504 "signature": "ed25519:3s1dvMqNDCByoMnDnkhB4GPjTSXCRt4nt3Af5n1RX8W7aJ2FC6MfRf5BNXZ52EBifNJnNVBsGvke6GRYuaEYJXt5"
1505 }
1506 });
1507 let action: ActionView = serde_json::from_value(json).unwrap();
1508 match action {
1509 ActionView::Delegate {
1510 delegate_action,
1511 signature,
1512 } => {
1513 assert_eq!(delegate_action.sender_id.as_str(), "alice.near");
1514 assert_eq!(delegate_action.receiver_id.as_str(), "contract.near");
1515 assert_eq!(delegate_action.nonce, 42);
1516 assert_eq!(delegate_action.max_block_height, 100000);
1517 assert_eq!(delegate_action.actions.len(), 1);
1518 assert!(signature.to_string().starts_with("ed25519:"));
1519 }
1520 _ => panic!("Expected Delegate action"),
1521 }
1522 }
1523
1524 #[test]
1525 fn test_withdraw_from_gas_key_action_view_deserialization() {
1526 let json = serde_json::json!({
1527 "WithdrawFromGasKey": {
1528 "public_key": "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp",
1529 "amount": "500000000000000000000000"
1530 }
1531 });
1532 let action: ActionView = serde_json::from_value(json).unwrap();
1533 assert!(matches!(action, ActionView::WithdrawFromGasKey { .. }));
1534 }
1535
1536 #[test]
1541 fn test_final_execution_status_default() {
1542 let status = FinalExecutionStatus::default();
1543 assert!(matches!(status, FinalExecutionStatus::NotStarted));
1544 }
1545
1546 #[test]
1547 fn test_final_execution_status_not_started() {
1548 let json = serde_json::json!("NotStarted");
1549 let status: FinalExecutionStatus = serde_json::from_value(json).unwrap();
1550 assert!(matches!(status, FinalExecutionStatus::NotStarted));
1551 }
1552
1553 #[test]
1554 fn test_final_execution_status_started() {
1555 let json = serde_json::json!("Started");
1556 let status: FinalExecutionStatus = serde_json::from_value(json).unwrap();
1557 assert!(matches!(status, FinalExecutionStatus::Started));
1558 }
1559
1560 #[test]
1561 fn test_final_execution_status_success_value() {
1562 let json = serde_json::json!({"SuccessValue": "aGVsbG8="});
1563 let status: FinalExecutionStatus = serde_json::from_value(json).unwrap();
1564 assert!(matches!(status, FinalExecutionStatus::SuccessValue(ref s) if s == "aGVsbG8="));
1565 }
1566
1567 #[test]
1568 fn test_final_execution_status_failure() {
1569 let json = serde_json::json!({
1570 "Failure": {
1571 "ActionError": {
1572 "index": 0,
1573 "kind": {
1574 "FunctionCallError": {
1575 "ExecutionError": "Smart contract panicked"
1576 }
1577 }
1578 }
1579 }
1580 });
1581 let status: FinalExecutionStatus = serde_json::from_value(json).unwrap();
1582 assert!(matches!(status, FinalExecutionStatus::Failure(_)));
1583 }
1584
1585 #[test]
1590 fn test_send_tx_response_with_outcome() {
1591 let json = serde_json::json!({
1592 "final_execution_status": "FINAL",
1593 "status": {"SuccessValue": ""},
1594 "transaction": {
1595 "signer_id": "alice.near",
1596 "public_key": "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp",
1597 "nonce": 1,
1598 "receiver_id": "bob.near",
1599 "actions": [{"Transfer": {"deposit": "1000000000000000000000000"}}],
1600 "signature": "ed25519:3s1dvMqNDCByoMnDnkhB4GPjTSXCRt4nt3Af5n1RX8W7aJ2FC6MfRf5BNXZ52EBifNJnNVBsGvke6GRYuaEYJXt5",
1601 "hash": "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U"
1602 },
1603 "transaction_outcome": {
1604 "id": "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U",
1605 "outcome": {
1606 "executor_id": "alice.near",
1607 "gas_burnt": 223182562500_i64,
1608 "tokens_burnt": "22318256250000000000",
1609 "logs": [],
1610 "receipt_ids": ["3GTGoiN3FEoJenSw5ob4YMmFEV2Fbiichj3FDBnM78xK"],
1611 "status": {"SuccessReceiptId": "3GTGoiN3FEoJenSw5ob4YMmFEV2Fbiichj3FDBnM78xK"}
1612 },
1613 "block_hash": "A6DJpKBhmAMmBuQXtY3dWbo8dGVSQ9yH7BQSJBfn8rBo",
1614 "proof": []
1615 },
1616 "receipts_outcome": []
1617 });
1618 let response: RawTransactionResponse = serde_json::from_value(json).unwrap();
1619 assert_eq!(response.final_execution_status, TxExecutionStatus::Final);
1620 let outcome = response.outcome.unwrap();
1621 assert!(outcome.is_success());
1622 assert!(!outcome.is_failure());
1623 }
1624
1625 #[test]
1626 fn test_send_tx_response_pending_none() {
1627 let json = serde_json::json!({
1628 "final_execution_status": "NONE"
1629 });
1630 let response: RawTransactionResponse = serde_json::from_value(json).unwrap();
1631 assert_eq!(response.final_execution_status, TxExecutionStatus::None);
1632 assert!(response.outcome.is_none());
1633 assert!(response.transaction_hash.is_zero());
1635 }
1636
1637 #[test]
1638 fn test_final_execution_outcome_failure() {
1639 let json = serde_json::json!({
1640 "final_execution_status": "EXECUTED_OPTIMISTIC",
1641 "status": {
1642 "Failure": {
1643 "ActionError": {
1644 "index": 0,
1645 "kind": {
1646 "FunctionCallError": {
1647 "ExecutionError": "Smart contract panicked"
1648 }
1649 }
1650 }
1651 }
1652 },
1653 "transaction": {
1654 "signer_id": "alice.near",
1655 "public_key": "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp",
1656 "nonce": 1,
1657 "receiver_id": "bob.near",
1658 "actions": [],
1659 "signature": "ed25519:3s1dvMqNDCByoMnDnkhB4GPjTSXCRt4nt3Af5n1RX8W7aJ2FC6MfRf5BNXZ52EBifNJnNVBsGvke6GRYuaEYJXt5",
1660 "hash": "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U"
1661 },
1662 "transaction_outcome": {
1663 "id": "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U",
1664 "outcome": {
1665 "executor_id": "alice.near",
1666 "gas_burnt": 0,
1667 "tokens_burnt": "0",
1668 "logs": [],
1669 "receipt_ids": [],
1670 "status": "Unknown"
1671 },
1672 "block_hash": "A6DJpKBhmAMmBuQXtY3dWbo8dGVSQ9yH7BQSJBfn8rBo",
1673 "proof": []
1674 },
1675 "receipts_outcome": []
1676 });
1677 let response: RawTransactionResponse = serde_json::from_value(json).unwrap();
1678 let outcome = response.outcome.unwrap();
1679 assert!(outcome.is_failure());
1680 assert!(!outcome.is_success());
1681 assert!(outcome.failure_message().is_some());
1682 assert!(outcome.failure_error().is_some());
1683 }
1684
1685 fn make_success_outcome(base64_value: &str) -> FinalExecutionOutcome {
1690 let json = serde_json::json!({
1691 "final_execution_status": "FINAL",
1692 "status": {"SuccessValue": base64_value},
1693 "transaction": {
1694 "signer_id": "alice.near",
1695 "public_key": "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp",
1696 "nonce": 1,
1697 "receiver_id": "bob.near",
1698 "actions": [],
1699 "signature": "ed25519:3s1dvMqNDCByoMnDnkhB4GPjTSXCRt4nt3Af5n1RX8W7aJ2FC6MfRf5BNXZ52EBifNJnNVBsGvke6GRYuaEYJXt5",
1700 "hash": "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U"
1701 },
1702 "transaction_outcome": {
1703 "id": "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U",
1704 "outcome": {
1705 "executor_id": "alice.near",
1706 "gas_burnt": 223182562500_i64,
1707 "tokens_burnt": "22318256250000000000",
1708 "logs": [],
1709 "receipt_ids": [],
1710 "status": {"SuccessReceiptId": "3GTGoiN3FEoJenSw5ob4YMmFEV2Fbiichj3FDBnM78xK"}
1711 },
1712 "block_hash": "A6DJpKBhmAMmBuQXtY3dWbo8dGVSQ9yH7BQSJBfn8rBo",
1713 "proof": []
1714 },
1715 "receipts_outcome": []
1716 });
1717 let response: RawTransactionResponse = serde_json::from_value(json).unwrap();
1718 response.outcome.unwrap()
1719 }
1720
1721 #[test]
1722 fn test_outcome_result() {
1723 let outcome = make_success_outcome("aGVsbG8=");
1725 assert_eq!(outcome.result().unwrap(), b"hello");
1726 }
1727
1728 #[test]
1729 fn test_outcome_result_empty() {
1730 let outcome = make_success_outcome("");
1731 assert_eq!(outcome.result().unwrap(), b"");
1732 }
1733
1734 #[test]
1735 fn test_outcome_json() {
1736 let outcome = make_success_outcome("NDI=");
1738 let val: u64 = outcome.json().unwrap();
1739 assert_eq!(val, 42);
1740 }
1741
1742 #[test]
1743 fn test_outcome_json_bad_data() {
1744 let outcome = make_success_outcome("aGVsbG8=");
1746 let result: Result<u64, _> = outcome.json();
1747 assert!(result.is_err());
1748 }
1749
1750 #[test]
1751 fn test_outcome_result_invalid_base64_returns_err() {
1752 let outcome = make_success_outcome("not-valid-base64!!!");
1755 let err = outcome.result().unwrap_err();
1756 assert!(
1757 err.to_string().contains("base64"),
1758 "Error should mention base64 decode failure, got: {err}"
1759 );
1760 }
1761
1762 #[test]
1763 fn test_outcome_failure_result_returns_err() {
1764 let json = serde_json::json!({
1765 "final_execution_status": "FINAL",
1766 "status": {"Failure": {"ActionError": {"index": 0, "kind": {"FunctionCallError": {"ExecutionError": "test error"}}}}},
1767 "transaction": {
1768 "signer_id": "alice.near",
1769 "public_key": "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp",
1770 "nonce": 1,
1771 "receiver_id": "bob.near",
1772 "actions": [],
1773 "signature": "ed25519:3s1dvMqNDCByoMnDnkhB4GPjTSXCRt4nt3Af5n1RX8W7aJ2FC6MfRf5BNXZ52EBifNJnNVBsGvke6GRYuaEYJXt5",
1774 "hash": "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U"
1775 },
1776 "transaction_outcome": {
1777 "id": "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U",
1778 "outcome": {
1779 "executor_id": "alice.near",
1780 "gas_burnt": 223182562500_i64,
1781 "tokens_burnt": "22318256250000000000",
1782 "logs": [],
1783 "receipt_ids": [],
1784 "status": {"SuccessReceiptId": "3GTGoiN3FEoJenSw5ob4YMmFEV2Fbiichj3FDBnM78xK"}
1785 },
1786 "block_hash": "A6DJpKBhmAMmBuQXtY3dWbo8dGVSQ9yH7BQSJBfn8rBo",
1787 "proof": []
1788 },
1789 "receipts_outcome": []
1790 });
1791 let outcome: FinalExecutionOutcome = serde_json::from_value(json).unwrap();
1792
1793 assert!(outcome.is_failure());
1794 assert!(!outcome.is_success());
1795 assert!(outcome.result().is_err());
1796 assert!(outcome.json::<u64>().is_err());
1797 assert!(!outcome.transaction_hash().is_zero());
1799 assert!(outcome.total_gas_used().as_gas() > 0);
1800 }
1801
1802 #[test]
1807 fn test_execution_status_unknown() {
1808 let json = serde_json::json!("Unknown");
1809 let status: ExecutionStatus = serde_json::from_value(json).unwrap();
1810 assert!(matches!(status, ExecutionStatus::Unknown));
1811 }
1812
1813 #[test]
1814 fn test_execution_status_success_value() {
1815 let json = serde_json::json!({"SuccessValue": "aGVsbG8="});
1816 let status: ExecutionStatus = serde_json::from_value(json).unwrap();
1817 assert!(matches!(status, ExecutionStatus::SuccessValue(_)));
1818 }
1819
1820 #[test]
1821 fn test_execution_status_success_receipt_id() {
1822 let json =
1823 serde_json::json!({"SuccessReceiptId": "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U"});
1824 let status: ExecutionStatus = serde_json::from_value(json).unwrap();
1825 assert!(matches!(status, ExecutionStatus::SuccessReceiptId(_)));
1826 }
1827
1828 #[test]
1829 fn test_execution_status_failure_action_error() {
1830 let json = serde_json::json!({
1831 "Failure": {
1832 "ActionError": {
1833 "index": 0,
1834 "kind": {
1835 "FunctionCallError": {
1836 "ExecutionError": "Smart contract panicked"
1837 }
1838 }
1839 }
1840 }
1841 });
1842 let status: ExecutionStatus = serde_json::from_value(json).unwrap();
1843 match status {
1844 ExecutionStatus::Failure(ae) => {
1845 assert_eq!(ae.index, Some(0));
1846 }
1847 other => panic!("expected Failure, got: {other:?}"),
1848 }
1849 }
1850
1851 #[test]
1852 fn test_execution_status_failure_invalid_tx_error_rejected() {
1853 let json = serde_json::json!({
1854 "Failure": {
1855 "InvalidTxError": "InvalidSignature"
1856 }
1857 });
1858 let err = serde_json::from_value::<ExecutionStatus>(json)
1859 .expect_err("InvalidTxError should be rejected in receipt execution status");
1860 let msg = err.to_string();
1861 assert!(
1862 msg.contains("unexpected InvalidTxError"),
1863 "Expected descriptive error containing \"unexpected InvalidTxError\", got: {msg}"
1864 );
1865 }
1866
1867 #[test]
1872 fn test_global_contract_id_view_new_format_hash() {
1873 let json = serde_json::json!({"hash": "9SP8Y3sVADWNN5QoEB5CsvPUE5HT4o8YfBaCnhLss87K"});
1874 let id: GlobalContractIdentifierView = serde_json::from_value(json).unwrap();
1875 assert!(matches!(id, GlobalContractIdentifierView::CodeHash(_)));
1876 }
1877
1878 #[test]
1879 fn test_global_contract_id_view_new_format_account() {
1880 let json = serde_json::json!({"account_id": "alice.near"});
1881 let id: GlobalContractIdentifierView = serde_json::from_value(json).unwrap();
1882 assert!(matches!(id, GlobalContractIdentifierView::AccountId(_)));
1883 }
1884
1885 #[test]
1886 fn test_global_contract_id_view_deprecated_hash() {
1887 let json = serde_json::json!("9SP8Y3sVADWNN5QoEB5CsvPUE5HT4o8YfBaCnhLss87K");
1888 let id: GlobalContractIdentifierView = serde_json::from_value(json).unwrap();
1889 assert!(matches!(id, GlobalContractIdentifierView::CodeHash(_)));
1890 }
1891
1892 #[test]
1893 fn test_global_contract_id_view_deprecated_account() {
1894 let json = serde_json::json!("alice.near");
1895 let id: GlobalContractIdentifierView = serde_json::from_value(json).unwrap();
1896 assert!(matches!(id, GlobalContractIdentifierView::AccountId(_)));
1897 }
1898
1899 #[test]
1904 fn test_action_view_deterministic_state_init() {
1905 let json = serde_json::json!({
1906 "DeterministicStateInit": {
1907 "code": {"hash": "9SP8Y3sVADWNN5QoEB5CsvPUE5HT4o8YfBaCnhLss87K"},
1908 "data": {"a2V5": "dmFsdWU="},
1909 "deposit": "1000000000000000000000000"
1910 }
1911 });
1912 let action: ActionView = serde_json::from_value(json).unwrap();
1913 match action {
1914 ActionView::DeterministicStateInit {
1915 code,
1916 data,
1917 deposit,
1918 } => {
1919 assert!(matches!(code, GlobalContractIdentifierView::CodeHash(_)));
1920 assert_eq!(data.len(), 1);
1921 assert_eq!(data.get("a2V5").unwrap(), "dmFsdWU=");
1922 assert_eq!(deposit, NearToken::from_near(1));
1923 }
1924 _ => panic!("Expected DeterministicStateInit"),
1925 }
1926 }
1927
1928 #[test]
1929 fn test_action_view_deterministic_state_init_empty_data() {
1930 let json = serde_json::json!({
1931 "DeterministicStateInit": {
1932 "code": {"account_id": "publisher.near"},
1933 "deposit": "0"
1934 }
1935 });
1936 let action: ActionView = serde_json::from_value(json).unwrap();
1937 match action {
1938 ActionView::DeterministicStateInit { code, data, .. } => {
1939 assert!(matches!(code, GlobalContractIdentifierView::AccountId(_)));
1940 assert!(data.is_empty());
1941 }
1942 _ => panic!("Expected DeterministicStateInit"),
1943 }
1944 }
1945
1946 #[test]
1951 fn test_receipt_global_contract_distribution() {
1952 let json = serde_json::json!({
1953 "GlobalContractDistribution": {
1954 "id": {"hash": "9SP8Y3sVADWNN5QoEB5CsvPUE5HT4o8YfBaCnhLss87K"},
1955 "target_shard": 3,
1956 "already_delivered_shards": [0, 1, 2],
1957 "code": "AGFzbQ==",
1958 "nonce": 42
1959 }
1960 });
1961 let content: ReceiptContent = serde_json::from_value(json).unwrap();
1962 match content {
1963 ReceiptContent::GlobalContractDistribution {
1964 id,
1965 target_shard,
1966 already_delivered_shards,
1967 code,
1968 nonce,
1969 } => {
1970 assert!(matches!(id, GlobalContractIdentifierView::CodeHash(_)));
1971 assert_eq!(target_shard, 3);
1972 assert_eq!(already_delivered_shards, vec![0, 1, 2]);
1973 assert_eq!(code, "AGFzbQ==");
1974 assert_eq!(nonce, Some(42));
1975 }
1976 _ => panic!("Expected GlobalContractDistribution"),
1977 }
1978 }
1979
1980 #[test]
1981 fn test_receipt_global_contract_distribution_without_nonce() {
1982 let json = serde_json::json!({
1983 "GlobalContractDistribution": {
1984 "id": {"account_id": "publisher.near"},
1985 "target_shard": 0,
1986 "already_delivered_shards": [],
1987 "code": "AGFzbQ=="
1988 }
1989 });
1990 let content: ReceiptContent = serde_json::from_value(json).unwrap();
1991 match content {
1992 ReceiptContent::GlobalContractDistribution { nonce, .. } => {
1993 assert_eq!(nonce, None);
1994 }
1995 _ => panic!("Expected GlobalContractDistribution"),
1996 }
1997 }
1998
1999 #[test]
2000 fn test_gas_profile_entry_deserialization() {
2001 let json = serde_json::json!({
2002 "cost_category": "WASM_HOST_COST",
2003 "cost": "BASE",
2004 "gas_used": "2646228750"
2005 });
2006 let entry: GasProfileEntry = serde_json::from_value(json).unwrap();
2007 assert_eq!(entry.cost_category, "WASM_HOST_COST");
2008 assert_eq!(entry.cost, "BASE");
2009 assert_eq!(entry.gas_used, Gas::from_gas(2646228750));
2010 }
2011
2012 #[test]
2013 fn test_transaction_view_with_signature() {
2014 let json = serde_json::json!({
2015 "signer_id": "alice.near",
2016 "public_key": "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp",
2017 "nonce": 1,
2018 "receiver_id": "bob.near",
2019 "hash": "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U",
2020 "actions": [{"Transfer": {"deposit": "1000000000000000000000000"}}],
2021 "signature": "ed25519:3s1dvMqNDCByoMnDnkhB4GPjTSXCRt4nt3Af5n1RX8W7aJ2FC6MfRf5BNXZ52EBifNJnNVBsGvke6GRYuaEYJXt5"
2022 });
2023 let tx: TransactionView = serde_json::from_value(json).unwrap();
2024 assert_eq!(tx.signer_id.as_str(), "alice.near");
2025 assert!(tx.signature.to_string().starts_with("ed25519:"));
2026 }
2027}