Skip to main content

defuse/contract/accounts/
mod.rs

1mod account;
2mod force;
3mod state;
4
5pub use self::{account::*, state::*};
6
7use std::{borrow::Cow, collections::HashSet};
8
9use borsh::{BorshDeserialize, BorshSerialize};
10use defuse_core::{
11    DefuseError, Nonce, PublicKey, Result,
12    accounts::{AccountEvent, PublicKeyEvent},
13    engine::{State, StateView},
14    events::DefuseEvent,
15    intents::{MaybeIntentEvent, account::SetAuthByPredecessorId},
16};
17
18use defuse_near_utils::{Lock, NestPrefix};
19use defuse_serde_utils::base64::AsBase64;
20
21use near_sdk::{
22    AccountId, AccountIdRef, BorshStorageKey, FunctionError, IntoStorageKey, assert_one_yocto, env,
23    near, store::IterableMap,
24};
25
26use crate::{
27    accounts::AccountManager,
28    contract::{Contract, ContractExt, accounts::AccountEntry},
29};
30
31#[near]
32impl AccountManager for Contract {
33    fn has_public_key(&self, account_id: &AccountId, public_key: &PublicKey) -> bool {
34        StateView::has_public_key(self, account_id, public_key)
35    }
36
37    fn public_keys_of(&self, account_id: &AccountId) -> HashSet<PublicKey> {
38        StateView::iter_public_keys(self, account_id).collect()
39    }
40
41    #[payable]
42    fn add_public_key(&mut self, public_key: PublicKey) {
43        assert_one_yocto();
44        let account_id = self.ensure_auth_predecessor_id();
45
46        self.add_public_key_and_emit_event(account_id.as_ref(), public_key);
47    }
48
49    #[payable]
50    fn remove_public_key(&mut self, public_key: PublicKey) {
51        assert_one_yocto();
52        let account_id = self.ensure_auth_predecessor_id();
53
54        self.remove_public_key_and_emit_event(account_id.as_ref(), public_key);
55    }
56
57    fn is_nonce_used(&self, account_id: &AccountId, nonce: AsBase64<Nonce>) -> bool {
58        StateView::is_nonce_used(self, account_id, nonce.into_inner())
59    }
60
61    fn is_auth_by_predecessor_id_enabled(&self, account_id: &AccountId) -> bool {
62        StateView::is_auth_by_predecessor_id_enabled(self, account_id)
63    }
64
65    #[payable]
66    fn disable_auth_by_predecessor_id(&mut self) {
67        assert_one_yocto();
68
69        self.set_auth_by_predecessor_id_and_emit_event(
70            &self.ensure_auth_predecessor_id(),
71            false,
72            false,
73        )
74        .unwrap_or_else(|err| err.panic());
75    }
76}
77
78impl Contract {
79    #[inline]
80    pub fn ensure_auth_predecessor_id(&self) -> AccountId {
81        let predecessor_account_id = env::predecessor_account_id();
82        if !StateView::is_auth_by_predecessor_id_enabled(self, &predecessor_account_id) {
83            DefuseError::AuthByPredecessorIdDisabled(predecessor_account_id).panic();
84        }
85        predecessor_account_id
86    }
87
88    /// Sets whether authentication by `PREDECESSOR_ID` is enabled.
89    /// Emits an event if the authentication status was toggled.
90    /// Returns whether authentication by `PREDECESSOR_ID` was toggled.
91    pub fn set_auth_by_predecessor_id_and_emit_event(
92        &mut self,
93        account_id: &AccountIdRef,
94        enable: bool,
95        force: bool,
96    ) -> Result<bool> {
97        let toggled = self.internal_set_auth_by_predecessor_id(account_id, enable, force)?;
98
99        if toggled {
100            DefuseEvent::SetAuthByPredecessorId(MaybeIntentEvent::new_fn_call(AccountEvent::new(
101                Cow::Borrowed(account_id),
102                Cow::Owned(SetAuthByPredecessorId { enabled: enable }),
103            )))
104            .emit();
105        }
106
107        Ok(toggled)
108    }
109
110    /// Sets whether authentication by `PREDECESSOR_ID` is enabled.
111    /// Returns whether authentication by `PREDECESSOR_ID` was toggled.
112    pub(crate) fn internal_set_auth_by_predecessor_id(
113        &mut self,
114        account_id: &AccountIdRef,
115        enable: bool,
116        force: bool,
117    ) -> Result<bool> {
118        if enable {
119            let Some(account) = self.accounts.get_mut(account_id) else {
120                // no need to create an account: not-yet-existing accounts
121                // have auth by PREDECESSOR_ID enabled by default
122                return Ok(false);
123            };
124            account
125        } else {
126            self.accounts.get_or_create(account_id.into())
127        }
128        .get_mut_maybe_forced(force)
129        .ok_or_else(|| DefuseError::AccountLocked(account_id.into()))
130        .map(|account| account.set_auth_by_predecessor_id(enable))
131    }
132
133    pub fn add_public_key_and_emit_event(
134        &mut self,
135        account_id: &AccountIdRef,
136        public_key: PublicKey,
137    ) {
138        State::add_public_key(self, account_id.into(), public_key)
139            .unwrap_or_else(|err| err.panic());
140
141        DefuseEvent::PublicKeyAdded(MaybeIntentEvent::new_fn_call(AccountEvent::new(
142            Cow::Borrowed(account_id),
143            PublicKeyEvent {
144                public_key: Cow::Borrowed(&public_key),
145            },
146        )))
147        .emit();
148    }
149
150    pub fn remove_public_key_and_emit_event(
151        &mut self,
152        account_id: &AccountIdRef,
153        public_key: PublicKey,
154    ) {
155        State::remove_public_key(self, account_id.into(), public_key)
156            .unwrap_or_else(|err| err.panic());
157
158        DefuseEvent::PublicKeyRemoved(MaybeIntentEvent::new_fn_call(AccountEvent::new(
159            Cow::Borrowed(account_id),
160            PublicKeyEvent {
161                public_key: Cow::Borrowed(&public_key),
162            },
163        )))
164        .emit();
165    }
166}
167
168#[cfg_attr(feature = "abi", derive(::borsh::BorshSchema))]
169#[derive(Debug, BorshSerialize, BorshDeserialize)]
170pub struct Accounts {
171    accounts: IterableMap<AccountId, AccountEntry>,
172    prefix: Vec<u8>,
173}
174
175impl Accounts {
176    #[inline]
177    pub fn new<S>(prefix: S) -> Self
178    where
179        S: IntoStorageKey,
180    {
181        let prefix = prefix.into_storage_key();
182
183        Self {
184            accounts: IterableMap::new(prefix.as_slice().nest(AccountsPrefix::Accounts)),
185            prefix,
186        }
187    }
188
189    #[inline]
190    pub fn get(&self, account_id: &AccountIdRef) -> Option<&Lock<Account>> {
191        self.accounts.get(account_id).map(|a| &**a)
192    }
193
194    #[inline]
195    pub fn get_mut(&mut self, account_id: &AccountIdRef) -> Option<&mut Lock<Account>> {
196        self.accounts.get_mut(account_id).map(|a| &mut **a)
197    }
198
199    /// Gets or creates an account with given `account_id`.
200    /// NOTE: The created account will be unblocked by default.
201    #[inline]
202    pub fn get_or_create(&mut self, account_id: AccountId) -> &mut Lock<Account> {
203        self.accounts
204            .entry(account_id)
205            .or_insert_with_key(|account_id| {
206                Lock::unlocked(Account::new(
207                    self.prefix
208                        .as_slice()
209                        .nest(AccountsPrefix::Account(account_id)),
210                    account_id,
211                ))
212                .into()
213            })
214    }
215}
216
217#[derive(BorshSerialize, BorshStorageKey)]
218#[borsh(crate = "::near_sdk::borsh")]
219enum AccountsPrefix<'a> {
220    Accounts,
221    Account(&'a AccountIdRef),
222}