defuse_core/engine/state/
cached.rs

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