Skip to main content

ink! Framework Details

Understanding ink! for Rust contracts

ink! is Rust's smart contract framework built on Substrate contracts pallet.

What is ink!

  • Rust eDSL - Uses macros, no new language
  • WASM output - Small, efficient bytecode
  • Substrate native - Works on Selendra and other chains
  • Type safe - Rust compiler catches errors early

Core Attributes

#[ink::contract]

#[ink::contract]
mod my_contract {
    // Contract code here
}

#[ink(storage)]

#[ink(storage)]
pub struct MyContract {
    value: i32,
    owner: AccountId,
    data: Vec<u8>,
}

Supported types: bool, u8-u128, i32-i128, Balance, Vec<T>, Mapping<K, V>, Option<T>, custom structs with scale::Encode + scale::Decode

#[ink(constructor)]

#[ink(constructor)]
pub fn new(initial_value: i32) -> Self {
    Self {
        value: initial_value,
        owner: Self::env().caller(),
        data: Vec::new(),
    }
}

Runs once during deployment.

#[ink(message)]

#[ink(message)]
pub fn get_value(&self) -> i32 {
    self.value
}

#[ink(message)]
pub fn set_value(&mut self, new_value: i32) {
    self.value = new_value;
}

State changes require &mut self. Read-only uses &self.

#[ink(payable)]

#[ink(message, payable)]
pub fn buy_item(&mut self, item_id: u32) {
    let payment = self.env().transferred_value();
    // Handle payment
}

#[ink(event)]

#[ink(event)]
pub struct ValueChanged {
    #[ink(topic)]
    old_value: i32,
    #[ink(topic)]
    new_value: i32,
}

impl MyContract {
    #[ink(message)]
    pub fn set_value(&mut self, new_value: i32) {
        let old_value = self.value;
        self.value = new_value;
        self.env().emit_event(ValueChanged { old_value, new_value });
    }
}

Up to 4 indexed topics for searchability.

Environment API

impl MyContract {
    #[ink(message)]
    pub fn get_info(&self) -> (AccountId, AccountId, u64, Balance) {
        (
            self.env().caller(),        // Transaction sender
            self.env().account_id(),    // Contract address
            self.env().block_number(),  // Current block
            self.env().balance(),       // Contract balance
        )
    }
}

Other: block_timestamp(), transferred_value() (payable)

Error Handling

#[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum Error {
    InsufficientBalance,
    Unauthorized,
    InvalidAmount,
}

pub type Result<T> = core::result::Result<T, Error>;

impl MyContract {
    #[ink(message)]
    pub fn withdraw(&mut self, amount: Balance) -> Result<()> {
        if self.env().caller() != self.owner {
            return Err(Error::Unauthorized);
        }
        if self.env().balance() < amount {
            return Err(Error::InsufficientBalance);
        }
        self.env().transfer(self.owner, amount)
            .map_err(|_| Error::TransferFailed)?;
        Ok(())
    }
}

Cross-Contract Calls

use ink::env::call::{build_call, ExecutionInput, Selector};

impl MyContract {
    #[ink(message)]
    pub fn call_other(&self, target: AccountId) -> Result<u32> {
        build_call::<Environment>()
            .call(target)
            .gas_limit(0)
            .exec_input(ExecutionInput::new(Selector::new(ink::selector_bytes!("get_value"))))
            .returns::<Result<u32>>()
            .invoke()
    }
}

Cargo.toml Setup

[dependencies]
ink = { version = "5.0.0-rc", default-features = false }

[features]
default = ["std"]
std = ["ink/std"]

Selendra uses ink! 5.0.0-rc. Check compatibility between versions.

Testing

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

    #[ink::test]
    fn constructor_works() {
        let contract = MyContract::new(42);
        assert_eq!(contract.get_value(), 42);
    }

    #[ink::test]
    fn value_change() {
        let mut contract = MyContract::new(0);
        contract.set_value(100);
        assert_eq!(contract.get_value(), 100);
    }
}

Build & Deploy

cargo contract build --release

Outputs: target/ink/my_contract.wasm and target/ink/metadata.json

ink! vs Solidity

Featureink!Solidity
Memory safetyCompiler enforcedManual
PerformanceWASM optimizedEVM bytecode
ToolingRust ecosystemEthereum ecosystem
Best forComplex logic, performanceQuick prototyping, familiarity

Resources

Next Steps

ink! Framework Details - Selendra