Attribute Macro ink_macro::contract

source ·
#[contract]
Expand description

Entry point for writing ink! smart contracts.

If you are a beginner trying to learn ink! we recommend you to check out our extensive ink! workshop.

Description

The macro does analysis on the provided smart contract code and generates proper code.

ink! smart contracts can compile in several different modes. There are two main compilation models using either

  • on-chain mode: no_std and WebAssembly as target
  • off-chain mode: std

We generally use the on-chain mode for actual smart contract instantiation whereas we use the off-chain mode for smart contract testing using the off-chain environment provided by the ink_env crate.

Usage

Header Arguments

The #[ink::contract] macro can be provided with some additional comma-separated header arguments:

  • keep_attr: String

    Tells the ink! code generator which attributes should be passed to call builders. Call builders are used to doing cross-contract calls and are automatically generated for contracts.

    Usage Example:

    #[ink::contract(keep_attr = "foo, bar")]
    mod my_contract {
        //    #[bar]
        //    #[foo]
        // ...
    }

    Allowed attributes by default: cfg, cfg_attr, allow, warn, deny, forbid, deprecated, must_use, doc, rustfmt.

  • env: impl Environment

    Tells the ink! code generator which environment to use for the ink! smart contract. The environment must implement the Environment (defined in ink_env) trait and provides all the necessary fundamental type definitions for Balance, AccountId etc.

    When using a custom Environment implementation for a smart contract all types that it exposes to the ink! smart contract and the mirrored types used in the runtime must be aligned with respect to SCALE encoding and semantics.

    Usage Example:

    Given a custom Environment implementation:

    pub struct MyEnvironment;
    
    impl ink_env::Environment for MyEnvironment {
        const MAX_EVENT_TOPICS: usize = 3;
        type AccountId = [u8; 16];
        type Balance = u128;
        type Hash = [u8; 32];
        type Timestamp = u64;
        type BlockNumber = u32;
        type ChainExtension = ::ink::env::NoChainExtension;
    }

    A user might implement their ink! smart contract using the above custom Environment implementation as demonstrated below:

    #[ink::contract(env = MyEnvironment)]
    mod my_contract {
        // ...
    }

    Default value: DefaultEnvironment defined in ink_env crate.

Analysis

The #[ink::contract] macro fully analyses its input smart contract against invalid arguments and structure.

Some example rules include but are not limited to:

  • There must be exactly one #[ink(storage)] struct.

    This struct defines the layout of the storage that the ink! smart contract operates on. The user is able to use a variety of built-in facilities, combine them in various ways or even provide their own implementations of storage data structures.

    For more information visit the ink::storage crate documentation.

    Example:

    #[ink::contract]
    mod flipper {
        #[ink(storage)]
        pub struct Flipper {
            value: bool,
        }
    }
  • There must be at least one #[ink(constructor)] defined method.

    Methods flagged with #[ink(constructor)] are special in that they are dispatchable upon contract instantiation. A contract may define multiple such constructors which allow users of the contract to instantiate a contract in multiple different ways.

    Example:

    Given the Flipper contract definition above we add an #[ink(constructor)] as follows:

    impl Flipper {
        #[ink(constructor)]
        pub fn new(initial_value: bool) -> Self {
            Flipper { value: false }
        }
    }
  • There must be at least one #[ink(message)] defined method.

    Methods flagged with #[ink(message)] are special in that they are dispatchable upon contract invocation. The set of ink! messages defined for an ink! smart contract define its API surface with which users are allowed to interact.

    An ink! smart contract can have multiple such ink! messages defined.

    Note:

    • An ink! message with a &self receiver may only read state whereas an ink! message with a &mut self receiver may mutate the contract’s storage.

    Example:

    Given the Flipper contract definition above we add some #[ink(message)] definitions as follows:

    impl Flipper {
        /// Flips the current value.
        #[ink(message)]
        pub fn flip(&mut self) {
            self.value = !self.value;
        }
    
        /// Returns the current value.
        #[ink(message)]
        pub fn get(&self) -> bool {
            self.value
        }
    }

    Payable Messages:

    An ink! message by default will reject calls that additional fund the smart contract. Authors of ink! smart contracts can make an ink! message payable by adding the payable flag to it. An example below:

    Note that ink! constructors are always implicitly payable and thus cannot be flagged as such.

    impl Flipper {
        /// Flips the current value.
        #[ink(message)]
        #[ink(payable)] // You can either specify payable out-of-line.
        pub fn flip(&mut self) {
            self.value = !self.value;
        }
    
        /// Returns the current value.
        #[ink(message, payable)] // ...or specify payable inline.
        pub fn get(&self) -> bool {
            self.value
        }
    }

    Controlling the messages selector:

    Every ink! message and ink! constructor has a unique selector with which the message or constructor can be uniquely identified within the ink! smart contract. These selectors are mainly used to drive the contract’s dispatch upon calling it.

    An ink! smart contract author can control the selector of an ink! message or ink! constructor using the selector flag. An example is shown below:

    impl Flipper {
        #[ink(constructor)]
        #[ink(selector = 0xDEADBEEF)] // Works on constructors as well.
        pub fn new(initial_value: bool) -> Self {
            Flipper { value: false }
        }
    
        /// Returns the current value.
        #[ink(message, selector = 0xFEEDBEEF)] // ...or specify selector inline.
        pub fn get(&self) -> bool {
            self.value
        }
    }

