Unverified Commit b29a2cb1 authored by hammeWang's avatar hammeWang Committed by GitHub
Browse files

add example chain extension contract (#645)

* add #[ink:chain_extension] example contract

* modify return content

* fix rustfmt CI fail
parent 27c6ba05
Pipeline #122137 failed with stages
in 54 minutes and 8 seconds
# Ignore build artifacts from the local tests sub-crate.
/target/
# Ignore backup files creates by cargo fmt.
**/*.rs.bk
# Remove Cargo.lock when creating an executable, leave it for libraries
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
Cargo.lock
[package]
name = "rand_extension"
version = "0.1.0"
authors = ["Hammewang <maggiedong@parity.io>"]
edition = "2018"
[dependencies]
ink_primitives = { path = "../../crates/primitives", default-features = false }
ink_metadata = { path = "../../crates/metadata", default-features = false, features = ["derive"], optional = true }
ink_env = { path = "../../crates/env", default-features = false }
ink_storage = { path = "../../crates/storage", default-features = false }
ink_lang = { path = "../../crates/lang", default-features = false }
scale = { package = "parity-scale-codec", version = "1.3", default-features = false, features = ["derive"] }
scale-info = { version = "0.4.1", default-features = false, features = ["derive"], optional = true }
[lib]
name = "rand_extension"
path = "lib.rs"
crate-type = [
# Used for normal contract Wasm blobs.
"cdylib",
]
[features]
default = ["std"]
std = [
"ink_metadata/std",
"ink_env/std",
"ink_storage/std",
"ink_primitives/std",
"scale/std",
"scale-info",
"scale-info/std",
]
ink-as-dependency = []
// Copyright 2018-2021 Parity Technologies (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#![cfg_attr(not(feature = "std"), no_std)]
use ink_env::Environment;
use ink_lang as ink;
/// This is an example of how ink! contract should
/// call substrate runtime `RandomnessCollectiveFlip::random_seed`.
/// Define the operations to interact with the substrate runtime
#[ink::chain_extension]
pub trait FetchRandom {
type ErrorCode = RandomReadErr;
/// Note: this gives the operation a corresponding func_id (1101 in this case),
/// and the chain-side chain_extension will get the func_id to do further operations.
#[ink(extension = 1101, returns_result = false)]
fn fetch_random() -> [u8; 32];
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, scale::Encode, scale::Decode)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum RandomReadErr {
FailGetRandomSource,
}
impl ink_env::chain_extension::FromStatusCode for RandomReadErr {
fn from_status_code(status_code: u32) -> Result<(), Self> {
match status_code {
0 => Ok(()),
1 => Err(Self::FailGetRandomSource),
_ => panic!("encountered unknown status code"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum CustomEnvironment {}
impl Environment for CustomEnvironment {
const MAX_EVENT_TOPICS: usize =
<ink_env::DefaultEnvironment as Environment>::MAX_EVENT_TOPICS;
type AccountId = <ink_env::DefaultEnvironment as Environment>::AccountId;
type Balance = <ink_env::DefaultEnvironment as Environment>::Balance;
type Hash = <ink_env::DefaultEnvironment as Environment>::Hash;
type BlockNumber = <ink_env::DefaultEnvironment as Environment>::BlockNumber;
type Timestamp = <ink_env::DefaultEnvironment as Environment>::Timestamp;
type ChainExtension = FetchRandom;
}
#[ink::contract(env = crate::CustomEnvironment)]
mod rand_extension {
use super::RandomReadErr;
/// Defines the storage of your contract.
/// Here we store the random seed fetched from the chain
#[ink(storage)]
pub struct RandExtension {
/// Stores a single `bool` value on the storage.
value: [u8; 32],
}
#[ink(event)]
pub struct RandomUpdated {
#[ink(topic)]
new: [u8; 32],
}
impl RandExtension {
/// Constructor that initializes the `bool` value to the given `init_value`.
#[ink(constructor)]
pub fn new(init_value: [u8; 32]) -> Self {
Self { value: init_value }
}
/// Constructor that initializes the `bool` value to `false`.
///
/// Constructors can delegate to other constructors.
#[ink(constructor)]
pub fn default() -> Self {
Self::new(Default::default())
}
/// update the value from runtime random source
#[ink(message)]
pub fn update(&mut self) -> Result<(), RandomReadErr> {
// Get the on-chain random seed
let new_random = self.env().extension().fetch_random()?;
self.value = new_random;
// emit the RandomUpdated event when the random seed
// is successfully fetched.
self.env().emit_event(RandomUpdated { new: new_random });
Ok(())
}
/// Simply returns the current value.
#[ink(message)]
pub fn get(&self) -> [u8; 32] {
self.value
}
}
/// Unit tests in Rust are normally defined within such a `#[cfg(test)]`
#[cfg(test)]
mod tests {
/// Imports all the definitions from the outer scope so we can use them here.
use super::*;
use ink_lang as ink;
/// We test if the default constructor does its job.
#[ink::test]
fn default_works() {
let rand_extension = RandExtension::default();
assert_eq!(rand_extension.get(), [0; 32]);
}
}
}
# Chain-side Extension
use this as an implement of trait `ChainExtension` in substrate and use it as the associated type ChainExtension of trait `pallet_contracts::Config`
\ No newline at end of file
use codec::{Encode, Decode};
use frame_support::debug::{error, native};
use frame_support::traits::Randomness;
use pallet_contracts::chain_extension::{
ChainExtension, Environment, Ext, InitState, RetVal, SysConfig, UncheckedFrom,
};
use sp_runtime::DispatchError;
use sp_std::vec::Vec;
/// contract extension for `FetchRandom`
pub struct FetchRandomExtension;
impl ChainExtension for FetchRandomExtension {
fn call<E: Ext>(func_id: u32, env: Environment<E, InitState>) -> Result<RetVal, DispatchError>
where
<E::T as SysConfig>::AccountId: UncheckedFrom<<E::T as SysConfig>::Hash> + AsRef<[u8]>,
{
match func_id {
1101 => {
let mut env = env.buf_in_buf_out();
let random_seed: [u8; 32] = super::RandomnessCollectiveFlip::random_seed().0;
let random_slice = random_seed.encode();
native::trace!(
target: "runtime",
"[ChainExtension]|call|func_id:{:}",
func_id
);
env.write(&random_slice, false, None)
.map_err(|_| DispatchError::Other("ChainExtension failed to call random"))?;
}
_ => {
error!("call an unregistered `func_id`, func_id:{:}", func_id);
return Err(DispatchError::Other("Unimplemented func_id"));
}
}
Ok(RetVal::Converging(0))
}
fn enabled() -> bool {
true
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment