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::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 success_value(&self) -> Option<Vec<u8>> {
595 match &self.status {
596 FinalExecutionStatus::SuccessValue(s) => STANDARD.decode(s).ok(),
597 _ => None,
598 }
599 }
600
601 pub fn success_value_string(&self) -> Option<String> {
603 self.success_value().and_then(|v| String::from_utf8(v).ok())
604 }
605
606 pub fn success_value_json<T: serde::de::DeserializeOwned>(&self) -> Option<T> {
608 self.success_value()
609 .and_then(|v| serde_json::from_slice(&v).ok())
610 }
611
612 pub fn failure_message(&self) -> Option<String> {
614 match &self.status {
615 FinalExecutionStatus::Failure(err) => Some(err.to_string()),
616 _ => None,
617 }
618 }
619
620 pub fn failure_error(&self) -> Option<&TxExecutionError> {
622 match &self.status {
623 FinalExecutionStatus::Failure(err) => Some(err),
624 _ => None,
625 }
626 }
627
628 pub fn transaction_hash(&self) -> &CryptoHash {
630 &self.transaction_outcome.id
631 }
632
633 pub fn total_gas_used(&self) -> Gas {
635 let tx_gas = self.transaction_outcome.outcome.gas_burnt.as_gas();
636 let receipt_gas: u64 = self
637 .receipts_outcome
638 .iter()
639 .map(|r| r.outcome.gas_burnt.as_gas())
640 .sum();
641 Gas::from_gas(tx_gas + receipt_gas)
642 }
643}
644
645#[derive(Debug, Clone, Deserialize)]
649pub enum ExecutionStatus {
650 Unknown,
652 Failure(TxExecutionError),
654 SuccessValue(String),
656 SuccessReceiptId(CryptoHash),
658}
659
660#[derive(Debug, Clone, Deserialize)]
662pub struct TransactionView {
663 pub signer_id: AccountId,
665 pub public_key: PublicKey,
667 pub nonce: u64,
669 pub receiver_id: AccountId,
671 pub hash: CryptoHash,
673 #[serde(default)]
675 pub actions: Vec<ActionView>,
676 pub signature: Signature,
678 #[serde(default)]
680 pub priority_fee: Option<u64>,
681 #[serde(default)]
683 pub nonce_index: Option<u16>,
684}
685
686#[derive(Deserialize)]
695#[serde(untagged)]
696enum GlobalContractIdCompat {
697 CodeHash { hash: CryptoHash },
698 AccountId { account_id: AccountId },
699 DeprecatedCodeHash(CryptoHash),
700 DeprecatedAccountId(AccountId),
701}
702
703#[derive(Debug, Clone, Deserialize)]
709#[serde(from = "GlobalContractIdCompat")]
710pub enum GlobalContractIdentifierView {
711 CodeHash(CryptoHash),
713 AccountId(AccountId),
715}
716
717impl From<GlobalContractIdCompat> for GlobalContractIdentifierView {
718 fn from(compat: GlobalContractIdCompat) -> Self {
719 match compat {
720 GlobalContractIdCompat::CodeHash { hash }
721 | GlobalContractIdCompat::DeprecatedCodeHash(hash) => Self::CodeHash(hash),
722 GlobalContractIdCompat::AccountId { account_id }
723 | GlobalContractIdCompat::DeprecatedAccountId(account_id) => {
724 Self::AccountId(account_id)
725 }
726 }
727 }
728}
729
730#[derive(Debug, Clone, Deserialize)]
736pub struct DelegateActionView {
737 pub sender_id: AccountId,
739 pub receiver_id: AccountId,
741 pub actions: Vec<ActionView>,
743 pub nonce: u64,
745 pub max_block_height: u64,
747 pub public_key: PublicKey,
749}
750
751#[derive(Debug, Clone, Deserialize)]
753#[serde(rename_all = "PascalCase")]
754pub enum ActionView {
755 CreateAccount,
756 DeployContract {
757 code: String, },
759 FunctionCall {
760 method_name: String,
761 args: String, gas: Gas,
763 deposit: NearToken,
764 },
765 Transfer {
766 deposit: NearToken,
767 },
768 Stake {
769 stake: NearToken,
770 public_key: PublicKey,
771 },
772 AddKey {
773 public_key: PublicKey,
774 access_key: AccessKeyDetails,
775 },
776 DeleteKey {
777 public_key: PublicKey,
778 },
779 DeleteAccount {
780 beneficiary_id: AccountId,
781 },
782 Delegate {
783 delegate_action: DelegateActionView,
784 signature: Signature,
785 },
786 #[serde(rename = "DeployGlobalContract")]
787 DeployGlobalContract {
788 code: String,
789 },
790 #[serde(rename = "DeployGlobalContractByAccountId")]
791 DeployGlobalContractByAccountId {
792 code: String,
793 },
794 #[serde(rename = "UseGlobalContract")]
795 UseGlobalContract {
796 code_hash: CryptoHash,
797 },
798 #[serde(rename = "UseGlobalContractByAccountId")]
799 UseGlobalContractByAccountId {
800 account_id: AccountId,
801 },
802 #[serde(rename = "DeterministicStateInit")]
803 DeterministicStateInit {
804 code: GlobalContractIdentifierView,
805 #[serde(default)]
806 data: BTreeMap<String, String>,
807 deposit: NearToken,
808 },
809 TransferToGasKey {
810 public_key: PublicKey,
811 deposit: NearToken,
812 },
813 WithdrawFromGasKey {
814 public_key: PublicKey,
815 amount: NearToken,
816 },
817}
818
819#[derive(Debug, Clone, Deserialize)]
821pub struct MerklePathItem {
822 pub hash: CryptoHash,
824 pub direction: MerkleDirection,
826}
827
828#[derive(Debug, Clone, Deserialize)]
830pub enum MerkleDirection {
831 Left,
832 Right,
833}
834
835#[derive(Debug, Clone, Deserialize)]
837pub struct ExecutionOutcomeWithId {
838 pub id: CryptoHash,
840 pub outcome: ExecutionOutcome,
842 #[serde(default)]
844 pub proof: Vec<MerklePathItem>,
845 pub block_hash: CryptoHash,
847}
848
849#[derive(Debug, Clone, Deserialize)]
851pub struct ExecutionOutcome {
852 pub executor_id: AccountId,
854 pub gas_burnt: Gas,
856 pub tokens_burnt: NearToken,
858 pub logs: Vec<String>,
860 pub receipt_ids: Vec<CryptoHash>,
862 pub status: ExecutionStatus,
864 #[serde(default)]
866 pub metadata: Option<ExecutionMetadata>,
867}
868
869#[derive(Debug, Clone, Deserialize)]
871pub struct ExecutionMetadata {
872 pub version: u32,
874 #[serde(default)]
876 pub gas_profile: Option<Vec<GasProfileEntry>>,
877}
878
879#[derive(Debug, Clone, Deserialize)]
881pub struct GasProfileEntry {
882 pub cost_category: String,
884 pub cost: String,
886 #[serde(deserialize_with = "gas_dec_format")]
888 pub gas_used: Gas,
889}
890
891#[derive(Debug, Clone, Deserialize)]
893pub struct ViewFunctionResult {
894 pub result: Vec<u8>,
896 pub logs: Vec<String>,
898 pub block_height: u64,
900 pub block_hash: CryptoHash,
902}
903
904impl ViewFunctionResult {
905 pub fn bytes(&self) -> &[u8] {
907 &self.result
908 }
909
910 pub fn as_string(&self) -> Result<String, std::string::FromUtf8Error> {
912 String::from_utf8(self.result.clone())
913 }
914
915 pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T, serde_json::Error> {
924 serde_json::from_slice(&self.result)
925 }
926
927 pub fn borsh<T: borsh::BorshDeserialize>(&self) -> Result<T, borsh::io::Error> {
938 borsh::from_slice(&self.result)
939 }
940}
941
942#[derive(Debug, Clone, Deserialize)]
948pub struct Receipt {
949 pub predecessor_id: AccountId,
951 pub receiver_id: AccountId,
953 pub receipt_id: CryptoHash,
955 pub receipt: ReceiptContent,
957 #[serde(default)]
959 pub priority: Option<u64>,
960}
961
962#[derive(Debug, Clone, Deserialize)]
964pub enum ReceiptContent {
965 Action(ActionReceiptData),
967 Data(DataReceiptData),
969 GlobalContractDistribution {
971 id: GlobalContractIdentifierView,
973 target_shard: u64,
975 #[serde(default)]
977 already_delivered_shards: Vec<u64>,
978 code: String,
980 #[serde(default)]
982 nonce: Option<u64>,
983 },
984}
985
986#[derive(Debug, Clone, Deserialize)]
988pub struct DataReceiverView {
989 pub data_id: CryptoHash,
991 pub receiver_id: AccountId,
993}
994
995#[derive(Debug, Clone, Deserialize)]
997pub struct ActionReceiptData {
998 pub signer_id: AccountId,
1000 pub signer_public_key: PublicKey,
1002 pub gas_price: NearToken,
1004 #[serde(default)]
1006 pub output_data_receivers: Vec<DataReceiverView>,
1007 #[serde(default)]
1009 pub input_data_ids: Vec<CryptoHash>,
1010 pub actions: Vec<ActionView>,
1012 #[serde(default)]
1014 pub is_promise_yield: Option<bool>,
1015}
1016
1017#[derive(Debug, Clone, Deserialize)]
1019pub struct DataReceiptData {
1020 pub data_id: CryptoHash,
1022 #[serde(default)]
1024 pub data: Option<String>,
1025}
1026
1027#[derive(Debug, Clone, Deserialize)]
1031pub struct SendTxWithReceiptsResponse {
1032 pub final_execution_status: TxExecutionStatus,
1034 #[serde(flatten)]
1036 pub outcome: Option<FinalExecutionOutcomeWithReceipts>,
1037}
1038
1039#[derive(Debug, Clone, Deserialize)]
1044pub struct FinalExecutionOutcomeWithReceipts {
1045 pub status: FinalExecutionStatus,
1047 pub transaction: TransactionView,
1049 pub transaction_outcome: ExecutionOutcomeWithId,
1051 pub receipts_outcome: Vec<ExecutionOutcomeWithId>,
1053 #[serde(default)]
1055 pub receipts: Vec<Receipt>,
1056}
1057
1058impl FinalExecutionOutcomeWithReceipts {
1059 pub fn is_success(&self) -> bool {
1061 matches!(&self.status, FinalExecutionStatus::SuccessValue(_))
1062 }
1063
1064 pub fn is_failure(&self) -> bool {
1066 matches!(&self.status, FinalExecutionStatus::Failure(_))
1067 }
1068
1069 pub fn transaction_hash(&self) -> &CryptoHash {
1071 &self.transaction_outcome.id
1072 }
1073}
1074
1075#[derive(Debug, Clone, Deserialize)]
1081pub struct StatusResponse {
1082 pub protocol_version: u32,
1084 pub latest_protocol_version: u32,
1086 pub chain_id: String,
1088 pub genesis_hash: CryptoHash,
1090 #[serde(default)]
1092 pub rpc_addr: Option<String>,
1093 #[serde(default)]
1095 pub node_public_key: Option<String>,
1096 #[serde(default)]
1098 pub node_key: Option<String>,
1099 #[serde(default)]
1101 pub validator_account_id: Option<AccountId>,
1102 #[serde(default)]
1104 pub validator_public_key: Option<PublicKey>,
1105 #[serde(default)]
1107 pub validators: Vec<ValidatorInfo>,
1108 pub sync_info: SyncInfo,
1110 pub version: NodeVersion,
1112 #[serde(default)]
1114 pub uptime_sec: Option<u64>,
1115}
1116
1117#[derive(Debug, Clone, Deserialize)]
1119pub struct ValidatorInfo {
1120 pub account_id: AccountId,
1122}
1123
1124#[derive(Debug, Clone, Deserialize)]
1126pub struct SyncInfo {
1127 pub latest_block_hash: CryptoHash,
1129 pub latest_block_height: u64,
1131 #[serde(default)]
1133 pub latest_state_root: Option<CryptoHash>,
1134 pub latest_block_time: String,
1136 pub syncing: bool,
1138 #[serde(default)]
1140 pub earliest_block_hash: Option<CryptoHash>,
1141 #[serde(default)]
1143 pub earliest_block_height: Option<u64>,
1144 #[serde(default)]
1146 pub earliest_block_time: Option<String>,
1147 #[serde(default)]
1149 pub epoch_id: Option<CryptoHash>,
1150 #[serde(default)]
1152 pub epoch_start_height: Option<u64>,
1153}
1154
1155#[derive(Debug, Clone, Deserialize)]
1157pub struct NodeVersion {
1158 pub version: String,
1160 pub build: String,
1162 #[serde(default)]
1164 pub commit: Option<String>,
1165 #[serde(default)]
1167 pub rustc_version: Option<String>,
1168}
1169
1170#[cfg(test)]
1171mod tests {
1172 use super::*;
1173
1174 fn make_account_view(amount: u128, locked: u128, storage_usage: u64) -> AccountView {
1175 AccountView {
1176 amount: NearToken::from_yoctonear(amount),
1177 locked: NearToken::from_yoctonear(locked),
1178 code_hash: CryptoHash::default(),
1179 storage_usage,
1180 storage_paid_at: 0,
1181 global_contract_hash: None,
1182 global_contract_account_id: None,
1183 block_height: 0,
1184 block_hash: CryptoHash::default(),
1185 }
1186 }
1187
1188 #[test]
1189 fn test_available_balance_no_stake_no_storage() {
1190 let view = make_account_view(1_000_000_000_000_000_000_000_000, 0, 0); assert_eq!(view.available(), view.amount);
1193 }
1194
1195 #[test]
1196 fn test_available_balance_with_storage_no_stake() {
1197 let amount = 1_000_000_000_000_000_000_000_000u128; let storage_usage = 1000u64;
1202 let storage_cost = STORAGE_AMOUNT_PER_BYTE * storage_usage as u128; let view = make_account_view(amount, 0, storage_usage);
1205 let expected = NearToken::from_yoctonear(amount - storage_cost);
1206 assert_eq!(view.available(), expected);
1207 }
1208
1209 #[test]
1210 fn test_available_balance_stake_covers_storage() {
1211 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;
1217
1218 let view = make_account_view(amount, locked, storage_usage);
1219 assert_eq!(view.available(), view.amount);
1221 }
1222
1223 #[test]
1224 fn test_available_balance_stake_partially_covers_storage() {
1225 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;
1233 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);
1237 let expected = NearToken::from_yoctonear(amount - reserved);
1238 assert_eq!(view.available(), expected);
1239 }
1240
1241 #[test]
1242 fn test_storage_cost_calculation() {
1243 let storage_usage = 1000u64;
1244 let view = make_account_view(1_000_000_000_000_000_000_000_000, 0, storage_usage);
1245
1246 let expected_cost = STORAGE_AMOUNT_PER_BYTE * storage_usage as u128;
1247 assert_eq!(
1248 view.storage_cost(),
1249 NearToken::from_yoctonear(expected_cost)
1250 );
1251 }
1252
1253 #[test]
1254 fn test_storage_cost_zero_when_stake_covers() {
1255 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);
1258
1259 assert_eq!(view.storage_cost(), NearToken::ZERO);
1260 }
1261
1262 #[test]
1263 fn test_account_balance_from_view() {
1264 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;
1267
1268 let view = make_account_view(amount, locked, storage_usage);
1269 let balance = AccountBalance::from(view.clone());
1270
1271 assert_eq!(balance.total, view.amount);
1272 assert_eq!(balance.available, view.available());
1273 assert_eq!(balance.locked, view.locked);
1274 assert_eq!(balance.storage_cost, view.storage_cost());
1275 assert_eq!(balance.storage_usage, storage_usage);
1276 }
1277
1278 fn make_view_result(result: Vec<u8>) -> ViewFunctionResult {
1283 ViewFunctionResult {
1284 result,
1285 logs: vec![],
1286 block_height: 12345,
1287 block_hash: CryptoHash::default(),
1288 }
1289 }
1290
1291 #[test]
1292 fn test_view_function_result_bytes() {
1293 let data = vec![1, 2, 3, 4, 5];
1294 let result = make_view_result(data.clone());
1295 assert_eq!(result.bytes(), &data[..]);
1296 }
1297
1298 #[test]
1299 fn test_view_function_result_as_string() {
1300 let result = make_view_result(b"hello world".to_vec());
1301 assert_eq!(result.as_string().unwrap(), "hello world");
1302 }
1303
1304 #[test]
1305 fn test_view_function_result_json() {
1306 let result = make_view_result(b"42".to_vec());
1307 let value: u64 = result.json().unwrap();
1308 assert_eq!(value, 42);
1309 }
1310
1311 #[test]
1312 fn test_view_function_result_json_object() {
1313 let result = make_view_result(b"{\"count\":123}".to_vec());
1314 let value: serde_json::Value = result.json().unwrap();
1315 assert_eq!(value["count"], 123);
1316 }
1317
1318 #[test]
1319 fn test_view_function_result_borsh() {
1320 let original: u64 = 42;
1322 let encoded = borsh::to_vec(&original).unwrap();
1323 let result = make_view_result(encoded);
1324
1325 let decoded: u64 = result.borsh().unwrap();
1326 assert_eq!(decoded, original);
1327 }
1328
1329 #[test]
1330 fn test_view_function_result_borsh_struct() {
1331 #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, PartialEq, Debug)]
1332 struct TestStruct {
1333 value: u64,
1334 name: String,
1335 }
1336
1337 let original = TestStruct {
1338 value: 123,
1339 name: "test".to_string(),
1340 };
1341 let encoded = borsh::to_vec(&original).unwrap();
1342 let result = make_view_result(encoded);
1343
1344 let decoded: TestStruct = result.borsh().unwrap();
1345 assert_eq!(decoded, original);
1346 }
1347
1348 #[test]
1349 fn test_view_function_result_borsh_error() {
1350 let result = make_view_result(vec![1, 2, 3]);
1352 let decoded: Result<u64, _> = result.borsh();
1353 assert!(decoded.is_err());
1354 }
1355
1356 #[test]
1361 fn test_gas_profile_entry_string_gas_used() {
1362 let json = serde_json::json!({
1363 "cost_category": "WASM_HOST_COST",
1364 "cost": "BASE",
1365 "gas_used": "123456789"
1366 });
1367 let entry: GasProfileEntry = serde_json::from_value(json).unwrap();
1368 assert_eq!(entry.gas_used.as_gas(), 123456789);
1369 }
1370
1371 #[test]
1372 fn test_gas_profile_entry_numeric_gas_used() {
1373 let json = serde_json::json!({
1374 "cost_category": "ACTION_COST",
1375 "cost": "FUNCTION_CALL",
1376 "gas_used": 999000000
1377 });
1378 let entry: GasProfileEntry = serde_json::from_value(json).unwrap();
1379 assert_eq!(entry.gas_used.as_gas(), 999000000);
1380 }
1381
1382 #[test]
1383 fn test_gas_key_function_call_deserialization() {
1384 let json = serde_json::json!({
1385 "GasKeyFunctionCall": {
1386 "balance": "1000000000000000000000000",
1387 "num_nonces": 5,
1388 "allowance": "500000000000000000000000",
1389 "receiver_id": "app.near",
1390 "method_names": ["call_method"]
1391 }
1392 });
1393 let perm: AccessKeyPermissionView = serde_json::from_value(json).unwrap();
1394 assert!(matches!(
1395 perm,
1396 AccessKeyPermissionView::GasKeyFunctionCall { .. }
1397 ));
1398 }
1399
1400 #[test]
1401 fn test_gas_key_full_access_deserialization() {
1402 let json = serde_json::json!({
1403 "GasKeyFullAccess": {
1404 "balance": "1000000000000000000000000",
1405 "num_nonces": 10
1406 }
1407 });
1408 let perm: AccessKeyPermissionView = serde_json::from_value(json).unwrap();
1409 assert!(matches!(
1410 perm,
1411 AccessKeyPermissionView::GasKeyFullAccess { .. }
1412 ));
1413 }
1414
1415 #[test]
1416 fn test_transfer_to_gas_key_action_view_deserialization() {
1417 let json = serde_json::json!({
1418 "TransferToGasKey": {
1419 "public_key": "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp",
1420 "deposit": "1000000000000000000000000"
1421 }
1422 });
1423 let action: ActionView = serde_json::from_value(json).unwrap();
1424 assert!(matches!(action, ActionView::TransferToGasKey { .. }));
1425 }
1426
1427 #[test]
1428 fn test_delegate_action_view_deserialization() {
1429 let json = serde_json::json!({
1430 "Delegate": {
1431 "delegate_action": {
1432 "sender_id": "alice.near",
1433 "receiver_id": "contract.near",
1434 "actions": [
1435 {"FunctionCall": {
1436 "method_name": "do_something",
1437 "args": "e30=",
1438 "gas": 30000000000000_u64,
1439 "deposit": "0"
1440 }}
1441 ],
1442 "nonce": 42,
1443 "max_block_height": 100000,
1444 "public_key": "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp"
1445 },
1446 "signature": "ed25519:3s1dvMqNDCByoMnDnkhB4GPjTSXCRt4nt3Af5n1RX8W7aJ2FC6MfRf5BNXZ52EBifNJnNVBsGvke6GRYuaEYJXt5"
1447 }
1448 });
1449 let action: ActionView = serde_json::from_value(json).unwrap();
1450 match action {
1451 ActionView::Delegate {
1452 delegate_action,
1453 signature,
1454 } => {
1455 assert_eq!(delegate_action.sender_id.as_ref(), "alice.near");
1456 assert_eq!(delegate_action.receiver_id.as_ref(), "contract.near");
1457 assert_eq!(delegate_action.nonce, 42);
1458 assert_eq!(delegate_action.max_block_height, 100000);
1459 assert_eq!(delegate_action.actions.len(), 1);
1460 assert!(signature.to_string().starts_with("ed25519:"));
1461 }
1462 _ => panic!("Expected Delegate action"),
1463 }
1464 }
1465
1466 #[test]
1467 fn test_withdraw_from_gas_key_action_view_deserialization() {
1468 let json = serde_json::json!({
1469 "WithdrawFromGasKey": {
1470 "public_key": "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp",
1471 "amount": "500000000000000000000000"
1472 }
1473 });
1474 let action: ActionView = serde_json::from_value(json).unwrap();
1475 assert!(matches!(action, ActionView::WithdrawFromGasKey { .. }));
1476 }
1477
1478 #[test]
1483 fn test_final_execution_status_default() {
1484 let status = FinalExecutionStatus::default();
1485 assert!(matches!(status, FinalExecutionStatus::NotStarted));
1486 }
1487
1488 #[test]
1489 fn test_final_execution_status_not_started() {
1490 let json = serde_json::json!("NotStarted");
1491 let status: FinalExecutionStatus = serde_json::from_value(json).unwrap();
1492 assert!(matches!(status, FinalExecutionStatus::NotStarted));
1493 }
1494
1495 #[test]
1496 fn test_final_execution_status_started() {
1497 let json = serde_json::json!("Started");
1498 let status: FinalExecutionStatus = serde_json::from_value(json).unwrap();
1499 assert!(matches!(status, FinalExecutionStatus::Started));
1500 }
1501
1502 #[test]
1503 fn test_final_execution_status_success_value() {
1504 let json = serde_json::json!({"SuccessValue": "aGVsbG8="});
1505 let status: FinalExecutionStatus = serde_json::from_value(json).unwrap();
1506 assert!(matches!(status, FinalExecutionStatus::SuccessValue(ref s) if s == "aGVsbG8="));
1507 }
1508
1509 #[test]
1510 fn test_final_execution_status_failure() {
1511 let json = serde_json::json!({
1512 "Failure": {
1513 "ActionError": {
1514 "index": 0,
1515 "kind": {
1516 "FunctionCallError": {
1517 "ExecutionError": "Smart contract panicked"
1518 }
1519 }
1520 }
1521 }
1522 });
1523 let status: FinalExecutionStatus = serde_json::from_value(json).unwrap();
1524 assert!(matches!(status, FinalExecutionStatus::Failure(_)));
1525 }
1526
1527 #[test]
1532 fn test_send_tx_response_with_outcome() {
1533 let json = serde_json::json!({
1534 "final_execution_status": "FINAL",
1535 "status": {"SuccessValue": ""},
1536 "transaction": {
1537 "signer_id": "alice.near",
1538 "public_key": "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp",
1539 "nonce": 1,
1540 "receiver_id": "bob.near",
1541 "actions": [{"Transfer": {"deposit": "1000000000000000000000000"}}],
1542 "signature": "ed25519:3s1dvMqNDCByoMnDnkhB4GPjTSXCRt4nt3Af5n1RX8W7aJ2FC6MfRf5BNXZ52EBifNJnNVBsGvke6GRYuaEYJXt5",
1543 "hash": "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U"
1544 },
1545 "transaction_outcome": {
1546 "id": "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U",
1547 "outcome": {
1548 "executor_id": "alice.near",
1549 "gas_burnt": 223182562500_i64,
1550 "tokens_burnt": "22318256250000000000",
1551 "logs": [],
1552 "receipt_ids": ["3GTGoiN3FEoJenSw5ob4YMmFEV2Fbiichj3FDBnM78xK"],
1553 "status": {"SuccessReceiptId": "3GTGoiN3FEoJenSw5ob4YMmFEV2Fbiichj3FDBnM78xK"}
1554 },
1555 "block_hash": "A6DJpKBhmAMmBuQXtY3dWbo8dGVSQ9yH7BQSJBfn8rBo",
1556 "proof": []
1557 },
1558 "receipts_outcome": []
1559 });
1560 let response: SendTxResponse = serde_json::from_value(json).unwrap();
1561 assert_eq!(response.final_execution_status, TxExecutionStatus::Final);
1562 let outcome = response.outcome.unwrap();
1563 assert!(outcome.is_success());
1564 assert!(!outcome.is_failure());
1565 }
1566
1567 #[test]
1568 fn test_send_tx_response_pending_none() {
1569 let json = serde_json::json!({
1570 "final_execution_status": "NONE"
1571 });
1572 let response: SendTxResponse = serde_json::from_value(json).unwrap();
1573 assert_eq!(response.final_execution_status, TxExecutionStatus::None);
1574 assert!(response.outcome.is_none());
1575 assert!(response.transaction_hash.is_zero());
1577 }
1578
1579 #[test]
1580 fn test_final_execution_outcome_failure() {
1581 let json = serde_json::json!({
1582 "final_execution_status": "EXECUTED_OPTIMISTIC",
1583 "status": {
1584 "Failure": {
1585 "ActionError": {
1586 "index": 0,
1587 "kind": {
1588 "FunctionCallError": {
1589 "ExecutionError": "Smart contract panicked"
1590 }
1591 }
1592 }
1593 }
1594 },
1595 "transaction": {
1596 "signer_id": "alice.near",
1597 "public_key": "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp",
1598 "nonce": 1,
1599 "receiver_id": "bob.near",
1600 "actions": [],
1601 "signature": "ed25519:3s1dvMqNDCByoMnDnkhB4GPjTSXCRt4nt3Af5n1RX8W7aJ2FC6MfRf5BNXZ52EBifNJnNVBsGvke6GRYuaEYJXt5",
1602 "hash": "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U"
1603 },
1604 "transaction_outcome": {
1605 "id": "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U",
1606 "outcome": {
1607 "executor_id": "alice.near",
1608 "gas_burnt": 0,
1609 "tokens_burnt": "0",
1610 "logs": [],
1611 "receipt_ids": [],
1612 "status": "Unknown"
1613 },
1614 "block_hash": "A6DJpKBhmAMmBuQXtY3dWbo8dGVSQ9yH7BQSJBfn8rBo",
1615 "proof": []
1616 },
1617 "receipts_outcome": []
1618 });
1619 let response: SendTxResponse = serde_json::from_value(json).unwrap();
1620 let outcome = response.outcome.unwrap();
1621 assert!(outcome.is_failure());
1622 assert!(!outcome.is_success());
1623 assert!(outcome.failure_message().is_some());
1624 assert!(outcome.failure_error().is_some());
1625 }
1626
1627 #[test]
1632 fn test_execution_status_unknown() {
1633 let json = serde_json::json!("Unknown");
1634 let status: ExecutionStatus = serde_json::from_value(json).unwrap();
1635 assert!(matches!(status, ExecutionStatus::Unknown));
1636 }
1637
1638 #[test]
1639 fn test_execution_status_success_value() {
1640 let json = serde_json::json!({"SuccessValue": "aGVsbG8="});
1641 let status: ExecutionStatus = serde_json::from_value(json).unwrap();
1642 assert!(matches!(status, ExecutionStatus::SuccessValue(_)));
1643 }
1644
1645 #[test]
1646 fn test_execution_status_success_receipt_id() {
1647 let json =
1648 serde_json::json!({"SuccessReceiptId": "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U"});
1649 let status: ExecutionStatus = serde_json::from_value(json).unwrap();
1650 assert!(matches!(status, ExecutionStatus::SuccessReceiptId(_)));
1651 }
1652
1653 #[test]
1658 fn test_global_contract_id_view_new_format_hash() {
1659 let json = serde_json::json!({"hash": "9SP8Y3sVADWNN5QoEB5CsvPUE5HT4o8YfBaCnhLss87K"});
1660 let id: GlobalContractIdentifierView = serde_json::from_value(json).unwrap();
1661 assert!(matches!(id, GlobalContractIdentifierView::CodeHash(_)));
1662 }
1663
1664 #[test]
1665 fn test_global_contract_id_view_new_format_account() {
1666 let json = serde_json::json!({"account_id": "alice.near"});
1667 let id: GlobalContractIdentifierView = serde_json::from_value(json).unwrap();
1668 assert!(matches!(id, GlobalContractIdentifierView::AccountId(_)));
1669 }
1670
1671 #[test]
1672 fn test_global_contract_id_view_deprecated_hash() {
1673 let json = serde_json::json!("9SP8Y3sVADWNN5QoEB5CsvPUE5HT4o8YfBaCnhLss87K");
1674 let id: GlobalContractIdentifierView = serde_json::from_value(json).unwrap();
1675 assert!(matches!(id, GlobalContractIdentifierView::CodeHash(_)));
1676 }
1677
1678 #[test]
1679 fn test_global_contract_id_view_deprecated_account() {
1680 let json = serde_json::json!("alice.near");
1681 let id: GlobalContractIdentifierView = serde_json::from_value(json).unwrap();
1682 assert!(matches!(id, GlobalContractIdentifierView::AccountId(_)));
1683 }
1684
1685 #[test]
1690 fn test_action_view_deterministic_state_init() {
1691 let json = serde_json::json!({
1692 "DeterministicStateInit": {
1693 "code": {"hash": "9SP8Y3sVADWNN5QoEB5CsvPUE5HT4o8YfBaCnhLss87K"},
1694 "data": {"a2V5": "dmFsdWU="},
1695 "deposit": "1000000000000000000000000"
1696 }
1697 });
1698 let action: ActionView = serde_json::from_value(json).unwrap();
1699 match action {
1700 ActionView::DeterministicStateInit {
1701 code,
1702 data,
1703 deposit,
1704 } => {
1705 assert!(matches!(code, GlobalContractIdentifierView::CodeHash(_)));
1706 assert_eq!(data.len(), 1);
1707 assert_eq!(data.get("a2V5").unwrap(), "dmFsdWU=");
1708 assert_eq!(deposit, NearToken::from_near(1));
1709 }
1710 _ => panic!("Expected DeterministicStateInit"),
1711 }
1712 }
1713
1714 #[test]
1715 fn test_action_view_deterministic_state_init_empty_data() {
1716 let json = serde_json::json!({
1717 "DeterministicStateInit": {
1718 "code": {"account_id": "publisher.near"},
1719 "deposit": "0"
1720 }
1721 });
1722 let action: ActionView = serde_json::from_value(json).unwrap();
1723 match action {
1724 ActionView::DeterministicStateInit { code, data, .. } => {
1725 assert!(matches!(code, GlobalContractIdentifierView::AccountId(_)));
1726 assert!(data.is_empty());
1727 }
1728 _ => panic!("Expected DeterministicStateInit"),
1729 }
1730 }
1731
1732 #[test]
1737 fn test_receipt_global_contract_distribution() {
1738 let json = serde_json::json!({
1739 "GlobalContractDistribution": {
1740 "id": {"hash": "9SP8Y3sVADWNN5QoEB5CsvPUE5HT4o8YfBaCnhLss87K"},
1741 "target_shard": 3,
1742 "already_delivered_shards": [0, 1, 2],
1743 "code": "AGFzbQ==",
1744 "nonce": 42
1745 }
1746 });
1747 let content: ReceiptContent = serde_json::from_value(json).unwrap();
1748 match content {
1749 ReceiptContent::GlobalContractDistribution {
1750 id,
1751 target_shard,
1752 already_delivered_shards,
1753 code,
1754 nonce,
1755 } => {
1756 assert!(matches!(id, GlobalContractIdentifierView::CodeHash(_)));
1757 assert_eq!(target_shard, 3);
1758 assert_eq!(already_delivered_shards, vec![0, 1, 2]);
1759 assert_eq!(code, "AGFzbQ==");
1760 assert_eq!(nonce, Some(42));
1761 }
1762 _ => panic!("Expected GlobalContractDistribution"),
1763 }
1764 }
1765
1766 #[test]
1767 fn test_receipt_global_contract_distribution_without_nonce() {
1768 let json = serde_json::json!({
1769 "GlobalContractDistribution": {
1770 "id": {"account_id": "publisher.near"},
1771 "target_shard": 0,
1772 "already_delivered_shards": [],
1773 "code": "AGFzbQ=="
1774 }
1775 });
1776 let content: ReceiptContent = serde_json::from_value(json).unwrap();
1777 match content {
1778 ReceiptContent::GlobalContractDistribution { nonce, .. } => {
1779 assert_eq!(nonce, None);
1780 }
1781 _ => panic!("Expected GlobalContractDistribution"),
1782 }
1783 }
1784
1785 #[test]
1786 fn test_gas_profile_entry_deserialization() {
1787 let json = serde_json::json!({
1788 "cost_category": "WASM_HOST_COST",
1789 "cost": "BASE",
1790 "gas_used": "2646228750"
1791 });
1792 let entry: GasProfileEntry = serde_json::from_value(json).unwrap();
1793 assert_eq!(entry.cost_category, "WASM_HOST_COST");
1794 assert_eq!(entry.cost, "BASE");
1795 assert_eq!(entry.gas_used, Gas::from_gas(2646228750));
1796 }
1797
1798 #[test]
1799 fn test_transaction_view_with_signature() {
1800 let json = serde_json::json!({
1801 "signer_id": "alice.near",
1802 "public_key": "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp",
1803 "nonce": 1,
1804 "receiver_id": "bob.near",
1805 "hash": "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U",
1806 "actions": [{"Transfer": {"deposit": "1000000000000000000000000"}}],
1807 "signature": "ed25519:3s1dvMqNDCByoMnDnkhB4GPjTSXCRt4nt3Af5n1RX8W7aJ2FC6MfRf5BNXZ52EBifNJnNVBsGvke6GRYuaEYJXt5"
1808 });
1809 let tx: TransactionView = serde_json::from_value(json).unwrap();
1810 assert_eq!(tx.signer_id.as_str(), "alice.near");
1811 assert!(tx.signature.to_string().starts_with("ed25519:"));
1812 }
1813}