near_primitives_core_v01/
account.rs

1use borsh::{BorshDeserialize, BorshSerialize};
2use serde::{Deserialize, Serialize};
3use std::io;
4
5pub use near_account_id as id;
6
7use crate::hash::CryptoHash;
8use crate::serialize::{option_u128_dec_format, u128_dec_format_compatible};
9use crate::types::{Balance, Nonce, StorageUsage};
10#[derive(
11    BorshSerialize, BorshDeserialize, Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Copy,
12)]
13pub enum AccountVersion {
14    V1,
15}
16
17impl Default for AccountVersion {
18    fn default() -> Self {
19        AccountVersion::V1
20    }
21}
22
23/// Per account information stored in the state.
24#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
25pub struct Account {
26    /// The total not locked tokens.
27    #[serde(with = "u128_dec_format_compatible")]
28    amount: Balance,
29    /// The amount locked due to staking.
30    #[serde(with = "u128_dec_format_compatible")]
31    locked: Balance,
32    /// Hash of the code stored in the storage for this account.
33    code_hash: CryptoHash,
34    /// Storage used by the given account, includes account id, this struct, access keys and other data.
35    storage_usage: StorageUsage,
36    /// Version of Account in re migrations and similar
37    #[serde(default)]
38    version: AccountVersion,
39}
40
41impl Account {
42    /// Max number of bytes an account can have in its state (excluding contract code)
43    /// before it is infeasible to delete.
44    pub const MAX_ACCOUNT_DELETION_STORAGE_USAGE: u64 = 10_000;
45
46    pub fn new(
47        amount: Balance,
48        locked: Balance,
49        code_hash: CryptoHash,
50        storage_usage: StorageUsage,
51    ) -> Self {
52        Account { amount, locked, code_hash, storage_usage, version: AccountVersion::V1 }
53    }
54
55    #[inline]
56    pub fn amount(&self) -> Balance {
57        self.amount
58    }
59
60    #[inline]
61    pub fn locked(&self) -> Balance {
62        self.locked
63    }
64
65    #[inline]
66    pub fn code_hash(&self) -> CryptoHash {
67        self.code_hash
68    }
69
70    #[inline]
71    pub fn storage_usage(&self) -> StorageUsage {
72        self.storage_usage
73    }
74
75    #[inline]
76    pub fn version(&self) -> AccountVersion {
77        self.version
78    }
79
80    #[inline]
81    pub fn set_amount(&mut self, amount: Balance) {
82        self.amount = amount;
83    }
84
85    #[inline]
86    pub fn set_locked(&mut self, locked: Balance) {
87        self.locked = locked;
88    }
89
90    #[inline]
91    pub fn set_code_hash(&mut self, code_hash: CryptoHash) {
92        self.code_hash = code_hash;
93    }
94
95    #[inline]
96    pub fn set_storage_usage(&mut self, storage_usage: StorageUsage) {
97        self.storage_usage = storage_usage;
98    }
99
100    pub fn set_version(&mut self, version: AccountVersion) {
101        self.version = version;
102    }
103}
104
105#[derive(BorshSerialize, BorshDeserialize)]
106struct LegacyAccount {
107    amount: Balance,
108    locked: Balance,
109    code_hash: CryptoHash,
110    storage_usage: StorageUsage,
111}
112
113impl BorshDeserialize for Account {
114    fn deserialize(buf: &mut &[u8]) -> Result<Self, io::Error> {
115        if buf.len() == std::mem::size_of::<LegacyAccount>() {
116            // This should only ever happen if we have pre-transition account serialized in state
117            // See test_account_size
118            let deserialized_account = LegacyAccount::deserialize(buf)?;
119            Ok(Account {
120                amount: deserialized_account.amount,
121                locked: deserialized_account.locked,
122                code_hash: deserialized_account.code_hash,
123                storage_usage: deserialized_account.storage_usage,
124                version: AccountVersion::V1,
125            })
126        } else {
127            unreachable!();
128        }
129    }
130}
131
132impl BorshSerialize for Account {
133    fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
134        match self.version {
135            AccountVersion::V1 => LegacyAccount {
136                amount: self.amount,
137                locked: self.locked,
138                code_hash: self.code_hash,
139                storage_usage: self.storage_usage,
140            }
141            .serialize(writer),
142        }
143    }
144}
145
146/// Access key provides limited access to an account. Each access key belongs to some account and
147/// is identified by a unique (within the account) public key. One account may have large number of
148/// access keys. Access keys allow to act on behalf of the account by restricting transactions
149/// that can be issued.
150/// `account_id,public_key` is a key in the state
151#[derive(
152    BorshSerialize, BorshDeserialize, Serialize, Deserialize, PartialEq, Eq, Hash, Clone, Debug,
153)]
154pub struct AccessKey {
155    /// The nonce for this access key.
156    /// NOTE: In some cases the access key needs to be recreated. If the new access key reuses the
157    /// same public key, the nonce of the new access key should be equal to the nonce of the old
158    /// access key. It's required to avoid replaying old transactions again.
159    pub nonce: Nonce,
160
161    /// Defines permissions for this access key.
162    pub permission: AccessKeyPermission,
163}
164
165impl AccessKey {
166    pub const ACCESS_KEY_NONCE_RANGE_MULTIPLIER: u64 = 1_000_000;
167
168    pub fn full_access() -> Self {
169        Self { nonce: 0, permission: AccessKeyPermission::FullAccess }
170    }
171}
172
173/// Defines permissions for AccessKey
174#[derive(
175    BorshSerialize, BorshDeserialize, Serialize, Deserialize, PartialEq, Eq, Hash, Clone, Debug,
176)]
177pub enum AccessKeyPermission {
178    FunctionCall(FunctionCallPermission),
179
180    /// Grants full access to the account.
181    /// NOTE: It's used to replace account-level public keys.
182    FullAccess,
183}
184
185/// Grants limited permission to make transactions with FunctionCallActions
186/// The permission can limit the allowed balance to be spent on the prepaid gas.
187/// It also restrict the account ID of the receiver for this function call.
188/// It also can restrict the method name for the allowed function calls.
189#[derive(
190    BorshSerialize, BorshDeserialize, Serialize, Deserialize, PartialEq, Eq, Hash, Clone, Debug,
191)]
192pub struct FunctionCallPermission {
193    /// Allowance is a balance limit to use by this access key to pay for function call gas and
194    /// transaction fees. When this access key is used, both account balance and the allowance is
195    /// decreased by the same value.
196    /// `None` means unlimited allowance.
197    /// NOTE: To change or increase the allowance, the old access key needs to be deleted and a new
198    /// access key should be created.
199    #[serde(with = "option_u128_dec_format")]
200    pub allowance: Option<Balance>,
201
202    // This isn't an AccountId because already existing records in testnet genesis have invalid
203    // values for this field (see: https://github.com/near/nearcore/pull/4621#issuecomment-892099860)
204    // we accomodate those by using a string, allowing us to read and parse genesis.
205    /// The access key only allows transactions with the given receiver's account id.
206    pub receiver_id: String,
207
208    /// A list of method names that can be used. The access key only allows transactions with the
209    /// function call of one of the given method names.
210    /// Empty list means any method name can be used.
211    pub method_names: Vec<String>,
212}
213
214#[cfg(test)]
215mod tests {
216    use borsh::BorshSerialize;
217
218    use crate::hash::hash;
219    use crate::serialize::to_base;
220
221    use super::*;
222
223    #[test]
224    fn test_account_serialization() {
225        let acc = Account::new(1_000_000, 1_000_000, CryptoHash::default(), 100);
226        let bytes = acc.try_to_vec().unwrap();
227        assert_eq!(to_base(&hash(&bytes)), "EVk5UaxBe8LQ8r8iD5EAxVBs6TJcMDKqyH7PBuho6bBJ");
228    }
229
230    #[test]
231    fn test_account_deserialization() {
232        let old_account = LegacyAccount {
233            amount: 100,
234            locked: 200,
235            code_hash: CryptoHash::default(),
236            storage_usage: 300,
237        };
238        let mut old_bytes = &old_account.try_to_vec().unwrap()[..];
239        let new_account = <Account as BorshDeserialize>::deserialize(&mut old_bytes).unwrap();
240        assert_eq!(new_account.amount, old_account.amount);
241        assert_eq!(new_account.locked, old_account.locked);
242        assert_eq!(new_account.code_hash, old_account.code_hash);
243        assert_eq!(new_account.storage_usage, old_account.storage_usage);
244        assert_eq!(new_account.version, AccountVersion::V1);
245        let mut new_bytes = &new_account.try_to_vec().unwrap()[..];
246        let deserialized_account =
247            <Account as BorshDeserialize>::deserialize(&mut new_bytes).unwrap();
248        assert_eq!(deserialized_account, new_account);
249    }
250}