unc_primitives_core/
account.rs

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