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