defuse_core/engine/state/
cached.rs1use crate::{
2 DefuseError, 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 token_id::{TokenId, nep141::Nep141TokenId, nep171::Nep171TokenId, nep245::Nep245TokenId},
12};
13use defuse_bitmap::{U248, U256};
14use defuse_crypto::PublicKey;
15use defuse_near_utils::Lock;
16use near_sdk::{AccountId, AccountIdRef};
17use std::{
18 borrow::Cow,
19 collections::{HashMap, HashSet},
20};
21
22use super::{State, StateView};
23
24#[derive(Debug)]
25pub struct CachedState<W: StateView> {
26 view: W,
27 accounts: CachedAccounts,
28}
29
30impl<W> CachedState<W>
31where
32 W: StateView,
33{
34 #[inline]
35 pub fn new(view: W) -> Self {
36 Self {
37 view,
38 accounts: CachedAccounts::new(),
39 }
40 }
41}
42
43impl<W> StateView for CachedState<W>
44where
45 W: StateView,
46{
47 #[inline]
48 fn verifying_contract(&self) -> Cow<'_, AccountIdRef> {
49 self.view.verifying_contract()
50 }
51
52 #[inline]
53 fn wnear_id(&self) -> Cow<'_, AccountIdRef> {
54 self.view.wnear_id()
55 }
56
57 #[inline]
58 fn fee(&self) -> Pips {
59 self.view.fee()
60 }
61
62 #[inline]
63 fn fee_collector(&self) -> Cow<'_, AccountIdRef> {
64 self.view.fee_collector()
65 }
66
67 fn has_public_key(&self, account_id: &AccountIdRef, public_key: &PublicKey) -> bool {
68 if let Some(account) = self.accounts.get(account_id).map(Lock::as_inner_unchecked) {
69 if account.public_keys_added.contains(public_key) {
70 return true;
71 }
72 if account.public_keys_removed.contains(public_key) {
73 return false;
74 }
75 }
76 self.view.has_public_key(account_id, public_key)
77 }
78
79 fn iter_public_keys(&self, account_id: &AccountIdRef) -> impl Iterator<Item = PublicKey> + '_ {
80 let account = self.accounts.get(account_id).map(Lock::as_inner_unchecked);
81 self.view
82 .iter_public_keys(account_id)
83 .filter(move |pk| account.is_none_or(|a| !a.public_keys_removed.contains(pk)))
84 .chain(
85 account
86 .map(|a| &a.public_keys_added)
87 .into_iter()
88 .flatten()
89 .copied(),
90 )
91 }
92
93 fn is_nonce_used(&self, account_id: &AccountIdRef, nonce: Nonce) -> bool {
94 self.accounts
95 .get(account_id)
96 .map(Lock::as_inner_unchecked)
97 .is_some_and(|account| account.is_nonce_used(nonce))
98 || self.view.is_nonce_used(account_id, nonce)
99 }
100
101 fn balance_of(&self, account_id: &AccountIdRef, token_id: &TokenId) -> u128 {
102 self.accounts
103 .get(account_id)
104 .map(Lock::as_inner_unchecked)
105 .and_then(|account| account.token_amounts.get(token_id).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 account.token_amounts.get(&token_id).is_none() {
215 account
216 .token_amounts
217 .add(token_id.clone(), self.view.balance_of(&owner_id, &token_id))
218 .ok_or(DefuseError::BalanceOverflow)?;
219 }
220 account
221 .token_amounts
222 .add(token_id, amount)
223 .ok_or(DefuseError::BalanceOverflow)?;
224 }
225 Ok(())
226 }
227
228 fn internal_sub_balance(
229 &mut self,
230 owner_id: &AccountIdRef,
231 token_amounts: impl IntoIterator<Item = (TokenId, u128)>,
232 ) -> Result<()> {
233 let account = self
234 .accounts
235 .get_or_create(owner_id.to_owned(), |owner_id| {
236 self.view.is_account_locked(owner_id)
237 })
238 .get_mut()
239 .ok_or_else(|| DefuseError::AccountLocked(owner_id.to_owned()))?;
240 for (token_id, amount) in token_amounts {
241 if amount == 0 {
242 return Err(DefuseError::InvalidIntent);
243 }
244
245 if account.token_amounts.get(&token_id).is_none() {
246 account
247 .token_amounts
248 .add(token_id.clone(), self.view.balance_of(owner_id, &token_id))
249 .ok_or(DefuseError::BalanceOverflow)?;
250 }
251 account
252 .token_amounts
253 .sub(token_id, amount)
254 .ok_or(DefuseError::BalanceOverflow)?;
255 }
256 Ok(())
257 }
258
259 fn ft_withdraw(&mut self, owner_id: &AccountIdRef, withdraw: FtWithdraw) -> Result<()> {
260 self.internal_sub_balance(
261 owner_id,
262 std::iter::once((
263 Nep141TokenId::new(withdraw.token.clone()).into(),
264 withdraw.amount.0,
265 ))
266 .chain(withdraw.storage_deposit.map(|amount| {
267 (
268 Nep141TokenId::new(self.wnear_id().into_owned()).into(),
269 amount.as_yoctonear(),
270 )
271 })),
272 )
273 }
274
275 fn nft_withdraw(&mut self, owner_id: &AccountIdRef, withdraw: NftWithdraw) -> Result<()> {
276 self.internal_sub_balance(
277 owner_id,
278 std::iter::once((
279 Nep171TokenId::new(withdraw.token.clone(), withdraw.token_id.clone())?.into(),
280 1,
281 ))
282 .chain(withdraw.storage_deposit.map(|amount| {
283 (
284 Nep141TokenId::new(self.wnear_id().into_owned()).into(),
285 amount.as_yoctonear(),
286 )
287 })),
288 )
289 }
290
291 fn mt_withdraw(&mut self, owner_id: &AccountIdRef, withdraw: MtWithdraw) -> Result<()> {
292 if withdraw.token_ids.len() != withdraw.amounts.len() || withdraw.token_ids.is_empty() {
293 return Err(DefuseError::InvalidIntent);
294 }
295
296 let token_ids = std::iter::repeat(withdraw.token.clone())
297 .zip(withdraw.token_ids.iter().cloned())
298 .map(|(token, token_id)| Nep245TokenId::new(token, token_id))
299 .collect::<Result<Vec<_>, _>>()?;
300
301 self.internal_sub_balance(
302 owner_id,
303 token_ids
304 .into_iter()
305 .map(Into::into)
306 .zip(withdraw.amounts.iter().map(|a| a.0))
307 .chain(
308 withdraw
309 .storage_deposit
310 .map(|amount| (self.wnear_token_id(), amount.as_yoctonear())),
311 ),
312 )
313 }
314
315 fn native_withdraw(&mut self, owner_id: &AccountIdRef, withdraw: NativeWithdraw) -> Result<()> {
316 self.internal_sub_balance(
317 owner_id,
318 [(
319 Nep141TokenId::new(self.wnear_id().into_owned()).into(),
320 withdraw.amount.as_yoctonear(),
321 )],
322 )
323 }
324
325 #[inline]
327 fn notify_on_transfer(
328 &self,
329 _sender_id: &AccountIdRef,
330 _receiver_id: AccountId,
331 _tokens: Amounts,
332 _notification: NotifyOnTransfer,
333 ) {
334 }
335
336 fn storage_deposit(
337 &mut self,
338 owner_id: &AccountIdRef,
339 storage_deposit: StorageDeposit,
340 ) -> Result<()> {
341 self.internal_sub_balance(
342 owner_id,
343 [(
344 Nep141TokenId::new(self.wnear_id().into_owned()).into(),
345 storage_deposit.amount.as_yoctonear(),
346 )],
347 )
348 }
349
350 fn set_auth_by_predecessor_id(&mut self, account_id: AccountId, enable: bool) -> Result<bool> {
351 let was_enabled = self.is_auth_by_predecessor_id_enabled(&account_id);
352 let toggle = was_enabled ^ enable;
353 if toggle {
354 self.accounts
355 .get_or_create(account_id.clone(), |owner_id| {
356 self.view.is_account_locked(owner_id)
357 })
358 .get_mut()
359 .ok_or(DefuseError::AccountLocked(account_id))?
360 .auth_by_predecessor_id_toggled ^= true;
362 }
363 Ok(was_enabled)
364 }
365
366 fn auth_call(&mut self, signer_id: &AccountIdRef, auth_call: AuthCall) -> Result<()> {
367 if !auth_call.attached_deposit.is_zero() {
368 self.internal_sub_balance(
369 signer_id,
370 [(
371 Nep141TokenId::new(self.wnear_id().into_owned()).into(),
372 auth_call.attached_deposit.as_yoctonear(),
373 )],
374 )?;
375 }
376
377 Ok(())
378 }
379}
380
381#[derive(Debug, Default)]
382pub struct CachedAccounts(HashMap<AccountId, Lock<CachedAccount>>);
383
384impl CachedAccounts {
385 #[must_use]
386 #[inline]
387 pub fn new() -> Self {
388 Self(HashMap::new())
389 }
390
391 #[inline]
392 pub fn get(&self, account_id: &AccountIdRef) -> Option<&Lock<CachedAccount>> {
393 self.0.get(account_id)
394 }
395
396 #[inline]
397 pub fn get_mut(&mut self, account_id: &AccountIdRef) -> Option<&mut Lock<CachedAccount>> {
398 self.0.get_mut(account_id)
399 }
400
401 #[inline]
402 pub fn get_or_create(
403 &mut self,
404 account_id: AccountId,
405 is_initially_locked: impl FnOnce(&AccountId) -> bool,
406 ) -> &mut Lock<CachedAccount> {
407 self.0.entry(account_id).or_insert_with_key(|account_id| {
408 Lock::new(is_initially_locked(account_id), CachedAccount::default())
409 })
410 }
411}
412
413#[derive(Debug, Clone, Default)]
414pub struct CachedAccount {
415 nonces: Nonces<HashMap<U248, U256>>,
416
417 auth_by_predecessor_id_toggled: bool,
418
419 public_keys_added: HashSet<PublicKey>,
420 public_keys_removed: HashSet<PublicKey>,
421
422 token_amounts: Amounts<HashMap<TokenId, u128>>,
423}
424
425impl CachedAccount {
426 #[inline]
427 pub fn is_nonce_used(&self, nonce: U256) -> bool {
428 self.nonces.is_used(nonce)
429 }
430
431 #[inline]
432 pub fn commit_nonce(&mut self, n: U256) -> Result<()> {
433 self.nonces.commit(n)
434 }
435
436 #[inline]
437 pub fn cleanup_nonce_by_prefix(&mut self, prefix: NoncePrefix) -> bool {
438 self.nonces.cleanup_by_prefix(prefix)
439 }
440}