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 defuse_core::{
10    DefuseError, Nonce, Result,
11    accounts::{AccountEvent, PublicKeyEvent},
12    crypto::PublicKey,
13    engine::{State, StateView},
14    events::DefuseEvent,
15    intents::{MaybeIntentEvent, account::SetAuthByPredecessorId},
16};
17
18use defuse_near_utils::{Lock, NestPrefix, UnwrapOrPanic};
19use defuse_serde_utils::base64::AsBase64;
20
21use near_sdk::{
22    AccountId, AccountIdRef, BorshStorageKey, FunctionError, IntoStorageKey, assert_one_yocto,
23    borsh::BorshSerialize, env, 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_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).unwrap_or_panic();
139
140        DefuseEvent::PublicKeyAdded(MaybeIntentEvent::new_fn_call(AccountEvent::new(
141            Cow::Borrowed(account_id),
142            PublicKeyEvent {
143                public_key: Cow::Borrowed(&public_key),
144            },
145        )))
146        .emit();
147    }
148
149    pub fn remove_public_key_and_emit_event(
150        &mut self,
151        account_id: &AccountIdRef,
152        public_key: PublicKey,
153    ) {
154        State::remove_public_key(self, account_id.into(), public_key).unwrap_or_panic();
155
156        DefuseEvent::PublicKeyRemoved(MaybeIntentEvent::new_fn_call(AccountEvent::new(
157            Cow::Borrowed(account_id),
158            PublicKeyEvent {
159                public_key: Cow::Borrowed(&public_key),
160            },
161        )))
162        .emit();
163    }
164}
165
166#[derive(Debug)]
167#[near(serializers = [borsh])]
168pub struct Accounts {
169    accounts: IterableMap<AccountId, AccountEntry>,
170    prefix: Vec<u8>,
171}
172
173impl Accounts {
174    #[inline]
175    pub fn new<S>(prefix: S) -> Self
176    where
177        S: IntoStorageKey,
178    {
179        let prefix = prefix.into_storage_key();
180
181        Self {
182            accounts: IterableMap::new(prefix.as_slice().nest(AccountsPrefix::Accounts)),
183            prefix,
184        }
185    }
186
187    #[inline]
188    pub fn get(&self, account_id: &AccountIdRef) -> Option<&Lock<Account>> {
189        self.accounts.get(account_id).map(|a| &**a)
190    }
191
192    #[inline]
193    pub fn get_mut(&mut self, account_id: &AccountIdRef) -> Option<&mut Lock<Account>> {
194        self.accounts.get_mut(account_id).map(|a| &mut **a)
195    }
196
197    /// Gets or creates an account with given `account_id`.
198    /// NOTE: The created account will be unblocked by default.
199    #[inline]
200    pub fn get_or_create(&mut self, account_id: AccountId) -> &mut Lock<Account> {
201        self.accounts
202            .entry(account_id)
203            .or_insert_with_key(|account_id| {
204                Lock::unlocked(Account::new(
205                    self.prefix
206                        .as_slice()
207                        .nest(AccountsPrefix::Account(account_id)),
208                    account_id,
209                ))
210                .into()
211            })
212    }
213}
214
215#[derive(BorshSerialize, BorshStorageKey)]
216#[borsh(crate = "::near_sdk::borsh")]
217enum AccountsPrefix<'a> {
218    Accounts,
219    Account(&'a AccountIdRef),
220}