Skip to content

Cross-contract unit test PoV #1358

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions crates/env/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,12 @@ where
Args: scale::Encode,
R: scale::Decode,
{
let call = <EnvInstance as OnInstance>::on_instance(|instance| {
TypedEnvBackend::invoke_contract_begin::<E, Args, R>(instance, params)
})?;
call();
<EnvInstance as OnInstance>::on_instance(|instance| {
TypedEnvBackend::invoke_contract::<E, Args, R>(instance, params)
TypedEnvBackend::invoke_contract_end::<E, R>(instance)
})
}

Expand Down Expand Up @@ -303,8 +307,12 @@ where
Args: scale::Encode,
Salt: AsRef<[u8]>,
{
let deploy = <EnvInstance as OnInstance>::on_instance(|instance| {
TypedEnvBackend::instantiate_contract_begin::<E, Args, Salt, C>(instance, params)
})?;
deploy();
<EnvInstance as OnInstance>::on_instance(|instance| {
TypedEnvBackend::instantiate_contract::<E, Args, Salt, C>(instance, params)
TypedEnvBackend::instantiate_contract_end::<E>(instance)
})
}

Expand Down Expand Up @@ -387,7 +395,7 @@ where
/// # Note
///
/// This function stops the execution of the contract immediately.
pub fn return_value<R>(return_flags: ReturnFlags, return_value: &R) -> !
pub fn return_value<R>(return_flags: ReturnFlags, return_value: &R)
where
R: scale::Encode,
{
Expand Down
40 changes: 34 additions & 6 deletions crates/env/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use crate::{
use ink_primitives::Key;

/// The flags to indicate further information about the end of a contract execution.
#[derive(Default)]
#[derive(Default, Debug, Clone)]
pub struct ReturnFlags {
value: u32,
}
Expand All @@ -46,6 +46,11 @@ impl ReturnFlags {
self
}

/// Returns if the flag is set to reverted.
pub fn reverted(&self) -> bool {
self.value > 0
}

/// Returns the underlying `u32` representation.
#[cfg(not(feature = "std"))]
pub(crate) fn into_u32(self) -> u32 {
Expand Down Expand Up @@ -219,7 +224,7 @@ pub trait EnvBackend {
///
/// The `flags` parameter can be used to revert the state changes of the
/// entire execution if necessary.
fn return_value<R>(&mut self, flags: ReturnFlags, return_value: &R) -> !
fn return_value<R>(&mut self, flags: ReturnFlags, return_value: &R)
where
R: scale::Encode;

Expand Down Expand Up @@ -386,15 +391,27 @@ pub trait TypedEnvBackend: EnvBackend {
/// # Note
///
/// For more details visit: [`invoke_contract`][`crate::invoke_contract`]
fn invoke_contract<E, Args, R>(
fn invoke_contract_begin<E, Args, R>(
&mut self,
call_data: &CallParams<E, Call<E>, Args, R>,
) -> Result<R>
) -> Result<fn()>
where
E: Environment,
Args: scale::Encode,
R: scale::Decode;

/// Invokes a contract message and returns its result.
///
/// # Note
///
/// For more details visit: [`invoke_contract`][`crate::invoke_contract`]
fn invoke_contract_end<E, R>(
&mut self,
) -> Result<R>
where
E: Environment,
R: scale::Decode;

/// Invokes a contract message via delegate call and returns its result.
///
/// # Note
Expand All @@ -414,15 +431,26 @@ pub trait TypedEnvBackend: EnvBackend {
/// # Note
///
/// For more details visit: [`instantiate_contract`][`crate::instantiate_contract`]
fn instantiate_contract<E, Args, Salt, C>(
fn instantiate_contract_begin<E, Args, Salt, C>(
&mut self,
params: &CreateParams<E, Args, Salt, C>,
) -> Result<E::AccountId>
) -> Result<fn()>
where
E: Environment,
Args: scale::Encode,
Salt: AsRef<[u8]>;

/// Instantiates another contract.
///
/// # Note
///
/// For more details visit: [`instantiate_contract`][`crate::instantiate_contract`]
fn instantiate_contract_end<E>(
&mut self,
) -> Result<E::AccountId>
where
E: Environment;

/// Terminates a smart contract.
///
/// # Note
Expand Down
142 changes: 118 additions & 24 deletions crates/env/src/engine/off_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,14 +218,18 @@ impl EnvBackend for EnvInstance {
where
T: scale::Decode,
{
unimplemented!("the off-chain env does not implement `seal_input`")
scale::Decode::decode(&mut &self.stack.peek().input[..])
.map_err(|err| Error::Decode(err))
}

fn return_value<R>(&mut self, _flags: ReturnFlags, _return_value: &R) -> !
fn return_value<R>(&mut self, flags: ReturnFlags, return_value: &R)
where
R: scale::Encode,
{
unimplemented!("the off-chain env does not implement `seal_return_value`")
if flags.reverted() {
panic!("reverted.");
}
self.stack.set_return_value(flags, return_value.encode());
}

fn debug_message(&mut self, message: &str) {
Expand Down Expand Up @@ -337,8 +341,12 @@ impl EnvBackend for EnvInstance {
Ok(decoded)
}

fn set_code_hash(&mut self, _code_hash: &[u8]) -> Result<()> {
unimplemented!("off-chain environment does not support `set_code_hash`")
fn set_code_hash(&mut self, code_hash: &[u8]) -> Result<()> {
let me = self.stack.peek().callee;
let hash: crate::Hash = scale::Decode::decode(&mut &code_hash[..])
.map_err(|err| Error::Decode(err))?;
self.contracts.update_code(me, hash);
Ok(())
}
}

Expand Down Expand Up @@ -410,23 +418,55 @@ impl TypedEnvBackend for EnvInstance {
self.engine.deposit_event(&enc_topics[..], enc_data);
}

fn invoke_contract<E, Args, R>(
fn invoke_contract_begin<E, Args, R>(
&mut self,
params: &CallParams<E, Call<E>, Args, R>,
) -> Result<R>
) -> Result<fn()>
where
E: Environment,
Args: scale::Encode,
R: scale::Decode,
{
let _gas_limit = params.gas_limit();
let _callee = params.callee();
let callee = params.callee();
let _call_flags = params.call_flags().into_u32();
let _transferred_value = params.transferred_value();
let _input = params.exec_input();
unimplemented!("off-chain environment does not support contract invocation")
let transferred_value = params.transferred_value();
if *transferred_value != num_traits::Zero::zero() {
unimplemented!("off-chain environment does not support value trnsfer when calling contracts")
}
let input = params.exec_input();
let input = scale::Encode::encode(&input);
let callee = from_env::account_id::<E>(callee);

self.push_frame(&callee, input.clone());
let (_deploy, call) = self.contracts.entrypoints(&callee)
.ok_or(Error::NotCallable)?;
// TODO: snapshot the db
// TODO: unwind panic?
Ok(call)
}

fn invoke_contract_end<E, R>(&mut self) -> Result<R>
where
E: Environment,
R: scale::Decode,
{
// Read return value & process revert
let frame = self.pop_frame().expect("frame exists; qed.");
let data = if let Some((flags, data)) = frame.return_value {
if flags.reverted() {
// TODO: revert the db snapshot
return Err(Error::CalleeReverted)
}
data
} else {
Default::default()
};
scale::Decode::decode(&mut &data[..])
.map_err(|err| Error::Decode(err))
}


fn invoke_contract_delegate<E, Args, R>(
&mut self,
params: &CallParams<E, DelegateCall<E>, Args, R>,
Expand All @@ -442,21 +482,44 @@ impl TypedEnvBackend for EnvInstance {
)
}

fn instantiate_contract<E, Args, Salt, C>(
fn instantiate_contract_begin<E, Args, Salt, C>(
&mut self,
params: &CreateParams<E, Args, Salt, C>,
) -> Result<E::AccountId>
) -> Result<fn()>
where
E: Environment,
Args: scale::Encode,
Salt: AsRef<[u8]>,
{
let _code_hash = params.code_hash();
let code_hash = params.code_hash();
let _gas_limit = params.gas_limit();
let _endowment = params.endowment();
let _input = params.exec_input();
let _salt_bytes = params.salt_bytes();
unimplemented!("off-chain environment does not support contract instantiation")
let endowment = params.endowment(); // TODO: not considered yet
let input = params.exec_input();
let _salt_bytes = params.salt_bytes(); // TODO: not considered yet

if *endowment != num_traits::Zero::zero() {
unimplemented!("off-chain environment does not support value trnsfer when instantiating contracts")
}

let hash = from_env::hash::<E>(code_hash);
let input = scale::Encode::encode(&input);

// gen address (hash [n])
let account = self.contracts.next_address_of(&hash);
self.contracts.register_contract(hash, account.clone());

self.push_frame(&account, input.clone());
let (deploy, _call) = self.contracts.entrypoints(&account)
.ok_or(Error::NotCallable)?;
Ok(deploy)
}

fn instantiate_contract_end<E>(&mut self) -> Result<E::AccountId>
where
E: Environment,
{
let frame = self.pop_frame().expect("frame must exist; qed.");
Ok(to_env::account_id::<E>(&frame.callee))
}

fn terminate_contract<E>(&mut self, beneficiary: E::AccountId) -> !
Expand Down Expand Up @@ -495,31 +558,62 @@ impl TypedEnvBackend for EnvInstance {
scale::Decode::decode(&mut &output[..]).map_err(Into::into)
}

fn is_contract<E>(&mut self, _account: &E::AccountId) -> bool
fn is_contract<E>(&mut self, account: &E::AccountId) -> bool
where
E: Environment,
{
unimplemented!("off-chain environment does not support contract instantiation")
self.contracts.is_contract(&from_env::account_id::<E>(account))
}

fn caller_is_origin<E>(&mut self) -> bool
where
E: Environment,
{
unimplemented!("off-chain environment does not support cross-contract calls")
let origin = self.stack.origin();
let ctx = self.stack.peek();
assert!(ctx.level > 0, "should never reach when there's no running contract");
ctx.caller.expect("contract has caller; qed.") == origin
}

fn code_hash<E>(&mut self, _account: &E::AccountId) -> Result<E::Hash>
fn code_hash<E>(&mut self, account: &E::AccountId) -> Result<E::Hash>
where
E: Environment,
{
unimplemented!("off-chain environment does not support `code_hash`")
self
.contracts
.code_hash(&from_env::account_id::<E>(account))
.map(|h| to_env::hash::<E>(&h))
.ok_or(Error::CodeNotFound)
}

fn own_code_hash<E>(&mut self) -> Result<E::Hash>
where
E: Environment,
{
unimplemented!("off-chain environment does not support `own_code_hash`")
let me = self.stack.peek().callee;
let hash = self.contracts.code_hash(&me)
.expect("contract must has code hash");
Ok(to_env::hash::<E>(&hash))
}
}

mod from_env {
pub fn account_id<E: crate::Environment>(account: &E::AccountId) -> crate::AccountId {
crate::AccountId::try_from(account.as_ref()).unwrap()
}
pub fn hash<E: crate::Environment>(hash: &E::Hash) -> crate::Hash {
crate::Hash::try_from(hash.as_ref()).unwrap()
}
}

mod to_env {
use scale::{Encode, Decode};
pub fn account_id<E: crate::Environment>(account: &crate::AccountId) -> E::AccountId {
let raw = account.encode();
Decode::decode(&mut &raw[..]).unwrap()
}
pub fn hash<E: crate::Environment>(hash: &crate::Hash) -> E::Hash {
let raw = hash.encode();
Decode::decode(&mut &raw[..]).unwrap()
}
}
Loading