Substrate ink!
ink! from Substrate is a domain-specific language, written in Rust, used for developing smart contracts for Substrate-based blockchains. Other languages for smart contracts are Ask!/AssemblyScript (opens in a new tab) (on Substrate-based chains) or Solidity (opens in a new tab) (on Ethereum -ased chains).
ink! enjoys all the benefits of the Rust programming language, like type and memory safety, performance, security etc.
Substrate and Smart Contracts
What are Smart Contracts?
"Smart contracts allow permissionless deployment of unstrusted programs on blockchain" (ref. ink! (opens in a new tab)). "The objectives of smart contracts are the reduction of need for trusted intermediators, arbitration costs, and fraud losses, as well as the reduction of malicious and accidental exceptions" (ref.) (opens in a new tab).
If a person or a program holds a token of the chain, then it can upload a smart contract to the chain without requiring further permission.
A smart contract will be compiled to WebAssembly and executed on an execution environment.
In the case of Substrate-based blockchains,
the execution environment needs to have the Substrate module pallet-contracts
already installed,
for smart contracts to be deployed on the chain.
The blockchain, together with the pallet-contracts
module, defines security and other types of constraints,
to which the smart contracts need to comply for them to be executed on chain.
Why WebAssemply? WebAssembly (Wasm) is a high-performant and platform-independent binary instruction format for high-level programming languages like Rust, and others.
The source code of a smart contract is usually structured into two files:
lib.rs # the logic of the smart contract, written in Rust
Cargo.toml # configurations for Rust dependencies and ink!
A compiled smart contract consists of 3 files:
-
hello.wasm : WebAssembly binary of the smart contract, this file is deployed on chain
-
hello.json : not stored on chain, app clients use it to know how to interact with the on-chain contract
-
hello.contract : based on the two files hello.wasm and hello.json, this file is usually uploaded to platforms supporting smart contract deployments
The WebAssembly file of the contract (hello.wasm) will be deployed on the chain, so the smart contract may be programmed in any language as long as it compiles to WebAssembly.
The pallet-contracts
exposes APIs e.g. for storage access, block information and self-termination for contracts.
The smart contracts use these pallet-contracts
APIs to communicate with the blockchain.
Smart contracts may be used for different purposes:
-
on some blockchains, smart contracts are the main business value. These types of blockchains act like specialised hosting platforms for smart contracts, e.g. Astar, Phala, Aleph Zero, t3rn.
-
on other blockchains, smart contracts act like add-ons, where users have to pay gas fees for the execution of these smart contracts.
-
thirdly, smart contracts can be used as a proof-of-concept, after which smart contracts may be promoted to their own slot in a (Polkadot or Kusama) parachain.
CLI for Smart Contracts
Prerequisites for the CLI for smart contracts are Rust and Cargo.
For installation instructions, please see Install Required Packages and Rust (opens in a new tab).
Cargo, the build tool for Rust, can be used as a CLI to set up and manage smart contracts. Some commands are:
$ cargo contract new <contract-name> # creates new contract
$ cargo contract build # generates WebAssembly binaries
$ cargo contract build --lint # identifies typical security issues
$ cargo test # runs the tests of this smart contract
$ cargo contract upload --suri //Alice # uploads the contract to a single node chain
# instantiates and executes the smart contract, the contract address will be printed out
$ cargo contract instantiate --execute --suri //Alice --args true
# calls the smart contracts' flip() function
$ cargo contract call --contract <contract_address> --message flip --suri //Alice
These CLI commands work only if a local blockchain with activated pallet-contracts, is already set up and running. For installing a local blockchain please refer to Substrate Contract Node Docs (opens in a new tab).
For more details on how to deploy a smart contract with the CLI, please refer to LogosLabs Dev Env (opens in a new tab).
For details on how to install the cargo contract linter, please refer to ink! docs (opens in a new tab).
UI for Smart Contracts
While having a local blockchain running, for test purposes, we can upload our smart contract to Polkadot.JS (opens in a new tab) or ContractsUI (opens in a new tab). These two platforms take over the part of deploying and instantiating the contract to the local blockchain. The same as cargo would do in the CLI, as described above.
ink! and Smart Contracts
ink! uses the Rust syntax plus some ink! specific syntax and components that are needed for developing smart contracts.
In a smart contract's code written in plain Rust, we find specialized macros like #[ink(…)]
,
which instructs ink! what the annotated part of the code is,
and facilitates the compilation into a Substrate compatible Wasm bytecode.
The source code of a smart contract resides in the lib.rs
file which is a Rust file enriched with ink! syntax.
A smart contract written in ink! needs the following mandatory components:
- one
#[ink(storage)]
struct. - one or more
#[ink(constructor)]
functions. - one or more
#[ink(message)]
functions.
A basic example, based on ink!'s example template (opens in a new tab):
#[ink::contract]
pub mod MyContractWithFlips {
/// the contract's storage
#[ink(storage)]
pub struct MyContract {
/// stores a single `bool` value on the storage
value: bool,
}
impl MyContract {
/// the constructor of this smart contract.
/// it initializes the `bool` value to the given `init_value`.
#[ink(constructor)]
pub fn new(init_value: bool) -> Self {
Self { value: init_value }
}
/// the public method of this contract,
/// for flipping values from `true` to `false` and vice versa.
///
/// by default functions are private, they have to be annotated
/// with `#[ink(message)]` and `pub` to be public
/// and accessible from the outside
#[ink(message)]
pub fn flip(&mut self) {
self.value = !self.value;
}
}
}
ink! supports all storage types available in Rust, plus the Substrate types like AccoundId
, Balance
, Hash
.
Besides the already seen makros #[ink::contract]
, #[ink(storage)]
, #[ink(message)]
,
there also other makros available in ink!, like #[ink::event] (opens in a new tab), #[ink::trait_definition] (opens in a new tab),
#[cfg(test)] (opens in a new tab) etc., please refer to the ink! official documentation (opens in a new tab).
References
https://use.ink/how-it-works (opens in a new tab)
https://use.ink/smart-contracts-polkadot (opens in a new tab)
https://use.ink/basics/contract-template (opens in a new tab)