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(OwnershipTransferred {
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    };
166
167    #[test]
168    fn init() {
169        // given new contacts
170        let (env, ownable, ownable_2step, deployer) = setup_owned();
171
172        // then the deployer is the owner
173        assert_eq!(deployer, ownable.get_owner());
174        assert_eq!(deployer, ownable_2step.get_owner());
175        // then a OwnershipTransferred event was emitted
176
177        let event = OwnershipTransferred {
178            previous_owner: None,
179            new_owner: Some(deployer)
180        };
181
182        env.emitted_event(ownable.address(), &event);
183        env.emitted_event(ownable_2step.address(), &event);
184    }
185
186    #[test]
187    fn plain_ownership_transfer() {
188        // given a new contract
189        let (mut contract, initial_owner) = setup_ownable();
190
191        // when the current owner transfers ownership
192        let new_owner = contract.env().get_account(1);
193        contract.transfer_ownership(&new_owner);
194
195        // then the new owner is set
196        assert_eq!(new_owner, contract.get_owner());
197        // then a OwnershipTransferred event was emitted
198        contract.env().emitted_event(
199            contract.address(),
200            &OwnershipTransferred {
201                previous_owner: Some(initial_owner),
202                new_owner: Some(new_owner)
203            }
204        );
205    }
206
207    #[test]
208    fn two_step_ownership_transfer() {
209        // given a new contract
210        let (mut contract, initial_owner) = setup_ownable_2_step();
211
212        // when the current owner transfers ownership
213        let new_owner = contract.env().get_account(1);
214        contract.transfer_ownership(&new_owner);
215
216        // when the pending owner accepts the transfer
217        contract.env().set_caller(new_owner);
218        contract.accept_ownership();
219
220        // then the new owner is set
221        assert_eq!(new_owner, contract.get_owner());
222        // then the pending owner is unset
223        assert_eq!(None, contract.get_pending_owner());
224        // then OwnershipTransferStarted and OwnershipTransferred events were emitted
225        contract.env().emitted_event(
226            contract.address(),
227            &OwnershipTransferStarted {
228                previous_owner: Some(initial_owner),
229                new_owner: Some(new_owner)
230            }
231        );
232        contract.env().emitted_event(
233            contract.address(),
234            &OwnershipTransferred {
235                previous_owner: Some(initial_owner),
236                new_owner: Some(new_owner)
237            }
238        );
239    }
240
241    #[test]
242    fn failing_plain_ownership_transfer() {
243        // given a new contract
244        let (mut contract, _) = setup_ownable();
245
246        // when a non-owner account is the caller
247        let (caller, new_owner) = (contract.env().get_account(1), contract.env().get_account(2));
248        contract.env().set_caller(caller);
249
250        // then ownership transfer fails
251        let err = contract.try_transfer_ownership(&new_owner).unwrap_err();
252        assert_eq!(err, Error::CallerNotTheOwner.into());
253    }
254
255    #[test]
256    fn failing_two_step_transfer() {
257        // given a new contract
258        let (mut contract, initial_owner) = setup_ownable_2_step();
259
260        // when a non-owner account is the caller
261        let (caller, new_owner) = (contract.env().get_account(1), contract.env().get_account(2));
262        contract.env().set_caller(caller);
263
264        // then ownership transfer fails
265        let err = contract.try_transfer_ownership(&new_owner).unwrap_err();
266        assert_eq!(err, Error::CallerNotTheOwner.into());
267
268        // when the owner is the caller
269        contract.env().set_caller(initial_owner);
270        contract.transfer_ownership(&new_owner);
271
272        // then the pending owner is set
273        assert_eq!(contract.get_pending_owner(), Some(new_owner));
274
275        // when someone else than the pending owner accepts the ownership
276        // transfer, it should fail
277        let err = contract.try_accept_ownership().unwrap_err();
278        assert_eq!(err, Error::CallerNotTheNewOwner.into());
279
280        // then the owner remain the same
281        assert_eq!(contract.get_owner(), initial_owner);
282        // then the pending owner remain the same
283        assert_eq!(contract.get_pending_owner(), Some(new_owner));
284    }
285
286    #[test]
287    fn renounce_ownership() {
288        // given new contracts
289        let (mut contracts, initial_owner) = setup_renounceable();
290
291        contracts
292            .iter_mut()
293            .for_each(|contract: &mut RenounceableHostRef| {
294                // when the current owner renounce ownership
295                contract.renounce_ownership();
296
297                // then an event is emitted
298                contract.env().emitted_event(
299                    contract.address(),
300                    &OwnershipTransferred {
301                        previous_owner: Some(initial_owner),
302                        new_owner: None
303                    }
304                );
305                // then the owner is not set
306                let err = contract.try_get_owner().unwrap_err();
307                assert_eq!(err, Error::OwnerNotSet.into());
308                // then cannot renounce ownership again
309                let err = contract.try_renounce_ownership().unwrap_err();
310                assert_eq!(err, Error::CallerNotTheOwner.into());
311            });
312    }
313
314    #[test]
315    fn renounce_ownership_fail() {
316        // given new contracts
317        let (mut contracts, _) = setup_renounceable();
318
319        contracts.iter_mut().for_each(|contract| {
320            // when a non-owner account is the caller
321            let caller = contract.env().get_account(1);
322            contract.env().set_caller(caller);
323
324            // then renounce ownership fails
325            let err = contract.try_renounce_ownership().unwrap_err();
326            assert_eq!(err, Error::CallerNotTheOwner.into());
327        });
328    }
329
330    #[external_contract]
331    trait Owned {
332        fn get_owner(&self) -> Address;
333    }
334
335    #[external_contract]
336    trait Renounceable {
337        fn renounce_ownership(&mut self);
338        fn get_owner(&self) -> Address;
339    }
340
341    fn setup_ownable() -> (OwnableHostRef, Address) {
342        let env = odra_test::env();
343        (
344            Ownable::deploy(
345                &env,
346                OwnableInitArgs {
347                    owner: env.get_account(0)
348                }
349            ),
350            env.get_account(0)
351        )
352    }
353
354    fn setup_ownable_2_step() -> (Ownable2StepHostRef, Address) {
355        let env = odra_test::env();
356        (
357            Ownable2Step::deploy(
358                &env,
359                Ownable2StepInitArgs {
360                    owner: env.get_account(0)
361                }
362            ),
363            env.get_account(0)
364        )
365    }
366
367    fn setup_renounceable() -> (Vec<RenounceableHostRef>, Address) {
368        let env = odra_test::env();
369        let owner = env.caller();
370        let ownable = Ownable::deploy(&env, OwnableInitArgs { owner });
371        let ownable_2_step = Ownable2Step::deploy(&env, Ownable2StepInitArgs { owner });
372        let renouncable_ref = RenounceableHostRef::new(*ownable.address(), env.clone());
373        let renouncable_2_step_ref =
374            RenounceableHostRef::new(*ownable_2_step.address(), env.clone());
375        (
376            vec![renouncable_ref, renouncable_2_step_ref],
377            env.get_account(0)
378        )
379    }
380
381    fn setup_owned() -> (HostEnv, OwnableHostRef, Ownable2StepHostRef, Address) {
382        let env = odra_test::env();
383        let owner = env.caller();
384        let ownable = Ownable::deploy(&env, OwnableInitArgs { owner });
385        let ownable_2_step = Ownable2Step::deploy(&env, Ownable2StepInitArgs { owner });
386        (env.clone(), ownable, ownable_2_step, env.get_account(0))
387    }
388}