Interacting with the Contract Executor

The ink_env crate provides facilities to interact with the contract executor that connects ink! smart contracts with the outer world.

For example it is possible to query the current call’s caller via:

let caller = ink_env::caller::<ink_env::DefaultEnvironment>();

However, ink! provides a much simpler way to interact with the contract executor via its environment accessor. An example below:

#[ink::contract]
mod greeter {
    use ink_env::debug_println;

    #[ink(storage)]
    pub struct Greeter;

    impl Greeter {
        #[ink(constructor)]
        pub fn new() -> Self {
            let caller = Self::env().caller();
            debug_println!("thanks for instantiation {:?}", caller);
            Greeter {}
        }

        #[ink(message, payable)]
        pub fn fund(&self) {
            let caller = self.env().caller();
            let value = self.env().transferred_value();
            debug_println!("thanks for the funding of {:?} from {:?}", value, caller);
        }
    }
}

Events

An ink! smart contract may define events that it can emit during contract execution. Emitting events can be used by third party tools to query information about a contract’s execution and state.

The following example ink! contract shows how an event Transferred is defined and emitted in the #[ink(constructor)].

#[ink::contract]
mod erc20 {
    /// Defines an event that is emitted every time value is transferred.
    #[ink(event)]
    pub struct Transferred {
        from: Option<AccountId>,
        to: Option<AccountId>,
        value: Balance,
    }

    #[ink(storage)]
    pub struct Erc20 {
        total_supply: Balance,
        // more fields...
    }

    impl Erc20 {
        #[ink(constructor)]
        pub fn new(initial_supply: Balance) -> Self {
            let caller = Self::env().caller();
            Self::env().emit_event(Transferred {
                from: None,
                to: Some(caller),
                value: initial_supply,
            });
            Self { total_supply: initial_supply }
        }

        #[ink(message)]
        pub fn total_supply(&self) -> Balance {
            self.total_supply
        }
    }
}

Example: Flipper

The below code shows the complete implementation of the so-called Flipper ink! smart contract. For us it acts as the “Hello, World!” of the ink! smart contracts because it is minimal while still providing some more or less useful functionality.

It controls a single bool value that can be either false or true and allows the user to flip this value using the Flipper::flip message or retrieve the current value using Flipper::get.

#[ink::contract]
pub mod flipper {
    #[ink(storage)]
    pub struct Flipper {
        value: bool,
    }

    impl Flipper {
        /// Creates a new flipper smart contract initialized with the given value.
        #[ink(constructor)]
        pub fn new(init_value: bool) -> Self {
            Self { value: init_value }
        }

        /// Flips the current value of the Flipper's boolean.
        #[ink(message)]
        pub fn flip(&mut self) {
            self.value = !self.value;
        }

        /// Returns the current value of the Flipper's boolean.
        #[ink(message)]
        pub fn get(&self) -> bool {
            self.value
        }
    }
}