1use crate::{
2 AccountId, AccountIdRef, DefuseError, Lock, Nonce, NoncePrefix, Nonces, Result, Salt,
3 amounts::Amounts,
4 fees::Pips,
5 intents::{
6 auth::AuthCall,
7 tokens::{
8 FtWithdraw, MtWithdraw, NativeWithdraw, NftWithdraw, NotifyOnTransfer, StorageDeposit,
9 },
10 },
11 public_key::PublicKey,
12 token_id::{TokenId, nep141::Nep141TokenId, nep171::Nep171TokenId, nep245::Nep245TokenId},
13 tokens::BATCH_MAX_TOKENS,
14};
15use defuse_bitmap::{U248, U256};
16use std::{
17 borrow::Cow,
18 collections::{HashMap, HashSet},
19};
20
21use super::{State, StateView};
22
23#[derive(Debug)]
24pub struct CachedState<W: StateView> {
25 view: W,
26 accounts: CachedAccounts,
27}
28
29impl<W> CachedState<W>
30where
31 W: StateView,
32{
33 #[inline]
34 pub fn new(view: W) -> Self {
35 Self {
36 view,
37 accounts: CachedAccounts::new(),
38 }
39 }
40}
41
42impl<W> StateView for CachedState<W>
43where
44 W: StateView,
45{
46 #[inline]
47 fn verifying_contract(&self) -> Cow<'_, AccountIdRef> {
48 self.view.verifying_contract()
49 }
50
51 #[inline]
52 fn wnear_id(&self) -> Cow<'_, AccountIdRef> {
53 self.view.wnear_id()
54 }
55
56 #[inline]
57 fn fee(&self) -> Pips {
58 self.view.fee()
59 }
60
61 #[inline]
62 fn fee_collector(&self) -> Cow<'_, AccountIdRef> {
63 self.view.fee_collector()
64 }
65
66 fn has_public_key(&self, account_id: &AccountIdRef, public_key: &PublicKey) -> bool {
67 if let Some(account) = self.accounts.get(account_id).map(Lock::as_inner_unchecked) {
68 if account.public_keys_added.contains(public_key) {
69 return true;
70 }
71 if account.public_keys_removed.contains(public_key) {
72 return false;
73 }
74 }
75 self.view.has_public_key(account_id, public_key)
76 }
77
78 fn iter_public_keys(&self, account_id: &AccountIdRef) -> impl Iterator<Item = PublicKey> + '_ {
79 let account = self.accounts.get(account_id).map(Lock::as_inner_unchecked);
80 self.view
81 .iter_public_keys(account_id)
82 .filter(move |pk| account.is_none_or(|a| !a.public_keys_removed.contains(pk)))
83 .chain(
84 account
85 .map(|a| &a.public_keys_added)
86 .into_iter()
87 .flatten()
88 .copied(),
89 )
90 }
91
92 fn is_nonce_used(&self, account_id: &AccountIdRef, nonce: Nonce) -> bool {
93 self.accounts
94 .get(account_id)
95 .map(Lock::as_inner_unchecked)
96 .is_some_and(|account| account.is_nonce_used(nonce))
97 || self.view.is_nonce_used(account_id, nonce)
98 }
99
100 fn balance_of(&self, account_id: &AccountIdRef, token_id: &TokenId) -> u128 {
101 self.accounts
102 .get(account_id)
103 .map(Lock::as_inner_unchecked)
104 .and_then(|account| account.token_amounts.get(token_id))
105 .copied()
106 .unwrap_or_else(|| self.view.balance_of(account_id, token_id))
107 }
108
109 fn is_account_locked(&self, account_id: &AccountIdRef) -> bool {
110 self.accounts
111 .get(account_id)
112 .map_or_else(|| self.view.is_account_locked(account_id), Lock::is_locked)
113 }
114
115 fn is_auth_by_predecessor_id_enabled(&self, account_id: &AccountIdRef) -> bool {
116 let was_enabled = self.view.is_auth_by_predecessor_id_enabled(account_id);
117 let toggled = self
118 .accounts
119 .get(account_id)
120 .map(Lock::as_inner_unchecked)
121 .is_some_and(|a| a.auth_by_predecessor_id_toggled);
122 was_enabled ^ toggled
123 }
124
125 fn is_valid_salt(&self, salt: Salt) -> bool {
126 self.view.is_valid_salt(salt)
127 }
128}
129
130impl<W> State for CachedState<W>
131where
132 W: StateView,
133{
134 fn add_public_key(&mut self, account_id: AccountId, public_key: PublicKey) -> Result<()> {
135 let had = self.view.has_public_key(&account_id, &public_key);
136 let account = self
137 .accounts
138 .get_or_create(account_id.clone(), |account_id| {
139 self.view.is_account_locked(account_id)
140 })
141 .get_mut()
142 .ok_or_else(|| DefuseError::AccountLocked(account_id.clone()))?;
143 let added = if had {
144 account.public_keys_removed.remove(&public_key)
145 } else {
146 account.public_keys_added.insert(public_key)
147 };
148 if !added {
149 return Err(DefuseError::PublicKeyExists(account_id, public_key));
150 }
151 Ok(())
152 }
153
154 fn remove_public_key(&mut self, account_id: AccountId, public_key: PublicKey) -> Result<()> {
155 let had = self.view.has_public_key(&account_id, &public_key);
156 let account = self
157 .accounts
158 .get_or_create(account_id.clone(), |account_id| {
159 self.view.is_account_locked(account_id)
160 })
161 .get_mut()
162 .ok_or_else(|| DefuseError::AccountLocked(account_id.clone()))?;
163 let removed = if had {
164 account.public_keys_removed.insert(public_key)
165 } else {
166 account.public_keys_added.remove(&public_key)
167 };
168 if !removed {
169 return Err(DefuseError::PublicKeyNotExist(account_id, public_key));
170 }
171 Ok(())
172 }
173
174 fn commit_nonce(&mut self, account_id: AccountId, nonce: Nonce) -> Result<()> {
175 if self.view.is_nonce_used(&account_id, nonce) {
176 return Err(DefuseError::NonceUsed);
177 }
178
179 self.accounts
180 .get_or_create(account_id.clone(), |account_id| {
181 self.view.is_account_locked(account_id)
182 })
183 .get_mut()
184 .ok_or(DefuseError::AccountLocked(account_id))?
185 .commit_nonce(nonce)
186 }
187
188 fn cleanup_nonce_by_prefix(
189 &mut self,
190 account_id: &AccountIdRef,
191 prefix: NoncePrefix,
192 ) -> Result<bool> {
193 let account = self
194 .accounts
195 .get_mut(account_id)
196 .ok_or_else(|| DefuseError::AccountNotFound(account_id.to_owned()))?
197 .as_inner_unchecked_mut();
198
199 Ok(account.cleanup_nonce_by_prefix(prefix))
200 }
201
202 fn internal_add_balance(
203 &mut self,
204 owner_id: AccountId,
205 token_amounts: impl IntoIterator<Item = (TokenId, u128)>,
206 ) -> Result<()> {
207 let account = self
208 .accounts
209 .get_or_create(owner_id.clone(), |owner_id| {
210 self.view.is_account_locked(owner_id)
211 })
212 .as_inner_unchecked_mut();
213 for (token_id, amount) in token_amounts {
214 if amount == 0 {
215 return Err(DefuseError::InvalidIntent);
216 }
217
218 let balance = account
219 .token_amounts
220 .entry(token_id)
221 .or_insert_with_key(|token_id| self.view.balance_of(&owner_id, token_id));
223
224 *balance = balance
225 .checked_add(amount)
226 .ok_or(DefuseError::BalanceOverflow)?;
227 }
228 Ok(())
229 }
230
231 fn internal_sub_balance(
232 &mut self,
233 owner_id: &AccountIdRef,
234 token_amounts: impl IntoIterator<Item = (TokenId, u128)>,
235 ) -> Result<()> {
236 let account = self
237 .accounts
238 .get_or_create(owner_id.to_owned(), |owner_id| {
239 self.view.is_account_locked(owner_id)
240 })
241 .get_mut()
242 .ok_or_else(|| DefuseError::AccountLocked(owner_id.to_owned()))?;
243 for (token_id, amount) in token_amounts {
244 if amount == 0 {
245 return Err(DefuseError::InvalidIntent);
246 }
247
248 let balance = account
249 .token_amounts
250 .entry(token_id)
251 .or_insert_with_key(|token_id| self.view.balance_of(owner_id, token_id));
253
254 *balance = balance
255 .checked_sub(amount)
256 .ok_or(DefuseError::BalanceOverflow)?;
257 }
258 Ok(())
259 }
260
261 fn ft_withdraw(&mut self, owner_id: &AccountIdRef, withdraw: FtWithdraw) -> Result<()> {
262 self.internal_sub_balance(
263 owner_id,
264 std::iter::once((
265 Nep141TokenId::new(withdraw.token.clone()).into(),
266 withdraw.amount,
267 ))
268 .chain(withdraw.storage_deposit.map(|amount| {
269 (
270 Nep141TokenId::new(self.wnear_id().into_owned()).into(),
271 amount.as_yoctonear(),
272 )
273 })),
274 )
275 }
276
277 fn nft_withdraw(&mut self, owner_id: &AccountIdRef, withdraw: NftWithdraw) -> Result<()> {
278 self.internal_sub_balance(
279 owner_id,
280 std::iter::once((
281 Nep171TokenId::new(withdraw.token.clone(), withdraw.token_id.clone()).into(),
282 1,
283 ))
284 .chain(withdraw.storage_deposit.map(|amount| {
285 (
286 Nep141TokenId::new(self.wnear_id().into_owned()).into(),
287 amount.as_yoctonear(),
288 )
289 })),
290 )
291 }
292
293 fn mt_withdraw(&mut self, owner_id: &AccountIdRef, withdraw: MtWithdraw) -> Result<()> {
294 if withdraw.token_ids.len() != withdraw.amounts.len() || withdraw.token_ids.is_empty() {
295 return Err(DefuseError::InvalidIntent);
296 }
297
298 if withdraw.token_ids.len() > BATCH_MAX_TOKENS {
299 return Err(DefuseError::TooManyTokens(withdraw.token_ids.len()));
300 }
301
302 self.internal_sub_balance(
303 owner_id,
304 withdraw
305 .token_ids
306 .iter()
307 .cloned()
308 .map(|token_id| Nep245TokenId::new(withdraw.token.clone(), token_id))
309 .map(Into::into)
310 .zip(withdraw.amounts.iter().copied())
311 .chain(
312 withdraw
313 .storage_deposit
314 .map(|amount| (self.wnear_token_id(), amount.as_yoctonear())),
315 ),
316 )
317 }
318
319 fn native_withdraw(&mut self, owner_id: &AccountIdRef, withdraw: NativeWithdraw) -> Result<()> {
320 self.internal_sub_balance(
321 owner_id,
322 [(
323 Nep141TokenId::new(self.wnear_id().into_owned()).into(),
324 withdraw.amount.as_yoctonear(),
325 )],
326 )
327 }
328
329 #[inline]
331 fn notify_on_transfer(
332 &self,
333 _sender_id: &AccountIdRef,
334 _receiver_id: AccountId,
335 tokens: Amounts,
336 _notification: NotifyOnTransfer,
337 ) -> Result<()> {
338 if tokens.len() > BATCH_MAX_TOKENS {
339 return Err(DefuseError::TooManyTokens(tokens.len()));
340 }
341
342 Ok(())
343 }
344
345 fn storage_deposit(
346 &mut self,
347 owner_id: &AccountIdRef,
348 storage_deposit: StorageDeposit,
349 ) -> Result<()> {
350 self.internal_sub_balance(
351 owner_id,
352 [(
353 Nep141TokenId::new(self.wnear_id().into_owned()).into(),
354 storage_deposit.amount.as_yoctonear(),
355 )],
356 )
357 }
358
359 fn set_auth_by_predecessor_id(&mut self, account_id: AccountId, enable: bool) -> Result<bool> {
360 let was_enabled = self.is_auth_by_predecessor_id_enabled(&account_id);
361 let toggle = was_enabled ^ enable;
362 if toggle {
363 self.accounts
364 .get_or_create(account_id.clone(), |owner_id| {
365 self.view.is_account_locked(owner_id)
366 })
367 .get_mut()
368 .ok_or(DefuseError::AccountLocked(account_id))?
369 .auth_by_predecessor_id_toggled ^= true;
371 }
372 Ok(toggle)
373 }
374
375 fn auth_call(&mut self, signer_id: &AccountIdRef, auth_call: AuthCall) -> Result<()> {
376 if !auth_call.attached_deposit.is_zero() {
377 self.internal_sub_balance(
378 signer_id,
379 [(
380 Nep141TokenId::new(self.wnear_id().into_owned()).into(),
381 auth_call.attached_deposit.as_yoctonear(),
382 )],
383 )?;
384 }
385
386 Ok(())
387 }
388
389 #[inline]
390 fn mint(&mut self, owner_id: AccountId, tokens: Amounts, _memo: Option<String>) -> Result<()> {
391 self.internal_add_balance(owner_id, tokens)
392 }
393
394 #[inline]
395 fn burn(
396 &mut self,
397 owner_id: &AccountIdRef,
398 tokens: Amounts,
399 _memo: Option<String>,
400 ) -> Result<()> {
401 self.internal_sub_balance(owner_id, tokens)
402 }
403}
404
405#[derive(Debug, Default)]
406pub struct CachedAccounts(HashMap<AccountId, Lock<CachedAccount>>);
407
408impl CachedAccounts {
409 #[must_use]
410 #[inline]
411 pub fn new() -> Self {
412 Self(HashMap::new())
413 }
414
415 #[inline]
416 pub fn get(&self, account_id: &AccountIdRef) -> Option<&Lock<CachedAccount>> {
417 self.0.get(account_id)
418 }
419
420 #[inline]
421 pub fn get_mut(&mut self, account_id: &AccountIdRef) -> Option<&mut Lock<CachedAccount>> {
422 self.0.get_mut(account_id)
423 }
424
425 #[inline]
426 pub fn get_or_create(
427 &mut self,
428 account_id: AccountId,
429 is_initially_locked: impl FnOnce(&AccountId) -> bool,
430 ) -> &mut Lock<CachedAccount> {
431 self.0.entry(account_id).or_insert_with_key(|account_id| {
432 Lock::new(is_initially_locked(account_id), CachedAccount::default())
433 })
434 }
435}
436
437#[derive(Debug, Clone, Default)]
438pub struct CachedAccount {
439 nonces: Nonces<HashMap<U248, U256>>,
440
441 auth_by_predecessor_id_toggled: bool,
442
443 public_keys_added: HashSet<PublicKey>,
444 public_keys_removed: HashSet<PublicKey>,
445
446 token_amounts: HashMap<TokenId, u128>,
447}
448
449impl CachedAccount {
450 #[inline]
451 pub fn is_nonce_used(&self, nonce: U256) -> bool {
452 self.nonces.is_used(nonce)
453 }
454
455 #[inline]
456 pub fn commit_nonce(&mut self, n: U256) -> Result<()> {
457 self.nonces.commit(n)
458 }
459
460 #[inline]
461 pub fn cleanup_nonce_by_prefix(&mut self, prefix: NoncePrefix) -> bool {
462 self.nonces.cleanup_by_prefix(prefix)
463 }
464}