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 self.internal_sub_balance(
297 owner_id,
298 withdraw
299 .token_ids
300 .iter()
301 .cloned()
302 .map(|token_id| Nep245TokenId::new(withdraw.token.clone(), token_id))
303 .map(Into::into)
304 .zip(withdraw.amounts.iter().map(|a| a.0))
305 .chain(
306 withdraw
307 .storage_deposit
308 .map(|amount| (self.wnear_token_id(), amount.as_yoctonear())),
309 ),
310 )
311 }
312
313 fn native_withdraw(&mut self, owner_id: &AccountIdRef, withdraw: NativeWithdraw) -> Result<()> {
314 self.internal_sub_balance(
315 owner_id,
316 [(
317 Nep141TokenId::new(self.wnear_id().into_owned()).into(),
318 withdraw.amount.as_yoctonear(),
319 )],
320 )
321 }
322
323 #[inline]
325 fn notify_on_transfer(
326 &self,
327 _sender_id: &AccountIdRef,
328 _receiver_id: AccountId,
329 _tokens: Amounts,
330 _notification: NotifyOnTransfer,
331 ) {
332 }
333
334 fn storage_deposit(
335 &mut self,
336 owner_id: &AccountIdRef,
337 storage_deposit: StorageDeposit,
338 ) -> Result<()> {
339 self.internal_sub_balance(
340 owner_id,
341 [(
342 Nep141TokenId::new(self.wnear_id().into_owned()).into(),
343 storage_deposit.amount.as_yoctonear(),
344 )],
345 )
346 }
347
348 fn set_auth_by_predecessor_id(&mut self, account_id: AccountId, enable: bool) -> Result<bool> {
349 let was_enabled = self.is_auth_by_predecessor_id_enabled(&account_id);
350 let toggle = was_enabled ^ enable;
351 if toggle {
352 self.accounts
353 .get_or_create(account_id.clone(), |owner_id| {
354 self.view.is_account_locked(owner_id)
355 })
356 .get_mut()
357 .ok_or(DefuseError::AccountLocked(account_id))?
358 .auth_by_predecessor_id_toggled ^= true;
360 }
361 Ok(was_enabled)
362 }
363
364 fn auth_call(&mut self, signer_id: &AccountIdRef, auth_call: AuthCall) -> Result<()> {
365 if !auth_call.attached_deposit.is_zero() {
366 self.internal_sub_balance(
367 signer_id,
368 [(
369 Nep141TokenId::new(self.wnear_id().into_owned()).into(),
370 auth_call.attached_deposit.as_yoctonear(),
371 )],
372 )?;
373 }
374
375 Ok(())
376 }
377}
378
379#[derive(Debug, Default)]
380pub struct CachedAccounts(HashMap<AccountId, Lock<CachedAccount>>);
381
382impl CachedAccounts {
383 #[must_use]
384 #[inline]
385 pub fn new() -> Self {
386 Self(HashMap::new())
387 }
388
389 #[inline]
390 pub fn get(&self, account_id: &AccountIdRef) -> Option<&Lock<CachedAccount>> {
391 self.0.get(account_id)
392 }
393
394 #[inline]
395 pub fn get_mut(&mut self, account_id: &AccountIdRef) -> Option<&mut Lock<CachedAccount>> {
396 self.0.get_mut(account_id)
397 }
398
399 #[inline]
400 pub fn get_or_create(
401 &mut self,
402 account_id: AccountId,
403 is_initially_locked: impl FnOnce(&AccountId) -> bool,
404 ) -> &mut Lock<CachedAccount> {
405 self.0.entry(account_id).or_insert_with_key(|account_id| {
406 Lock::new(is_initially_locked(account_id), CachedAccount::default())
407 })
408 }
409}
410
411#[derive(Debug, Clone, Default)]
412pub struct CachedAccount {
413 nonces: Nonces<HashMap<U248, U256>>,
414
415 auth_by_predecessor_id_toggled: bool,
416
417 public_keys_added: HashSet<PublicKey>,
418 public_keys_removed: HashSet<PublicKey>,
419
420 token_amounts: Amounts<HashMap<TokenId, u128>>,
421}
422
423impl CachedAccount {
424 #[inline]
425 pub fn is_nonce_used(&self, nonce: U256) -> bool {
426 self.nonces.is_used(nonce)
427 }
428
429 #[inline]
430 pub fn commit_nonce(&mut self, n: U256) -> Result<()> {
431 self.nonces.commit(n)
432 }
433
434 #[inline]
435 pub fn cleanup_nonce_by_prefix(&mut self, prefix: NoncePrefix) -> bool {
436 self.nonces.cleanup_by_prefix(prefix)
437 }
438}