1use std::collections::BTreeMap;
4
5use base64::{Engine as _, engine::general_purpose::STANDARD};
6use serde::Deserialize;
7
8use super::block_reference::TxExecutionStatus;
9use super::error::{ActionError, TxExecutionError};
10use super::{AccountId, CryptoHash, Gas, NearToken, PublicKey, Signature};
11
12pub const STORAGE_AMOUNT_PER_BYTE: u128 = 10_000_000_000_000_000_000; #[derive(Debug, Clone, Deserialize)]
31pub struct AccountView {
32 pub amount: NearToken,
34 pub locked: NearToken,
36 pub code_hash: CryptoHash,
38 pub storage_usage: u64,
40 #[serde(default)]
42 pub storage_paid_at: u64,
43 #[serde(default)]
45 pub global_contract_hash: Option<CryptoHash>,
46 #[serde(default)]
48 pub global_contract_account_id: Option<AccountId>,
49 pub block_height: u64,
51 pub block_hash: CryptoHash,
53}
54
55impl AccountView {
56 fn storage_required(&self) -> NearToken {
58 let yocto = STORAGE_AMOUNT_PER_BYTE.saturating_mul(self.storage_usage as u128);
59 NearToken::from_yoctonear(yocto)
60 }
61
62 pub fn available(&self) -> NearToken {
71 let storage_required = self.storage_required();
72
73 if self.locked >= storage_required {
75 return self.amount;
76 }
77
78 let reserved_for_storage = storage_required.saturating_sub(self.locked);
80 self.amount.saturating_sub(reserved_for_storage)
81 }
82
83 pub fn storage_cost(&self) -> NearToken {
87 let storage_required = self.storage_required();
88
89 if self.locked >= storage_required {
90 NearToken::ZERO
91 } else {
92 storage_required.saturating_sub(self.locked)
93 }
94 }
95
96 pub fn has_contract(&self) -> bool {
98 !self.code_hash.is_zero()
99 }
100}
101
102#[derive(Debug, Clone)]
104pub struct AccountBalance {
105 pub total: NearToken,
107 pub available: NearToken,
109 pub locked: NearToken,
111 pub storage_cost: NearToken,
113 pub storage_usage: u64,
115}
116
117impl std::fmt::Display for AccountBalance {
118 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119 write!(f, "{}", self.available)
120 }
121}
122
123impl From<AccountView> for AccountBalance {
124 fn from(view: AccountView) -> Self {
125 Self {
126 total: view.amount,
127 available: view.available(),
128 locked: view.locked,
129 storage_cost: view.storage_cost(),
130 storage_usage: view.storage_usage,
131 }
132 }
133}
134
135#[derive(Debug, Clone, Deserialize)]
137pub struct AccessKeyView {
138 pub nonce: u64,
140 pub permission: AccessKeyPermissionView,
142 pub block_height: u64,
144 pub block_hash: CryptoHash,
146}
147
148#[derive(Debug, Clone, Deserialize)]
150pub struct AccessKeyDetails {
151 pub nonce: u64,
153 pub permission: AccessKeyPermissionView,
155}
156
157#[derive(Debug, Clone, Deserialize)]
159#[serde(rename_all = "PascalCase")]
160pub enum AccessKeyPermissionView {
161 FullAccess,
163 FunctionCall {
165 allowance: Option<NearToken>,
167 receiver_id: AccountId,
169 method_names: Vec<String>,
171 },
172 GasKeyFunctionCall {
174 balance: NearToken,
176 num_nonces: u16,
178 allowance: Option<NearToken>,
180 receiver_id: AccountId,
182 method_names: Vec<String>,
184 },
185 GasKeyFullAccess {
187 balance: NearToken,
189 num_nonces: u16,
191 },
192}
193
194#[derive(Debug, Clone, Deserialize)]
196pub struct AccessKeyListView {
197 pub keys: Vec<AccessKeyInfoView>,
199 pub block_height: u64,
201 pub block_hash: CryptoHash,
203}
204
205#[derive(Debug, Clone, Deserialize)]
207pub struct AccessKeyInfoView {
208 pub public_key: PublicKey,
210 pub access_key: AccessKeyDetails,
212}
213
214#[derive(Debug, Clone, Deserialize)]
220pub struct BlockView {
221 pub author: AccountId,
223 pub header: BlockHeaderView,
225 pub chunks: Vec<ChunkHeaderView>,
227}
228
229#[derive(Debug, Clone, Deserialize)]
231pub struct BlockHeaderView {
232 pub height: u64,
234 #[serde(default)]
236 pub prev_height: Option<u64>,
237 pub hash: CryptoHash,
239 pub prev_hash: CryptoHash,
241 pub prev_state_root: CryptoHash,
243 pub chunk_receipts_root: CryptoHash,
245 pub chunk_headers_root: CryptoHash,
247 pub chunk_tx_root: CryptoHash,
249 pub outcome_root: CryptoHash,
251 pub chunks_included: u64,
253 pub challenges_root: CryptoHash,
255 pub timestamp: u64,
257 pub timestamp_nanosec: String,
259 pub random_value: CryptoHash,
261 #[serde(default)]
263 pub validator_proposals: Vec<ValidatorStakeView>,
264 #[serde(default)]
266 pub chunk_mask: Vec<bool>,
267 pub gas_price: NearToken,
269 #[serde(default)]
271 pub block_ordinal: Option<u64>,
272 pub total_supply: NearToken,
274 #[serde(default)]
276 pub challenges_result: Vec<SlashedValidator>,
277 pub last_final_block: CryptoHash,
279 pub last_ds_final_block: CryptoHash,
281 pub epoch_id: CryptoHash,
283 pub next_epoch_id: CryptoHash,
285 pub next_bp_hash: CryptoHash,
287 pub block_merkle_root: CryptoHash,
289 #[serde(default)]
291 pub epoch_sync_data_hash: Option<CryptoHash>,
292 #[serde(default)]
294 pub block_body_hash: Option<CryptoHash>,
295 #[serde(default)]
297 pub approvals: Vec<Option<Signature>>,
298 pub signature: Signature,
300 pub latest_protocol_version: u32,
302 #[serde(default)]
304 pub rent_paid: Option<NearToken>,
305 #[serde(default)]
307 pub validator_reward: Option<NearToken>,
308 #[serde(default)]
310 pub chunk_endorsements: Option<Vec<Vec<u8>>>,
311 #[serde(default)]
313 pub shard_split: Option<(u64, AccountId)>,
314}
315
316#[derive(Debug, Clone, Deserialize)]
320#[serde(untagged)]
321pub enum ValidatorStakeView {
322 V1(ValidatorStakeViewV1),
324}
325
326#[derive(Debug, Clone, Deserialize)]
328pub struct ValidatorStakeViewV1 {
329 pub account_id: AccountId,
331 pub public_key: PublicKey,
333 pub stake: NearToken,
335}
336
337impl ValidatorStakeView {
338 pub fn into_v1(self) -> ValidatorStakeViewV1 {
340 match self {
341 Self::V1(v) => v,
342 }
343 }
344
345 pub fn account_id(&self) -> &AccountId {
347 match self {
348 Self::V1(v) => &v.account_id,
349 }
350 }
351
352 pub fn stake(&self) -> NearToken {
354 match self {
355 Self::V1(v) => v.stake,
356 }
357 }
358}
359
360#[derive(Debug, Clone, Deserialize)]
362pub struct SlashedValidator {
363 pub account_id: AccountId,
365 pub is_double_sign: bool,
367}
368
369#[derive(Debug, Clone, Deserialize)]
371pub struct ChunkHeaderView {
372 pub chunk_hash: CryptoHash,
374 pub prev_block_hash: CryptoHash,
376 pub outcome_root: CryptoHash,
378 pub prev_state_root: CryptoHash,
380 pub encoded_merkle_root: CryptoHash,
382 pub encoded_length: u64,
384 pub height_created: u64,
386 pub height_included: u64,
388 pub shard_id: u64,
390 pub gas_used: u64,
392 pub gas_limit: u64,
394 pub validator_reward: NearToken,
396 pub balance_burnt: NearToken,
398 pub outgoing_receipts_root: CryptoHash,
400 pub tx_root: CryptoHash,
402 #[serde(default)]
404 pub validator_proposals: Vec<ValidatorStakeView>,
405 #[serde(default)]
407 pub congestion_info: Option<CongestionInfoView>,
408 #[serde(default)]
410 pub bandwidth_requests: Option<BandwidthRequests>,
411 #[serde(default)]
413 pub rent_paid: Option<NearToken>,
414 #[serde(default)]
420 pub proposed_split: Option<Option<TrieSplit>>,
421 pub signature: Signature,
423}
424
425#[derive(Debug, Clone, Deserialize)]
427pub enum BandwidthRequests {
428 V1(BandwidthRequestsV1),
430}
431
432#[derive(Debug, Clone, Deserialize)]
434pub struct BandwidthRequestsV1 {
435 pub requests: Vec<BandwidthRequest>,
437}
438
439#[derive(Debug, Clone, Deserialize)]
441pub struct BandwidthRequest {
442 pub to_shard: u16,
444 pub requested_values_bitmap: BandwidthRequestBitmap,
446}
447
448#[derive(Debug, Clone, Deserialize)]
450pub struct BandwidthRequestBitmap {
451 pub data: [u8; 5],
453}
454
455#[derive(Debug, Clone, Deserialize)]
457pub struct TrieSplit {
458 pub boundary_account: AccountId,
460 pub left_memory: u64,
462 pub right_memory: u64,
464}
465
466#[derive(Debug, Clone, Deserialize)]
468pub struct CongestionInfoView {
469 #[serde(default, deserialize_with = "dec_format")]
471 pub delayed_receipts_gas: u128,
472 #[serde(default, deserialize_with = "dec_format")]
474 pub buffered_receipts_gas: u128,
475 #[serde(default)]
477 pub receipt_bytes: u64,
478 #[serde(default)]
480 pub allowed_shard: u16,
481}
482
483fn dec_format<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<u128, D::Error> {
485 #[derive(Deserialize)]
486 #[serde(untagged)]
487 enum StringOrNum {
488 String(String),
489 Num(u128),
490 }
491 match StringOrNum::deserialize(deserializer)? {
492 StringOrNum::String(s) => s.parse().map_err(serde::de::Error::custom),
493 StringOrNum::Num(n) => Ok(n),
494 }
495}
496
497fn gas_dec_format<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Gas, D::Error> {
499 #[derive(Deserialize)]
500 #[serde(untagged)]
501 enum StringOrNum {
502 String(String),
503 Num(u64),
504 }
505 let raw = match StringOrNum::deserialize(deserializer)? {
506 StringOrNum::String(s) => s.parse::<u64>().map_err(serde::de::Error::custom)?,
507 StringOrNum::Num(n) => n,
508 };
509 Ok(Gas::from_gas(raw))
510}
511
512#[derive(Debug, Clone, Deserialize)]
514pub struct GasPrice {
515 pub gas_price: NearToken,
517}
518
519impl GasPrice {
520 pub fn as_u128(&self) -> u128 {
522 self.gas_price.as_yoctonear()
523 }
524}
525
526#[derive(Debug, Clone, Default, Deserialize)]
535pub enum FinalExecutionStatus {
536 #[default]
538 NotStarted,
539 Started,
541 Failure(TxExecutionError),
543 SuccessValue(String),
545}
546
547#[derive(Debug, Clone, Deserialize)]
555pub struct SendTxResponse {
556 #[serde(skip)]
558 pub transaction_hash: CryptoHash,
559 pub final_execution_status: TxExecutionStatus,
561 #[serde(flatten)]
563 pub outcome: Option<FinalExecutionOutcome>,
564}
565
566#[derive(Debug, Clone, Deserialize)]
571pub struct FinalExecutionOutcome {
572 pub status: FinalExecutionStatus,
574 pub transaction: TransactionView,
576 pub transaction_outcome: ExecutionOutcomeWithId,
578 pub receipts_outcome: Vec<ExecutionOutcomeWithId>,
580}
581
582impl FinalExecutionOutcome {
583 pub fn is_success(&self) -> bool {
585 matches!(&self.status, FinalExecutionStatus::SuccessValue(_))
586 }
587
588 pub fn is_failure(&self) -> bool {
590 matches!(&self.status, FinalExecutionStatus::Failure(_))
591 }
592
593 pub fn failure_message(&self) -> Option<String> {
595 match &self.status {
596 FinalExecutionStatus::Failure(err) => Some(err.to_string()),
597 _ => None,
598 }
599 }
600
601 pub fn failure_error(&self) -> Option<&TxExecutionError> {
603 match &self.status {
604 FinalExecutionStatus::Failure(err) => Some(err),
605 _ => None,
606 }
607 }
608
609 pub fn transaction_hash(&self) -> &CryptoHash {
611 &self.transaction_outcome.id
612 }
613
614 pub fn total_gas_used(&self) -> Gas {
616 let tx_gas = self.transaction_outcome.outcome.gas_burnt.as_gas();
617 let receipt_gas: u64 = self
618 .receipts_outcome
619 .iter()
620 .map(|r| r.outcome.gas_burnt.as_gas())
621 .sum();
622 Gas::from_gas(tx_gas + receipt_gas)
623 }
624
625 pub fn result(&self) -> Result<Vec<u8>, crate::error::Error> {
638 match &self.status {
639 FinalExecutionStatus::Failure(TxExecutionError::InvalidTxError(e)) => {
640 Err(crate::error::Error::InvalidTx(Box::new(e.clone())))
641 }
642 FinalExecutionStatus::Failure(TxExecutionError::ActionError(e)) => Err(
643 crate::error::Error::InvalidTransaction(format!("Action error: {e}")),
644 ),
645 FinalExecutionStatus::SuccessValue(s) => STANDARD.decode(s).map_err(|e| {
646 crate::error::Error::InvalidTransaction(format!(
647 "Failed to decode base64 SuccessValue: {e}"
648 ))
649 }),
650 other => Err(crate::error::Error::InvalidTransaction(format!(
651 "Transaction status is {:?}, expected SuccessValue",
652 other,
653 ))),
654 }
655 }
656
657 pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T, crate::error::Error> {
662 let bytes = self.result()?;
663 serde_json::from_slice(&bytes).map_err(crate::error::Error::from)
664 }
665}
666
667#[derive(Debug, Clone)]
679pub enum ExecutionStatus {
680 Unknown,
682 Failure(ActionError),
684 SuccessValue(String),
686 SuccessReceiptId(CryptoHash),
688}
689
690impl<'de> serde::Deserialize<'de> for ExecutionStatus {
691 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
692 #[derive(Deserialize)]
694 enum Raw {
695 Unknown,
696 Failure(TxExecutionError),
697 SuccessValue(String),
698 SuccessReceiptId(CryptoHash),
699 }
700
701 match Raw::deserialize(deserializer)? {
702 Raw::Unknown => Ok(Self::Unknown),
703 Raw::Failure(TxExecutionError::ActionError(e)) => Ok(Self::Failure(e)),
704 Raw::Failure(TxExecutionError::InvalidTxError(e)) => Err(serde::de::Error::custom(
705 format!("unexpected InvalidTxError in receipt execution status: {e}"),
706 )),
707 Raw::SuccessValue(v) => Ok(Self::SuccessValue(v)),
708 Raw::SuccessReceiptId(h) => Ok(Self::SuccessReceiptId(h)),
709 }
710 }
711}
712
713#[derive(Debug, Clone, Deserialize)]
715pub struct TransactionView {
716 pub signer_id: AccountId,
718 pub public_key: PublicKey,
720 pub nonce: u64,
722 pub receiver_id: AccountId,
724 pub hash: CryptoHash,
726 #[serde(default)]
728 pub actions: Vec<ActionView>,
729 pub signature: Signature,
731 #[serde(default)]
733 pub priority_fee: Option<u64>,
734 #[serde(default)]
736 pub nonce_index: Option<u16>,
737}
738
739#[derive(Deserialize)]
748#[serde(untagged)]
749enum GlobalContractIdCompat {
750 CodeHash { hash: CryptoHash },
751 AccountId { account_id: AccountId },
752 DeprecatedCodeHash(CryptoHash),
753 DeprecatedAccountId(AccountId),
754}
755
756#[derive(Debug, Clone, Deserialize)]
762#[serde(from = "GlobalContractIdCompat")]
763pub enum GlobalContractIdentifierView {
764 CodeHash(CryptoHash),
766 AccountId(AccountId),
768}
769
770impl From<GlobalContractIdCompat> for GlobalContractIdentifierView {
771 fn from(compat: GlobalContractIdCompat) -> Self {
772 match compat {
773 GlobalContractIdCompat::CodeHash { hash }
774 | GlobalContractIdCompat::DeprecatedCodeHash(hash) => Self::CodeHash(hash),
775 GlobalContractIdCompat::AccountId { account_id }
776 | GlobalContractIdCompat::DeprecatedAccountId(account_id) => {
777 Self::AccountId(account_id)
778 }
779 }
780 }
781}
782
783#[derive(Debug, Clone, Deserialize)]
789pub struct DelegateActionView {
790 pub sender_id: AccountId,
792 pub receiver_id: AccountId,
794 pub actions: Vec<ActionView>,
796 pub nonce: u64,
798 pub max_block_height: u64,
800 pub public_key: PublicKey,
802}
803
804#[derive(Debug, Clone, Deserialize)]
806#[serde(rename_all = "PascalCase")]
807pub enum ActionView {
808 CreateAccount,
809 DeployContract {
810 code: String, },
812 FunctionCall {
813 method_name: String,
814 args: String, gas: Gas,
816 deposit: NearToken,
817 },
818 Transfer {
819 deposit: NearToken,
820 },
821 Stake {
822 stake: NearToken,
823 public_key: PublicKey,
824 },
825 AddKey {
826 public_key: PublicKey,
827 access_key: AccessKeyDetails,
828 },
829 DeleteKey {
830 public_key: PublicKey,
831 },
832 DeleteAccount {
833 beneficiary_id: AccountId,
834 },
835 Delegate {
836 delegate_action: DelegateActionView,
837 signature: Signature,
838 },
839 #[serde(rename = "DeployGlobalContract")]
840 DeployGlobalContract {
841 code: String,
842 },
843 #[serde(rename = "DeployGlobalContractByAccountId")]
844 DeployGlobalContractByAccountId {
845 code: String,
846 },
847 #[serde(rename = "UseGlobalContract")]
848 UseGlobalContract {
849 code_hash: CryptoHash,
850 },
851 #[serde(rename = "UseGlobalContractByAccountId")]
852 UseGlobalContractByAccountId {
853 account_id: AccountId,
854 },
855 #[serde(rename = "DeterministicStateInit")]
856 DeterministicStateInit {
857 code: GlobalContractIdentifierView,
858 #[serde(default)]
859 data: BTreeMap<String, String>,
860 deposit: NearToken,
861 },
862 TransferToGasKey {
863 public_key: PublicKey,
864 deposit: NearToken,
865 },
866 WithdrawFromGasKey {
867 public_key: PublicKey,
868 amount: NearToken,
869 },
870}
871
872#[derive(Debug, Clone, Deserialize)]
874pub struct MerklePathItem {
875 pub hash: CryptoHash,
877 pub direction: MerkleDirection,
879}
880
881#[derive(Debug, Clone, Deserialize)]
883pub enum MerkleDirection {
884 Left,
885 Right,
886}
887
888#[derive(Debug, Clone, Deserialize)]
890pub struct ExecutionOutcomeWithId {
891 pub id: CryptoHash,
893 pub outcome: ExecutionOutcome,
895 #[serde(default)]
897 pub proof: Vec<MerklePathItem>,
898 pub block_hash: CryptoHash,
900}
901
902#[derive(Debug, Clone, Deserialize)]
904pub struct ExecutionOutcome {
905 pub executor_id: AccountId,
907 pub gas_burnt: Gas,
909 pub tokens_burnt: NearToken,
911 pub logs: Vec<String>,
913 pub receipt_ids: Vec<CryptoHash>,
915 pub status: ExecutionStatus,
917 #[serde(default)]
919 pub metadata: Option<ExecutionMetadata>,
920}
921
922#[derive(Debug, Clone, Deserialize)]
924pub struct ExecutionMetadata {
925 pub version: u32,
927 #[serde(default)]
929 pub gas_profile: Option<Vec<GasProfileEntry>>,
930}
931
932#[derive(Debug, Clone, Deserialize)]
934pub struct GasProfileEntry {
935 pub cost_category: String,
937 pub cost: String,
939 #[serde(deserialize_with = "gas_dec_format")]
941 pub gas_used: Gas,
942}
943
944#[derive(Debug, Clone, Deserialize)]
946pub struct ViewFunctionResult {
947 pub result: Vec<u8>,
949 pub logs: Vec<String>,
951 pub block_height: u64,
953 pub block_hash: CryptoHash,
955}
956
957impl ViewFunctionResult {
958 pub fn bytes(&self) -> &[u8] {
960 &self.result
961 }
962
963 pub fn as_string(&self) -> Result<String, std::string::FromUtf8Error> {
965 String::from_utf8(self.result.clone())
966 }
967
968 pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T, serde_json::Error> {
977 serde_json::from_slice(&self.result)
978 }
979
980 pub fn borsh<T: borsh::BorshDeserialize>(&self) -> Result<T, borsh::io::Error> {
991 borsh::from_slice(&self.result)
992 }
993}
994
995#[derive(Debug, Clone, Deserialize)]
1001pub struct Receipt {
1002 pub predecessor_id: AccountId,
1004 pub receiver_id: AccountId,
1006 pub receipt_id: CryptoHash,
1008 pub receipt: ReceiptContent,
1010 #[serde(default)]
1012 pub priority: Option<u64>,
1013}
1014
1015#[derive(Debug, Clone, Deserialize)]
1017pub enum ReceiptContent {
1018 Action(ActionReceiptData),
1020 Data(DataReceiptData),
1022 GlobalContractDistribution {
1024 id: GlobalContractIdentifierView,
1026 target_shard: u64,
1028 #[serde(default)]
1030 already_delivered_shards: Vec<u64>,
1031 code: String,
1033 #[serde(default)]
1035 nonce: Option<u64>,
1036 },
1037}
1038
1039#[derive(Debug, Clone, Deserialize)]
1041pub struct DataReceiverView {
1042 pub data_id: CryptoHash,
1044 pub receiver_id: AccountId,
1046}
1047
1048#[derive(Debug, Clone, Deserialize)]
1050pub struct ActionReceiptData {
1051 pub signer_id: AccountId,
1053 pub signer_public_key: PublicKey,
1055 pub gas_price: NearToken,
1057 #[serde(default)]
1059 pub output_data_receivers: Vec<DataReceiverView>,
1060 #[serde(default)]
1062 pub input_data_ids: Vec<CryptoHash>,
1063 pub actions: Vec<ActionView>,
1065 #[serde(default)]
1067 pub is_promise_yield: Option<bool>,
1068}
1069
1070#[derive(Debug, Clone, Deserialize)]
1072pub struct DataReceiptData {
1073 pub data_id: CryptoHash,
1075 #[serde(default)]
1077 pub data: Option<String>,
1078}
1079
1080#[derive(Debug, Clone, Deserialize)]
1084pub struct SendTxWithReceiptsResponse {
1085 pub final_execution_status: TxExecutionStatus,
1087 #[serde(flatten)]
1089 pub outcome: Option<FinalExecutionOutcomeWithReceipts>,
1090}
1091
1092#[derive(Debug, Clone, Deserialize)]
1097pub struct FinalExecutionOutcomeWithReceipts {
1098 pub status: FinalExecutionStatus,
1100 pub transaction: TransactionView,
1102 pub transaction_outcome: ExecutionOutcomeWithId,
1104 pub receipts_outcome: Vec<ExecutionOutcomeWithId>,
1106 #[serde(default)]
1108 pub receipts: Vec<Receipt>,
1109}
1110
1111impl FinalExecutionOutcomeWithReceipts {
1112 pub fn is_success(&self) -> bool {
1114 matches!(&self.status, FinalExecutionStatus::SuccessValue(_))
1115 }
1116
1117 pub fn is_failure(&self) -> bool {
1119 matches!(&self.status, FinalExecutionStatus::Failure(_))
1120 }
1121
1122 pub fn transaction_hash(&self) -> &CryptoHash {
1124 &self.transaction_outcome.id
1125 }
1126}
1127
1128#[derive(Debug, Clone, Deserialize)]
1134pub struct StatusResponse {
1135 pub protocol_version: u32,
1137 pub latest_protocol_version: u32,
1139 pub chain_id: String,
1141 pub genesis_hash: CryptoHash,
1143 #[serde(default)]
1145 pub rpc_addr: Option<String>,
1146 #[serde(default)]
1148 pub node_public_key: Option<String>,
1149 #[serde(default)]
1151 pub node_key: Option<String>,
1152 #[serde(default)]
1154 pub validator_account_id: Option<AccountId>,
1155 #[serde(default)]
1157 pub validator_public_key: Option<PublicKey>,
1158 #[serde(default)]
1160 pub validators: Vec<ValidatorInfo>,
1161 pub sync_info: SyncInfo,
1163 pub version: NodeVersion,
1165 #[serde(default)]
1167 pub uptime_sec: Option<u64>,
1168}
1169
1170#[derive(Debug, Clone, Deserialize)]
1172pub struct ValidatorInfo {
1173 pub account_id: AccountId,
1175}
1176
1177#[derive(Debug, Clone, Deserialize)]
1179pub struct SyncInfo {
1180 pub latest_block_hash: CryptoHash,
1182 pub latest_block_height: u64,
1184 #[serde(default)]
1186 pub latest_state_root: Option<CryptoHash>,
1187 pub latest_block_time: String,
1189 pub syncing: bool,
1191 #[serde(default)]
1193 pub earliest_block_hash: Option<CryptoHash>,
1194 #[serde(default)]
1196 pub earliest_block_height: Option<u64>,
1197 #[serde(default)]
1199 pub earliest_block_time: Option<String>,
1200 #[serde(default)]
1202 pub epoch_id: Option<CryptoHash>,
1203 #[serde(default)]
1205 pub epoch_start_height: Option<u64>,
1206}
1207
1208#[derive(Debug, Clone, Deserialize)]
1210pub struct NodeVersion {
1211 pub version: String,
1213 pub build: String,
1215 #[serde(default)]
1217 pub commit: Option<String>,
1218 #[serde(default)]
1220 pub rustc_version: Option<String>,
1221}
1222
1223#[cfg(test)]
1224mod tests {
1225 use super::*;
1226
1227 fn make_account_view(amount: u128, locked: u128, storage_usage: u64) -> AccountView {
1228 AccountView {
1229 amount: NearToken::from_yoctonear(amount),
1230 locked: NearToken::from_yoctonear(locked),
1231 code_hash: CryptoHash::default(),
1232 storage_usage,
1233 storage_paid_at: 0,
1234 global_contract_hash: None,
1235 global_contract_account_id: None,
1236 block_height: 0,
1237 block_hash: CryptoHash::default(),
1238 }
1239 }
1240
1241 #[test]
1242 fn test_available_balance_no_stake_no_storage() {
1243 let view = make_account_view(1_000_000_000_000_000_000_000_000, 0, 0); assert_eq!(view.available(), view.amount);
1246 }
1247
1248 #[test]
1249 fn test_available_balance_with_storage_no_stake() {
1250 let amount = 1_000_000_000_000_000_000_000_000u128; let storage_usage = 1000u64;
1255 let storage_cost = STORAGE_AMOUNT_PER_BYTE * storage_usage as u128; let view = make_account_view(amount, 0, storage_usage);
1258 let expected = NearToken::from_yoctonear(amount - storage_cost);
1259 assert_eq!(view.available(), expected);
1260 }
1261
1262 #[test]
1263 fn test_available_balance_stake_covers_storage() {
1264 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;
1270
1271 let view = make_account_view(amount, locked, storage_usage);
1272 assert_eq!(view.available(), view.amount);
1274 }
1275
1276 #[test]
1277 fn test_available_balance_stake_partially_covers_storage() {
1278 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;
1286 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);
1290 let expected = NearToken::from_yoctonear(amount - reserved);
1291 assert_eq!(view.available(), expected);
1292 }
1293
1294 #[test]
1295 fn test_storage_cost_calculation() {
1296 let storage_usage = 1000u64;
1297 let view = make_account_view(1_000_000_000_000_000_000_000_000, 0, storage_usage);
1298
1299 let expected_cost = STORAGE_AMOUNT_PER_BYTE * storage_usage as u128;
1300 assert_eq!(
1301 view.storage_cost(),
1302 NearToken::from_yoctonear(expected_cost)
1303 );
1304 }
1305
1306 #[test]
1307 fn test_storage_cost_zero_when_stake_covers() {
1308 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);
1311
1312 assert_eq!(view.storage_cost(), NearToken::ZERO);
1313 }
1314
1315 #[test]
1316 fn test_account_balance_from_view() {
1317 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;
1320
1321 let view = make_account_view(amount, locked, storage_usage);
1322 let balance = AccountBalance::from(view.clone());
1323
1324 assert_eq!(balance.total, view.amount);
1325 assert_eq!(balance.available, view.available());
1326 assert_eq!(balance.locked, view.locked);
1327 assert_eq!(balance.storage_cost, view.storage_cost());
1328 assert_eq!(balance.storage_usage, storage_usage);
1329 }
1330
1331 fn make_view_result(result: Vec<u8>) -> ViewFunctionResult {
1336 ViewFunctionResult {
1337 result,
1338 logs: vec![],
1339 block_height: 12345,
1340 block_hash: CryptoHash::default(),
1341 }
1342 }
1343
1344 #[test]
1345 fn test_view_function_result_bytes() {
1346 let data = vec![1, 2, 3, 4, 5];
1347 let result = make_view_result(data.clone());
1348 assert_eq!(result.bytes(), &data[..]);
1349 }
1350
1351 #[test]
1352 fn test_view_function_result_as_string() {
1353 let result = make_view_result(b"hello world".to_vec());
1354 assert_eq!(result.as_string().unwrap(), "hello world");
1355 }
1356
1357 #[test]
1358 fn test_view_function_result_json() {
1359 let result = make_view_result(b"42".to_vec());
1360 let value: u64 = result.json().unwrap();
1361 assert_eq!(value, 42);
1362 }
1363
1364 #[test]
1365 fn test_view_function_result_json_object() {
1366 let result = make_view_result(b"{\"count\":123}".to_vec());
1367 let value: serde_json::Value = result.json().unwrap();
1368 assert_eq!(value["count"], 123);
1369 }
1370
1371 #[test]
1372 fn test_view_function_result_borsh() {
1373 let original: u64 = 42;
1375 let encoded = borsh::to_vec(&original).unwrap();
1376 let result = make_view_result(encoded);
1377
1378 let decoded: u64 = result.borsh().unwrap();
1379 assert_eq!(decoded, original);
1380 }
1381
1382 #[test]
1383 fn test_view_function_result_borsh_struct() {
1384 #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, PartialEq, Debug)]
1385 struct TestStruct {
1386 value: u64,
1387 name: String,
1388 }
1389
1390 let original = TestStruct {
1391 value: 123,
1392 name: "test".to_string(),
1393 };
1394 let encoded = borsh::to_vec(&original).unwrap();
1395 let result = make_view_result(encoded);
1396
1397 let decoded: TestStruct = result.borsh().unwrap();
1398 assert_eq!(decoded, original);
1399 }
1400
1401 #[test]
1402 fn test_view_function_result_borsh_error() {
1403 let result = make_view_result(vec![1, 2, 3]);
1405 let decoded: Result<u64, _> = result.borsh();
1406 assert!(decoded.is_err());
1407 }
1408
1409 #[test]
1414 fn test_gas_profile_entry_string_gas_used() {
1415 let json = serde_json::json!({
1416 "cost_category": "WASM_HOST_COST",
1417 "cost": "BASE",
1418 "gas_used": "123456789"
1419 });
1420 let entry: GasProfileEntry = serde_json::from_value(json).unwrap();
1421 assert_eq!(entry.gas_used.as_gas(), 123456789);
1422 }
1423
1424 #[test]
1425 fn test_gas_profile_entry_numeric_gas_used() {
1426 let json = serde_json::json!({
1427 "cost_category": "ACTION_COST",
1428 "cost": "FUNCTION_CALL",
1429 "gas_used": 999000000
1430 });
1431 let entry: GasProfileEntry = serde_json::from_value(json).unwrap();
1432 assert_eq!(entry.gas_used.as_gas(), 999000000);
1433 }
1434
1435 #[test]
1436 fn test_gas_key_function_call_deserialization() {
1437 let json = serde_json::json!({
1438 "GasKeyFunctionCall": {
1439 "balance": "1000000000000000000000000",
1440 "num_nonces": 5,
1441 "allowance": "500000000000000000000000",
1442 "receiver_id": "app.near",
1443 "method_names": ["call_method"]
1444 }
1445 });
1446 let perm: AccessKeyPermissionView = serde_json::from_value(json).unwrap();
1447 assert!(matches!(
1448 perm,
1449 AccessKeyPermissionView::GasKeyFunctionCall { .. }
1450 ));
1451 }
1452
1453 #[test]
1454 fn test_gas_key_full_access_deserialization() {
1455 let json = serde_json::json!({
1456 "GasKeyFullAccess": {
1457 "balance": "1000000000000000000000000",
1458 "num_nonces": 10
1459 }
1460 });
1461 let perm: AccessKeyPermissionView = serde_json::from_value(json).unwrap();
1462 assert!(matches!(
1463 perm,
1464 AccessKeyPermissionView::GasKeyFullAccess { .. }
1465 ));
1466 }
1467
1468 #[test]
1469 fn test_transfer_to_gas_key_action_view_deserialization() {
1470 let json = serde_json::json!({
1471 "TransferToGasKey": {
1472 "public_key": "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp",
1473 "deposit": "1000000000000000000000000"
1474 }
1475 });
1476 let action: ActionView = serde_json::from_value(json).unwrap();
1477 assert!(matches!(action, ActionView::TransferToGasKey { .. }));
1478 }
1479
1480 #[test]
1481 fn test_delegate_action_view_deserialization() {
1482 let json = serde_json::json!({
1483 "Delegate": {
1484 "delegate_action": {
1485 "sender_id": "alice.near",
1486 "receiver_id": "contract.near",
1487 "actions": [
1488 {"FunctionCall": {
1489 "method_name": "do_something",
1490 "args": "e30=",
1491 "gas": 30000000000000_u64,
1492 "deposit": "0"
1493 }}
1494 ],
1495 "nonce": 42,
1496 "max_block_height": 100000,
1497 "public_key": "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp"
1498 },
1499 "signature": "ed25519:3s1dvMqNDCByoMnDnkhB4GPjTSXCRt4nt3Af5n1RX8W7aJ2FC6MfRf5BNXZ52EBifNJnNVBsGvke6GRYuaEYJXt5"
1500 }
1501 });
1502 let action: ActionView = serde_json::from_value(json).unwrap();
1503 match action {
1504 ActionView::Delegate {
1505 delegate_action,
1506 signature,
1507 } => {
1508 assert_eq!(delegate_action.sender_id.as_str(), "alice.near");
1509 assert_eq!(delegate_action.receiver_id.as_str(), "contract.near");
1510 assert_eq!(delegate_action.nonce, 42);
1511 assert_eq!(delegate_action.max_block_height, 100000);
1512 assert_eq!(delegate_action.actions.len(), 1);
1513 assert!(signature.to_string().starts_with("ed25519:"));
1514 }
1515 _ => panic!("Expected Delegate action"),
1516 }
1517 }
1518
1519 #[test]
1520 fn test_withdraw_from_gas_key_action_view_deserialization() {
1521 let json = serde_json::json!({
1522 "WithdrawFromGasKey": {
1523 "public_key": "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp",
1524 "amount": "500000000000000000000000"
1525 }
1526 });
1527 let action: ActionView = serde_json::from_value(json).unwrap();
1528 assert!(matches!(action, ActionView::WithdrawFromGasKey { .. }));
1529 }
1530
1531 #[test]
1536 fn test_final_execution_status_default() {
1537 let status = FinalExecutionStatus::default();
1538 assert!(matches!(status, FinalExecutionStatus::NotStarted));
1539 }
1540
1541 #[test]
1542 fn test_final_execution_status_not_started() {
1543 let json = serde_json::json!("NotStarted");
1544 let status: FinalExecutionStatus = serde_json::from_value(json).unwrap();
1545 assert!(matches!(status, FinalExecutionStatus::NotStarted));
1546 }
1547
1548 #[test]
1549 fn test_final_execution_status_started() {
1550 let json = serde_json::json!("Started");
1551 let status: FinalExecutionStatus = serde_json::from_value(json).unwrap();
1552 assert!(matches!(status, FinalExecutionStatus::Started));
1553 }
1554
1555 #[test]
1556 fn test_final_execution_status_success_value() {
1557 let json = serde_json::json!({"SuccessValue": "aGVsbG8="});
1558 let status: FinalExecutionStatus = serde_json::from_value(json).unwrap();
1559 assert!(matches!(status, FinalExecutionStatus::SuccessValue(ref s) if s == "aGVsbG8="));
1560 }
1561
1562 #[test]
1563 fn test_final_execution_status_failure() {
1564 let json = serde_json::json!({
1565 "Failure": {
1566 "ActionError": {
1567 "index": 0,
1568 "kind": {
1569 "FunctionCallError": {
1570 "ExecutionError": "Smart contract panicked"
1571 }
1572 }
1573 }
1574 }
1575 });
1576 let status: FinalExecutionStatus = serde_json::from_value(json).unwrap();
1577 assert!(matches!(status, FinalExecutionStatus::Failure(_)));
1578 }
1579
1580 #[test]
1585 fn test_send_tx_response_with_outcome() {
1586 let json = serde_json::json!({
1587 "final_execution_status": "FINAL",
1588 "status": {"SuccessValue": ""},
1589 "transaction": {
1590 "signer_id": "alice.near",
1591 "public_key": "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp",
1592 "nonce": 1,
1593 "receiver_id": "bob.near",
1594 "actions": [{"Transfer": {"deposit": "1000000000000000000000000"}}],
1595 "signature": "ed25519:3s1dvMqNDCByoMnDnkhB4GPjTSXCRt4nt3Af5n1RX8W7aJ2FC6MfRf5BNXZ52EBifNJnNVBsGvke6GRYuaEYJXt5",
1596 "hash": "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U"
1597 },
1598 "transaction_outcome": {
1599 "id": "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U",
1600 "outcome": {
1601 "executor_id": "alice.near",
1602 "gas_burnt": 223182562500_i64,
1603 "tokens_burnt": "22318256250000000000",
1604 "logs": [],
1605 "receipt_ids": ["3GTGoiN3FEoJenSw5ob4YMmFEV2Fbiichj3FDBnM78xK"],
1606 "status": {"SuccessReceiptId": "3GTGoiN3FEoJenSw5ob4YMmFEV2Fbiichj3FDBnM78xK"}
1607 },
1608 "block_hash": "A6DJpKBhmAMmBuQXtY3dWbo8dGVSQ9yH7BQSJBfn8rBo",
1609 "proof": []
1610 },
1611 "receipts_outcome": []
1612 });
1613 let response: SendTxResponse = serde_json::from_value(json).unwrap();
1614 assert_eq!(response.final_execution_status, TxExecutionStatus::Final);
1615 let outcome = response.outcome.unwrap();
1616 assert!(outcome.is_success());
1617 assert!(!outcome.is_failure());
1618 }
1619
1620 #[test]
1621 fn test_send_tx_response_pending_none() {
1622 let json = serde_json::json!({
1623 "final_execution_status": "NONE"
1624 });
1625 let response: SendTxResponse = serde_json::from_value(json).unwrap();
1626 assert_eq!(response.final_execution_status, TxExecutionStatus::None);
1627 assert!(response.outcome.is_none());
1628 assert!(response.transaction_hash.is_zero());
1630 }
1631
1632 #[test]
1633 fn test_final_execution_outcome_failure() {
1634 let json = serde_json::json!({
1635 "final_execution_status": "EXECUTED_OPTIMISTIC",
1636 "status": {
1637 "Failure": {
1638 "ActionError": {
1639 "index": 0,
1640 "kind": {
1641 "FunctionCallError": {
1642 "ExecutionError": "Smart contract panicked"
1643 }
1644 }
1645 }
1646 }
1647 },
1648 "transaction": {
1649 "signer_id": "alice.near",
1650 "public_key": "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp",
1651 "nonce": 1,
1652 "receiver_id": "bob.near",
1653 "actions": [],
1654 "signature": "ed25519:3s1dvMqNDCByoMnDnkhB4GPjTSXCRt4nt3Af5n1RX8W7aJ2FC6MfRf5BNXZ52EBifNJnNVBsGvke6GRYuaEYJXt5",
1655 "hash": "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U"
1656 },
1657 "transaction_outcome": {
1658 "id": "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U",
1659 "outcome": {
1660 "executor_id": "alice.near",
1661 "gas_burnt": 0,
1662 "tokens_burnt": "0",
1663 "logs": [],
1664 "receipt_ids": [],
1665 "status": "Unknown"
1666 },
1667 "block_hash": "A6DJpKBhmAMmBuQXtY3dWbo8dGVSQ9yH7BQSJBfn8rBo",
1668 "proof": []
1669 },
1670 "receipts_outcome": []
1671 });
1672 let response: SendTxResponse = serde_json::from_value(json).unwrap();
1673 let outcome = response.outcome.unwrap();
1674 assert!(outcome.is_failure());
1675 assert!(!outcome.is_success());
1676 assert!(outcome.failure_message().is_some());
1677 assert!(outcome.failure_error().is_some());
1678 }
1679
1680 fn make_success_outcome(base64_value: &str) -> FinalExecutionOutcome {
1685 let json = serde_json::json!({
1686 "final_execution_status": "FINAL",
1687 "status": {"SuccessValue": base64_value},
1688 "transaction": {
1689 "signer_id": "alice.near",
1690 "public_key": "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp",
1691 "nonce": 1,
1692 "receiver_id": "bob.near",
1693 "actions": [],
1694 "signature": "ed25519:3s1dvMqNDCByoMnDnkhB4GPjTSXCRt4nt3Af5n1RX8W7aJ2FC6MfRf5BNXZ52EBifNJnNVBsGvke6GRYuaEYJXt5",
1695 "hash": "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U"
1696 },
1697 "transaction_outcome": {
1698 "id": "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U",
1699 "outcome": {
1700 "executor_id": "alice.near",
1701 "gas_burnt": 223182562500_i64,
1702 "tokens_burnt": "22318256250000000000",
1703 "logs": [],
1704 "receipt_ids": [],
1705 "status": {"SuccessReceiptId": "3GTGoiN3FEoJenSw5ob4YMmFEV2Fbiichj3FDBnM78xK"}
1706 },
1707 "block_hash": "A6DJpKBhmAMmBuQXtY3dWbo8dGVSQ9yH7BQSJBfn8rBo",
1708 "proof": []
1709 },
1710 "receipts_outcome": []
1711 });
1712 let response: SendTxResponse = serde_json::from_value(json).unwrap();
1713 response.outcome.unwrap()
1714 }
1715
1716 #[test]
1717 fn test_outcome_result() {
1718 let outcome = make_success_outcome("aGVsbG8=");
1720 assert_eq!(outcome.result().unwrap(), b"hello");
1721 }
1722
1723 #[test]
1724 fn test_outcome_result_empty() {
1725 let outcome = make_success_outcome("");
1726 assert_eq!(outcome.result().unwrap(), b"");
1727 }
1728
1729 #[test]
1730 fn test_outcome_json() {
1731 let outcome = make_success_outcome("NDI=");
1733 let val: u64 = outcome.json().unwrap();
1734 assert_eq!(val, 42);
1735 }
1736
1737 #[test]
1738 fn test_outcome_json_bad_data() {
1739 let outcome = make_success_outcome("aGVsbG8=");
1741 let result: Result<u64, _> = outcome.json();
1742 assert!(result.is_err());
1743 }
1744
1745 #[test]
1746 fn test_outcome_result_invalid_base64_returns_err() {
1747 let outcome = make_success_outcome("not-valid-base64!!!");
1750 let err = outcome.result().unwrap_err();
1751 assert!(
1752 err.to_string().contains("base64"),
1753 "Error should mention base64 decode failure, got: {err}"
1754 );
1755 }
1756
1757 #[test]
1758 fn test_outcome_failure_result_returns_err() {
1759 let json = serde_json::json!({
1760 "final_execution_status": "FINAL",
1761 "status": {"Failure": {"ActionError": {"index": 0, "kind": {"FunctionCallError": {"ExecutionError": "test error"}}}}},
1762 "transaction": {
1763 "signer_id": "alice.near",
1764 "public_key": "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp",
1765 "nonce": 1,
1766 "receiver_id": "bob.near",
1767 "actions": [],
1768 "signature": "ed25519:3s1dvMqNDCByoMnDnkhB4GPjTSXCRt4nt3Af5n1RX8W7aJ2FC6MfRf5BNXZ52EBifNJnNVBsGvke6GRYuaEYJXt5",
1769 "hash": "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U"
1770 },
1771 "transaction_outcome": {
1772 "id": "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U",
1773 "outcome": {
1774 "executor_id": "alice.near",
1775 "gas_burnt": 223182562500_i64,
1776 "tokens_burnt": "22318256250000000000",
1777 "logs": [],
1778 "receipt_ids": [],
1779 "status": {"SuccessReceiptId": "3GTGoiN3FEoJenSw5ob4YMmFEV2Fbiichj3FDBnM78xK"}
1780 },
1781 "block_hash": "A6DJpKBhmAMmBuQXtY3dWbo8dGVSQ9yH7BQSJBfn8rBo",
1782 "proof": []
1783 },
1784 "receipts_outcome": []
1785 });
1786 let outcome: FinalExecutionOutcome = serde_json::from_value(json).unwrap();
1787
1788 assert!(outcome.is_failure());
1789 assert!(!outcome.is_success());
1790 assert!(outcome.result().is_err());
1791 assert!(outcome.json::<u64>().is_err());
1792 assert!(!outcome.transaction_hash().is_zero());
1794 assert!(outcome.total_gas_used().as_gas() > 0);
1795 }
1796
1797 #[test]
1802 fn test_execution_status_unknown() {
1803 let json = serde_json::json!("Unknown");
1804 let status: ExecutionStatus = serde_json::from_value(json).unwrap();
1805 assert!(matches!(status, ExecutionStatus::Unknown));
1806 }
1807
1808 #[test]
1809 fn test_execution_status_success_value() {
1810 let json = serde_json::json!({"SuccessValue": "aGVsbG8="});
1811 let status: ExecutionStatus = serde_json::from_value(json).unwrap();
1812 assert!(matches!(status, ExecutionStatus::SuccessValue(_)));
1813 }
1814
1815 #[test]
1816 fn test_execution_status_success_receipt_id() {
1817 let json =
1818 serde_json::json!({"SuccessReceiptId": "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U"});
1819 let status: ExecutionStatus = serde_json::from_value(json).unwrap();
1820 assert!(matches!(status, ExecutionStatus::SuccessReceiptId(_)));
1821 }
1822
1823 #[test]
1824 fn test_execution_status_failure_action_error() {
1825 let json = serde_json::json!({
1826 "Failure": {
1827 "ActionError": {
1828 "index": 0,
1829 "kind": {
1830 "FunctionCallError": {
1831 "ExecutionError": "Smart contract panicked"
1832 }
1833 }
1834 }
1835 }
1836 });
1837 let status: ExecutionStatus = serde_json::from_value(json).unwrap();
1838 match status {
1839 ExecutionStatus::Failure(ae) => {
1840 assert_eq!(ae.index, Some(0));
1841 }
1842 other => panic!("expected Failure, got: {other:?}"),
1843 }
1844 }
1845
1846 #[test]
1847 fn test_execution_status_failure_invalid_tx_error_rejected() {
1848 let json = serde_json::json!({
1849 "Failure": {
1850 "InvalidTxError": "InvalidSignature"
1851 }
1852 });
1853 let err = serde_json::from_value::<ExecutionStatus>(json)
1854 .expect_err("InvalidTxError should be rejected in receipt execution status");
1855 let msg = err.to_string();
1856 assert!(
1857 msg.contains("unexpected InvalidTxError"),
1858 "Expected descriptive error containing \"unexpected InvalidTxError\", got: {msg}"
1859 );
1860 }
1861
1862 #[test]
1867 fn test_global_contract_id_view_new_format_hash() {
1868 let json = serde_json::json!({"hash": "9SP8Y3sVADWNN5QoEB5CsvPUE5HT4o8YfBaCnhLss87K"});
1869 let id: GlobalContractIdentifierView = serde_json::from_value(json).unwrap();
1870 assert!(matches!(id, GlobalContractIdentifierView::CodeHash(_)));
1871 }
1872
1873 #[test]
1874 fn test_global_contract_id_view_new_format_account() {
1875 let json = serde_json::json!({"account_id": "alice.near"});
1876 let id: GlobalContractIdentifierView = serde_json::from_value(json).unwrap();
1877 assert!(matches!(id, GlobalContractIdentifierView::AccountId(_)));
1878 }
1879
1880 #[test]
1881 fn test_global_contract_id_view_deprecated_hash() {
1882 let json = serde_json::json!("9SP8Y3sVADWNN5QoEB5CsvPUE5HT4o8YfBaCnhLss87K");
1883 let id: GlobalContractIdentifierView = serde_json::from_value(json).unwrap();
1884 assert!(matches!(id, GlobalContractIdentifierView::CodeHash(_)));
1885 }
1886
1887 #[test]
1888 fn test_global_contract_id_view_deprecated_account() {
1889 let json = serde_json::json!("alice.near");
1890 let id: GlobalContractIdentifierView = serde_json::from_value(json).unwrap();
1891 assert!(matches!(id, GlobalContractIdentifierView::AccountId(_)));
1892 }
1893
1894 #[test]
1899 fn test_action_view_deterministic_state_init() {
1900 let json = serde_json::json!({
1901 "DeterministicStateInit": {
1902 "code": {"hash": "9SP8Y3sVADWNN5QoEB5CsvPUE5HT4o8YfBaCnhLss87K"},
1903 "data": {"a2V5": "dmFsdWU="},
1904 "deposit": "1000000000000000000000000"
1905 }
1906 });
1907 let action: ActionView = serde_json::from_value(json).unwrap();
1908 match action {
1909 ActionView::DeterministicStateInit {
1910 code,
1911 data,
1912 deposit,
1913 } => {
1914 assert!(matches!(code, GlobalContractIdentifierView::CodeHash(_)));
1915 assert_eq!(data.len(), 1);
1916 assert_eq!(data.get("a2V5").unwrap(), "dmFsdWU=");
1917 assert_eq!(deposit, NearToken::from_near(1));
1918 }
1919 _ => panic!("Expected DeterministicStateInit"),
1920 }
1921 }
1922
1923 #[test]
1924 fn test_action_view_deterministic_state_init_empty_data() {
1925 let json = serde_json::json!({
1926 "DeterministicStateInit": {
1927 "code": {"account_id": "publisher.near"},
1928 "deposit": "0"
1929 }
1930 });
1931 let action: ActionView = serde_json::from_value(json).unwrap();
1932 match action {
1933 ActionView::DeterministicStateInit { code, data, .. } => {
1934 assert!(matches!(code, GlobalContractIdentifierView::AccountId(_)));
1935 assert!(data.is_empty());
1936 }
1937 _ => panic!("Expected DeterministicStateInit"),
1938 }
1939 }
1940
1941 #[test]
1946 fn test_receipt_global_contract_distribution() {
1947 let json = serde_json::json!({
1948 "GlobalContractDistribution": {
1949 "id": {"hash": "9SP8Y3sVADWNN5QoEB5CsvPUE5HT4o8YfBaCnhLss87K"},
1950 "target_shard": 3,
1951 "already_delivered_shards": [0, 1, 2],
1952 "code": "AGFzbQ==",
1953 "nonce": 42
1954 }
1955 });
1956 let content: ReceiptContent = serde_json::from_value(json).unwrap();
1957 match content {
1958 ReceiptContent::GlobalContractDistribution {
1959 id,
1960 target_shard,
1961 already_delivered_shards,
1962 code,
1963 nonce,
1964 } => {
1965 assert!(matches!(id, GlobalContractIdentifierView::CodeHash(_)));
1966 assert_eq!(target_shard, 3);
1967 assert_eq!(already_delivered_shards, vec![0, 1, 2]);
1968 assert_eq!(code, "AGFzbQ==");
1969 assert_eq!(nonce, Some(42));
1970 }
1971 _ => panic!("Expected GlobalContractDistribution"),
1972 }
1973 }
1974
1975 #[test]
1976 fn test_receipt_global_contract_distribution_without_nonce() {
1977 let json = serde_json::json!({
1978 "GlobalContractDistribution": {
1979 "id": {"account_id": "publisher.near"},
1980 "target_shard": 0,
1981 "already_delivered_shards": [],
1982 "code": "AGFzbQ=="
1983 }
1984 });
1985 let content: ReceiptContent = serde_json::from_value(json).unwrap();
1986 match content {
1987 ReceiptContent::GlobalContractDistribution { nonce, .. } => {
1988 assert_eq!(nonce, None);
1989 }
1990 _ => panic!("Expected GlobalContractDistribution"),
1991 }
1992 }
1993
1994 #[test]
1995 fn test_gas_profile_entry_deserialization() {
1996 let json = serde_json::json!({
1997 "cost_category": "WASM_HOST_COST",
1998 "cost": "BASE",
1999 "gas_used": "2646228750"
2000 });
2001 let entry: GasProfileEntry = serde_json::from_value(json).unwrap();
2002 assert_eq!(entry.cost_category, "WASM_HOST_COST");
2003 assert_eq!(entry.cost, "BASE");
2004 assert_eq!(entry.gas_used, Gas::from_gas(2646228750));
2005 }
2006
2007 #[test]
2008 fn test_transaction_view_with_signature() {
2009 let json = serde_json::json!({
2010 "signer_id": "alice.near",
2011 "public_key": "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp",
2012 "nonce": 1,
2013 "receiver_id": "bob.near",
2014 "hash": "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U",
2015 "actions": [{"Transfer": {"deposit": "1000000000000000000000000"}}],
2016 "signature": "ed25519:3s1dvMqNDCByoMnDnkhB4GPjTSXCRt4nt3Af5n1RX8W7aJ2FC6MfRf5BNXZ52EBifNJnNVBsGvke6GRYuaEYJXt5"
2017 });
2018 let tx: TransactionView = serde_json::from_value(json).unwrap();
2019 assert_eq!(tx.signer_id.as_str(), "alice.near");
2020 assert!(tx.signature.to_string().starts_with("ed25519:"));
2021 }
2022}