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