Inkpad
Inkpad provides an enviroment for running ink! contract outside of substrate.
- debug ink! contract
- write tests for ink! contract
- run ink! contract in browser
- write your implementation of inkpad
Quickstart
0. Install cargo-contract
cargo install cargo-contract
1. Install inkpad
cargo install inkpad
2. Run cargo contract new
cargo contract new flipper
3. Compile with debug info
cargo contract build
4. Test flipper
inkpad target/ink/flipper.contract info
Prerequisites
First you'll want to install the inkpad CLI, and wasm-pack -V should print the version that you just installed.
Next, you'll need to donwload cargo-contract
for compiling your ink! contract to wasm
binaries.
Finally, use the commands of the CLI or follow the Tutorials to start your journey.
ink! contract
Since inkpad
is an execution of ink! contract, we need to have an ink! contract
first of all.
0. Download cargo-contract
For generating an ink! contract, we need to download cargo-contract
cargo install cargo-contract --force
1. New ink! contract
Once we have cargo-contract
installed in our machine, we can run
cargo new my-ink-contract
to generate a template ink contract.
2. Compile ink! contract
cd my-ink-contract
cargo contract build
patractlabs/cargo-contract
For compiling ink! contract with debug info, we can use patractlabs/cargo-contract
along with inkpad
.
0. Installation
cargo install --git https://github.com/patractlabs/cargo-contract.git --branch tag-v0.12.1 --force
1. Compile with debug info
𝝺 cargo contract build -h
cargo-contract-build 0.10.0
Executes build of the smart-contract which produces a wasm binary that is ready for deploying.
It does so by invoking `cargo build` and then post processing the final binary.
USAGE:
cargo contract build [FLAGS] [OPTIONS]
FLAGS:
-d, --debug Enable debug info in the wasm bundle
-h, --help Prints help information
--quiet No output printed to stdout
-V, --version Prints version information
--verbose Use verbose output
OPTIONS:
--generate <all | code-only> Which build artifacts to generate. [default: all]
--manifest-path <manifest-path> Path to the Cargo.toml of the contract to build
-Z, --unstable-options <options>... Use the original manifest (Cargo.toml), do not modify for build optimizations
See the --debug
flag, run cargo build -d
in the contract path will embed
debug info in the wasm of our contract.
CLI
inkpad
has an CLI implementation to help you debug contracts in command line.
𝝺 inkpad
inkpad 0.2.0
Inkpad command tool
USAGE:
inkpad [*.contract | name | code-hash] <SUBCOMMAND>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
ARGS:
<*.contract | name | code-hash> If empty, inkpad will load the last contract which has been executed
SUBCOMMANDS:
call Calls a call method
deploy Calls a deploy method
help Prints this message or the help of the given subcommand(s)
info Prints info of *.contract
list Lists all contracts
Arguments
Once we use *.contract
as the argument of inkpad
, inkpad will load and record
the target contract in database(~/.inkpad) by default.
inkpad call
The inkpad call
command call a method of the supplied contract.
𝝺 inkpad call -h
inkpad-call
Calls a call method
USAGE:
inkpad call [OPTIONS] <method>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
--address <address> Contract callee
-b, --balance <balance> contract balance
--caller <caller> Contract caller
-m, --minimum-balance <minimum-balance> minimum balance
-n, --now <now> current time
-a, --args <string,>... Arguments
-v, --value-transferred <value-transferred> transferred value
ARGS:
<method> Calling method
The options of method call
are destructed from Transaction
address
Callee address
balance
Contract balance
caller
Caller address
minimum-balance
minimum_balance
in transaction
now
Transaction time
args
Transaction arguments, should be parity-scale-codec
format
value
Transferred value
inkpad deploy
The inkpad deploy
command deploy the supplied contract with constructor, like inkpad call
𝝺 inkpad deploy -h
inkpad-deploy
Calls a deploy method
USAGE:
inkpad deploy [OPTIONS] <method>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
--address <address> Contract callee
-b, --balance <balance> contract balance
--caller <caller> Contract caller
-m, --minimum-balance <minimum-balance> minimum balance
-n, --now <now> current time
-a, --args <string,>... Arguments
-v, --value-transferred <value-transferred> transferred value
ARGS:
<method> Calling method
The options of method deploy
are destructed from Transaction
address
Callee address
balance
Contract balance
caller
Caller address
minimum-balance
minimum_balance
in transaction
now
Transaction time
args
Transaction arguments, should be parity-scale-codec
format
value
Transferred value
inkpad info
Shows info
of the current contract
𝝺 inkpad info -h
inkpad-info 0.2.0
Prints info of *.contract
USAGE:
inkpad info
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
This will print the info of the last contract inkpad rememberred or we can execute this with contract argument.
inkpad list
Lists loaded contracts
𝝺 inkpad list -h
inkpad-list 0.2.0
Lists all contracts
USAGE:
inkpad list
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
for example
𝝺 inkpad list
contract code-hash
---------------------------------------------------------------------------------------
delegator 0x85970b066ab92b4495bba682917ded604019a6fce9b247c36b35e8c967287f4f
accumulator 0x906811f0dca85909fd267df762c09de7a6af62cd1f1aa73d3778d58d308fd376
adder 0xc60c90582f3e6767d9b8cca5b83f747b8b86afb8c425b2e23dfad5e4784409e1
subber 0xcc9b22fda1a527754190c8833b919a26cf440bd9aca7f0e5810104f09a8e0b82
The code-hash displayed could be used in call-contracts
Tutorials
We have three tutorials that help you get started with inkpad
:
- If you want to debug ink! contract
- If you want to write tests for ink! contract
- If you want to run ink! contract in browser
- If you want to write a implementation for inkpad
debug ink! contract
The goal of this tutorial is to introduce you to use inkpad for debuging ink! contract.
Be sure to have read and followed the Prerequisites.
Getting Started
0. Compile ink! contract with debug info
For cargo-contract
, see 3.2.
1. Introduce Trap
in inkpad
#![allow(unused)] fn main() { /// A trap code describing the reason for a trap. /// /// All trap instructions have an explicit trap code. #[non_exhaustive] #[derive(Clone, Debug, PartialEq, Eq)] pub enum TrapCode { /// The current stack space was exhausted. StackOverflow, /// An out-of-bounds memory access. MemoryOutOfBounds, /// A wasm atomic operation was presented with a not-naturally-aligned linear-memory address. HeapMisaligned, /// An out-of-bounds access to a table. TableOutOfBounds, /// Indirect call to a null table entry. IndirectCallToNull, /// Signature mismatch on indirect call. BadSignature, /// An integer arithmetic operation caused an overflow. IntegerOverflow, /// An integer division by zero. IntegerDivisionByZero, /// Failed float-to-int conversion. BadConversionToInteger, /// Code that was supposed to have been unreachable was reached. UnreachableCodeReached, /// Execution has potentially run too long and may be interrupted. Interrupt, /// HostError HostError(Box<Error>), // Unknown Error Unknown, // Termination Termination, // Restoration Restoration, } }
inkpad
supports Trap
both from wasmi
and wasmtime
2. Embed panic
in contract
#![allow(unused)] #![cfg_attr(not(feature = "std"), no_std)] fn main() { use ink_lang as ink; #[ink::contract] mod flipper_trap { #[ink(storage)] pub struct FlipperTrap { value: bool, } impl FlipperTrap { #[ink(constructor)] pub fn new(init_value: bool) -> Self { Self { value: init_value } } #[ink(constructor)] pub fn default() -> Self { Self::new(Default::default()) } #[ink(message)] pub fn flip(&mut self) { panic!("trap here"); self.value = !self.value; } /// Simply returns the current value of our `bool`. #[ink(message)] pub fn get(&self) -> bool { self.value } } } }
3. Catch the trap in contract
#![allow(unused)] fn main() { #[test] fn test_flipper_trap() { // Assume we compile the contract in `2.` to `flipper_trap.contract` let mut rt = Runtime::contract( include_bytes!("flipper_trap.contract"), Some(Instance), ) .expect("Create runtime failed"); rt.deploy("default", vec![], None).expect("Deploy failed"); assert_eq!(rt.call("get", vec![], None), Ok(Some(vec![0]))); if let Some(inkpad_runtime::Error::CallContractFailed { error: inkpad_executor::Error::Trap(Trap { code, .. }), }) = rt.call("flip", vec![], None).err() { assert_eq!(code, TrapCode::UnreachableCodeReached); } else { panic!("Call flipper_trap with unexpected error"); } } }
write tests for ink! contract
In this chapter, we'll introduce how to write tests of ink! contract with inkpad, as an ink! contract executor, it is really easy.
Getting Started
0. Template contract
First of all, we need an ink! contract, flipper here.
cargo contract new flipper
1. add build.rs
to our contract
// flipper/build.rs use std::process::Command; fn main() { println!("cargo:rerun-if-changed=src/lib.rs"); Command::new("cargo").args(&["contract", "build"]); }
2. add inkpad as dev-dependencies
[dev-dependencies]
inkpad = "^0"
3. write tests with inkpad
#![allow(unused)] fn main() { // /tests/flipper.rs use inkpad_executor::{Trap, TrapCode}; use inkpad_ri::Instance; use inkpad_runtime::Runtime; use parity_scale_codec::Encode; #[test] fn test_flipper() { let mut rt = Runtime::contract( include_bytes!("../contracts/flipper.contract"), Some(Instance), ) .expect("Create runtime failed"); rt.deploy("default", vec![], None).expect("Deploy failed"); assert_eq!(rt.call("get", vec![], None), Ok(Some(vec![0]))); rt.deploy("new", vec![true.encode()], None) .expect("Deploy failed"); assert_eq!(rt.call("get", vec![], None), Ok(Some(vec![1]))); rt.call("flip", vec![], None).expect("Call contract failed"); assert_eq!(rt.call("get", vec![], None), Ok(Some(vec![0]))); } }
4. run tests
# flipper/
cargo test
run ink! contract in browser
Inkpad supports no_std
which means we can run ink! contract in browser with
inkpad as well.
This chapter will introduce how to run ink! contract in browser!
Getting Started
0. prepare your ink! contract
Please follow the instructions of prerequisites/ink! contract to generate an ink! contract.
1. use @patract/inkpad-browser
in your package
{
dependencies: {
"@patract/inkpad-browser": "^0.1.4"
}
}
2. run ink! contract with inkpad
import { Runtime } from "@inkpad/browser";
// this flipper.contract is the output after `0.`
// under /target/ink/flipper.contract
import CONTRACT from "flipper.contract";
(async () => {
const rt = new Runtime(contract.toString());
// arguments of call or deploy should be
// parity-scale-codec encoded
rt.deploy("default", []);
rt.call("default", []);
});
The usage of @patract/inkpad-browser
is as same as the rust exports,
more functions please check docs.rs/inkpad.
write your implementation of inkpad
inkpad supports both wasmi
and wasmtime
as wasm executor, which means
you can run ink! contract with inkpad anywhere.
When should I use inkpad with wsmtime
feature?
wasmtime
is the default and recommanded executor of inkpad, fast and
full features.
When should I use inkpad with wasmi
feature?
If you have requirements running inkpad in browser or some IoT devices,
you don't want any std
dependencies of rust, you need to use inkpad
with wasmi
feature.
Why customized implementation?
inkpad supports the seal functions provided by pallet-contracts
of
substrate by default, so if you want to use inkpad in some special
devices, you need to re-implement the seal functions yourselves.
Getting Started
0. implement your storage
The Storage
trait in inkpad/crate/support
is the entry of inkpad storage,
first of all, we need to construct a storage for our implementation.
For example, the storage implementation of inkpad-cli
is like:
#![allow(unused)] fn main() { use inkpad_support::traits::{self, Cache, Frame}; /// A inkpad storage implementation using sled #[derive(Clone)] pub struct Storage { pub db: Db, cache: Tree, frame: Vec<Rc<RefCell<State<Memory>>>>, state: HostState, } impl traits::Storage for Storage { fn set(&mut self, key: Vec<u8>, value: Vec<u8>) -> Option<Vec<u8>> { self.cache.insert(key, value).ok()?.map(|v| v.to_vec()) } fn remove(&mut self, key: &[u8]) -> Option<Vec<u8>> { self.cache.remove(key).ok()?.map(|v| v.to_vec()) } fn get(&self, key: &[u8]) -> Option<Vec<u8>> { self.cache.get(key).ok()?.map(|v| v.to_vec()) } } impl Cache<Memory> for Storage { fn frame(&self) -> &Vec<Rc<RefCell<State<Memory>>>> { &self.frame } fn frame_mut(&mut self) -> &mut Vec<Rc<RefCell<State<Memory>>>> { &mut self.frame } fn memory(&self) -> Option<Memory> { Some(self.frame.last()?.borrow().memory.clone()) } /// Flush data fn flush(&mut self) -> Option<()> { for state in self.frame.iter() { let state = state.borrow().clone(); self.state.insert(state.hash, state.state); } let mut data = if let Some(state) = self.db.get(PREVIOUS_STATE).ok()? { HostState::decode(&mut state.as_ref()).ok()? } else { BTreeMap::new() }; data.append(&mut self.state.clone()); self.db.insert(PREVIOUS_STATE, data.encode()).ok()?; self.db.flush().ok()?; Some(()) } } impl Frame<Memory> for Storage {} }
1. construct your seal calls
we need to construct seal calls for different platforms, for example, inkpad-cli
use
system interfaces, inkpad-browser
use browser interfaces.
For example, the seal calls of inkpad-browser
is like
#![allow(unused)] fn main() { use inkpad_sandbox::{RuntimeInterfaces, Sandbox}; /// Browser interface pub struct Interface; impl RuntimeInterfaces for Interface { /// Println fn seal_println(sandbox: &mut Sandbox, args: &[Value]) -> Ret { if args.len() != 2 { return Err(Error::WrongArugmentLength); } let data = sandbox.read_sandbox_memory(args[0].into(), args[1].into())?; if let Ok(utf8) = core::str::from_utf8(&data) { log(utf8); } Ok(None) } /// Generate random value fn seal_random(sandbox: &mut Sandbox, args: &[Value]) -> Ret { if args.len() != 4 { return Err(Error::WrongArugmentLength); } let subject_ptr = args[0].into(); let subject_len = args[1].into(); let output_ptr: u32 = args[2].into(); let output_len: u32 = args[2].into(); // random let mut dest: [u8; 1] = [0]; err_check(getrandom(&mut dest)); let mut subject_buf = sandbox .read_sandbox_memory(subject_ptr, subject_len)? .to_vec(); subject_buf.push(dest[0]); let output = blake2b::blake2b(32, &[], &subject_buf); sandbox.write_sandbox_output(output_ptr, output_len, output.as_bytes())?; Ok(None) } /// sha2 256 fn seal_hash_sha2_256(sandbox: &mut Sandbox, args: &[Value]) -> Ret { if args.len() != 3 { return Err(Error::WrongArugmentLength); } let input_ptr = args[0].into(); let input_len = args[1].into(); let output_ptr = args[2].into(); // hash let mut dest: [u8; 32] = [0; 32]; let mut hasher = Sha256::new(); let input = sandbox.read_sandbox_memory(input_ptr, input_len)?; hasher.update(&input); dest.copy_from_slice(&hasher.finalize()); sandbox.write_sandbox_memory(output_ptr, dest.as_ref())?; // result Ok(None) } /// keccak 256 fn seal_hash_keccak_256(sandbox: &mut Sandbox, args: &[Value]) -> Ret { if args.len() != 3 { return Err(Error::WrongArugmentLength); } let input_ptr = args[0].into(); let input_len = args[1].into(); let output_ptr = args[2].into(); // hash let mut dest: [u8; 32] = [0; 32]; let mut keccak = Keccak::v256(); let input = sandbox.read_sandbox_memory(input_ptr, input_len)?; keccak.update(&input); keccak.finalize(&mut dest); sandbox.write_sandbox_memory(output_ptr, dest.as_ref())?; // result Ok(None) } /// blake2 256 fn seal_hash_blake2_256(sandbox: &mut Sandbox, args: &[Value]) -> Ret { if args.len() != 3 { return Err(Error::WrongArugmentLength); } let input_ptr = args[0].into(); let input_len = args[1].into(); let output_ptr = args[2].into(); // hash let mut dest = [0; 32]; let input = sandbox.read_sandbox_memory(input_ptr, input_len)?; dest.copy_from_slice(blake2b::blake2b(32, &[], &input).as_bytes()); sandbox.write_sandbox_memory(output_ptr, dest.as_ref())?; // result Ok(None) } /// blake2 128 fn seal_hash_blake2_128(sandbox: &mut Sandbox, args: &[Value]) -> Ret { if args.len() != 3 { return Err(Error::WrongArugmentLength); } let input_ptr = args[0].into(); let input_len = args[1].into(); let output_ptr = args[2].into(); // hash let mut dest = [0; 16]; let input = sandbox.read_sandbox_memory(input_ptr, input_len)?; dest.copy_from_slice(blake2b::blake2b(16, &[], &input).as_bytes()); sandbox.write_sandbox_memory(output_ptr, dest.as_ref())?; // result Ok(None) } } }
2. Build Runtime
with your implementations
/// This Instance has `RuntimeInterfaces` trait bundled use my_runtime_interfaces::Instance; use my_storage::Storage; fn main() { let storage = Storage::new(); let rt = Runtime::from_contract(&source, storage, Some(Instance)); }
Here we go, a new implementation of inkpad.