1use crate::hash::CryptoHash;
2use crate::types::{Balance, Nonce, NonceIndex, StorageUsage};
3use borsh::{BorshDeserialize, BorshSerialize};
4pub use near_account_id as id;
5use near_account_id::AccountId;
6use near_schema_checker_lib::ProtocolSchema;
7use std::borrow::Cow;
8use std::io;
9
10#[derive(
11 BorshSerialize,
12 BorshDeserialize,
13 PartialEq,
14 PartialOrd,
15 Eq,
16 Clone,
17 Copy,
18 Debug,
19 Default,
20 serde::Serialize,
21 serde::Deserialize,
22 ProtocolSchema,
23)]
24#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
25pub enum AccountVersion {
26 #[default]
27 V1,
28 V2,
29}
30
31#[derive(PartialEq, Eq, Debug, Clone, ProtocolSchema)]
39pub enum Account {
40 V1(AccountV1),
41 V2(AccountV2),
42}
43
44#[derive(
46 BorshSerialize,
47 serde::Serialize,
48 serde::Deserialize,
49 PartialEq,
50 Eq,
51 Debug,
52 Clone,
53 ProtocolSchema,
54)]
55pub struct AccountV1 {
56 amount: Balance,
58 locked: Balance,
60 code_hash: CryptoHash,
62 storage_usage: StorageUsage,
64}
65
66#[allow(dead_code)]
67impl AccountV1 {
68 fn to_v2(&self) -> AccountV2 {
69 AccountV2 {
70 amount: self.amount,
71 locked: self.locked,
72 storage_usage: self.storage_usage,
73 contract: AccountContract::from_local_code_hash(self.code_hash),
74 }
75 }
76}
77
78#[derive(
79 BorshSerialize,
80 BorshDeserialize,
81 serde::Serialize,
82 serde::Deserialize,
83 PartialEq,
84 Eq,
85 Debug,
86 Clone,
87 ProtocolSchema,
88)]
89pub enum AccountContract {
90 None,
91 Local(CryptoHash),
92 Global(CryptoHash),
93 GlobalByAccount(AccountId),
94}
95
96impl AccountContract {
97 pub fn local_code(&self) -> Option<CryptoHash> {
98 match self {
99 AccountContract::None
100 | AccountContract::GlobalByAccount(_)
101 | AccountContract::Global(_) => None,
102 AccountContract::Local(hash) => Some(*hash),
103 }
104 }
105
106 pub fn from_local_code_hash(code_hash: CryptoHash) -> AccountContract {
107 if code_hash == CryptoHash::default() {
108 AccountContract::None
109 } else {
110 AccountContract::Local(code_hash)
111 }
112 }
113
114 pub fn is_none(&self) -> bool {
115 matches!(self, Self::None)
116 }
117
118 pub fn is_local(&self) -> bool {
119 matches!(self, Self::Local(_))
120 }
121
122 pub fn identifier_storage_usage(&self) -> u64 {
123 match self {
124 AccountContract::None | AccountContract::Local(_) => 0u64,
125 AccountContract::Global(_) => 32u64,
126 AccountContract::GlobalByAccount(id) => id.len() as u64,
127 }
128 }
129}
130
131#[derive(
132 BorshSerialize,
133 BorshDeserialize,
134 serde::Serialize,
135 serde::Deserialize,
136 PartialEq,
137 Eq,
138 Debug,
139 Clone,
140 ProtocolSchema,
141)]
142pub struct AccountV2 {
143 amount: Balance,
145 locked: Balance,
147 storage_usage: StorageUsage,
149 contract: AccountContract,
151}
152
153impl Account {
154 pub const MAX_ACCOUNT_DELETION_STORAGE_USAGE: u64 = 10_000;
157 const SERIALIZATION_SENTINEL: Balance = Balance::MAX;
161
162 pub fn new(
163 amount: Balance,
164 locked: Balance,
165 contract: AccountContract,
166 storage_usage: StorageUsage,
167 ) -> Self {
168 match contract {
169 AccountContract::None => Self::V1(AccountV1 {
170 amount,
171 locked,
172 code_hash: CryptoHash::default(),
173 storage_usage,
174 }),
175 AccountContract::Local(code_hash) => {
176 Self::V1(AccountV1 { amount, locked, code_hash, storage_usage })
177 }
178 _ => Self::V2(AccountV2 { amount, locked, storage_usage, contract }),
179 }
180 }
181
182 #[inline]
183 pub fn amount(&self) -> Balance {
184 match self {
185 Self::V1(account) => account.amount,
186 Self::V2(account) => account.amount,
187 }
188 }
189
190 #[inline]
191 pub fn locked(&self) -> Balance {
192 match self {
193 Self::V1(account) => account.locked,
194 Self::V2(account) => account.locked,
195 }
196 }
197
198 #[inline]
199 pub fn contract(&self) -> Cow<'_, AccountContract> {
200 match self {
201 Self::V1(account) => {
202 Cow::Owned(AccountContract::from_local_code_hash(account.code_hash))
203 }
204 Self::V2(account) => Cow::Borrowed(&account.contract),
205 }
206 }
207
208 #[inline]
209 pub fn storage_usage(&self) -> StorageUsage {
210 match self {
211 Self::V1(account) => account.storage_usage,
212 Self::V2(account) => account.storage_usage,
213 }
214 }
215
216 #[inline]
217 pub fn version(&self) -> AccountVersion {
218 match self {
219 Self::V1(_) => AccountVersion::V1,
220 Self::V2(_) => AccountVersion::V2,
221 }
222 }
223
224 #[inline]
225 pub fn global_contract_hash(&self) -> Option<CryptoHash> {
226 match self {
227 Self::V2(AccountV2 { contract: AccountContract::Global(hash), .. }) => Some(*hash),
228 Self::V1(_) | Self::V2(_) => None,
229 }
230 }
231
232 #[inline]
233 pub fn global_contract_account_id(&self) -> Option<&AccountId> {
234 match self {
235 Self::V2(AccountV2 { contract: AccountContract::GlobalByAccount(account), .. }) => {
236 Some(account)
237 }
238 Self::V1(_) | Self::V2(_) => None,
239 }
240 }
241
242 #[inline]
243 pub fn local_contract_hash(&self) -> Option<CryptoHash> {
244 match self {
245 Self::V1(account) => {
246 AccountContract::from_local_code_hash(account.code_hash).local_code()
247 }
248 Self::V2(AccountV2 { contract: AccountContract::Local(hash), .. }) => Some(*hash),
249 Self::V2(AccountV2 { contract: AccountContract::None, .. })
250 | Self::V2(AccountV2 { contract: AccountContract::Global(_), .. })
251 | Self::V2(AccountV2 { contract: AccountContract::GlobalByAccount(_), .. }) => None,
252 }
253 }
254
255 #[inline]
256 pub fn set_amount(&mut self, amount: Balance) {
257 match self {
258 Self::V1(account) => account.amount = amount,
259 Self::V2(account) => account.amount = amount,
260 }
261 }
262
263 #[inline]
264 pub fn set_locked(&mut self, locked: Balance) {
265 match self {
266 Self::V1(account) => account.locked = locked,
267 Self::V2(account) => account.locked = locked,
268 }
269 }
270
271 #[inline]
272 pub fn set_contract(&mut self, contract: AccountContract) {
273 match self {
274 Self::V1(account) => match contract {
275 AccountContract::None | AccountContract::Local(_) => {
276 account.code_hash = contract.local_code().unwrap_or_default();
277 }
278 _ => {
279 let mut account_v2 = account.to_v2();
280 account_v2.contract = contract;
281 *self = Self::V2(account_v2);
282 }
283 },
284 Self::V2(account) => {
285 account.contract = contract;
286 }
287 }
288 }
289
290 #[inline]
291 pub fn set_storage_usage(&mut self, storage_usage: StorageUsage) {
292 match self {
293 Self::V1(account) => account.storage_usage = storage_usage,
294 Self::V2(account) => account.storage_usage = storage_usage,
295 }
296 }
297}
298
299#[derive(serde::Serialize, serde::Deserialize, PartialEq, Eq, Debug, Clone, ProtocolSchema)]
302#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
303struct SerdeAccount {
304 amount: Balance,
305 locked: Balance,
306 code_hash: CryptoHash,
307 storage_usage: StorageUsage,
308 #[serde(default)]
310 version: AccountVersion,
311 #[serde(default, skip_serializing_if = "Option::is_none")]
313 global_contract_hash: Option<CryptoHash>,
314 #[serde(default, skip_serializing_if = "Option::is_none")]
315 global_contract_account_id: Option<AccountId>,
316}
317
318impl<'de> serde::Deserialize<'de> for Account {
319 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
320 where
321 D: serde::Deserializer<'de>,
322 {
323 let account_data = SerdeAccount::deserialize(deserializer)?;
324 if account_data.code_hash != CryptoHash::default()
325 && (account_data.global_contract_hash.is_some()
326 || account_data.global_contract_account_id.is_some())
327 {
328 return Err(serde::de::Error::custom(
329 "An Account can't contain both a local and global contract",
330 ));
331 }
332 if account_data.global_contract_hash.is_some()
333 && account_data.global_contract_account_id.is_some()
334 {
335 return Err(serde::de::Error::custom(
336 "An Account can't contain both types of global contracts",
337 ));
338 }
339
340 match account_data.version {
341 AccountVersion::V1 => Ok(Account::V1(AccountV1 {
342 amount: account_data.amount,
343 locked: account_data.locked,
344 code_hash: account_data.code_hash,
345 storage_usage: account_data.storage_usage,
346 })),
347 AccountVersion::V2 => {
348 let contract = match account_data.global_contract_account_id {
349 Some(account_id) => AccountContract::GlobalByAccount(account_id),
350 None => match account_data.global_contract_hash {
351 Some(hash) => AccountContract::Global(hash),
352 None => AccountContract::from_local_code_hash(account_data.code_hash),
353 },
354 };
355
356 Ok(Account::V2(AccountV2 {
357 amount: account_data.amount,
358 locked: account_data.locked,
359 storage_usage: account_data.storage_usage,
360 contract,
361 }))
362 }
363 }
364 }
365}
366
367impl serde::Serialize for Account {
368 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
369 where
370 S: serde::Serializer,
371 {
372 let version = self.version();
373 let code_hash = self.local_contract_hash().unwrap_or_default();
374 let repr = SerdeAccount {
375 amount: self.amount(),
376 locked: self.locked(),
377 code_hash,
378 storage_usage: self.storage_usage(),
379 version,
380 global_contract_hash: self.global_contract_hash(),
381 global_contract_account_id: self.global_contract_account_id().cloned(),
382 };
383 repr.serialize(serializer)
384 }
385}
386
387#[cfg(feature = "schemars")]
388impl schemars::JsonSchema for Account {
389 fn schema_name() -> std::borrow::Cow<'static, str> {
390 "Account".to_string().into()
391 }
392
393 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
394 SerdeAccount::json_schema(generator)
395 }
396}
397
398#[derive(BorshSerialize, BorshDeserialize)]
399#[borsh(use_discriminant = true)]
400#[repr(u8)]
401enum BorshVersionedAccount {
402 V2(AccountV2) = 0,
404}
405
406impl BorshDeserialize for Account {
407 fn deserialize_reader<R: io::Read>(rd: &mut R) -> io::Result<Self> {
408 let sentinel_or_amount = Balance::deserialize_reader(rd)?;
411 if sentinel_or_amount == Account::SERIALIZATION_SENTINEL {
412 let versioned_account = BorshVersionedAccount::deserialize_reader(rd)?;
413 let account = match versioned_account {
414 BorshVersionedAccount::V2(account_v2) => Account::V2(account_v2),
415 };
416 Ok(account)
417 } else {
418 let locked = Balance::deserialize_reader(rd)?;
420 let code_hash = CryptoHash::deserialize_reader(rd)?;
421 let storage_usage = StorageUsage::deserialize_reader(rd)?;
422
423 Ok(Account::V1(AccountV1 {
424 amount: sentinel_or_amount,
425 locked,
426 code_hash,
427 storage_usage,
428 }))
429 }
430 }
431}
432
433impl BorshSerialize for Account {
434 fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
435 let versioned_account = match self {
436 Account::V1(account_v1) => return account_v1.serialize(writer),
437 Account::V2(account_v2) => BorshVersionedAccount::V2(account_v2.clone()),
438 };
439 let sentinel = Account::SERIALIZATION_SENTINEL;
440 BorshSerialize::serialize(&sentinel, writer)?;
441 BorshSerialize::serialize(&versioned_account, writer)
442 }
443}
444
445#[derive(
451 BorshSerialize,
452 BorshDeserialize,
453 PartialEq,
454 Eq,
455 Hash,
456 Clone,
457 Debug,
458 serde::Serialize,
459 serde::Deserialize,
460 ProtocolSchema,
461)]
462#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
463pub struct AccessKey {
464 pub nonce: Nonce,
468
469 pub permission: AccessKeyPermission,
471}
472
473impl AccessKey {
474 pub const ACCESS_KEY_NONCE_RANGE_MULTIPLIER: u64 = 1_000_000;
475
476 pub fn full_access() -> Self {
477 Self { nonce: 0, permission: AccessKeyPermission::FullAccess }
478 }
479}
480
481#[derive(
484 BorshSerialize,
485 BorshDeserialize,
486 PartialEq,
487 Eq,
488 Hash,
489 Clone,
490 Debug,
491 serde::Serialize,
492 serde::Deserialize,
493 ProtocolSchema,
494)]
495#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
496pub struct GasKey {
497 pub num_nonces: NonceIndex,
499 pub balance: Balance,
501 pub permission: AccessKeyPermission,
504}
505
506#[derive(
508 BorshSerialize,
509 BorshDeserialize,
510 PartialEq,
511 Eq,
512 Hash,
513 Clone,
514 Debug,
515 serde::Serialize,
516 serde::Deserialize,
517 ProtocolSchema,
518)]
519#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
520pub enum AccessKeyPermission {
521 FunctionCall(FunctionCallPermission),
522
523 FullAccess,
526}
527
528#[derive(
533 BorshSerialize,
534 BorshDeserialize,
535 serde::Serialize,
536 serde::Deserialize,
537 PartialEq,
538 Eq,
539 Hash,
540 Clone,
541 Debug,
542 ProtocolSchema,
543)]
544#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
545pub struct FunctionCallPermission {
546 pub allowance: Option<Balance>,
553
554 pub receiver_id: String,
559
560 pub method_names: Vec<String>,
564}
565
566#[cfg(test)]
567mod tests {
568 use super::*;
569
570 fn create_serde_account(
571 code_hash: CryptoHash,
572 global_contract_hash: Option<CryptoHash>,
573 global_contract_account_id: Option<AccountId>,
574 ) -> SerdeAccount {
575 SerdeAccount {
576 amount: Balance::from_yoctonear(10_000_000),
577 locked: Balance::from_yoctonear(100_000),
578 code_hash,
579 storage_usage: 1000,
580 version: AccountVersion::V2,
581 global_contract_hash,
582 global_contract_account_id,
583 }
584 }
585
586 #[test]
587 fn test_v1_account_serde_serialization() {
588 let old_account = AccountV1 {
589 amount: Balance::from_yoctonear(1_000_000),
590 locked: Balance::from_yoctonear(1_000_000),
591 code_hash: CryptoHash::hash_bytes(&[42]),
592 storage_usage: 100,
593 };
594
595 let serialized_account = serde_json::to_string(&old_account).unwrap();
596 let expected_serde_repr = SerdeAccount {
597 amount: old_account.amount,
598 locked: old_account.locked,
599 code_hash: old_account.code_hash,
600 storage_usage: old_account.storage_usage,
601 version: AccountVersion::V1,
602 global_contract_hash: None,
603 global_contract_account_id: None,
604 };
605 let actual_serde_repr: SerdeAccount = serde_json::from_str(&serialized_account).unwrap();
606 assert_eq!(actual_serde_repr, expected_serde_repr);
607
608 let new_account: Account = serde_json::from_str(&serialized_account).unwrap();
609 assert_eq!(new_account, Account::V1(old_account));
610
611 let new_serialized_account = serde_json::to_string(&new_account).unwrap();
612 let deserialized_account: Account = serde_json::from_str(&new_serialized_account).unwrap();
613 assert_eq!(deserialized_account, new_account);
614 }
615
616 #[test]
617 fn test_v1_account_borsh_serialization() {
618 let old_account = AccountV1 {
619 amount: Balance::from_yoctonear(100),
620 locked: Balance::from_yoctonear(200),
621 code_hash: CryptoHash::hash_bytes(&[42]),
622 storage_usage: 300,
623 };
624 let old_bytes = borsh::to_vec(&old_account).unwrap();
625 let new_account = <Account as BorshDeserialize>::deserialize(&mut &old_bytes[..]).unwrap();
626 assert_eq!(new_account, Account::V1(old_account));
627
628 let new_bytes = borsh::to_vec(&new_account).unwrap();
629 assert_eq!(new_bytes, old_bytes);
630 let deserialized_account =
631 <Account as BorshDeserialize>::deserialize(&mut &new_bytes[..]).unwrap();
632 assert_eq!(deserialized_account, new_account);
633 }
634
635 #[test]
636 fn test_account_v2_serde_serialization() {
637 let account_v2 = AccountV2 {
638 amount: Balance::from_yoctonear(10_000_000),
639 locked: Balance::from_yoctonear(100_000),
640 storage_usage: 1000,
641 contract: AccountContract::Local(CryptoHash::hash_bytes(&[42])),
642 };
643 let account = Account::V2(account_v2.clone());
644
645 let serialized_account = serde_json::to_string(&account).unwrap();
646 let expected_serde_repr = SerdeAccount {
647 amount: account_v2.amount,
648 locked: account_v2.locked,
649 code_hash: account_v2.contract.local_code().unwrap_or_default(),
650 storage_usage: account_v2.storage_usage,
651 version: AccountVersion::V2,
652 global_contract_hash: None,
653 global_contract_account_id: None,
654 };
655 let actual_serde_repr: SerdeAccount = serde_json::from_str(&serialized_account).unwrap();
656 assert_eq!(actual_serde_repr, expected_serde_repr);
657
658 let deserialized_account: Account = serde_json::from_str(&serialized_account).unwrap();
659 assert_eq!(deserialized_account, account);
660 }
661
662 #[test]
663 fn test_account_v2_borsh_serialization() {
664 let account_v2 = AccountV2 {
665 amount: Balance::from_yoctonear(10_000_000),
666 locked: Balance::from_yoctonear(100_000),
667 storage_usage: 1000,
668 contract: AccountContract::Global(CryptoHash::hash_bytes(&[42])),
669 };
670 let account = Account::V2(account_v2);
671 let serialized_account = borsh::to_vec(&account).unwrap();
672 let deserialized_account =
673 <Account as BorshDeserialize>::deserialize(&mut &serialized_account[..]).unwrap();
674 assert_eq!(deserialized_account, account);
675 }
676
677 #[test]
678 fn test_account_v2_serde_deserialization_fails_with_local_hash_and_global_account_id() {
679 let id = AccountId::try_from("test.near".to_string()).unwrap();
680 let code_hash = CryptoHash::hash_bytes(&[42]);
681
682 let serde_repr = create_serde_account(code_hash, None, Some(id));
683
684 let serde_string = serde_json::to_string(&serde_repr).unwrap();
685 let deserialization_attempt: Result<Account, _> = serde_json::from_str(&serde_string);
686 assert!(deserialization_attempt.is_err());
687 }
688
689 #[test]
690 fn test_account_v2_serde_deserialization_fails_with_local_and_global_hashes() {
691 let code_hash = CryptoHash::hash_bytes(&[42]);
692
693 let serde_repr = create_serde_account(code_hash, Some(code_hash), None);
694
695 let serde_string = serde_json::to_string(&serde_repr).unwrap();
696 let deserialization_attempt: Result<Account, _> = serde_json::from_str(&serde_string);
697 assert!(deserialization_attempt.is_err());
698 }
699
700 #[test]
701 fn test_account_v2_serde_deserialization_fails_if_both_types_of_global_contract_are_present() {
702 let id = AccountId::try_from("test.near".to_string()).unwrap();
703 let serde_repr = create_serde_account(
704 CryptoHash::default(),
705 Some(CryptoHash::hash_bytes(&[42])),
706 Some(id),
707 );
708
709 let serde_string = serde_json::to_string(&serde_repr).unwrap();
710 let deserialization_attempt: Result<Account, _> = serde_json::from_str(&serde_string);
711 assert!(deserialization_attempt.is_err());
712 }
713
714 #[test]
715 fn test_account_version_upgrade_behaviour() {
716 let account_v1 = AccountV1 {
717 amount: Balance::from_yoctonear(100),
718 locked: Balance::from_yoctonear(200),
719 code_hash: CryptoHash::hash_bytes(&[42]),
720 storage_usage: 300,
721 };
722 let mut account = Account::V1(account_v1);
723 let contract = AccountContract::Local(CryptoHash::hash_bytes(&[42]));
724 account.set_contract(contract);
725 assert!(matches!(account, Account::V1(_)));
726
727 let contract = AccountContract::None;
728 account.set_contract(contract);
729 assert!(matches!(account, Account::V1(_)));
730
731 let contract = AccountContract::Global(CryptoHash::hash_bytes(&[42]));
732 account.set_contract(contract);
733 assert!(matches!(account, Account::V2(_)));
734 }
735}