Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(N-03): inconsistent error types #339

Draft
wants to merge 15 commits into
base: v0.1.0
Choose a base branch
from
6 changes: 2 additions & 4 deletions .github/workflows/check-links.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Link Checker
uses: lycheeverse/lychee-action@v1
with:
args: --no-progress './**/*.md'
fail: true

- name: Run linkspector
uses: umbrelladocs/action-linkspector@v1
with:
fail_on_error: true


2 changes: 1 addition & 1 deletion benches/src/access_control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ sol!(
);

const DEFAULT_ADMIN_ROLE: [u8; 32] =
openzeppelin_stylus::access::control::AccessControl::DEFAULT_ADMIN_ROLE;
<openzeppelin_stylus::access::control::AccessControl as openzeppelin_stylus::access::control::IAccessControl>::DEFAULT_ADMIN_ROLE;
// There's no way to query constants of a Stylus contract, so this one is
// hard-coded :(
const ROLE: [u8; 32] =
Expand Down
94 changes: 63 additions & 31 deletions contracts/src/access/control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,13 @@ sol_storage! {
}
}

#[public]
impl AccessControl {
/// Required interface of an [`AccessControl`] compliant contract.
pub trait IAccessControl {
/// The error type associated to the trait implementation.
type Error: Into<alloc::vec::Vec<u8>>;

/// The default admin role. `[0; 32]` by default.
pub const DEFAULT_ADMIN_ROLE: [u8; 32] = [0; 32];
const DEFAULT_ADMIN_ROLE: [u8; 32] = [0; 32];

/// Returns `true` if `account` has been granted `role`.
///
Expand All @@ -128,9 +131,7 @@ impl AccessControl {
/// * `role` - The role identifier.
/// * `account` - The account to check for membership.
#[must_use]
pub fn has_role(&self, role: B256, account: Address) -> bool {
self._roles.getter(role).has_role.get(account)
}
fn has_role(&self, role: B256, account: Address) -> bool;

/// Checks if [`msg::sender`] has been granted `role`.
///
Expand All @@ -143,23 +144,17 @@ impl AccessControl {
///
/// If [`msg::sender`] has not been granted `role`, then the error
/// [`Error::UnauthorizedAccount`] is returned.
pub fn only_role(&self, role: B256) -> Result<(), Error> {
self._check_role(role, msg::sender())
}
fn only_role(&self, role: B256) -> Result<(), Self::Error>;

/// Returns the admin role that controls `role`. See [`Self::grant_role`]
/// and [`Self::revoke_role`].
///
/// To change a role's admin, use [`Self::_set_role_admin`].
///
/// # Arguments
///
/// * `&self` - Read access to the contract's state.
/// * `role` - The role identifier.
#[must_use]
pub fn get_role_admin(&self, role: B256) -> B256 {
*self._roles.getter(role).admin_role
}
fn get_role_admin(&self, role: B256) -> B256;

/// Grants `role` to `account`.
///
Expand All @@ -184,16 +179,11 @@ impl AccessControl {
/// # Events
///
/// May emit a [`RoleGranted`] event.
pub fn grant_role(
fn grant_role(
&mut self,
role: B256,
account: Address,
) -> Result<(), Error> {
let admin_role = self.get_role_admin(role);
self.only_role(admin_role)?;
self._grant_role(role, account);
Ok(())
}
) -> Result<(), Self::Error>;

/// Revokes `role` from `account`.
///
Expand All @@ -217,16 +207,11 @@ impl AccessControl {
/// # Events
///
/// May emit a [`RoleRevoked`] event.
pub fn revoke_role(
fn revoke_role(
&mut self,
role: B256,
account: Address,
) -> Result<(), Error> {
let admin_role = self.get_role_admin(role);
self.only_role(admin_role)?;
self._revoke_role(role, account);
Ok(())
}
) -> Result<(), Self::Error>;

/// Revokes `role` from the calling account.
///
Expand Down Expand Up @@ -254,11 +239,58 @@ impl AccessControl {
///
/// If the calling account has its `role` revoked, emits a [`RoleRevoked`]
/// event.
pub fn renounce_role(
fn renounce_role(
&mut self,
role: B256,
confirmation: Address,
) -> Result<(), Error> {
) -> Result<(), Self::Error>;
}

#[public]
impl IAccessControl for AccessControl {
type Error = Error;

#[must_use]
fn has_role(&self, role: B256, account: Address) -> bool {
self._roles.getter(role).has_role.get(account)
}

fn only_role(&self, role: B256) -> Result<(), Self::Error> {
self._check_role(role, msg::sender())
}

#[must_use]
fn get_role_admin(&self, role: B256) -> B256 {
*self._roles.getter(role).admin_role
}

fn grant_role(
&mut self,
role: B256,
account: Address,
) -> Result<(), Self::Error> {
let admin_role = self.get_role_admin(role);
self.only_role(admin_role)?;
self._grant_role(role, account);
Ok(())
}

fn revoke_role(
&mut self,
role: B256,
account: Address,
) -> Result<(), Self::Error> {
let admin_role = self.get_role_admin(role);
self.only_role(admin_role)?;
self._revoke_role(role, account);
Ok(())
}

fn renounce_role(
&mut self,
role: B256,
confirmation: Address,
) -> Result<(), Self::Error> {
if msg::sender() != confirmation {
return Err(Error::BadConfirmation(
AccessControlBadConfirmation {},
Expand Down Expand Up @@ -372,7 +404,7 @@ mod tests {
use alloy_primitives::{address, Address};
use stylus_sdk::msg;

use super::{AccessControl, Error};
use super::{AccessControl, Error, IAccessControl};

/// Shorthand for declaring variables converted from a hex literal to a
/// fixed 32-byte slice;
Expand Down
77 changes: 48 additions & 29 deletions contracts/src/access/ownable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,29 +54,21 @@ sol_storage! {
}
}

#[public]
impl Ownable {
/// Required interface of an [`Ownable`] compliant contract.
pub trait IOwnable {
/// The error type associated to the trait implementation.
type Error: Into<alloc::vec::Vec<u8>>;

/// Returns the address of the current owner.
pub fn owner(&self) -> Address {
self._owner.get()
}
fn owner(&self) -> Address;

/// Checks if the [`msg::sender`] is set as the owner.
///
/// # Errors
///
/// If called by any account other than the owner, then the error
/// [`Error::UnauthorizedAccount`] is returned.
pub fn only_owner(&self) -> Result<(), Error> {
let account = msg::sender();
if self.owner() != account {
return Err(Error::UnauthorizedAccount(
OwnableUnauthorizedAccount { account },
));
}

Ok(())
}
fn only_owner(&self) -> Result<(), Self::Error>;

/// Transfers ownership of the contract to a new account (`new_owner`). Can
/// only be called by the current owner.
Expand All @@ -90,10 +82,47 @@ impl Ownable {
///
/// If `new_owner` is the zero address, then the error
/// [`OwnableInvalidOwner`] is returned.
pub fn transfer_ownership(
fn transfer_ownership(
&mut self,
new_owner: Address,
) -> Result<(), Self::Error>;

/// Leaves the contract without owner. It will not be possible to call
/// [`Self::only_owner`] functions. Can only be called by the current owner.
///
/// NOTE: Renouncing ownership will leave the contract without an owner,
/// thereby disabling any functionality that is only available to the owner.
///
/// # Errors
///
/// If not called by the owner, then the error
/// [`Error::UnauthorizedAccount`] is returned.
fn renounce_ownership(&mut self) -> Result<(), Self::Error>;
}

#[public]
impl IOwnable for Ownable {
type Error = Error;

fn owner(&self) -> Address {
self._owner.get()
}

fn only_owner(&self) -> Result<(), Self::Error> {
let account = msg::sender();
if self.owner() != account {
return Err(Error::UnauthorizedAccount(
OwnableUnauthorizedAccount { account },
));
}

Ok(())
}

fn transfer_ownership(
&mut self,
new_owner: Address,
) -> Result<(), Error> {
) -> Result<(), Self::Error> {
self.only_owner()?;

if new_owner == Address::ZERO {
Expand All @@ -107,17 +136,7 @@ impl Ownable {
Ok(())
}

/// Leaves the contract without owner. It will not be possible to call
/// [`Self::only_owner`] functions. Can only be called by the current owner.
///
/// NOTE: Renouncing ownership will leave the contract without an owner,
/// thereby disabling any functionality that is only available to the owner.
///
/// # Errors
///
/// If not called by the owner, then the error
/// [`Error::UnauthorizedAccount`] is returned.
pub fn renounce_ownership(&mut self) -> Result<(), Error> {
fn renounce_ownership(&mut self) -> Result<(), Self::Error> {
self.only_owner()?;
self._transfer_ownership(Address::ZERO);
Ok(())
Expand All @@ -144,7 +163,7 @@ mod tests {
use alloy_primitives::{address, Address};
use stylus_sdk::msg;

use super::{Error, Ownable};
use super::{Error, IOwnable, Ownable};

const ALICE: Address = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d");

Expand Down
2 changes: 1 addition & 1 deletion contracts/src/token/erc20/extensions/burnable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::token::erc20::{Erc20, Error};
/// their own tokens and those that they have an allowance for,
/// in a way that can be recognized off-chain (via event analysis).
pub trait IErc20Burnable {
/// The error type associated to this ERC-20 Burnable trait implementation.
/// The error type associated to the trait implementation.
type Error: Into<alloc::vec::Vec<u8>>;

/// Destroys a `value` amount of tokens from the caller. lowering the total
Expand Down
20 changes: 16 additions & 4 deletions contracts/src/token/erc20/extensions/capped.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Capped Contract.
//! Optional Capped Contract.
//!
//! Extension of ERC-20 standard that adds a cap to the supply of tokens.
//!
Expand Down Expand Up @@ -43,10 +43,22 @@ sol_storage! {
}
}

/// Extension of [`crate::token::erc20::Erc20`] token
/// that adds a cap to the supply of tokens.
pub trait IErc20Capped {
/// The error type associated to the trait implementation.
type Error: Into<alloc::vec::Vec<u8>>;

/// Returns the cap on the token's total supply.
fn cap(&self) -> U256;
}

#[public]
impl Capped {
impl IErc20Capped for Capped {
type Error = Error;

/// Returns the cap on the token's total supply.
pub fn cap(&self) -> U256 {
fn cap(&self) -> U256 {
self._cap.get()
}
}
Expand All @@ -55,7 +67,7 @@ impl Capped {
mod tests {
use alloy_primitives::uint;

use super::Capped;
use super::{Capped, IErc20Capped};

#[motsu::test]
fn cap_works(contract: Capped) {
Expand Down
2 changes: 1 addition & 1 deletion contracts/src/token/erc20/extensions/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::utils::introspection::erc165::IErc165;
/// Number of decimals used by default on implementors of [`Metadata`].
pub const DEFAULT_DECIMALS: u8 = 18;

use crate::utils::Metadata;
use crate::utils::{IMetadata, Metadata};

sol_storage! {
/// Metadata of the [`super::super::Erc20`] token.
Expand Down
2 changes: 1 addition & 1 deletion contracts/src/token/erc20/extensions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ pub mod metadata;
pub mod permit;

pub use burnable::IErc20Burnable;
pub use capped::Capped;
pub use capped::{Capped, IErc20Capped};
pub use metadata::{Erc20Metadata, IErc20Metadata};
pub use permit::Erc20Permit;
Loading
Loading