Skip to main content
Version: 5.x

Contract Template

On this page we'll go over how to create a basic contract and explain its elements.

Creating a template

Change into your working directory and run:

cargo contract new foobar

This will create a new project folder named foobar.

cd foobar/

In the lib.rs file you find initial scaffolded code, which you can use as a starting point.

Quickly check that it compiles, and the trivial tests pass with:

cargo test

Also check that you can build the Wasm file by running:

cargo contract build

If everything looks good, then we are ready to start programming!

Template Content

The template contains scaffolded code that provides a starting point for writing an ink! contract. In the following we'll take a look at what the files contain. The files you get locally will look similar, just that we added explanatory comments here.

Cargo.toml

[package]
name = "foobar"
version = "0.1.0"
authors = ["[your_name] <[your_email]>"]
edition = "2021"

[dependencies]
# The `ink` crate contains the ink! eDSL and re-exports
# a number of other ink! specific crates. For example,
# `ink::env` is the `ink_env` crate that contains functions
# to interact with a contract's environment (querying information
# about a caller, the current block number, etc.).
ink = { version = "4.0.0-beta", default-features = false }

# Substrate blockchains use the SCALE codec for anything to
# do with data encoding/decoding. If an ink! contract is called
# the passed values have to be SCALE-encoded and return values
# have to be SCALE-decoded. All values that are put into a
# contract's storage are SCALE-encoded as well.
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }

# This crate is used to write information about a contract's
# types into the contract's metadata (i.e. its ABI). This is
# needed so that a client knows that a contract message requires
# e.g. an Array and that it has to SCALE-encode the value as an Array.
scale-info = { version = "2.3", default-features = false, features = ["derive"], optional = true }

[dev-dependencies]
# This developer dependency is for the End-to-End testing framework.
ink_e2e = { path = "../../crates/e2e" }

[lib]
name = "foobar"
path = "lib.rs"

# This setting typically specifies that you'd like the compiler to
# create a dynamic system library. For WebAssembly though it specifies
# that the compiler should create a `*.wasm` without a start function.
crate-type = [
"cdylib",
]

[features]
default = ["std"]
std = [
"ink/std",
"scale/std",
"scale-info/std",
]
ink-as-dependency = []

# This feature is just a convention, so that the end-to-end tests
# are only executed if `cargo test` is explicitly invoked with
# `--features e2e-tests`.
e2e-tests = []

lib.rs

Every ink! contract is required to contain:

  • Exactly one #[ink(storage)] struct.
  • At least one #[ink(constructor)] function.
  • At least one #[ink(message)] function.

The scaffolded code will look similar to the following, we've changed the comments though to explain what is going on there on a high level.

// If the `std` feature from the `Cargo.toml` is not enabled
// we switch on `no_std`, this has the effect of Rusts standard
// library not being included in our contract.
//
// The Rust standard library is OS-dependent and Wasm is
// architecture independent.
#![cfg_attr(not(feature = "std"), no_std)]

// This is the ink! macro, the starting point for your contract.
// Everything below it might look like Rust code, but it is actually
// run through a parser in ink!.
#[ink::contract]
pub mod flipper {
/// This is the contract's storage.
#[ink(storage)]
pub struct Flipper {
value: bool,
}

impl Flipper {
/// A constructor that the contract can be initialized with.
#[ink(constructor)]
pub fn new(init_value: bool) -> Self {
/* --snip-- */
}

/// An alternative constructor that the contract can be
/// initialized with.
#[ink(constructor)]
pub fn new_default() -> Self {
/* --snip-- */
}

/// A state-mutating function that the contract exposes to the
/// outside world.
///
/// By default functions are private, they have to be annotated
/// with `#[ink(message)]` and `pub` to be available from the
/// outside.
#[ink(message)]
pub fn flip(&mut self) {
/* --snip-- */
}

/// A public contract function that has no side-effects.
///
/// Note that while purely reading functions can be invoked
/// by submitting a transaction on-chain, this is usually
/// not done as they have no side-effects and the transaction
/// costs would be wasted.
/// Instead those functions are typically invoked via RPC to
/// return a contract's state.
#[ink(message)]
pub fn get(&self) -> bool {
/* --snip-- */
}
}

#[cfg(test)]
mod tests {
use super::*;

/// This attribute denotes that the test is executed in
/// a simulated, mocked blockchain environment. There are
/// functions available to influence how the test environment
/// is configured (e.g. setting an account to a specified balance).
#[ink::test]
fn default_works() {
/* --snip-- */
}

/* --snip-- */
}

#[cfg(all(test, feature = "e2e-tests"))]
mod e2e_tests {
use super::*;
use ink_e2e::build_message;

type E2EResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;

/// With this attribute the contract will be compiled and deployed
/// to a Substrate node that is required to be running in the
/// background.
///
/// We offer API functions that enable developers to then interact
/// with the contract. ink! will take care of putting contract calls
/// into transactions that will be submitted to the Substrate chain.
///
/// Developers can define assertions on the outcome of their transactions,
/// such as checking for state mutations, transaction failures or
/// incurred gas costs.
#[ink_e2e::test]
async fn it_works(mut client: ink_e2e::Client<C, E>) -> E2EResult<()> {
/* --snip-- */
}

/* --snip-- */
}
}