use defuse_core::{ DefuseError, Nonce, NoncePrefix, Result, Salt, amounts::Amounts, crypto::PublicKey, engine::{State, StateView}, fees::Pips, intents::{ auth::AuthCall, tokens::{ FtWithdraw, MtWithdraw, NativeWithdraw, NftWithdraw, NotifyOnTransfer, StorageDeposit, }, }, token_id::{TokenId, nep141::Nep141TokenId}, }; use defuse_near_utils::{CURRENT_ACCOUNT_ID, Lock}; use defuse_wnear::{NEAR_WITHDRAW_GAS, ext_wnear}; use near_sdk::{AccountId, AccountIdRef, NearToken, json_types::U128}; use std::borrow::Cow; use crate::contract::{Contract, accounts::Account}; impl StateView for Contract { #[inline] fn verifying_contract(&self) -> Cow<'_, AccountIdRef> { Cow::Borrowed(CURRENT_ACCOUNT_ID.as_ref()) } #[inline] fn wnear_id(&self) -> Cow<'_, AccountIdRef> { Cow::Borrowed(self.state.wnear_id.as_ref()) } #[inline] fn fee(&self) -> Pips { self.state.fees.fee } #[inline] fn fee_collector(&self) -> Cow<'_, AccountIdRef> { Cow::Borrowed(self.state.fees.fee_collector.as_ref()) } #[inline] fn has_public_key(&self, account_id: &AccountIdRef, public_key: &PublicKey) -> bool { self.accounts .get(account_id) .map(Lock::as_inner_unchecked) .map_or_else( || account_id == public_key.to_implicit_account_id(), |account| account.has_public_key(account_id, public_key), ) } fn iter_public_keys(&self, account_id: &AccountIdRef) -> impl Iterator + '_ { let account = self.accounts.get(account_id).map(Lock::as_inner_unchecked); account .map(|account| account.iter_public_keys(account_id)) .into_iter() .flatten() .chain(if account.is_none() { PublicKey::from_implicit_account_id(account_id) } else { None }) } #[inline] fn is_nonce_used(&self, account_id: &AccountIdRef, nonce: Nonce) -> bool { self.accounts .get(account_id) .map(Lock::as_inner_unchecked) .is_some_and(|account| account.is_nonce_used(nonce)) } #[inline] fn balance_of(&self, account_id: &AccountIdRef, token_id: &TokenId) -> u128 { self.accounts .get(account_id) .map(Lock::as_inner_unchecked) .map(|account| account.token_balances.amount_for(token_id)) .unwrap_or_default() } #[inline] fn is_account_locked(&self, account_id: &AccountIdRef) -> bool { self.accounts.get(account_id).is_some_and(Lock::is_locked) } #[inline] fn is_auth_by_predecessor_id_enabled(&self, account_id: &AccountIdRef) -> bool { self.accounts .get(account_id) .map(Lock::as_inner_unchecked) .is_none_or(Account::is_auth_by_predecessor_id_enabled) } fn is_valid_salt(&self, salt: Salt) -> bool { self.salts.is_valid(salt) } } impl State for Contract { #[inline] fn add_public_key(&mut self, account_id: AccountId, public_key: PublicKey) -> Result<()> { self.accounts .get_or_create(account_id.clone()) .get_mut() .ok_or_else(|| DefuseError::AccountLocked(account_id.clone()))? .add_public_key(&account_id, public_key) .then_some(()) .ok_or(DefuseError::PublicKeyExists(account_id, public_key)) } #[inline] fn remove_public_key(&mut self, account_id: AccountId, public_key: PublicKey) -> Result<()> { self.accounts .get_or_create(account_id.clone()) .get_mut() .ok_or_else(|| DefuseError::AccountLocked(account_id.clone()))? .remove_public_key(&account_id, &public_key) .then_some(()) .ok_or(DefuseError::PublicKeyNotExist(account_id, public_key)) } #[inline] fn commit_nonce(&mut self, account_id: AccountId, nonce: Nonce) -> Result<()> { self.accounts .get_or_create(account_id.clone()) .get_mut() .ok_or(DefuseError::AccountLocked(account_id))? .commit_nonce(nonce) } #[inline] fn cleanup_nonce_by_prefix( &mut self, account_id: &AccountIdRef, prefix: NoncePrefix, ) -> Result { let account = self .accounts .get_mut(account_id) .ok_or_else(|| DefuseError::AccountNotFound(account_id.to_owned()))? .as_inner_unchecked_mut(); Ok(account.cleanup_nonce_by_prefix(prefix)) } fn internal_add_balance( &mut self, owner_id: AccountId, tokens: impl IntoIterator, ) -> Result<()> { let owner = self .accounts .get_or_create(owner_id) // we allow locked accounts to accept deposits and incoming deposits .as_inner_unchecked_mut(); for (token_id, amount) in tokens { if amount == 0 { return Err(DefuseError::InvalidIntent); } owner .token_balances .add(token_id, amount) .ok_or(DefuseError::BalanceOverflow)?; } Ok(()) } fn internal_sub_balance( &mut self, owner_id: &AccountIdRef, tokens: impl IntoIterator, ) -> Result<()> { let owner = self .accounts .get_mut(owner_id) .ok_or_else(|| DefuseError::AccountNotFound(owner_id.to_owned()))? .get_mut() .ok_or_else(|| DefuseError::AccountLocked(owner_id.to_owned()))?; for (token_id, amount) in tokens { if amount == 0 { return Err(DefuseError::InvalidIntent); } owner .token_balances .sub(token_id.clone(), amount) .ok_or(DefuseError::BalanceOverflow)?; } Ok(()) } fn ft_withdraw(&mut self, owner_id: &AccountIdRef, withdraw: FtWithdraw) -> Result<()> { self.internal_ft_withdraw(owner_id.to_owned(), withdraw, false) // detach promise .map(|_promise| ()) } fn nft_withdraw(&mut self, owner_id: &AccountIdRef, withdraw: NftWithdraw) -> Result<()> { self.internal_nft_withdraw(owner_id.to_owned(), withdraw, false) // detach promise .map(|_promise| ()) } fn mt_withdraw(&mut self, owner_id: &AccountIdRef, withdraw: MtWithdraw) -> Result<()> { self.internal_mt_withdraw(owner_id.to_owned(), withdraw, false) // detach promise .map(|_promise| ()) } fn native_withdraw(&mut self, owner_id: &AccountIdRef, withdraw: NativeWithdraw) -> Result<()> { self.withdraw( owner_id, [( Nep141TokenId::new(self.wnear_id().into_owned()).into(), withdraw.amount.as_yoctonear(), )], Some("withdraw"), false, )?; // detach promise let _ = ext_wnear::ext(self.wnear_id.clone()) .with_attached_deposit(NearToken::from_yoctonear(1)) .with_static_gas(NEAR_WITHDRAW_GAS) // do not distribute remaining gas here .with_unused_gas_weight(0) .near_withdraw(U128(withdraw.amount.as_yoctonear())) .then( // do_native_withdraw only after unwrapping NEAR Self::ext(CURRENT_ACCOUNT_ID.clone()) .with_static_gas(Self::DO_NATIVE_WITHDRAW_GAS) // do not distribute remaining gas here .with_unused_gas_weight(0) .do_native_withdraw(withdraw), ); Ok(()) } #[inline] fn notify_on_transfer( &self, sender_id: &AccountIdRef, receiver_id: AccountId, tokens: Amounts, notification: NotifyOnTransfer, ) { let (token_ids, amounts) = tokens .iter() .map(|(token_id, amount)| (token_id.to_string(), U128(*amount))) .unzip(); Self::notify_and_resolve_transfer( sender_id.to_owned(), receiver_id, token_ids, amounts, notification, ) .detach(); } fn storage_deposit( &mut self, owner_id: &AccountIdRef, storage_deposit: StorageDeposit, ) -> Result<()> { self.withdraw( owner_id, [( Nep141TokenId::new(self.wnear_id().into_owned()).into(), storage_deposit.amount.as_yoctonear(), )], Some("withdraw"), false, )?; // detach promise let _ = ext_wnear::ext(self.wnear_id.clone()) .with_attached_deposit(NearToken::from_yoctonear(1)) .with_static_gas(NEAR_WITHDRAW_GAS) // do not distribute remaining gas here .with_unused_gas_weight(0) .near_withdraw(U128(storage_deposit.amount.as_yoctonear())) .then( // do_storage_deposit only after unwrapping NEAR Self::ext(CURRENT_ACCOUNT_ID.clone()) .with_static_gas(Self::DO_STORAGE_DEPOSIT_GAS) // do not distribute remaining gas here .with_unused_gas_weight(0) .do_storage_deposit(storage_deposit), ); Ok(()) } fn set_auth_by_predecessor_id(&mut self, account_id: AccountId, enable: bool) -> Result { self.internal_set_auth_by_predecessor_id(&account_id, enable, false) } fn auth_call(&mut self, signer_id: &AccountIdRef, auth_call: AuthCall) -> Result<()> { // detach promise let _ = if auth_call.attached_deposit.is_zero() { Self::do_auth_call(signer_id.to_owned(), auth_call) } else { // withdraw from signer's wNEAR balance self.withdraw( signer_id, [( Nep141TokenId::new(self.wnear_id().into_owned()).into(), auth_call.attached_deposit.as_yoctonear(), )], Some("withdraw"), false, )?; ext_wnear::ext(self.wnear_id.clone()) .with_attached_deposit(NearToken::from_yoctonear(1)) .with_static_gas(NEAR_WITHDRAW_GAS) // do not distribute remaining gas here .with_unused_gas_weight(0) .near_withdraw(U128(auth_call.attached_deposit.as_yoctonear())) .then( // do_auth_call only after unwrapping NEAR Self::ext(CURRENT_ACCOUNT_ID.clone()) .with_static_gas( Self::DO_AUTH_CALL_MIN_GAS.saturating_add(auth_call.min_gas()), ) .do_auth_call(signer_id.to_owned(), auth_call), ) }; Ok(()) } }