defuse_core/engine/state/
cached.rs1use 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 .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}