Macro ink::contract_ref

source ·
macro_rules! contract_ref {
    ( $trait_path:path ) => { ... };
    ( $trait_path:path, $env:ty ) => { ... };
}
Expand description

Generates a wrapper which can be used for interacting with the contract.

This is done by creating a wrapper around the trait defined with the ink::trait_definition macro.

The macro returns the native Rust type that implements the corresponding trait, so it can be used in any Rust context that expects types.

Usage in the #[ink::contract] context

The macro expects one argument ‒ the path to the trait, in order to create a wrapper around it.

#[ink::contract]
mod trait_caller {
    use ink::contract_ref;

    #[ink::trait_definition]
    pub trait Erc20 {
        /// Returns the total supply of the ERC-20 smart contract.
        #[ink(message)]
        fn total_supply(&self) -> Balance;

        /// Transfers balance from the caller to the given address.
        #[ink(message)]
        fn transfer(&mut self, amount: Balance, to: AccountId) -> bool;
    }

    #[ink(storage)]
    pub struct Caller {
        /// The example of `contract_ref!` as a struct type.
        erc20: contract_ref!(Erc20),
    }

    impl Caller {
        /// Example of `contract_ref!` as an argument type.
        #[ink(constructor)]
        pub fn new(erc20: contract_ref!(Erc20)) -> Self {
            Self { erc20 }
        }

        /// Example of converting `AccountId` into `contract_ref!` implicitly.
        #[ink(message)]
        pub fn change_account_id_1(&mut self, new_erc20: AccountId) {
            self.erc20 = new_erc20.into();
        }

        /// Example of converting `AccountId` into `contract_ref!` explicitly.
        #[ink(message)]
        pub fn change_account_id_2(&mut self, new_erc20: AccountId) {
            let erc20: contract_ref!(Erc20) = new_erc20.into();
            self.erc20 = erc20;
        }

        /// Example of converting `AccountId` into an alias from `contract_ref!`.
        #[ink(message)]
        pub fn change_account_id_3(&mut self, new_erc20: AccountId) {
            type Erc20Wrapper = contract_ref!(Erc20);
            let erc20: Erc20Wrapper = new_erc20.into();
            self.erc20 = erc20;
        }

        /// Example of how to do common calls via fully qualified syntax.
        #[ink(message)]
        pub fn total_supply_1(&self) -> Balance {
            Erc20::total_supply(&self.erc20)
        }

        /// Example of how to do common calls without fully qualified syntax.
        #[ink(message)]
        pub fn total_supply_2(&self) -> Balance {
            self.erc20.total_supply()
        }

        /// Example of how to use the call builder with `contract_ref!`.
        #[ink(message)]
        pub fn total_supply_3(&self) -> Balance {
            use ink::codegen::TraitCallBuilder;
            // Returns the `CallBuilder` that implements `Erc20` trait.
            let erc20_builder = self.erc20.call();
            erc20_builder.total_supply().transferred_value(0).invoke()
        }

        /// Example of how to do common calls and convert
        /// the `contract_ref!` into `AccountId`.
        #[ink(message)]
        pub fn transfer_to_erc20(&mut self, amount: Balance) -> bool {
            let erc20_as_account_id = self.erc20.as_ref().clone();
            self.erc20.transfer(amount, erc20_as_account_id)
        }
    }
}

Usage outside of the #[ink::contract] context

The macro expects two arguments:

  • The first argument is the path to the trait, e.g. Erc20 or erc20::Erc20.
  • The second argument is the type of the [ink_env::Environment].

If the second argument is not specified, the macro uses the Environment type alias.

use ink::contract_ref;
use ink_env::DefaultEnvironment;
use ink_primitives::AccountId;

#[ink::trait_definition]
pub trait Erc20 {
    /// Returns the total supply of the ERC-20 smart contract.
    #[ink(message)]
    fn total_supply(&self) -> u128;

    /// Transfers balance from the caller to the given address.
    #[ink(message)]
    fn transfer(&mut self, amount: u128, to: AccountId) -> bool;
}

#[derive(Clone)]
pub struct CustomEnv;

impl ink_env::Environment for CustomEnv {
    const MAX_EVENT_TOPICS: usize = 3;
    type AccountId = [u8; 32];
    type Balance = u64;
    type Hash = [u8; 32];
    type Timestamp = u64;
    type BlockNumber = u64;
    type ChainExtension = ();
}

type AliasWithDefaultEnv = contract_ref!(Erc20, DefaultEnvironment);
type AliasWithCustomEnv = contract_ref!(Erc20, CustomEnv);
type AliasWithGenericEnv<E> = contract_ref!(Erc20, E);

fn default(mut contract: contract_ref!(Erc20, DefaultEnvironment)) {
    let total_supply = contract.total_supply();
    let to: AccountId = contract.as_ref().clone();
    contract.transfer(total_supply, to);
}

fn default_alias(mut contract: AliasWithDefaultEnv) {
    default(contract)
}

fn custom(mut contract: contract_ref!(Erc20, CustomEnv)) {
    let total_supply = contract.total_supply();
    let to: [u8; 32] = contract.as_ref().clone();
    contract.transfer(total_supply, to.into());
}

fn custom_alias(mut contract: AliasWithCustomEnv) {
    custom(contract)
}

fn generic<E, A>(mut contract: contract_ref!(Erc20, E))
where
    E: ink_env::Environment<AccountId = A>,
    A: Into<AccountId> + Clone,
{
    let total_supply = contract.total_supply();
    let to = contract.as_ref().clone();
    contract.transfer(total_supply, to.into());
}

fn generic_alias<E, A>(mut contract: AliasWithGenericEnv<E>)
where
    E: ink_env::Environment<AccountId = A>,
    A: Into<AccountId> + Clone,
{
    generic(contract)
}

type Environment = DefaultEnvironment;

fn contract_ref_default_behaviour(mut contract: contract_ref!(Erc20)) {
    let total_supply = contract.total_supply();
    let to: AccountId = contract.as_ref().clone();
    contract.transfer(total_supply, to);
}