Unverified Commit 9cf5a571 authored by Hero Bird's avatar Hero Bird Committed by GitHub

Add call infrastructure to ink_lang (#175)

* [core] add initial call builder implementation

* [core] add setters to CallBuilder

* [core] fix invalid mod import in no_std

* [lang] initial remote call implementation for ink_lang

* [examples] add ink-as-dependency crate feature to erc20 token contract

* [core] move calls and call error definitions

* [core] add ext_create support

* [*] apply rustfmt

* [core] expose ReturnType publicly

* [core] add ext_create impl on the SRML interfacing side

* [core] fix minor invalid module import

* [lang] split CallEnhancer into mut and immutable versions

* [lang] forward to call enhancers from short-version of remote calling

* [core] improve create builder to return the contract directly

* [lang] simplify env types code generation

* [lang] add create call builder code generation

* [lang] improve code gen by altering spans of the generated code

* [examples] add ink-as-dependency to all lang examples

* [cli] add ink-as-dependency to smart contract template

* [lang] fix code gen bug with forwarding references

* [lamg] move impls out of const item

It seems that inherent impls shouldn't be put inside a const item.

* [core] impl Default for Hash

* [lang] add Flush, scale::{Encode, Decode} for contracts as dependencies

* [examples] add example to call remote smart contracts

* [examples] remove invalid test code from delegator contract

* [abi] fix warning about suffixed tuple indices

* [lang] fix bug in generate-abi codegen for deploy handler with multiple args

* [lang] derive from type_metadata::Metadata for contract structs

* [examples] ran cargo fmt

* [examples] derive type_metadata::Metadata for Which

* [examples] make code hashes deploy inputs for delegator contract

* [core] rename gas_cost to gas_limit

* [core] remove parts that got re-introduced accidentally upon merge

* [examples] add a README to the delegator contract

* [examples] restructure delegator project structure

* [examples] fix Cargo.toml of delegator

* [lang] fix bug that ink-as-dependency derives Metadata for the state struct

* [lang] add getter for account_id of ink-as-dependency state structs

* [examples] adjust Delegator contract to make it work with old ABI generator

* [lang] import FromAccountId

* [examples] provide a build.sh script to deploy delegator contract

* [readme] add section about wabt and wasm-utils tools

* [readme] add off-chain test for the example smart contract

* [examples] add .value call to contract create calls of delegator

* [*] use fix-serialization branch of type-metadata

* [*] use type-metadata master branch again (PR was merged)

* [lang] update the noop compile test

* [lang] simplified the noop compile test doc comments

* [lang] fix compiletest for Flipper contract

* [lang] fix compile tests for incrementer contract

* [lang] fix compile tests for Events contract

* [scripts] do not check the Delegator for now (needs special treatment)

* [ci] update rust version from nightly-2019-08-13 to nightly-2019-08-30

* [examples] restructure delegator contract project structure

* [scripts] make check-examples script simpler again after delegator refactoring

* [examples] clean up Delegator contract

* [examples] improve build guidance of Delegator contract
parent d386482b
......@@ -7,7 +7,7 @@ rust:
# - stable
# - beta
- nightly
- nightly-2019-08-13
- nightly-2019-08-30
matrix:
allow_failures:
......
......@@ -39,6 +39,15 @@ ink! is an [eDSL](https://wiki.haskell.org/Embedded_domain_specific_language) to
Use the scripts provided under `scripts` directory in order to run checks on either the workspace or all examples. Please do this before pushing work in a PR.
### Examples
For running the example smart contracts found under `examples` you also need to install some further tools:
- `wabt` (WebAssembly Binary Toolkit)
- `wasm-utils` (Parity's Wasm Utilities)
For more information how to install these on your system go [here](https://substrate.dev/docs/en/contracts/installing-ink#wasm-utilities).
### Testing
Off-chain testing is done by `cargo test`.
......@@ -79,6 +88,20 @@ contract! {
}
}
}
/// Run off-chain tests with `cargo test`.
#[cfg(tests)]
mod tests {
use super::*;
#[test]
fn it_works() {
let mut flipper = Flipper::deploy_mock();
assert_eq!(flipper.get(), false);
flipper.flip();
assert_eq!(flipper.get(), true);
}
}
```
## Documentation
......
......@@ -74,6 +74,7 @@ fn generate_fields_layout<'a>(
_ink_abi::LayoutField::new(stringify!(#ident), self.#ident.layout())
}
} else {
let n = proc_macro2::Literal::usize_unsuffixed(n);
quote! {
_ink_abi::LayoutField::new(stringify!(#n), self.#n.layout())
}
......
......@@ -45,6 +45,7 @@ ink-generate-abi = [
"ink_core/ink-generate-abi",
"ink_lang/ink-generate-abi",
]
ink-as-dependency = []
[profile.release]
panic = "abort"
......
......@@ -22,6 +22,7 @@ use crate::{
EnvTypes,
},
CallError,
CreateError,
EnvStorage as _,
},
memory::vec::Vec,
......@@ -121,3 +122,18 @@ where
{
T::call_evaluate(callee, gas, value, input_data)
}
/// Instantiates a new smart contract.
///
/// Upon success returns the account ID of the newly created smart contract.
pub fn create<T>(
code_hash: T::Hash,
gas_limit: u64,
value: T::Balance,
input_data: &[u8],
) -> Result<T::AccountId, CreateError>
where
T: Env,
{
T::create(code_hash, gas_limit, value, input_data)
}
......@@ -14,12 +14,12 @@
// You should have received a copy of the GNU General Public License
// along with ink!. If not, see <http://www.gnu.org/licenses/>.
// We need this to fix a rustfmt issue. https://github.com/rust-lang/rustfmt/issues/3750
#[rustfmt::skip]
use crate::{
env::{
self,
CallError,
CreateError,
Env,
EnvTypes,
},
......@@ -33,7 +33,7 @@ use scale::Decode;
/// Consists of the input data to a call.
/// The first four bytes are the function selector and the rest are SCALE encoded inputs.
pub struct CallAbi {
struct CallAbi {
/// Already encoded function selector and inputs.
raw: Vec<u8>,
}
......@@ -69,6 +69,40 @@ impl CallAbi {
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct ReturnType<T>(PhantomData<T>);
/// Builds up contract instantiations.
pub struct CreateBuilder<E, C>
where
E: EnvTypes,
{
/// The code hash of the created contract.
code_hash: E::Hash,
/// The maximum gas costs allowed for the instantiation.
gas_limit: u64,
/// The transferred value for the newly created contract.
value: E::Balance,
/// The input data for the instantation.
raw_input: Vec<u8>,
/// The type of the instantiated contract.
contract_marker: PhantomData<fn() -> C>,
}
impl<E, C> CreateBuilder<E, C>
where
E: EnvTypes,
E::Balance: Default,
{
/// Creates a new create builder to guide instantiation of a smart contract.
pub fn new(code_hash: E::Hash) -> Self {
Self {
code_hash,
gas_limit: 0,
value: Default::default(),
raw_input: Vec::new(),
contract_marker: Default::default(),
}
}
}
/// Builds up a call.
pub struct CallBuilder<E, R>
where
......@@ -77,7 +111,7 @@ where
/// The account ID of the to-be-called smart contract.
account_id: E::AccountId,
/// The maximum gas costs allowed for the call.
gas_cost: u64,
gas_limit: u64,
/// The transferred value for the call.
value: E::Balance,
/// The expected return type.
......@@ -86,6 +120,57 @@ where
raw_input: CallAbi,
}
impl<E, C> CreateBuilder<E, C>
where
E: EnvTypes,
{
/// Sets the maximumly allowed gas costs for the call.
pub fn gas_limit(self, gas_limit: u64) -> Self {
let mut this = self;
this.gas_limit = gas_limit;
this
}
/// Sets the value transferred upon the execution of the call.
pub fn value(self, value: E::Balance) -> Self {
let mut this = self;
this.value = value;
this
}
/// Pushes an argument to the inputs of the call.
pub fn push_arg<A>(self, arg: &A) -> Self
where
A: scale::Encode,
{
let mut this = self;
this.raw_input.extend(&arg.encode());
this
}
}
/// Needed because of conflicting implementations of From<T> for T
/// resulting of generated `ink_lang` code.
pub trait FromAccountId<E>
where
E: Env,
{
fn from_account_id(account_id: <E as EnvTypes>::AccountId) -> Self;
}
impl<E, C> CreateBuilder<E, C>
where
E: Env,
C: FromAccountId<E>,
{
/// Runs the process to create and instantiate a new smart contract.
/// Returns the account ID of the newly created smart contract.
pub fn create(self) -> Result<C, CreateError> {
env::create::<E>(self.code_hash, self.gas_limit, self.value, &self.raw_input)
.map(FromAccountId::from_account_id)
}
}
impl<E, R> CallBuilder<E, ReturnType<R>>
where
E: EnvTypes,
......@@ -95,7 +180,24 @@ where
pub fn eval(account_id: E::AccountId, selector: u32) -> Self {
Self {
account_id,
gas_cost: 0,
gas_limit: 0,
value: E::Balance::default(),
return_type: PhantomData,
raw_input: CallAbi::new(selector),
}
}
}
impl<E> CallBuilder<E, ()>
where
E: EnvTypes,
E::Balance: Default,
{
/// Instantiates a non-evaluatable (returns no data) remote smart contract call.
pub fn invoke(account_id: E::AccountId, selector: u32) -> Self {
Self {
account_id,
gas_limit: 0,
value: E::Balance::default(),
return_type: PhantomData,
raw_input: CallAbi::new(selector),
......@@ -108,9 +210,9 @@ where
E: EnvTypes,
{
/// Sets the maximumly allowed gas costs for the call.
pub fn gas_cost(self, gas_cost: u64) -> Self {
pub fn gas_limit(self, gas_limit: u64) -> Self {
let mut this = self;
this.gas_cost = gas_cost;
this.gas_limit = gas_limit;
this
}
......@@ -132,23 +234,6 @@ where
}
}
impl<E> CallBuilder<E, ()>
where
E: EnvTypes,
E::Balance: Default,
{
/// Instantiates a non-evaluatable (returns no data) remote smart contract call.
pub fn invoke(account_id: E::AccountId, selector: u32) -> Self {
Self {
account_id,
gas_cost: 0,
value: E::Balance::default(),
return_type: PhantomData,
raw_input: CallAbi::new(selector),
}
}
}
impl<E, R> CallBuilder<E, ReturnType<R>>
where
E: Env,
......@@ -159,7 +244,7 @@ where
pub fn fire(self) -> Result<R, CallError> {
env::call_evaluate::<E, R>(
self.account_id,
self.gas_cost,
self.gas_limit,
self.value,
self.raw_input.to_bytes(),
)
......@@ -174,7 +259,7 @@ where
pub fn fire(self) -> Result<(), CallError> {
env::call_invoke::<E>(
self.account_id,
self.gas_cost,
self.gas_limit,
self.value,
self.raw_input.to_bytes(),
)
......
......@@ -28,6 +28,7 @@
//! has to be enabled.
mod api;
mod calls;
mod srml;
mod traits;
......@@ -40,15 +41,18 @@ mod test_env;
pub use api::*;
pub use traits::*;
pub use self::srml::{
CallError,
DefaultSrmlTypes,
};
#[cfg(not(feature = "test-env"))]
pub use self::srml::{
CallAbi,
CallBuilder,
pub use self::{
calls::{
CallBuilder,
CreateBuilder,
FromAccountId,
ReturnType,
},
srml::DefaultSrmlTypes,
traits::{
CallError,
CreateError,
},
};
/// The storage environment implementation that is currently being used.
......
......@@ -19,16 +19,11 @@ mod srml_only;
mod types;
pub use self::types::{
CallError,
DefaultSrmlTypes,
};
pub use self::types::DefaultSrmlTypes;
#[cfg(not(feature = "test-env"))]
pub use self::srml_only::{
sys,
CallAbi,
CallBuilder,
SrmlEnv,
SrmlEnvStorage,
};
......@@ -18,6 +18,7 @@ use crate::{
env::{
srml::sys,
CallError,
CreateError,
Env,
EnvStorage,
EnvTypes,
......@@ -216,4 +217,32 @@ where
}
U::decode(&mut &read_scratch_buffer()[..]).map_err(|_| CallError)
}
fn create(
code_hash: <Self as EnvTypes>::Hash,
gas_limit: u64,
value: <Self as EnvTypes>::Balance,
input_data: &[u8],
) -> Result<<Self as EnvTypes>::AccountId, CreateError> {
let result = {
let code_hash = code_hash.encode();
let value = value.encode();
unsafe {
sys::ext_create(
code_hash.as_ptr() as u32,
code_hash.len() as u32,
gas_limit,
value.as_ptr() as u32,
value.len() as u32,
input_data.as_ptr() as u32,
input_data.len() as u32,
)
}
};
if result != 0 {
return Err(CreateError)
}
<Self as EnvTypes>::AccountId::decode(&mut &read_scratch_buffer()[..])
.map_err(|_| CreateError)
}
}
......@@ -14,17 +14,10 @@
// You should have received a copy of the GNU General Public License
// along with ink!. If not, see <http://www.gnu.org/licenses/>.
mod calls;
mod impls;
pub mod sys;
pub use self::{
calls::{
CallAbi,
CallBuilder,
},
impls::{
SrmlEnv,
SrmlEnvStorage,
},
pub use self::impls::{
SrmlEnv,
SrmlEnvStorage,
};
......@@ -20,7 +20,6 @@
extern "C" {
/// Creates a new smart contract account.
#[allow(unused)]
pub fn ext_create(
init_code_ptr: u32,
init_code_len: u32,
......
......@@ -30,16 +30,7 @@ use scale::{
#[cfg(feature = "ink-generate-abi")]
use type_metadata::Metadata;
/// Errors encountered by calling a remote contract.
///
/// # Note
///
/// This is currently just a placeholder for potential future error codes.
#[derive(Debug, Copy, Clone)]
pub struct CallError;
/// The SRML fundamental types.
#[allow(unused)]
#[cfg_attr(feature = "test-env", derive(Debug, Clone, PartialEq, Eq))]
pub enum DefaultSrmlTypes {}
......@@ -102,6 +93,12 @@ pub type Balance = u128;
#[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
pub struct Hash([u8; 32]);
impl Default for Hash {
fn default() -> Self {
Hash(Default::default())
}
}
impl From<[u8; 32]> for Hash {
fn from(hash: [u8; 32]) -> Hash {
Hash(hash)
......
......@@ -43,6 +43,42 @@ pub struct EventData {
data: Vec<u8>,
}
/// Raw recorded data of smart contract creates and instantiations.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RawCreateData {
code_hash: Vec<u8>,
gas_limit: u64,
value: Vec<u8>,
input_data: Vec<u8>,
}
/// Decoded recorded data of smart contract creates and instantiations.
pub struct CreateData<E>
where
E: EnvTypes,
{
pub code_hash: E::Hash,
pub gas_limit: u64,
pub value: E::Balance,
pub input_data: Vec<u8>,
}
impl<E> From<RawCreateData> for CreateData<E>
where
E: EnvTypes,
{
fn from(raw: RawCreateData) -> Self {
Self {
code_hash: Decode::decode(&mut &raw.code_hash[..])
.expect("encountered invalid encoded code hash"),
gas_limit: raw.gas_limit,
value: Decode::decode(&mut &raw.value[..])
.expect("encountered invalid encoded value"),
input_data: raw.input_data,
}
}
}
impl EventData {
/// Returns the uninterpreted bytes of the emitted event.
fn data_as_bytes(&self) -> &[u8] {
......@@ -201,6 +237,10 @@ pub struct TestEnvData {
call_return: Vec<u8>,
/// Returned data.
return_data: Vec<u8>,
/// Recorded smart contract instantiations.
creates: Vec<RawCreateData>,
/// The address of the next instantiated smart contract.
next_create_address: Vec<u8>,
}
impl Default for TestEnvData {
......@@ -224,6 +264,8 @@ impl Default for TestEnvData {
calls: Vec::new(),
call_return: Vec::new(),
return_data: Vec::new(),
creates: Vec::new(),
next_create_address: Vec::new(),
}
}
}
......@@ -246,6 +288,8 @@ impl TestEnvData {
self.calls.clear();
self.call_return.clear();
self.return_data.clear();
self.creates.clear();
self.next_create_address.clear();
}
/// Increments the total number of reads from the storage.
......@@ -364,6 +408,33 @@ impl TestEnvData {
pub fn returned_data(&self) -> &[u8] {
&self.return_data
}
/// Records a new smart contract instantiation.
pub fn add_create(
&mut self,
code_hash: &[u8],
gas_limit: u64,
value: &[u8],
input_data: &[u8],
) {
let new_create = RawCreateData {
code_hash: code_hash.to_vec(),
gas_limit,
value: value.to_vec(),
input_data: input_data.to_vec(),
};
self.creates.push(new_create);
}
/// Returns an iterator over all recorded smart contract instantiations.
pub fn creates(&self) -> impl DoubleEndedIterator<Item = &RawCreateData> {
self.creates.iter()
}
/// Sets the address of the next instantiated smart contract.
pub fn set_next_create_address(&mut self, account_id: &[u8]) {
self.next_create_address = account_id.to_vec();
}
}
impl TestEnvData {
......@@ -454,6 +525,17 @@ impl TestEnvData {
self.add_call(callee, gas, value, input_data);
self.call_return.clone()
}
pub fn create(
&mut self,
code_hash: &[u8],
gas_limit: u64,
value: &[u8],
input_data: &[u8],
) -> Vec<u8> {
self.add_create(code_hash, gas_limit, value, input_data);
self.next_create_address.clone()
}
}
thread_local! {
......@@ -517,6 +599,15 @@ where
.with(|test_env| test_env.borrow_mut().set_return_data(expected_return_data))
}
/// Sets the address of the next instantiated smart contract.
pub fn set_next_create_address(account_id: T::AccountId) {
TEST_ENV_DATA.with(|test_env| {
test_env
.borrow_mut()
.set_next_create_address(&account_id.encode())
})
}
impl_env_setters_for_test_env!(
(set_address, address, T::AccountId),
(set_balance, balance, T::Balance),
......@@ -559,6 +650,19 @@ where
})
}
/// Returns an iterator over all recorded smart contract instantiations.
pub fn creates() -> impl DoubleEndedIterator<Item = CreateData<T>> {
TEST_ENV_DATA.with(|test_env| {
test_env
.borrow()
.creates()
.cloned()
.map(Into::into)
.collect::<Vec<_>>()
.into_iter()
})
}
/// Returns an iterator over all dispatched calls.
pub fn dispatched_calls() -> impl DoubleEndedIterator<Item = T::Call> {
TEST_ENV_DATA.with(|test_env| {
......@@ -653,12 +757,30 @@ where
let callee = &(callee.encode())[..];
let value = &(value.encode())[..];
TEST_ENV_DATA.with(|test_env| {
U::decode(
Decode::decode(
&mut &(test_env.borrow_mut().call(callee, gas, value, input_data))[..],
)
.map_err(|_| CallError)
})
}
fn create(
code_hash: T::Hash,
gas_limit: u64,
value: T::Balance,
input_data: &[u8],
) -> Result<T::AccountId, CreateError> {
let code_hash = &(code_hash.encode())[..];
let value = &(value.encode())[..];
TEST_ENV_DATA.with(|test_env| {
Decode::decode(
&mut &(test_env
.borrow_mut()
.create(code_hash, gas_limit, value, input_data))[..],
)
.map_err(|_| CreateError)
})
}
}
pub enum TestEnvStorage {}
......
......@@ -15,7 +15,6 @@
// along with ink!. If not, see <http://www.gnu.org/licenses/>.
use crate::{
env::CallError,
memory::vec::Vec,
storage::Key,
};
......@@ -24,6 +23,22 @@ use scale::{
Decode,
};
/// Error encountered by calling a remote contract.
///
/// # Note
///
/// This is currently just a placeholder for potential future error codes.
#[derive(Debug, Copy, Clone)]
pub struct CallError;
/// Error encountered upon creating and instantiation a new smart contract.
///
/// # Note
///
/// This is currently just a placeholder for potential future error codes.
#[derive(Debug, Copy, Clone)]
pub struct CreateError;
#[cfg(not(feature = "test-env"))]
/// The environmental types usable by contracts defined with ink!.
pub trait EnvTypes {
......@@ -156,4 +171,12 @@ pub trait Env: EnvTypes {
value: <Self as EnvTypes>::Balance,
input_data: &[u8],
) -> Result<T, CallError>;
/// Creates and instantiates a new smart contract.
fn create(
code_hash: <Self as EnvTypes>::Hash,
gas_limit: u64,