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: ...@@ -7,7 +7,7 @@ rust:
# - stable # - stable
# - beta # - beta
- nightly - nightly
- nightly-2019-08-13 - nightly-2019-08-30
matrix: matrix:
allow_failures: allow_failures:
......
...@@ -39,6 +39,15 @@ ink! is an [eDSL](https://wiki.haskell.org/Embedded_domain_specific_language) to ...@@ -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. 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 ### Testing
Off-chain testing is done by `cargo test`. Off-chain testing is done by `cargo test`.
...@@ -79,6 +88,20 @@ contract! { ...@@ -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 ## Documentation
......
...@@ -74,6 +74,7 @@ fn generate_fields_layout<'a>( ...@@ -74,6 +74,7 @@ fn generate_fields_layout<'a>(
_ink_abi::LayoutField::new(stringify!(#ident), self.#ident.layout()) _ink_abi::LayoutField::new(stringify!(#ident), self.#ident.layout())
} }
} else { } else {
let n = proc_macro2::Literal::usize_unsuffixed(n);
quote! { quote! {
_ink_abi::LayoutField::new(stringify!(#n), self.#n.layout()) _ink_abi::LayoutField::new(stringify!(#n), self.#n.layout())
} }
......
...@@ -45,6 +45,7 @@ ink-generate-abi = [ ...@@ -45,6 +45,7 @@ ink-generate-abi = [
"ink_core/ink-generate-abi", "ink_core/ink-generate-abi",
"ink_lang/ink-generate-abi", "ink_lang/ink-generate-abi",
] ]
ink-as-dependency = []
[profile.release] [profile.release]
panic = "abort" panic = "abort"
......
...@@ -22,6 +22,7 @@ use crate::{ ...@@ -22,6 +22,7 @@ use crate::{
EnvTypes, EnvTypes,
}, },
CallError, CallError,
CreateError,
EnvStorage as _, EnvStorage as _,
}, },
memory::vec::Vec, memory::vec::Vec,
...@@ -121,3 +122,18 @@ where ...@@ -121,3 +122,18 @@ where
{ {
T::call_evaluate(callee, gas, value, input_data) 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 @@ ...@@ -14,12 +14,12 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ink!. If not, see <http://www.gnu.org/licenses/>. // 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] #[rustfmt::skip]
use crate::{ use crate::{
env::{ env::{
self, self,
CallError, CallError,
CreateError,
Env, Env,
EnvTypes, EnvTypes,
}, },
...@@ -33,7 +33,7 @@ use scale::Decode; ...@@ -33,7 +33,7 @@ use scale::Decode;
/// Consists of the input data to a call. /// Consists of the input data to a call.
/// The first four bytes are the function selector and the rest are SCALE encoded inputs. /// 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. /// Already encoded function selector and inputs.
raw: Vec<u8>, raw: Vec<u8>,
} }
...@@ -69,6 +69,40 @@ impl CallAbi { ...@@ -69,6 +69,40 @@ impl CallAbi {
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct ReturnType<T>(PhantomData<T>); 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. /// Builds up a call.
pub struct CallBuilder<E, R> pub struct CallBuilder<E, R>
where where
...@@ -77,7 +111,7 @@ where ...@@ -77,7 +111,7 @@ where
/// The account ID of the to-be-called smart contract. /// The account ID of the to-be-called smart contract.
account_id: E::AccountId, account_id: E::AccountId,
/// The maximum gas costs allowed for the call. /// The maximum gas costs allowed for the call.
gas_cost: u64, gas_limit: u64,
/// The transferred value for the call. /// The transferred value for the call.
value: E::Balance, value: E::Balance,
/// The expected return type. /// The expected return type.
...@@ -86,6 +120,57 @@ where ...@@ -86,6 +120,57 @@ where
raw_input: CallAbi, 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>> impl<E, R> CallBuilder<E, ReturnType<R>>
where where
E: EnvTypes, E: EnvTypes,
...@@ -95,7 +180,24 @@ where ...@@ -95,7 +180,24 @@ where
pub fn eval(account_id: E::AccountId, selector: u32) -> Self { pub fn eval(account_id: E::AccountId, selector: u32) -> Self {
Self { Self {
account_id, 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(), value: E::Balance::default(),
return_type: PhantomData, return_type: PhantomData,
raw_input: CallAbi::new(selector), raw_input: CallAbi::new(selector),
...@@ -108,9 +210,9 @@ where ...@@ -108,9 +210,9 @@ where
E: EnvTypes, E: EnvTypes,
{ {
/// Sets the maximumly allowed gas costs for the call. /// 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; let mut this = self;
this.gas_cost = gas_cost; this.gas_limit = gas_limit;
this this
} }
...@@ -132,23 +234,6 @@ where ...@@ -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>> impl<E, R> CallBuilder<E, ReturnType<R>>
where where
E: Env, E: Env,
...@@ -159,7 +244,7 @@ where ...@@ -159,7 +244,7 @@ where
pub fn fire(self) -> Result<R, CallError> { pub fn fire(self) -> Result<R, CallError> {
env::call_evaluate::<E, R>( env::call_evaluate::<E, R>(
self.account_id, self.account_id,
self.gas_cost, self.gas_limit,
self.value, self.value,
self.raw_input.to_bytes(), self.raw_input.to_bytes(),
) )
...@@ -174,7 +259,7 @@ where ...@@ -174,7 +259,7 @@ where
pub fn fire(self) -> Result<(), CallError> { pub fn fire(self) -> Result<(), CallError> {
env::call_invoke::<E>( env::call_invoke::<E>(
self.account_id, self.account_id,
self.gas_cost, self.gas_limit,
self.value, self.value,
self.raw_input.to_bytes(), self.raw_input.to_bytes(),
) )
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
//! has to be enabled. //! has to be enabled.
mod api; mod api;
mod calls;
mod srml; mod srml;
mod traits; mod traits;
...@@ -40,15 +41,18 @@ mod test_env; ...@@ -40,15 +41,18 @@ mod test_env;
pub use api::*; pub use api::*;
pub use traits::*; pub use traits::*;
pub use self::srml::{ pub use self::{
CallError, calls::{
DefaultSrmlTypes, CallBuilder,
}; CreateBuilder,
FromAccountId,
#[cfg(not(feature = "test-env"))] ReturnType,
pub use self::srml::{ },
CallAbi, srml::DefaultSrmlTypes,
CallBuilder, traits::{
CallError,
CreateError,
},
}; };
/// The storage environment implementation that is currently being used. /// The storage environment implementation that is currently being used.
......
...@@ -19,16 +19,11 @@ mod srml_only; ...@@ -19,16 +19,11 @@ mod srml_only;
mod types; mod types;
pub use self::types::{ pub use self::types::DefaultSrmlTypes;
CallError,
DefaultSrmlTypes,
};
#[cfg(not(feature = "test-env"))] #[cfg(not(feature = "test-env"))]
pub use self::srml_only::{ pub use self::srml_only::{
sys, sys,
CallAbi,
CallBuilder,
SrmlEnv, SrmlEnv,
SrmlEnvStorage, SrmlEnvStorage,
}; };
...@@ -18,6 +18,7 @@ use crate::{ ...@@ -18,6 +18,7 @@ use crate::{
env::{ env::{
srml::sys, srml::sys,
CallError, CallError,
CreateError,
Env, Env,
EnvStorage, EnvStorage,
EnvTypes, EnvTypes,
...@@ -216,4 +217,32 @@ where ...@@ -216,4 +217,32 @@ where
} }
U::decode(&mut &read_scratch_buffer()[..]).map_err(|_| CallError) 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 @@ ...@@ -14,17 +14,10 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ink!. If not, see <http://www.gnu.org/licenses/>. // along with ink!. If not, see <http://www.gnu.org/licenses/>.
mod calls;
mod impls; mod impls;
pub mod sys; pub mod sys;
pub use self::{ pub use self::impls::{
calls::{ SrmlEnv,
CallAbi, SrmlEnvStorage,
CallBuilder,
},
impls::{
SrmlEnv,
SrmlEnvStorage,
},
}; };
...@@ -20,7 +20,6 @@ ...@@ -20,7 +20,6 @@
extern "C" { extern "C" {
/// Creates a new smart contract account. /// Creates a new smart contract account.
#[allow(unused)]
pub fn ext_create( pub fn ext_create(
init_code_ptr: u32, init_code_ptr: u32,
init_code_len: u32, init_code_len: u32,
......
...@@ -30,16 +30,7 @@ use scale::{ ...@@ -30,16 +30,7 @@ use scale::{
#[cfg(feature = "ink-generate-abi")] #[cfg(feature = "ink-generate-abi")]
use type_metadata::Metadata; 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. /// The SRML fundamental types.
#[allow(unused)]
#[cfg_attr(feature = "test-env", derive(Debug, Clone, PartialEq, Eq))] #[cfg_attr(feature = "test-env", derive(Debug, Clone, PartialEq, Eq))]
pub enum DefaultSrmlTypes {} pub enum DefaultSrmlTypes {}
...@@ -102,6 +93,12 @@ pub type Balance = u128; ...@@ -102,6 +93,12 @@ pub type Balance = u128;
#[cfg_attr(feature = "ink-generate-abi", derive(Metadata))] #[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
pub struct Hash([u8; 32]); pub struct Hash([u8; 32]);
impl Default for Hash {
fn default() -> Self {
Hash(Default::default())
}
}
impl From<[u8; 32]> for Hash { impl From<[u8; 32]> for Hash {
fn from(hash: [u8; 32]) -> Hash { fn from(hash: [u8; 32]) -> Hash {
Hash(hash) Hash(hash)
......
...@@ -43,6 +43,42 @@ pub struct EventData { ...@@ -43,6 +43,42 @@ pub struct EventData {
data: Vec<u8>, 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 { impl EventData {
/// Returns the uninterpreted bytes of the emitted event. /// Returns the uninterpreted bytes of the emitted event.
fn data_as_bytes(&self) -> &[u8] { fn data_as_bytes(&self) -> &[u8] {
...@@ -201,6 +237,10 @@ pub struct TestEnvData { ...@@ -201,6 +237,10 @@ pub struct TestEnvData {
call_return: Vec<u8>, call_return: Vec<u8>,
/// Returned data. /// Returned data.
return_data: Vec<u8>, return_data: Vec<u8>,
/// Recorded smart contract instantiations.
creates: Vec<RawCreateData>,
/// The address of the next instantiated smart contract.