Skip to main content

Metis vs OpenZeppelin

Metis refers the implementations from OpenZepplin and absorbs the successful experience. However, since Solidity is based inheritence but Rust, as a non OOP language, doesn't support inheritence.

Let's take Ownable as an example to help us understand the differences between OpenZepplin and Metis.

OpenZeppelin#

abstract contract Ownable is Context {    // Component Storage    address private _owner;
    // Component Event    event OwnershipTransferred(        address indexed previousOwner,        address indexed newOwner    );        // Component internal interface    modifier onlyOwner() {        ...    }
    // Component Event    function renounceOwnership() public virtual onlyOwner {        ...    }
    ...}

If a contract needs to be Ownable, just inherit the contract:

contract Escrow is Ownable {...}

Similarly, other components can also have Ownable through inheritance:

contract Pausable is Ownable {  ...  function unpause() public onlyOwner whenPaused {      ...  }}

The intention of inheritance in OpenZepplin is actually composition rather than an is-a relationship. Although excessive use of inheritance in Solidity will cause many problems, inheritance for specific intentions is still an important way to achieve it.

Metis#

In contract development based on ink!, through metis, we can also achieve the same motivation based on generics and traits:

Metis's version of Ownable#

...
#[metis::contract] // metis contract macro, will use ink_lang::contract automod flipper {    ...
    #[ink(storage)]    #[import(ownable)] // flipper import the ownable    pub struct Flipper {        ownable: ownable::Data<Flipper>, // data by ownable
        value: bool,    }
    #[ink(event)]    #[metis(ownable)] // event in ink! will refactor    pub struct OwnershipTransferred {        //...    }
    impl Flipper {        //...
        #[ink(message)]        pub fn flip(&mut self) {            // check owner            ownable::Impl::ensure_caller_is_owner(self);
            self.value = !self.value;        }
        //...
        #[ink(message)]        pub fn renounce_ownership(&mut self) {            ownable::Impl::renounce_ownership(self) // owner message        }
        //...    }}

Metis assists developers to achieve the same function through a series of helper macros. In order to improve the auditability of the contract, here we hope that users clearly implement storage, event and message declarations.

Stub#

At the same time, metis has simplified the implementation of components using stub:

//...
// Storage#[metis::component::storage]pub struct Data<E>where    E: Env,{    owner: Lazy<Option<E::AccountId>>,}
// Event traitpub trait EventEmit<E: Env>: EnvAccess<E> {    //...}
// Impl traitpub trait Impl<E: Env>: Storage<E> + EventEmit<E> {    // logics    fn init(&mut self) {        self.get_mut().set_ownership(&Some(Self::caller()));    }
    fn renounce_ownership(&mut self) {        self.ensure_caller_is_owner();
        self.emit_event_ownership_transferred(            self.get().get_ownership().clone(),            None);
        self.get_mut().set_ownership(&None);    }
    //...
    /// Panic if `owner` is not an owner    fn ensure_owner(&self, owner: &E::AccountId) {        assert!(&self.get().get_ownership().clone().unwrap() == owner);    }
    //...}