odra_modules/access/
ownable.rs

1//! Ownable module.
2use crate::access::errors::Error;
3use crate::access::events::{OwnershipTransferStarted, OwnershipTransferred};
4use odra::prelude::*;
5
6/// This module provides a straightforward access control feature that enables
7/// exclusive access to particular functions by an account, known as the owner.
8/// The account that initiates contract deployment is automatically assigned as
9/// the owner. However, ownership can be transferred later by using the
10/// `transfer_ownership()` function.
11///
12/// You can use this module as a standalone contract or integrate it into
13/// a custom module by adding it as a field.
14///
15/// When used in a custom module, the `only_owner()` function is available,
16/// allowing you to restrict function usage to the owner.
17#[odra::module(events = [OwnershipTransferred], errors = Error)]
18pub struct Ownable {
19    owner: Var<Option<Address>>
20}
21
22#[odra::module]
23impl Ownable {
24    /// Initializes the module setting the caller as the initial owner.
25    pub fn init(&mut self) {
26        let caller = self.env().caller();
27        let initial_owner = Some(caller);
28        self.unchecked_transfer_ownership(initial_owner);
29    }
30
31    /// Transfers ownership of the module to `new_owner`. This function can only
32    /// be accessed by the current owner of the module.
33    pub fn transfer_ownership(&mut self, new_owner: &Address) {
34        self.assert_owner(&self.env().caller());
35        self.unchecked_transfer_ownership(Some(*new_owner));
36    }
37
38    /// If the contract's owner chooses to renounce their ownership, the contract
39    /// will no longer have an owner. This means that any functions that can only
40    /// be accessed by the owner will no longer be available.
41    ///
42    /// The function can only be called by the current owner, and it will permanently
43    /// remove the owner's privileges.
44    pub fn renounce_ownership(&mut self) {
45        self.assert_owner(&self.env().caller());
46        self.unchecked_transfer_ownership(None);
47    }
48
49    /// Returns the address of the current owner.
50    pub fn get_owner(&self) -> Address {
51        self.get_optional_owner()
52            .unwrap_or_revert_with(&self.env(), Error::OwnerNotSet)
53    }
54
55    /// Reverts with [`Error::CallerNotTheOwner`] if the function called by
56    /// any account other than the owner.
57    pub fn assert_owner(&self, address: &Address) {
58        if Some(address) != self.get_optional_owner().as_ref() {
59            self.env().revert(Error::CallerNotTheOwner)
60        }
61    }
62
63    /// Returns the optional address of the current owner.
64    pub fn get_optional_owner(&self) -> Option<Address> {
65        self.owner.get().flatten()
66    }
67
68    /// Unchecked version of the ownership transfer. It emits an event and sets
69    /// the new owner.
70    pub fn unchecked_transfer_ownership(&mut self, new_owner: Option<Address>) {
71        let previous_owner = self.get_optional_owner();
72        self.owner.set(new_owner);
73
74        self.env().emit_event(OwnershipTransferred {
75            previous_owner,
76            new_owner
77        });
78    }
79}
80
81/// This module provides a straightforward access control feature that enables
82/// exclusive access to particular functions by an account, known as the owner.
83/// The account that initiates contract deployment is automatically assigned as
84/// the owner. However, ownership can be transferred later by using the
85/// `transfer_ownership()` and `accept_ownership()` functions.
86///
87/// You can use this module as a standalone contract or integrate it into
88/// a custom module by adding it as a field.
89///
90/// When used in a custom module, the `only_owner()` function is available,
91/// allowing you to restrict function usage to the owner.
92#[odra::module(events = [OwnershipTransferStarted], errors = Error)]
93pub struct Ownable2Step {
94    ownable: SubModule<Ownable>,
95    pending_owner: Var<Option<Address>>
96}
97
98#[odra::module]
99impl Ownable2Step {
100    /// Initializes the module setting the caller as the initial owner.
101    pub fn init(&mut self) {
102        self.ownable.init();
103    }
104
105    /// Returns the address of the current owner.
106    pub fn get_owner(&self) -> Address {
107        self.ownable.get_owner()
108    }
109
110    /// Returns the address of the pending owner.
111    pub fn get_pending_owner(&self) -> Option<Address> {
112        self.pending_owner.get().flatten()
113    }
114
115    /// Starts the ownership transfer of the module to a `new_owner`.
116    /// Replaces the `pending_owner`if there is one.
117    ///
118    /// This function can only be accessed by the current owner of the module.
119    pub fn transfer_ownership(&mut self, new_owner: &Address) {
120        self.ownable.assert_owner(&self.env().caller());
121
122        let previous_owner = self.ownable.get_optional_owner();
123        let new_owner = Some(*new_owner);
124        self.pending_owner.set(new_owner);
125        self.env().emit_event(OwnershipTransferred {
126            previous_owner,
127            new_owner
128        });
129    }
130
131    /// If the contract's owner chooses to renounce their ownership, the contract
132    /// will no longer have an owner. This means that any functions that can only
133    /// be accessed by the owner will no longer be available.
134    ///
135    /// The function can only be called by the current owner, and it will permanently
136    /// remove the owner's privileges.
137    pub fn renounce_ownership(&mut self) {
138        self.ownable.renounce_ownership()
139    }
140
141    /// The new owner accepts the ownership transfer. Replaces the current owner and clears
142    /// the pending owner.
143    pub fn accept_ownership(&mut self) {
144        let caller = self.env().caller();
145        let caller = Some(caller);
146        let pending_owner = self.pending_owner.get().flatten();
147        if pending_owner != caller {
148            self.env().revert(Error::CallerNotTheNewOwner)
149        }
150        self.pending_owner.set(None);
151        self.ownable.unchecked_transfer_ownership(caller);
152    }
153}
154
155#[cfg(test)]
156mod test {
157    use super::*;
158    use crate::access::errors::Error;
159    use odra::{
160        external_contract,
161        host::{Deployer, HostEnv, HostRef, NoArgs}
162    };
163
164    #[test]
165    fn init() {
166        // given new contacts
167        let (env, ownable, ownable_2step, deployer) = setup_owned();
168
169        // then the deployer is the owner
170        assert_eq!(deployer, ownable.get_owner());
171        assert_eq!(deployer, ownable_2step.get_owner());
172        // then a OwnershipTransferred event was emitted
173
174        let event = OwnershipTransferred {
175            previous_owner: None,
176            new_owner: Some(deployer)
177        };
178
179        env.emitted_event(ownable.address(), &event);
180        env.emitted_event(ownable_2step.address(), &event);
181    }
182
183    #[test]
184    fn plain_ownership_transfer() {
185        // given a new contract
186        let (mut contract, initial_owner) = setup_ownable();
187
188        // when the current owner transfers ownership
189        let new_owner = contract.env().get_account(1);
190        contract.transfer_ownership(&new_owner);
191
192        // then the new owner is set
193        assert_eq!(new_owner, contract.get_owner());
194        // then a OwnershipTransferred event was emitted
195        contract.env().emitted_event(
196            contract.address(),
197            &OwnershipTransferred {
198                previous_owner: Some(initial_owner),
199                new_owner: Some(new_owner)
200            }
201        );
202    }
203
204    #[test]
205    fn two_step_ownership_transfer() {
206        // given a new contract
207        let (mut contract, initial_owner) = setup_ownable_2_step();
208
209        // when the current owner transfers ownership
210        let new_owner = contract.env().get_account(1);
211        contract.transfer_ownership(&new_owner);
212
213        // when the pending owner accepts the transfer
214        contract.env().set_caller(new_owner);
215        contract.accept_ownership();
216
217        // then the new owner is set
218        assert_eq!(new_owner, contract.get_owner());
219        // then the pending owner is unset
220        assert_eq!(None, contract.get_pending_owner());
221        // then OwnershipTransferStarted and OwnershipTransferred events were emitted
222        contract.env().emitted_event(
223            contract.address(),
224            &OwnershipTransferStarted {
225                previous_owner: Some(initial_owner),
226                new_owner: Some(new_owner)
227            }
228        );
229        contract.env().emitted_event(
230            contract.address(),
231            &OwnershipTransferred {
232                previous_owner: Some(initial_owner),
233                new_owner: Some(new_owner)
234            }
235        );
236    }
237
238    #[test]
239    fn failing_plain_ownership_transfer() {
240        // given a new contract
241        let (mut contract, _) = setup_ownable();
242
243        // when a non-owner account is the caller
244        let (caller, new_owner) = (contract.env().get_account(1), contract.env().get_account(2));
245        contract.env().set_caller(caller);
246
247        // then ownership transfer fails
248        let err = contract.try_transfer_ownership(&new_owner).unwrap_err();
249        assert_eq!(err, Error::CallerNotTheOwner.into());
250    }
251
252    #[test]
253    fn failing_two_step_transfer() {
254        // given a new contract
255        let (mut contract, initial_owner) = setup_ownable_2_step();
256
257        // when a non-owner account is the caller
258        let (caller, new_owner) = (contract.env().get_account(1), contract.env().get_account(2));
259        contract.env().set_caller(caller);
260
261        // then ownership transfer fails
262        let err = contract.try_transfer_ownership(&new_owner).unwrap_err();
263        assert_eq!(err, Error::CallerNotTheOwner.into());
264
265        // when the owner is the caller
266        contract.env().set_caller(initial_owner);
267        contract.transfer_ownership(&new_owner);
268
269        // then the pending owner is set
270        assert_eq!(contract.get_pending_owner(), Some(new_owner));
271
272        // when someone else than the pending owner accepts the ownership
273        // transfer, it should fail
274        let err = contract.try_accept_ownership().unwrap_err();
275        assert_eq!(err, Error::CallerNotTheNewOwner.into());
276
277        // then the owner remain the same
278        assert_eq!(contract.get_owner(), initial_owner);
279        // then the pending owner remain the same
280        assert_eq!(contract.get_pending_owner(), Some(new_owner));
281    }
282
283    #[test]
284    fn renounce_ownership() {
285        // given new contracts
286        let (mut contracts, initial_owner) = setup_renounceable();
287
288        contracts
289            .iter_mut()
290            .for_each(|contract: &mut RenounceableHostRef| {
291                // when the current owner renounce ownership
292                contract.renounce_ownership();
293
294                // then an event is emitted
295                contract.env().emitted_event(
296                    contract.address(),
297                    &OwnershipTransferred {
298                        previous_owner: Some(initial_owner),
299                        new_owner: None
300                    }
301                );
302                // then the owner is not set
303                let err = contract.try_get_owner().unwrap_err();
304                assert_eq!(err, Error::OwnerNotSet.into());
305                // then cannot renounce ownership again
306                let err = contract.try_renounce_ownership().unwrap_err();
307                assert_eq!(err, Error::CallerNotTheOwner.into());
308            });
309    }
310
311    #[test]
312    fn renounce_ownership_fail() {
313        // given new contracts
314        let (mut contracts, _) = setup_renounceable();
315
316        contracts.iter_mut().for_each(|contract| {
317            // when a non-owner account is the caller
318            let caller = contract.env().get_account(1);
319            contract.env().set_caller(caller);
320
321            // then renounce ownership fails
322            let err = contract.try_renounce_ownership().unwrap_err();
323            assert_eq!(err, Error::CallerNotTheOwner.into());
324        });
325    }
326
327    #[external_contract]
328    trait Owned {
329        fn get_owner(&self) -> Address;
330    }
331
332    #[external_contract]
333    trait Renounceable {
334        fn renounce_ownership(&mut self);
335        fn get_owner(&self) -> Address;
336    }
337
338    fn setup_ownable() -> (OwnableHostRef, Address) {
339        let env = odra_test::env();
340        (Ownable::deploy(&env, NoArgs), env.get_account(0))
341    }
342
343    fn setup_ownable_2_step() -> (Ownable2StepHostRef, Address) {
344        let env = odra_test::env();
345        (Ownable2Step::deploy(&env, NoArgs), env.get_account(0))
346    }
347
348    fn setup_renounceable() -> (Vec<RenounceableHostRef>, Address) {
349        let env = odra_test::env();
350        let ownable = Ownable::deploy(&env, NoArgs);
351        let ownable_2_step = Ownable2Step::deploy(&env, NoArgs);
352        let renouncable_ref = RenounceableHostRef::new(*ownable.address(), env.clone());
353        let renouncable_2_step_ref =
354            RenounceableHostRef::new(*ownable_2_step.address(), env.clone());
355        (
356            vec![renouncable_ref, renouncable_2_step_ref],
357            env.get_account(0)
358        )
359    }
360
361    fn setup_owned() -> (HostEnv, OwnableHostRef, Ownable2StepHostRef, Address) {
362        let env = odra_test::env();
363        let ownable = Ownable::deploy(&env, NoArgs);
364        let ownable_2_step = Ownable2Step::deploy(&env, NoArgs);
365        (env.clone(), ownable, ownable_2_step, env.get_account(0))
366    }
367}