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