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