Selendra

Documentation

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

Contribute

Found an issue or want to contribute?

Help us improve this documentation by editing this page on GitHub.

Edit this page on GitHub