Commit 55fb1447 authored by Hero Bird's avatar Hero Bird Committed by GitHub

Sync with SRML contracts for call/create status codes (#170)

* [*] quick syncing of SRML contract call infrastructure

* [model] some minor cleanups

* [lang] adjust lang tests

* [model] change internal type of RetCode to u8

* [*] apply cargo fmt

* [model] improve docs of RetCode
parent f5249f37
......@@ -58,20 +58,18 @@ pub unsafe fn load(key: Key) -> Option<Vec<u8>> {
ContractEnvStorage::load(key)
}
/// Returns the current smart contract exection back to the caller
/// and return the given encoded value.
/// Returns the given data back to the caller.
///
/// # Safety
/// # Note
///
/// 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) -> !
/// This operation must be the last operation performed by a called
/// smart contract before it returns the execution back to its caller.
pub fn return_data<T, E>(data: T)
where
T: Encode,
E: Env,
{
E::r#return(&value.encode()[..])
E::return_data(&data.encode()[..])
}
/// Dispatches a Call into the runtime, for invoking other substrate
......
......@@ -40,6 +40,13 @@ fn read_scratch_buffer() -> Vec<u8> {
value
}
/// Writes the contents of `data` into the scratch buffer.
fn write_scratch_buffer(data: &[u8]) {
unsafe {
sys::ext_scratch_write(data.as_ptr() as u32, data.len() as u32);
}
}
/// The SRML contract environment storage
pub enum SrmlEnvStorage {}
......@@ -131,8 +138,8 @@ where
)
);
unsafe fn r#return(data: &[u8]) -> ! {
sys::ext_return(data.as_ptr() as u32, data.len() as u32);
fn return_data(data: &[u8]) {
write_scratch_buffer(data)
}
fn println(content: &str) {
......
......@@ -89,9 +89,8 @@ extern "C" {
/// buffer starting at `dst_ptr` with length `len` on the smart contract site.
pub fn ext_scratch_read(dst_ptr: u32, offset: u32, len: u32);
/// Immediately returns contract execution to the caller
/// with the provided data at `data_ptr`.
pub fn ext_return(data_ptr: u32, data_len: u32) -> !;
/// Writes the contents of the given data buffer into the scratch buffer on the host side.
pub fn ext_scratch_write(src_ptr: u32, len: u32);
/// Stores the address of the current contract into the scratch buffer.
pub fn ext_address();
......
......@@ -159,12 +159,6 @@ pub struct TestEnvData {
///
/// The current current block number can be adjusted by `TestEnvData::set_block_number`.
block_number: Vec<u8>,
/// The expected return data of the next contract invocation.
///
/// # Note
///
/// This can be set by `TestEnvData::set_expected_return`.
expected_return: Vec<u8>,
/// The total number of reads from the storage.
total_reads: Cell<u64>,
/// The total number of writes to the storage.
......@@ -179,6 +173,8 @@ pub struct TestEnvData {
gas_left: Vec<u8>,
/// The total transferred value.
value_transferred: Vec<u8>,
/// Returned data.
return_data: Vec<u8>,
}
impl Default for TestEnvData {
......@@ -192,7 +188,6 @@ impl Default for TestEnvData {
random_seed: Vec::new(),
now: Vec::new(),
block_number: Vec::new(),
expected_return: Vec::new(),
total_reads: Cell::new(0),
total_writes: 0,
events: Vec::new(),
......@@ -200,6 +195,7 @@ impl Default for TestEnvData {
gas_left: Vec::new(),
value_transferred: Vec::new(),
dispatched_calls: Vec::new(),
return_data: Vec::new(),
}
}
}
......@@ -215,11 +211,11 @@ impl TestEnvData {
self.random_seed.clear();
self.now.clear();
self.block_number.clear();
self.expected_return.clear();
self.total_reads.set(0);
self.total_writes = 0;
self.events.clear();
self.dispatched_calls.clear();
self.return_data.clear();
}
/// Increments the total number of reads from the storage.
......@@ -252,11 +248,6 @@ impl TestEnvData {
self.storage.get(&key).map(|loaded| loaded.writes())
}
/// Sets the expected return data for the next contract invocation.
pub fn set_expected_return(&mut self, expected_bytes: &[u8]) {
self.expected_return = expected_bytes.to_vec();
}
/// Sets the contract address for the next contract invocation.
pub fn set_address(&mut self, new_address: Vec<u8>) {
self.address = new_address;
......@@ -317,24 +308,14 @@ impl TestEnvData {
pub fn dispatched_calls(&self) -> impl DoubleEndedIterator<Item = &[u8]> {
self.dispatched_calls.iter().map(Vec::as_slice)
}
/// Returns the latest returned data.
pub fn returned_data(&self) -> &[u8] {
&self.return_data
}
}
impl TestEnvData {
/// The return code for successful contract invocations.
///
/// # Note
///
/// A contract invocation is successful if it returned the same data
/// as was expected upon invocation.
const SUCCESS: i32 = 0;
/// The return code for unsuccessful contract invocations.
///
/// # Note
///
/// A contract invocation is unsuccessful if it did not return the
/// same data as was expected upon invocation.
const FAILURE: i32 = -1;
pub fn address(&self) -> Vec<u8> {
self.address.clone()
}
......@@ -396,14 +377,8 @@ impl TestEnvData {
self.value_transferred.clone()
}
pub fn r#return(&self, data: &[u8]) -> ! {
let expected_bytes = self.expected_return.clone();
let exit_code = if expected_bytes == data {
Self::SUCCESS
} else {
Self::FAILURE
};
std::process::exit(exit_code)
pub fn return_data(&mut self, data: &[u8]) {
self.return_data = data.to_vec();
}
pub fn println(&self, content: &str) {
......@@ -463,10 +438,9 @@ where
TEST_ENV_DATA.with(|test_env| test_env.borrow().writes_for(key))
}
/// Sets the expected return data for the next contract invocation.
pub fn set_expected_return(expected_bytes: &[u8]) {
TEST_ENV_DATA
.with(|test_env| test_env.borrow_mut().set_expected_return(expected_bytes))
/// Returns the latest returned data.
pub fn returned_data() -> Vec<u8> {
TEST_ENV_DATA.with(|test_env| test_env.borrow().returned_data().to_vec())
}
/// Sets the input data for the next contract invocation.
......@@ -549,8 +523,8 @@ where
(value_transferred, T::Balance)
);
unsafe fn r#return(data: &[u8]) -> ! {
TEST_ENV_DATA.with(|test_env| test_env.borrow().r#return(data))
fn return_data(data: &[u8]) {
TEST_ENV_DATA.with(|test_env| test_env.borrow_mut().return_data(data))
}
fn println(content: &str) {
......
......@@ -122,7 +122,7 @@ pub trait Env: EnvTypes {
/// The external callers rely on the correct type of the encoded
/// returned value. This API is unsafe because it does not provide
/// guarantees on its own to always encode the expected type.
unsafe fn r#return(value: &[u8]) -> !;
fn return_data(data: &[u8]);
/// Prints the given content to Substrate output.
///
......
......@@ -209,14 +209,14 @@ fn codegen_for_entry_points(tokens: &mut TokenStream2, contract: &hir::Contract)
tokens.extend(quote! {
#[cfg(not(test))]
#[no_mangle]
fn deploy() {
#state_name::instantiate().deploy()
fn deploy() -> u32 {
#state_name::instantiate().deploy().to_u32()
}
#[cfg(not(test))]
#[no_mangle]
fn call() {
#state_name::instantiate().dispatch()
fn call() -> u32 {
#state_name::instantiate().dispatch().to_u32()
}
})
}
......
......@@ -166,8 +166,8 @@ fn contract_compiles() {
}
}
#[cfg(not(test))] #[no_mangle] fn deploy() { CallCounter::instantiate().deploy() }
#[cfg(not(test))] #[no_mangle] fn call() { CallCounter::instantiate().dispatch() }
#[cfg(not(test))] #[no_mangle] fn deploy() -> u32 { CallCounter::instantiate().deploy().to_u32() }
#[cfg(not(test))] #[no_mangle] fn call() -> u32 { CallCounter::instantiate().dispatch().to_u32() }
mod events {
use super::*;
......
......@@ -130,8 +130,8 @@ fn contract_compiles() {
}
}
#[cfg(not(test))] #[no_mangle] fn deploy() { Flipper::instantiate().deploy() }
#[cfg(not(test))] #[no_mangle] fn call() { Flipper::instantiate().dispatch() }
#[cfg(not(test))] #[no_mangle] fn deploy() -> u32 { Flipper::instantiate().deploy().to_u32() }
#[cfg(not(test))] #[no_mangle] fn call() -> u32 { Flipper::instantiate().dispatch().to_u32() }
#[cfg(test)]
mod test {
......
......@@ -148,8 +148,8 @@ fn contract_compiles() {
}
}
#[cfg(not(test))] #[no_mangle] fn deploy() { Incrementer::instantiate().deploy() }
#[cfg(not(test))] #[no_mangle] fn call() { Incrementer::instantiate().dispatch() }
#[cfg(not(test))] #[no_mangle] fn deploy() -> u32 { Incrementer::instantiate().deploy().to_u32() }
#[cfg(not(test))] #[no_mangle] fn call() -> u32 { Incrementer::instantiate().dispatch().to_u32() }
#[cfg(test)]
mod test {
......
......@@ -96,8 +96,8 @@ fn contract_compiles() {
}
}
#[cfg(not(test))] #[no_mangle] fn deploy() { Noop::instantiate().deploy() }
#[cfg(not(test))] #[no_mangle] fn call() { Noop::instantiate().dispatch() }
#[cfg(not(test))] #[no_mangle] fn deploy() -> u32 { Noop::instantiate().deploy().to_u32() }
#[cfg(not(test))] #[no_mangle] fn call() -> u32 { Noop::instantiate().dispatch().to_u32() }
#[cfg(test)]
mod test {
......
......@@ -286,13 +286,47 @@ where
}
}
/// A return status code for `deploy` and `dispatch` calls back to the SRML contracts module.
///
/// # Note
///
/// The `call` and `create` SRML contracts interfacing
/// instructions both return a `u32`, however, only the least-significant
/// 8 bits can be non-zero.
/// For a start we only allow `0` and `255` as return codes.
///
/// Zero (`0`) represents a successful execution, (`255`) means invalid
/// execution (e.g. trap) and any value in between represents a non-
/// specified invalid execution.
///
/// Other error codes are subject to future proposals.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct RetCode(u8);
impl RetCode {
/// Indicates a successful execution.
pub fn success() -> Self {
Self(0)
}
/// Indicates a failure upon execution.
pub fn failure() -> Self {
Self(255)
}
/// Returns the internal `u32` value.
pub fn to_u32(self) -> u32 {
self.0 as u32
}
}
/// A simple interface to work with contracts.
pub trait Contract {
/// Deploys the contract state.
///
/// Should be performed exactly once during contract lifetime.
/// Consumes the contract since nothing should be done afterwards.
fn deploy(self);
fn deploy(self) -> RetCode;
/// Dispatches the call input to a pre defined
/// contract message and runs its handler.
......@@ -306,7 +340,7 @@ pub trait Contract {
/// The call input is invalid if there was no matching
/// function selector found or if the data for a given
/// selected function was not decodable.
fn dispatch(self);
fn dispatch(self) -> RetCode;
}
/// An interface that allows for simple testing of contracts.
......@@ -377,21 +411,24 @@ where
///
/// Accessing uninitialized contract state can end in trapping execution
/// or in the worst case in undefined behaviour.
fn deploy(self) {
fn deploy(self) -> RetCode {
// Deploys the contract state.
//
// Should be performed exactly once during contract lifetime.
// Consumes the contract since nothing should be done afterwards.
let input = Env::input();
let mut this = self;
this.deploy_with(input.as_slice());
if let Err(err) = this.deploy_with(input.as_slice()) {
return err
}
core::mem::forget(this.env);
RetCode::success()
}
/// Dispatches the input buffer and calls the associated message.
///
/// Returns the result to the caller if there is any.
fn dispatch(self) {
fn dispatch(self) -> RetCode {
// Dispatches the given input to a pre defined
// contract message and runs its handler.
//
......@@ -403,8 +440,11 @@ where
let input = Env::input();
let call_data = CallData::decode(&mut &input[..]).unwrap();
let mut this = self;
this.call_with_and_return(call_data);
if let Err(err) = this.call_with_and_return(call_data) {
return err
}
core::mem::forget(this.env);
RetCode::success()
}
}
......@@ -425,25 +465,28 @@ where
///
/// Accessing uninitialized contract state can end in trapping execution
/// or in the worst case in undefined behaviour.
fn deploy_with(&mut self, input: &[u8]) {
fn deploy_with(&mut self, input: &[u8]) -> Result<(), RetCode> {
// Deploys the contract state.
//
// Should be performed exactly once during contract lifetime.
// Consumes the contract since nothing should be done afterwards.
use ink_core::storage::alloc::Initialize as _;
self.env.initialize(());
let deploy_params = DeployArgs::decode(&mut &input[..]).unwrap();
let deploy_params =
DeployArgs::decode(&mut &input[..]).map_err(|_err| RetCode::failure())?;
(self.deployer.deploy_fn)(&mut self.env, deploy_params);
self.env.state.flush()
self.env.state.flush();
Ok(())
}
/// Calls the message encoded by the given call data
/// and returns the resulting value back to the caller.
fn call_with_and_return(&mut self, call_data: CallData) {
let encoded_result = self.call_with(call_data);
if !encoded_result.is_empty() {
unsafe { self.env.r#return(encoded_result) }
fn call_with_and_return(&mut self, call_data: CallData) -> Result<(), RetCode> {
let result = self.call_with(call_data)?;
if !result.is_empty() {
self.env.return_data(result)
}
Ok(())
}
/// Calls the message encoded by the given call data.
......@@ -454,10 +497,10 @@ where
/// message that is encoded by the given call data.
/// - If the encoded input arguments for the message do not
/// match the expected format.
fn call_with(&mut self, call_data: CallData) -> Vec<u8> {
fn call_with(&mut self, call_data: CallData) -> Result<Vec<u8>, RetCode> {
match self.handlers.handle_call(&mut self.env, call_data) {
Ok(encoded_result) => encoded_result,
Err(err) => panic!(err.description()),
Ok(encoded_result) => Ok(encoded_result),
Err(_err) => Err(RetCode::failure()),
}
}
}
......@@ -474,6 +517,7 @@ where
fn deploy(&mut self, input: Self::DeployArgs) {
self.deploy_with(&input.encode()[..])
.expect("`deploy` failed to execute properly")
}
fn call<Msg>(&mut self, input: <Msg as Message>::Input) -> <Msg as Message>::Output
......@@ -482,7 +526,9 @@ where
<Msg as Message>::Input: scale::Encode,
<Msg as Message>::Output: scale::Decode,
{
let encoded_result = self.call_with(CallData::from_msg::<Msg>(input));
let encoded_result = self
.call_with(CallData::from_msg::<Msg>(input))
.expect("`call` failed to execute properly");
use scale::Decode;
<Msg as Message>::Output::decode(&mut &encoded_result[..])
.expect("`call_with` only encodes the correct types")
......
......@@ -146,12 +146,16 @@ impl<T: Env> EnvHandler<T> {
T::caller()
}
/// Returns from the current smart contract execution with the given value.
pub unsafe fn r#return<V>(&self, val: V) -> !
/// Returns the given data back to the caller.
///
/// # Note
///
/// This must be the last operation executed before returning execution back to the caller.
pub fn return_data<V>(&self, data: V)
where
V: scale::Encode,
{
env::r#return::<V, T>(val)
env::return_data::<V, T>(data)
}
/// Prints the given content.
......
Markdown is supported
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