Skip to content
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

Backport #1379 #1401 #1418 into v3.x.x and fix tests #1439

Merged
merged 6 commits into from
Oct 20, 2022
Merged
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
49 changes: 49 additions & 0 deletions crates/env/src/engine/off_chain/test_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ where
}

/// Sets the value transferred from the caller to the callee as part of the call.
///
/// Please note that the acting accounts should be set with [`set_caller()`] and [`set_callee()`] beforehand.
pub fn set_value_transferred<T>(value: T::Balance)
where
T: Environment<Balance = u128>, // Just temporary for the MVP!
Expand All @@ -209,6 +211,44 @@ where
})
}

/// Transfers value from the caller account to the contract.
///
/// Please note that the acting accounts should be set with [`set_caller()`] and [`set_callee()`] beforehand.
pub fn transfer_in<T>(value: T::Balance)
where
T: Environment<Balance = u128>, // Just temporary for the MVP!
{
<EnvInstance as OnInstance>::on_instance(|instance| {
let caller = instance
.engine
.exec_context
.caller
.as_ref()
.expect("no caller has been set")
.as_bytes()
.to_vec();

let caller_old_balance = instance
.engine
.get_balance(caller.clone())
.unwrap_or_default();

let callee = instance.engine.get_callee();
let contract_old_balance = instance
.engine
.get_balance(callee.clone())
.unwrap_or_default();

instance
.engine
.set_balance(caller, caller_old_balance - value);
instance
.engine
.set_balance(callee, contract_old_balance + value);
instance.engine.set_value_transferred(value);
});
}

/// Returns the amount of storage cells used by the account `account_id`.
///
/// Returns `None` if the `account_id` is non-existent.
Expand Down Expand Up @@ -355,3 +395,12 @@ pub fn assert_contract_termination<T, F>(
assert_eq!(value_transferred, expected_value_transferred_to_beneficiary);
assert_eq!(beneficiary, expected_beneficiary);
}

/// Prepend contract message call with value transfer. Used for tests in off-chain environment.
#[macro_export]
macro_rules! pay_with_call {
($contract:ident . $message:ident ( $( $params:expr ),* ) , $amount:expr) => {{
$crate::test::transfer_in::<Environment>($amount);
$contract.$message($ ($params) ,*)
}}
}
51 changes: 35 additions & 16 deletions crates/lang/codegen/src/generator/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use ir::{
};
use proc_macro2::TokenStream as TokenStream2;
use quote::{
format_ident,
quote,
quote_spanned,
};
Expand Down Expand Up @@ -488,19 +489,25 @@ impl Dispatch<'_> {
#constructor_ident(#constructor_input)
)
});
let constructor_match = (0..count_constructors).map(|index| {
let constructor_span = constructor_spans[index];
let constructor_ident = constructor_variant_ident(index);
let constructor_selector = quote_spanned!(span=>
<#storage_ident as ::ink_lang::reflect::DispatchableConstructorInfo<{

let constructor_selector = (0..count_constructors).map(|index| {
let const_ident = format_ident!("CONSTRUCTOR_{}", index);
quote_spanned!(span=>
const #const_ident: [::core::primitive::u8; 4usize] = <#storage_ident as ::ink_lang::reflect::DispatchableConstructorInfo<{
<#storage_ident as ::ink_lang::reflect::ContractDispatchableConstructors<{
<#storage_ident as ::ink_lang::reflect::ContractAmountDispatchables>::CONSTRUCTORS
}>>::IDS[#index]
}>>::SELECTOR
);
}>>::SELECTOR;
)
});

