Commit efe69028 authored by Andrew Jones's avatar Andrew Jones Committed by Hero Bird

Contract Runtime Types (#108)

* [model] Make EnvHandler generic over Env

* [core] Remove api env functions

* [core] Remove default srml types, separate EnvStorage trait

* [core] Remove TryFrom bounds

* [core, examples] Convert incrementer to use NodeRuntime types

* [examples] Ignore duplicate wasm runtime lang items

* [examples] Tidy up incrementer example

* [core] Add Hash constraint for AccountId

* [examples] Convert model ERC20 example

* [lang, examples] Generate env type aliases, convert ERC20 example

* [types] Add pre-baked node-runtime types lib

* [examples] Convert core/incrementer to use node-runtime-types

* [examples] Convert lang/erc20 to use node-runtime-types

* [examples] Convert model/erc20 to use node-runtime-types

* [types, examples] Rename node-runtime to node_runtime

* [types] Add default contract environment types

* [types] Rename type crates

* [types, core] Move default types back to core

* [examples] Convert flipper example to use default types

* [core] Fix up test_env

* [core] Fix syn Errors after merge

* [core] Temporarily suppress unused errors in test_env

* [tests] Fix up test compilation

* [tests] Remove unused test code overrides

* [core] Make SrmlEnvStorage enum

* [core] Fix empty enum

* Use Debug impl of AccountId

* [model] Remove stray license template line

* [lang] Replace Env type alias with explicit type

* [core, tests] Store raw bytes in TestEnv data

* [model] Fix tests

* [examples] Add missing env import

* [core] Remove Hash + Copy constraints

* [core] Replace unwrap with expect

* [core] Remove unused import

* [lang] Fix missing Env type param for test-env

* [examples] Construct AccountId in tests by decoding bytes

* [examples] set_caller helper function

* [core, examples] Extract TestEnv events, no type param required

* [core, examples] Restore `r#return` api call

* [examples] Use from impl for AccountId

* [core. lang] Encode concrete env type in contract

* [lang] Fix lang failure tests

* [examples] Fix lang incrementer example

* [lang] Fix up events test

* [lang] Increase recursion limit for lang test

* [lang] Encapsulate env types in module and use alias for ContractEnv

* [lang] Fix remaining lang tests

* [examples] Move node runtime types to separate repo

* [lang] Make types public and prefix ContractEnv alias

* [lang] Remove ContractEnv alias, inline type

* [fix] Remove types/node_runtime from workspace

* [lang] Use fully qualified ContractEnv, fix lang tests

* [examples] Fix events lang example

* [lang, examples] Convert erc20 example to use new ink-types-node-runtime

* [docs, template, examples] Add missing EnvTypes declaration

* [lang] Add super import to types mod

* [examples] use DefaultSrmlTypes in erc20 example

* [examples] Use ink-types-node-runtime lib

* [examples] Use remote git dependency

* [core] remove unused reexport

* [core] update docs

* [examples] Fix core incrementer example

* [lang] Move 'env' type alias to test module

* Revert "[lang] Move 'env' type alias to test module"

This reverts commit 8f73f37b

* [lang] use Env trait to allow calling methods from type alias

* [lang] Fix lang codegen tests

* [lang] Use inner attr for EnvTypes instead of type alias

* [lang] Add some tests for parsing env types meta attr

* [lang] Pass EnvHandler type to fix compile error after merge

* [core] Add extra trait bounds to EnvTypes

* [core] EnvTypes test-env feature

* [examples] Reference master ink! in anticipation of merge

* [core] Docs and pub(self)

* [examples] Convert to AccountId::from

* [lang] Revert recursion limit

* [core] Make ContractEnv pub again

* [lang] remove env type alias

* [core] restore test env emitted_events method

* [core] missing T

* [core] emitted_events missing type parameter

* [examples] update env usage in erc20

* [examples] update env usage in other lang examples

* [examples] fix up core examples

* [examples] fix up model examples

* [core] fix import

* [core] phantom marker grumble

* [core] comment grumble

* [core] return Iterator from emitted_events

* [lang] remove commented out code

* [core] remove redundant allow_unused

* [examples] use DefaultSrmlTypes for erc20 example
parent 4a858433
......@@ -33,6 +33,9 @@ that has a boolean state that can be flipped or returned.
```rust
contract! {
/// Specify concrete implementation of contract environment types
#![env = ink_core::env::DefaultSrmlTypes]
/// Flips its state between `true` and `false`.
struct Flipper {
/// The current state of our flag.
......
......@@ -8,6 +8,8 @@ use ink_core::{
use ink_lang::contract;
contract! {
#![env = ink_core::env::DefaultSrmlTypes]
/// This simple dummy contract has a `bool` value that can
/// alter between `true` and `false` using the `flip` message.
/// Users can retrieve its current state using the `get` message.
......
......@@ -14,103 +14,16 @@
// You should have received a copy of the GNU General Public License
// along with ink!. If not, see <http://www.gnu.org/licenses/>.
use super::ContractEnv;
use super::ContractEnvStorage;
use crate::{
env::{
Env as _,
EnvStorage as _,
EnvTypes,
traits::Env,
},
memory::vec::Vec,
storage::Key,
};
/// The environmental address type.
pub type AccountId = <ContractEnv as EnvTypes>::AccountId;
/// The environmental balance type.
pub type Balance = <ContractEnv as EnvTypes>::Balance;
/// The environmental hash type.
pub type Hash = <ContractEnv as EnvTypes>::Hash;
/// The environmental moment type.
pub type Moment = <ContractEnv as EnvTypes>::Moment;
/// Returns the address of the current smart contract.
pub fn address() -> AccountId {
ContractEnv::address()
}
/// Returns the balance of the current smart contract.
pub fn balance() -> Balance {
ContractEnv::balance()
}
/// Returns the address of the caller of the current smart contract execution.
pub fn caller() -> AccountId {
ContractEnv::caller()
}
/// Returns the uninterpreted input data of the current smart contract execution.
pub fn input() -> Vec<u8> {
ContractEnv::input()
}
/// Returns the random seed from the latest block.
pub fn random_seed() -> Hash {
ContractEnv::random_seed()
}
/// Returns the timestamp of the latest block.
pub fn now() -> Moment {
ContractEnv::now()
}
/// Returns the current gas price.
pub fn gas_price() -> Balance {
ContractEnv::gas_price()
}
/// Returns the amount of gas left for the contract execution.
pub fn gas_left() -> Balance {
ContractEnv::gas_left()
}
/// Returns the transferred value.
pub fn value_transferred() -> Balance {
ContractEnv::value_transferred()
}
/// Returns the current smart contract exection back to the caller
/// and return the given encoded value.
///
/// # Safety
///
/// External callers rely on the correct type of the encoded returned value.
/// This operation is unsafe because it does not provide guarantees on its
/// own to always encode the expected type.
pub unsafe fn r#return<T>(value: T) -> !
where
T: parity_codec::Encode,
{
ContractEnv::r#return(&value.encode()[..])
}
/// Prints the given content.
///
/// # Note
///
/// Usable only in development (`--dev`) chains.
pub fn println(content: &str) {
ContractEnv::println(content)
}
/// Deposits raw event data through the Contracts module.
pub fn deposit_raw_event(topics: &[Hash], data: &[u8]) {
ContractEnv::deposit_raw_event(topics, data)
}
/// Stores the given value under the specified key in the contract storage.
///
/// # Safety
......@@ -118,7 +31,7 @@ pub fn deposit_raw_event(topics: &[Hash], data: &[u8]) {
/// This operation is unsafe because it does not check for key integrity.
/// Users can compare this operation with a raw pointer dereferencing in Rust.
pub unsafe fn store(key: Key, value: &[u8]) {
ContractEnv::store(key, value)
ContractEnvStorage::store(key, value)
}
/// Clears the data stored at the given key from the contract storage.
......@@ -128,7 +41,7 @@ pub unsafe fn store(key: Key, value: &[u8]) {
/// This operation is unsafe because it does not check for key integrity.
/// Users can compare this operation with a raw pointer dereferencing in Rust.
pub unsafe fn clear(key: Key) {
ContractEnv::clear(key)
ContractEnvStorage::clear(key)
}
/// Loads the data stored at the given key from the contract storage.
......@@ -138,5 +51,21 @@ pub unsafe fn clear(key: Key) {
/// This operation is unsafe because it does not check for key integrity.
/// Users can compare this operation with a raw pointer dereferencing in Rust.
pub unsafe fn load(key: Key) -> Option<Vec<u8>> {
ContractEnv::load(key)
ContractEnvStorage::load(key)
}
/// Returns the current smart contract exection back to the caller
/// and return the given encoded value.
///
/// # Safety
///
/// External callers rely on the correct type of the encoded returned value.
/// This operation is unsafe because it does not provide guarantees on its
/// own to always encode the expected type.
pub unsafe fn r#return<T, E>(value: T) -> !
where
T: parity_codec::Encode,
E: Env,
{
E::r#return(&value.encode()[..])
}
......@@ -40,17 +40,29 @@ mod test_env;
pub use api::*;
pub use traits::*;
/// The environment implementation that is currently being used.
pub use self::srml::DefaultSrmlTypes;
/// The storage environment implementation that is currently being used.
///
/// This may be either
/// - `DefaultEnv` for real contract storage
/// - `SrmlEnvStorage` for real contract storage
/// manipulation that may happen on-chain.
/// - `TestEnv` for emulating a contract environment
/// - `TestEnvStorage` for emulating a contract environment
/// that can be inspected by the user and used
/// for testing contracts off-chain.
#[cfg(not(feature = "test-env"))]
pub(self) type ContractEnv = self::srml::DefaultSrmlEnv;
pub(self) type ContractEnvStorage = self::srml::SrmlEnvStorage;
/// The storage environment implementation for the test environment.
#[cfg(feature = "test-env")]
pub(self) type ContractEnvStorage = self::test_env::TestEnvStorage;
/// The contract environment implementation that is currently being used
///
/// Generic over user supplied EnvTypes for different runtimes
#[cfg(not(feature = "test-env"))]
pub type ContractEnv<T> = self::srml::SrmlEnv<T>;
/// The environment implementation that is currently being used.
/// The contract environment implementation for the test environment
#[cfg(feature = "test-env")]
pub(self) type ContractEnv = self::test_env::TestEnv;
pub type ContractEnv<T> = self::test_env::TestEnv<T>;
......@@ -19,17 +19,11 @@ mod srml_only;
mod types;
pub use self::types::{
AccountId,
Balance,
DefaultSrmlTypes,
Hash,
Moment,
};
pub use self::types::DefaultSrmlTypes;
#[cfg(not(feature = "test-env"))]
pub use self::srml_only::{
sys,
DefaultSrmlEnv,
SrmlEnvStorage,
SrmlEnv,
};
......@@ -18,7 +18,6 @@ use crate::{
env::{
srml::{
sys,
DefaultSrmlTypes,
},
Env,
EnvStorage,
......@@ -28,36 +27,27 @@ use crate::{
storage::Key,
};
use core::{
convert::TryFrom,
marker::PhantomData,
};
use parity_codec::Decode;
/// The default SRML environment.
pub type DefaultSrmlEnv = SrmlEnv<DefaultSrmlTypes>;
/// The SRML contracts environment.
pub struct SrmlEnv<T>
where
T: EnvTypes,
{
marker: PhantomData<T>,
/// Load the contents of the scratch buffer
fn read_scratch_buffer() -> Vec<u8> {
let size = unsafe { sys::ext_scratch_size() };
let mut value = Vec::new();
if size > 0 {
value.resize(size as usize, 0);
unsafe {
sys::ext_scratch_copy(value.as_mut_ptr() as u32, 0, size);
}
}
value
}
impl<T> EnvTypes for SrmlEnv<T>
where
T: EnvTypes,
{
type AccountId = <T as EnvTypes>::AccountId;
type Balance = <T as EnvTypes>::Balance;
type Hash = <T as EnvTypes>::Hash;
type Moment = <T as EnvTypes>::Moment;
}
/// The SRML contract environment storage
pub enum SrmlEnvStorage {}
impl<T> EnvStorage for SrmlEnv<T>
where
T: EnvTypes,
{
impl EnvStorage for SrmlEnvStorage {
/// Stores the given bytes under the given key.
unsafe fn store(key: Key, value: &[u8]) {
sys::ext_set_storage(
......@@ -77,25 +67,28 @@ where
unsafe fn load(key: Key) -> Option<Vec<u8>> {
const SUCCESS: u32 = 0;
if sys::ext_get_storage(key.as_bytes().as_ptr() as u32) == SUCCESS {
return Some(Self::read_scratch_buffer())
return Some(read_scratch_buffer())
}
None
}
}
impl<T: EnvTypes> SrmlEnv<T> {
/// Load the contents of the scratch buffer
fn read_scratch_buffer() -> Vec<u8> {
let size = unsafe { sys::ext_scratch_size() };
let mut value = Vec::new();
if size > 0 {
value.resize(size as usize, 0);
unsafe {
sys::ext_scratch_copy(value.as_mut_ptr() as u32, 0, size);
}
}
value
}
/// The SRML contracts environment.
pub struct SrmlEnv<T>
where
T: EnvTypes,
{
marker: PhantomData<fn () -> T>,
}
impl<T> EnvTypes for SrmlEnv<T>
where
T: EnvTypes,
{
type AccountId = <T as EnvTypes>::AccountId;
type Balance = <T as EnvTypes>::Balance;
type Hash = <T as EnvTypes>::Hash;
type Moment = <T as EnvTypes>::Moment;
}
macro_rules! impl_getters_for_srml_env {
......@@ -103,7 +96,7 @@ macro_rules! impl_getters_for_srml_env {
$(
fn $name() -> $ret_type {
unsafe { sys::$ext_name() };
Decode::decode(&mut &Self::read_scratch_buffer()[..])
Decode::decode(&mut &read_scratch_buffer()[..])
.ok_or(concat!(
stringify!($name), " received an incorrectly sized buffer from SRML"
))
......@@ -118,8 +111,6 @@ macro_rules! impl_getters_for_srml_env {
impl<T> Env for SrmlEnv<T>
where
T: EnvTypes,
<T as EnvTypes>::AccountId: for<'a> TryFrom<&'a [u8]>,
<T as EnvTypes>::Hash: for<'a> TryFrom<&'a [u8]>,
{
fn input() -> Vec<u8> {
let size = unsafe { sys::ext_input_size() };
......
......@@ -18,6 +18,6 @@ mod impls;
pub mod sys;
pub use self::impls::{
DefaultSrmlEnv,
SrmlEnvStorage,
SrmlEnv,
};
......@@ -16,25 +16,28 @@
//! Public api to interact with the special testing environment.
use super::ContractEnv;
use crate::env::AccountId;
use crate::env::{
ContractEnv,
ContractEnvStorage,
};
use crate::env::traits::EnvTypes;
/// Returns the total number of reads to all storage entries.
pub fn total_reads() -> u64 {
ContractEnv::total_reads()
ContractEnvStorage::total_reads()
}
/// Returns the total number of writes to all storage entries.
pub fn total_writes() -> u64 {
ContractEnv::total_writes()
ContractEnvStorage::total_writes()
}
/// Sets the caller for the next calls to the given address.
pub fn set_caller(address: AccountId) {
ContractEnv::set_caller(address)
pub fn set_caller<T: EnvTypes>(address: T::AccountId) {
ContractEnv::<T>::set_caller(address)
}
/// Returns an iterator over the uninterpreted bytes of all past emitted events.
pub fn emitted_events() -> impl Iterator<Item = Vec<u8>> {
ContractEnv::emitted_events().into_iter()
pub fn emitted_events<T: EnvTypes>() -> impl Iterator<Item = Vec<u8>> {
ContractEnv::<T>::emitted_events().into_iter()
}
This diff is collapsed.
......@@ -20,16 +20,30 @@ use crate::{
};
use parity_codec::Codec;
/// The environmental types usable by contracts defined with pDSL.
#[cfg(not(feature = "test-env"))]
/// The environmental types usable by contracts defined with ink!.
pub trait EnvTypes {
/// The type of an address.
type AccountId: Codec + PartialEq + Eq;
type AccountId: Codec + Clone + PartialEq + Eq;
/// The type of balances.
type Balance: Codec;
type Balance: Codec + Clone + PartialEq + Eq;
/// The type of hash.
type Hash: Codec;
type Hash: Codec + Clone + PartialEq + Eq;
/// The type of timestamps.
type Moment: Codec;
type Moment: Codec + Clone + PartialEq + Eq;
}
#[cfg(feature = "test-env")]
/// The environmental types usable by contracts defined with ink!.
pub trait EnvTypes {
/// The type of an address.
type AccountId: Codec + Clone + PartialEq + Eq + core::fmt::Debug;
/// The type of balances.
type Balance: Codec + Clone + PartialEq + Eq + core::fmt::Debug;
/// The type of hash.
type Hash: Codec + Clone + PartialEq + Eq + core::fmt::Debug;
/// The type of timestamps.
type Moment: Codec + Clone + PartialEq + Eq + core::fmt::Debug;
}
/// Types implementing this can act as contract storage.
......@@ -60,7 +74,7 @@ pub trait EnvStorage {
}
/// The environment API usable by contracts defined with pDSL.
pub trait Env: EnvTypes + EnvStorage {
pub trait Env: EnvTypes {
/// Returns the chain address of the contract.
fn address() -> <Self as EnvTypes>::AccountId;
......
......@@ -23,8 +23,10 @@ use parity_codec::{
use ink_core::{
env::{
self,
AccountId,
Balance,
ContractEnv,
DefaultSrmlTypes,
EnvTypes,
Env as _,
},
storage::{
self,
......@@ -39,6 +41,9 @@ use ink_core::{
},
};
type AccountId = <ContractEnv<DefaultSrmlTypes> as EnvTypes>::AccountId;
type Balance = <ContractEnv<DefaultSrmlTypes> as EnvTypes>::Balance;
/// The storage data that is hold by the ERC-20 token.
#[derive(Debug, Encode, Decode)]
pub struct Erc20Token {
......@@ -72,7 +77,7 @@ impl Erc20Token {
/// Transfers token from the sender to the `to` address.
pub fn transfer(&mut self, to: AccountId, value: Balance) -> bool {
self.transfer_impl(env::caller(), to, value);
self.transfer_impl(ContractEnv::<DefaultSrmlTypes>::caller(), to, value);
true
}
......@@ -88,7 +93,7 @@ impl Erc20Token {
/// the spender's allowance to 0 and set the desired value afterwards:
/// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
pub fn approve(&mut self, spender: AccountId, value: Balance) -> bool {
let owner = env::caller();
let owner = ContractEnv::<DefaultSrmlTypes>::caller();
self.allowances.insert((owner, spender), value);
// emit event (not ready yet)
true
......@@ -182,7 +187,7 @@ fn ret<T>(val: T) -> !
where
T: parity_codec::Encode,
{
unsafe { env::r#return(&val.encode()) }
unsafe { env::r#return::<T, ContractEnv<DefaultSrmlTypes>>(val) }
}
fn instantiate() -> Erc20Token {
......@@ -198,7 +203,7 @@ pub extern "C" fn deploy() {
}
fn decode_params() -> Action {
let input = env::input();
let input = ContractEnv::<DefaultSrmlTypes>::input();
Action::decode(&mut &input[..]).unwrap()
}
......
......@@ -14,7 +14,9 @@ crate-type = ["cdylib"]
[features]
default = []
test-env = ["ink_core/test-env"]
test-env = [
"ink_core/test-env",
]
[profile.release]
panic = "abort"
......
......@@ -19,7 +19,9 @@
use parity_codec::Decode;
use ink_core::{
env::{
self,
ContractEnv,
DefaultSrmlTypes,
Env,
},
storage::{
......@@ -35,9 +37,6 @@ use ink_core::{
},
};
// #[cfg(not(test))]
// use ink_core::println;
/// An incrementer smart contract.
///
/// Can only increment and return its current value.
......@@ -95,7 +94,7 @@ fn ret<T>(val: T) -> !
where
T: parity_codec::Encode,
{
ContractEnv::return_(&val.encode())
unsafe { env::r#return::<T, ContractEnv<DefaultSrmlTypes>>(val) }
}
fn instantiate() -> Incrementer {
......@@ -112,7 +111,7 @@ pub extern "C" fn deploy() {
#[no_mangle]
pub extern "C" fn call() {
let input = ContractEnv::input();
let input = <ContractEnv<DefaultSrmlTypes> as Env>::input();
let action = Action::decode(&mut &input[..]).unwrap();
let mut incrementer = instantiate();
......
......@@ -25,9 +25,10 @@ use parity_codec::{
};
use ink_core::{
env::{
srml::AccountId,
ContractEnv,
DefaultSrmlTypes,
Env,
EnvTypes,
},
memory::string::String,
storage::{
......@@ -42,6 +43,9 @@ use ink_core::{
},
};
type AccountId = <ContractEnv<DefaultSrmlTypes> as EnvTypes>::AccountId;
type Balance = <ContractEnv<DefaultSrmlTypes> as EnvTypes>::Balance;
/// A peep done by a registered user.
#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)]
pub struct Peep {
......@@ -75,7 +79,7 @@ impl AllocateUsing for UserData {
A: ink_core::storage::alloc::Allocate,
{
Self {
owner: AccountId::from(&[0x0; 32][..]),
owner: AccountId::from([0x0; 32]),
peeps: storage::Vec::allocate_using(alloc),
following: storage::Vec::allocate_using(alloc),
}
......@@ -152,7 +156,7 @@ impl Subpeep {
pub fn register(&mut self, username: &str) -> bool {
if self.users.get(username).is_none() {
let user_data = unsafe { UserData::allocate_using(&mut self.alloc) }
.initialize_into(ContractEnv::caller());
.initialize_into(ContractEnv::<DefaultSrmlTypes>::caller());
self.users.insert(username.into(), user_data);
return true
}
......@@ -164,7 +168,7 @@ impl Subpeep {
// Check if the caller is registered as the peeping user.
assert_eq!(
self.users.get(username).map(|data| data.owner).unwrap(),
ContractEnv::caller()
ContractEnv::<DefaultSrmlTypes>::caller()
);
self.peep_global(username, message);
self.users
......@@ -176,7 +180,7 @@ impl Subpeep {
// Check if the caller is registered as the following user.
assert_eq!(
self.users.get(following).map(|data| data.owner).unwrap(),
ContractEnv::caller()
ContractEnv::<DefaultSrmlTypes>::caller()
);
self.users.mutate_with(following, |following| {
following.following.push(followed.into())
......@@ -209,7 +213,7 @@ pub extern "C" fn deploy() {
#[no_mangle]
pub extern "C" fn call() {
let input = ContractEnv::input();
let input = ContractEnv::<DefaultSrmlTypes>::input();
let action = Action::decode(&mut &input[..]).unwrap();
let mut subpeep = instantiate();
......
......@@ -49,14 +49,15 @@ use ink_core::memory::vec;
#[test]
fn deploy() {
let subpeep = Subpeep::default();
let subpeep = instantiate().initialize_into(());
assert_eq!(subpeep.recent_peeps(10), Vec::new());
assert_eq!(subpeep.recent_user_peeps(10, "alice"), None);
}
#[test]
fn peep_message() {
let mut subpeep = Subpeep::default();
let mut subpeep = instantiate().initialize_into(());
let test_user = "Alice";
let test_message = "Hello, World!";
subpeep.register(test_user.into());
......
#!/bin/bash
set -e
PROJNAME=erc20
# cargo clean
......@@ -9,4 +11,4 @@ CARGO_INCREMENTAL=0 cargo build --release --features generate-api-description --
wasm2wat -o target/$PROJNAME.wat target/wasm32-unknown-unknown/release/$PROJNAME.wasm
cat target/$PROJNAME.wat | sed "s/(import \"env\" \"memory\" (memory (;0;) 2))/(import \"env\" \"memory\" (memory (;0;) 2 16))/" > target/$PROJNAME-fixed.wat