Skip to content

Commit

Permalink
Merge branch 'release/0.8.0' into feature/macro/delegate
Browse files Browse the repository at this point in the history
  • Loading branch information
kpob committed Jan 8, 2024
2 parents 7260f58 + 2d49ebd commit 3375ddd
Show file tree
Hide file tree
Showing 23 changed files with 521 additions and 384 deletions.
299 changes: 296 additions & 3 deletions modules/src/access/ownable.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::access::errors::Error::{CallerNotTheOwner, OwnerNotSet};
use crate::access::events::OwnershipTransferred;
use crate::access::errors::Error::{CallerNotTheNewOwner, CallerNotTheOwner, OwnerNotSet};
use crate::access::events::{OwnershipTransferStarted, OwnershipTransferred};
use odra::prelude::*;
use odra::{Address, Module, UnwrapOrRevert, Variable};
use odra::{Address, Module, ModuleWrapper, UnwrapOrRevert, Variable};

/// This module provides a straightforward access control feature that enables
/// exclusive access to particular functions by an account, known as the owner.
Expand Down Expand Up @@ -75,3 +75,296 @@ impl Ownable {
});
}
}

/// This module provides a straightforward access control feature that enables
/// exclusive access to particular functions by an account, known as the owner.
/// The account that initiates contract deployment is automatically assigned as
/// the owner. However, ownership can be transferred later by using the
/// `transfer_ownership()` and `accept_ownership()` functions.
///
/// You can use this module as a standalone contract or integrate it into
/// a custom module by adding it as a field.
///
/// When used in a custom module, the `only_owner()` function is available,
/// allowing you to restrict function usage to the owner.
#[odra::module(events = [OwnershipTransferStarted])]
pub struct Ownable2Step {
ownable: ModuleWrapper<Ownable>,
pending_owner: Variable<Option<Address>>
}

#[odra::module]
impl Ownable2Step {
/// Initializes the module setting the caller as the initial owner.
pub fn init(&mut self) {
self.ownable.init();
}

/// Returns the address of the current owner.
pub fn get_owner(&self) -> Address {
self.ownable.get_owner()
}

/// Returns the address of the pending owner.
pub fn get_pending_owner(&self) -> Option<Address> {
self.pending_owner.get().flatten()
}

/// Starts the ownership transfer of the module to a `new_owner`.
/// Replaces the `pending_owner`if there is one.
///
/// This function can only be accessed by the current owner of the module.
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(OwnershipTransferred {
previous_owner,
new_owner
});
}

/// If the contract's owner chooses to renounce their ownership, the contract
/// will no longer have an owner. This means that any functions that can only
/// be accessed by the owner will no longer be available.
///
/// The function can only be called by the current owner, and it will permanently
/// remove the owner's privileges.
pub fn renounce_ownership(&mut self) {
self.ownable.renounce_ownership()
}

/// The new owner accepts the ownership transfer. Replaces the current owner and clears
/// the pending owner.
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(CallerNotTheNewOwner)
}
self.pending_owner.set(None);
self.ownable.unchecked_transfer_ownership(caller);
}
}

#[cfg(test)]
mod test {
use super::*;
use crate::access::errors::Error;
use odra::{external_contract, HostEnv};

#[test]
fn init() {
// given new contacts
let (env, ownable, ownable_2step, deployer) = setup_owned();

// then the deployer is the owner
assert_eq!(deployer, ownable.get_owner());
assert_eq!(deployer, ownable_2step.get_owner());
// then a OwnershipTransferred event was emitted

let event = OwnershipTransferred {
previous_owner: None,
new_owner: Some(deployer)
};

env.emitted_event(&ownable.address, &event);
env.emitted_event(&ownable_2step.address, &event);
}

#[test]
fn plain_ownership_transfer() {
// given a new contract
let (mut contract, initial_owner) = setup_ownable();

// when the current owner transfers ownership
let new_owner = contract.env.get_account(1);
contract.transfer_ownership(new_owner);

// then the new owner is set
assert_eq!(new_owner, contract.get_owner());
// then a OwnershipTransferred event was emitted
contract.env.emitted_event(
&contract.address,
&OwnershipTransferred {
previous_owner: Some(initial_owner),
new_owner: Some(new_owner)
}
);
}

#[test]
fn two_step_ownership_transfer() {
// given a new contract
let (mut contract, initial_owner) = setup_ownable_2_step();

// when the current owner transfers ownership
let new_owner = contract.env.get_account(1);
contract.transfer_ownership(new_owner);

// when the pending owner accepts the transfer
contract.env.set_caller(new_owner);
contract.accept_ownership();

// then the new owner is set
assert_eq!(new_owner, contract.get_owner());
// then the pending owner is unset
assert_eq!(None, contract.get_pending_owner());
// then OwnershipTransferStarted and OwnershipTransferred events were emitted
contract.env.emitted_event(
&contract.address,
&OwnershipTransferStarted {
previous_owner: Some(initial_owner),
new_owner: Some(new_owner)
}
);
contract.env.emitted_event(
&contract.address,
&OwnershipTransferred {
previous_owner: Some(initial_owner),
new_owner: Some(new_owner)
}
);
}

#[test]
fn failing_plain_ownership_transfer() {
// given a new contract
let (mut contract, _) = setup_ownable();

// when a non-owner account is the caller
let (caller, new_owner) = (contract.env.get_account(1), contract.env.get_account(2));
contract.env.set_caller(caller);

// then ownership transfer fails
let err = contract.try_transfer_ownership(new_owner).unwrap_err();
assert_eq!(err, CallerNotTheOwner.into());
}

#[test]
fn failing_two_step_transfer() {
// given a new contract
let (mut contract, initial_owner) = setup_ownable_2_step();

// when a non-owner account is the caller
let (caller, new_owner) = (contract.env.get_account(1), contract.env.get_account(2));
contract.env.set_caller(caller);

// then ownership transfer fails
let err = contract.try_transfer_ownership(new_owner).unwrap_err();
assert_eq!(err, CallerNotTheOwner.into());

// when the owner is the caller
contract.env.set_caller(initial_owner);
contract.transfer_ownership(new_owner);

// then the pending owner is set
assert_eq!(contract.get_pending_owner(), Some(new_owner));

// when someone else than the pending owner accepts the ownership
// transfer, it should fail
let err = contract.try_accept_ownership().unwrap_err();
assert_eq!(err, Error::CallerNotTheNewOwner.into());

// then the owner remain the same
assert_eq!(contract.get_owner(), initial_owner);
// then the pending owner remain the same
assert_eq!(contract.get_pending_owner(), Some(new_owner));
}

#[test]
fn renounce_ownership() {
// given new contracts
let (mut contracts, initial_owner) = setup_renounceable();

contracts
.iter_mut()
.for_each(|contract: &mut RenounceableHostRef| {
// when the current owner renounce ownership
contract.renounce_ownership();

// then an event is emitted
contract.env.emitted_event(
&contract.address,
&OwnershipTransferred {
previous_owner: Some(initial_owner),
new_owner: None
}
);
// then the owner is not set
let err = contract.try_get_owner().unwrap_err();
assert_eq!(err, Error::OwnerNotSet.into());
// then cannot renounce ownership again
let err = contract.try_renounce_ownership().unwrap_err();
assert_eq!(err, Error::CallerNotTheOwner.into());
});
}

#[test]
fn renounce_ownership_fail() {
// given new contracts
let (mut contracts, _) = setup_renounceable();

contracts.iter_mut().for_each(|contract| {
// when a non-owner account is the caller
let caller = contract.env.get_account(1);
contract.env.set_caller(caller);

// then renounce ownership fails
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();
(OwnableDeployer::init(&env), env.get_account(0))
}

fn setup_ownable_2_step() -> (Ownable2StepHostRef, Address) {
let env = odra::test_env();
(Ownable2StepDeployer::init(&env), env.get_account(0))
}

fn setup_renounceable() -> (Vec<RenounceableHostRef>, Address) {
let env = odra::test_env();
let ownable = OwnableDeployer::init(&env);
let ownable_2_step = Ownable2StepDeployer::init(&env);
(
vec![
RenounceableHostRef {
address: ownable.address,
env: env.clone(),
attached_value: Default::default()
},
RenounceableHostRef {
address: ownable_2_step.address,
env: env.clone(),
attached_value: Default::default()
},
],
env.get_account(0)
)
}

fn setup_owned() -> (HostEnv, OwnableHostRef, Ownable2StepHostRef, Address) {
let env = odra::test_env();
let ownable = OwnableDeployer::init(&env);
let ownable_2_step = Ownable2StepDeployer::init(&env);
(env.clone(), ownable, ownable_2_step, env.get_account(0))
}
}
2 changes: 1 addition & 1 deletion modules/src/erc1155/erc1155_base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ impl Erc1155 for Erc1155Base {
self.balances.get_or_default(&(*owner, *id))
}

fn balance_of_batch(&self, owners: Vec<Address>, ids: Vec<U256>) -> Vec<U256> {
fn balance_of_batch(&self, owners: &[Address], ids: &[U256]) -> Vec<U256> {
if owners.len() != ids.len() {
self.env().revert(Error::AccountsAndIdsLengthMismatch);
}
Expand Down
2 changes: 1 addition & 1 deletion modules/src/erc1155/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub trait Erc1155 {
/// Batched version of [Erc1155::balance_of](Self::balance_of).
///
/// The length of `owners` and `ids` must be the same.
fn balance_of_batch(&self, owners: Vec<Address>, ids: Vec<U256>) -> Vec<U256>;
fn balance_of_batch(&self, owners: &[Address], ids: &[U256]) -> Vec<U256>;
/// Allows or denials the `operator` to transfer the caller’s tokens.
///
/// Emits [crate::erc1155::events::ApprovalForAll].
Expand Down
2 changes: 1 addition & 1 deletion modules/src/erc1155/owned_erc1155.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub trait OwnedErc1155 {
/// Batched version of [Erc1155::balance_of](Self::balance_of).
///
/// The length of `owners` and `ids` must be the same.
fn balance_of_batch(&self, owners: Vec<Address>, ids: Vec<U256>) -> Vec<U256>;
fn balance_of_batch(&self, owners: &[Address], ids: &[U256]) -> Vec<U256>;
/// Allows or denials the `operator` to transfer the caller’s tokens.
///
/// Emits [crate::erc1155::events::ApprovalForAll].
Expand Down
2 changes: 1 addition & 1 deletion modules/src/erc1155_token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ impl OwnedErc1155 for Erc1155Token {
fn balance_of(&self, owner: &Address, id: &U256) -> U256 {
self.core.balance_of(owner, id)
}
fn balance_of_batch(&self, owners: Vec<Address>, ids: Vec<U256>) -> Vec<U256> {
fn balance_of_batch(&self, owners: &[Address], ids: &[U256]) -> Vec<U256> {
self.core.balance_of_batch(owners, ids)
}
fn set_approval_for_all(&mut self, operator: &Address, approved: bool) {
Expand Down
7 changes: 3 additions & 4 deletions modules/src/erc721_token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,6 @@ mod tests {
// Then safe transfer the token to the contract which does not support nfts throws an error.
erc721_env.env.set_caller(erc721_env.alice);

// TODO: Enable this after fixing mockvm
assert_eq!(
Err(OdraError::VmError(VmError::NoSuchMethod(
"on_erc721_received".to_string()
Expand All @@ -528,14 +527,14 @@ mod tests {
erc721_env.env.set_caller(erc721_env.alice);
erc721_env.token.safe_transfer_from(
erc721_env.alice,
receiver.address().clone(),
*receiver.address(),
U256::from(1)
);

// Then the owner of the token is the contract
assert_eq!(
erc721_env.token.owner_of(U256::from(1)),
receiver.address().clone()
*receiver.address()
);
// And the receiver contract is aware of the transfer
erc721_env.env.emitted_event(
Expand Down Expand Up @@ -563,7 +562,7 @@ mod tests {
erc721_env.env.set_caller(erc721_env.alice);
erc721_env.token.safe_transfer_from_with_data(
erc721_env.alice,
receiver.address().clone(),
*receiver.address(),
U256::from(1),
b"data".to_vec().into()
);
Expand Down
1 change: 0 additions & 1 deletion modules/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,5 @@ pub mod erc20;
pub mod erc721;
pub mod erc721_receiver;
pub mod erc721_token;
pub mod ownable_2step;
pub mod security;
pub mod wrapped_native;
Loading

0 comments on commit 3375ddd

Please sign in to comment.