let constructor_match = (0..count_constructors).map(|index| {
let constructor_span = constructor_spans[index];
let constructor_ident = constructor_variant_ident(index);
let const_ident = format_ident!("CONSTRUCTOR_{}", index);
let constructor_input = expand_constructor_input(constructor_span, storage_ident, index);
quote_spanned!(constructor_span=>
#constructor_selector => {
#const_ident => {
::core::result::Result::Ok(Self::#constructor_ident(
<#constructor_input as ::scale::Decode>::decode(input)
.map_err(|_| ::ink_lang::reflect::DispatchError::InvalidParameters)?
Expand Down Expand Up @@ -576,6 +583,9 @@ impl Dispatch<'_> {
where
I: ::scale::Input,
{
#(
#constructor_selector
)*
match <[::core::primitive::u8; 4usize] as ::scale::Decode>::decode(input)
.map_err(|_| ::ink_lang::reflect::DispatchError::InvalidSelector)?
{
Expand Down Expand Up @@ -653,19 +663,25 @@ impl Dispatch<'_> {
#message_ident(#message_input)
)
});
let message_match = (0..count_messages).map(|index| {
let message_span = message_spans[index];
let message_ident = message_variant_ident(index);
let message_selector = quote_spanned!(span=>
<#storage_ident as ::ink_lang::reflect::DispatchableMessageInfo<{

let message_selector = (0..count_messages).map(|index| {
let const_ident = format_ident!("MESSAGE_{}", index);
quote_spanned!(span=>
const #const_ident: [::core::primitive::u8; 4usize] = <#storage_ident as ::ink_lang::reflect::DispatchableMessageInfo<{
<#storage_ident as ::ink_lang::reflect::ContractDispatchableMessages<{
<#storage_ident as ::ink_lang::reflect::ContractAmountDispatchables>::MESSAGES
}>>::IDS[#index]
}>>::SELECTOR
);
}>>::SELECTOR;
)
});

let message_match = (0..count_messages).map(|index| {
let message_span = message_spans[index];
let message_ident = message_variant_ident(index);
let const_ident = format_ident!("MESSAGE_{}", index);
let message_input = expand_message_input(message_span, storage_ident, index);
quote_spanned!(message_span=>
#message_selector => {
#const_ident => {
::core::result::Result::Ok(Self::#message_ident(
<#message_input as ::scale::Decode>::decode(input)
.map_err(|_| ::ink_lang::reflect::DispatchError::InvalidParameters)?
Expand Down Expand Up @@ -772,6 +788,9 @@ impl Dispatch<'_> {
where
I: ::scale::Input,
{
#(
#message_selector
)*
match <[::core::primitive::u8; 4usize] as ::scale::Decode>::decode(input)
.map_err(|_| ::ink_lang::reflect::DispatchError::InvalidSelector)?
{
Expand Down
2 changes: 2 additions & 0 deletions crates/lang/tests/compile_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,6 @@ fn ui_tests() {
t.compile_fail("tests/ui/trait_def/fail/*.rs");

t.pass("tests/ui/chain_extension/E-01-simple.rs");

t.pass("tests/ui/pay_with_call/pass/multiple_args.rs");
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ error[E0599]: the method `fire` exists for struct `ink_env::call::CallBuilder<De
= note: the following trait bounds were not satisfied:
`NonCodecType: parity_scale_codec::Decode`
note: the following trait must be implemented
--> $CARGO/parity-scale-codec-3.1.5/src/codec.rs
--> $CARGO/parity-scale-codec-3.2.1/src/codec.rs
|
| / pub trait Decode: Sized {
| | // !INTERNAL USE ONLY!
Expand Down
33 changes: 33 additions & 0 deletions crates/lang/tests/ui/pay_with_call/pass/multiple_args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use ink_lang as ink;

#[ink::contract]
mod contract {
use ink_env as env;

#[ink(storage)]
pub struct Contract {}

impl Contract {
#[ink(constructor)]
pub fn new() -> Self {
Self {}
}

#[ink(message)]
pub fn message0(&self) {}

#[ink(message)]
pub fn message1(&self, _arg1: u8) {}

#[ink(message)]
pub fn message2(&self, _arg1: u8, _arg2: (u8, AccountId)) {}

fn check_compiles(&self) {
env::pay_with_call!(self.message0(), 0);
env::pay_with_call!(self.message1(0), 0);
env::pay_with_call!(self.message2(0, (0, Self::env().account_id())), 0);
}
}
}

fn main() {}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ error[E0599]: the method `fire` exists for struct `CallBuilder<E, Set<Call<E>>,
= note: the following trait bounds were not satisfied:
`NonCodec: parity_scale_codec::Decode`
note: the following trait must be implemented
--> $CARGO/parity-scale-codec-3.1.5/src/codec.rs
--> $CARGO/parity-scale-codec-3.2.1/src/codec.rs
|
| / pub trait Decode: Sized {
| | // !INTERNAL USE ONLY!
Expand Down
23 changes: 17 additions & 6 deletions examples/contract-transfer/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,20 +101,31 @@ pub mod give_me {

#[ink::test]
fn test_transferred_value() {
use ink_lang::codegen::Env;
// given
let accounts = default_accounts();
let give_me = create_contract(100);
let contract_account = give_me.env().account_id();

// when
// Push the new execution context which sets Eve as caller and
// the `mock_transferred_value` as the value which the contract
// will see as transferred to it.
// Push the new execution context which sets initial balances and
// sets Eve as the caller
set_balance(accounts.eve, 100);
set_balance(contract_account, 0);
set_sender(accounts.eve);
ink_env::test::set_value_transferred::<ink_env::DefaultEnvironment>(10);

// then
// there must be no panic
give_me.was_it_ten();
// we use helper macro to emulate method invocation coming with payment,
// and there must be no panic
ink_env::pay_with_call!(give_me.was_it_ten(), 10);

// and
// balances should be changed properly
let contract_new_balance = get_balance(contract_account);
let caller_new_balance = get_balance(accounts.eve);

assert_eq!(caller_new_balance, 100 - 10);
assert_eq!(contract_new_balance, 10);
}

#[ink::test]
Expand Down