use crate::access::errors::Error;
use crate::access::events::{OwnershipTransferStarted, OwnershipTransferred};
use odra::prelude::*;
#[odra::module(events = [OwnershipTransferred], errors = Error)]
pub struct Ownable {
owner: Var<Option<Address>>
}
#[odra::module]
impl Ownable {
pub fn init(&mut self, owner: Address) {
self.unchecked_transfer_ownership(Some(owner));
}
pub fn transfer_ownership(&mut self, new_owner: &Address) {
self.assert_owner(&self.env().caller());
self.unchecked_transfer_ownership(Some(*new_owner));
}
pub fn renounce_ownership(&mut self) {
self.assert_owner(&self.env().caller());
self.unchecked_transfer_ownership(None);
}
pub fn get_owner(&self) -> Address {
self.get_optional_owner()
.unwrap_or_revert_with(&self.env(), Error::OwnerNotSet)
}
pub fn assert_owner(&self, address: &Address) {
if Some(address) != self.get_optional_owner().as_ref() {
self.env().revert(Error::CallerNotTheOwner)
}
}
pub fn get_optional_owner(&self) -> Option<Address> {
self.owner.get().flatten()
}
pub fn unchecked_transfer_ownership(&mut self, new_owner: Option<Address>) {
let previous_owner = self.get_optional_owner();
self.owner.set(new_owner);
self.env().emit_event(OwnershipTransferred {
previous_owner,
new_owner
});
}
}
#[odra::module(events = [OwnershipTransferStarted], errors = Error)]
pub struct Ownable2Step {
ownable: SubModule<Ownable>,
pending_owner: Var<Option<Address>>
}
#[odra::module]
impl Ownable2Step {
pub fn init(&mut self, owner: Address) {
self.ownable.init(owner);
}
pub fn get_owner(&self) -> Address {
self.ownable.get_owner()
}
pub fn get_pending_owner(&self) -> Option<Address> {
self.pending_owner.get().flatten()
}
pub fn transfer_ownership(&mut self, new_owner: &Address) {
self.ownable.assert_owner(&self.env().caller());
let previous_owner = self.ownable.get_optional_owner();
let new_owner = Some(*new_owner);
self.pending_owner.set(new_owner);
self.env().emit_event(OwnershipTransferStarted {
previous_owner,
new_owner
});
}
pub fn renounce_ownership(&mut self) {
self.ownable.renounce_ownership()
}
pub fn accept_ownership(&mut self) {
let caller = self.env().caller();
let caller = Some(caller);
let pending_owner = self.pending_owner.get().flatten();
if pending_owner != caller {
self.env().revert(Error::CallerNotTheNewOwner)
}
self.pending_owner.set(None);
self.ownable.unchecked_transfer_ownership(caller);
}
pub fn assert_owner(&self, address: &Address) {
self.ownable.assert_owner(address)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::access::errors::Error;
use odra::{
external_contract,
host::{Deployer, HostEnv, HostRef},
prelude::Addressable
};
#[test]
fn init() {
let (env, ownable, ownable_2step, deployer) = setup_owned();
assert_eq!(deployer, ownable.get_owner());
assert_eq!(deployer, ownable_2step.get_owner());
env.emitted_event(
&ownable,
OwnershipTransferred {
previous_owner: None,
new_owner: Some(deployer)
}
);
env.emitted_event(
&ownable_2step,
OwnershipTransferred {
previous_owner: None,
new_owner: Some(deployer)
}
);
}
#[test]
fn plain_ownership_transfer() {
let (mut contract, initial_owner) = setup_ownable();
let new_owner = contract.env().get_account(1);
contract.transfer_ownership(&new_owner);
assert_eq!(new_owner, contract.get_owner());
assert!(contract.env().emitted_event(
&contract,
OwnershipTransferred {
previous_owner: Some(initial_owner),
new_owner: Some(new_owner)
}
));
}
#[test]
fn two_step_ownership_transfer() {
let (mut contract, initial_owner) = setup_ownable_2_step();
let new_owner = contract.env().get_account(1);
contract.transfer_ownership(&new_owner);
contract.env().set_caller(new_owner);
contract.accept_ownership();
assert_eq!(new_owner, contract.get_owner());
assert_eq!(None, contract.get_pending_owner());
assert!(contract.env().emitted_event(
&contract,
OwnershipTransferStarted {
previous_owner: Some(initial_owner),
new_owner: Some(new_owner)
}
));
assert!(contract.env().emitted_event(
&contract,
OwnershipTransferred {
previous_owner: Some(initial_owner),
new_owner: Some(new_owner)
}
));
}
#[test]
fn failing_plain_ownership_transfer() {
let (mut contract, _) = setup_ownable();
let (caller, new_owner) = (contract.env().get_account(1), contract.env().get_account(2));
contract.env().set_caller(caller);
let err = contract.try_transfer_ownership(&new_owner).unwrap_err();
assert_eq!(err, Error::CallerNotTheOwner.into());
}
#[test]
fn failing_two_step_transfer() {
let (mut contract, initial_owner) = setup_ownable_2_step();
let (caller, new_owner) = (contract.env().get_account(1), contract.env().get_account(2));
contract.env().set_caller(caller);
let err = contract.try_transfer_ownership(&new_owner).unwrap_err();
assert_eq!(err, Error::CallerNotTheOwner.into());
contract.env().set_caller(initial_owner);
contract.transfer_ownership(&new_owner);
assert_eq!(contract.get_pending_owner(), Some(new_owner));
let err = contract.try_accept_ownership().unwrap_err();
assert_eq!(err, Error::CallerNotTheNewOwner.into());
assert_eq!(contract.get_owner(), initial_owner);
assert_eq!(contract.get_pending_owner(), Some(new_owner));
}
#[test]
fn renounce_ownership() {
let (mut contracts, initial_owner) = setup_renounceable();
contracts
.iter_mut()
.for_each(|contract: &mut RenounceableHostRef| {
contract.renounce_ownership();
assert!(contract.env().emitted_event(
contract,
OwnershipTransferred {
previous_owner: Some(initial_owner),
new_owner: None
}
));
let err = contract.try_get_owner().unwrap_err();
assert_eq!(err, Error::OwnerNotSet.into());
let err = contract.try_renounce_ownership().unwrap_err();
assert_eq!(err, Error::CallerNotTheOwner.into());
});
}
#[test]
fn renounce_ownership_fail() {
let (mut contracts, _) = setup_renounceable();
contracts.iter_mut().for_each(|contract| {
let caller = contract.env().get_account(1);
contract.env().set_caller(caller);
let err = contract.try_renounce_ownership().unwrap_err();
assert_eq!(err, Error::CallerNotTheOwner.into());
});
}
#[external_contract]
trait Owned {
fn get_owner(&self) -> Address;
}
#[external_contract]
trait Renounceable {
fn renounce_ownership(&mut self);
fn get_owner(&self) -> Address;
}
fn setup_ownable() -> (OwnableHostRef, Address) {
let env = odra_test::env();
(
Ownable::deploy(
&env,
OwnableInitArgs {
owner: env.get_account(0)
}
),
env.get_account(0)
)
}
fn setup_ownable_2_step() -> (Ownable2StepHostRef, Address) {
let env = odra_test::env();
(
Ownable2Step::deploy(
&env,
Ownable2StepInitArgs {
owner: env.get_account(0)
}
),
env.get_account(0)
)
}
fn setup_renounceable() -> (Vec<RenounceableHostRef>, Address) {
let env = odra_test::env();
let owner = env.caller();
let ownable = Ownable::deploy(&env, OwnableInitArgs { owner });
let ownable_2_step = Ownable2Step::deploy(&env, Ownable2StepInitArgs { owner });
let renouncable_ref = RenounceableHostRef::new(ownable.address(), env.clone());
let renouncable_2_step_ref =
RenounceableHostRef::new(ownable_2_step.address(), env.clone());
(
vec![renouncable_ref, renouncable_2_step_ref],
env.get_account(0)
)
}
fn setup_owned() -> (HostEnv, OwnableHostRef, Ownable2StepHostRef, Address) {
let env = odra_test::env();
let owner = env.caller();
let ownable = Ownable::deploy(&env, OwnableInitArgs { owner });
let ownable_2_step = Ownable2Step::deploy(&env, Ownable2StepInitArgs { owner });
(env.clone(), ownable, ownable_2_step, env.get_account(0))
}
}