diff --git a/crates/env/src/engine/off_chain/test_api.rs b/crates/env/src/engine/off_chain/test_api.rs index 4d73be03ac3..01bef923811 100644 --- a/crates/env/src/engine/off_chain/test_api.rs +++ b/crates/env/src/engine/off_chain/test_api.rs @@ -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(value: T::Balance) where T: Environment, // Just temporary for the MVP! @@ -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(value: T::Balance) +where + T: Environment, // Just temporary for the MVP! +{ + ::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. @@ -355,3 +395,12 @@ pub fn assert_contract_termination( 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::($amount); + $contract.$message($ ($params) ,*) + }} +} diff --git a/crates/lang/codegen/src/generator/dispatch.rs b/crates/lang/codegen/src/generator/dispatch.rs index fee922033e5..623e86d0e93 100644 --- a/crates/lang/codegen/src/generator/dispatch.rs +++ b/crates/lang/codegen/src/generator/dispatch.rs @@ -25,6 +25,7 @@ use ir::{ }; use proc_macro2::TokenStream as TokenStream2; use quote::{ + format_ident, quote, quote_spanned, }; @@ -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)? @@ -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)? { @@ -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)? @@ -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)? { diff --git a/crates/lang/tests/compile_tests.rs b/crates/lang/tests/compile_tests.rs index b24bb0409eb..aca8882eddc 100644 --- a/crates/lang/tests/compile_tests.rs +++ b/crates/lang/tests/compile_tests.rs @@ -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"); } diff --git a/crates/lang/tests/ui/contract/fail/message-returns-non-codec.stderr b/crates/lang/tests/ui/contract/fail/message-returns-non-codec.stderr index e4eb2a04588..574191435b8 100644 --- a/crates/lang/tests/ui/contract/fail/message-returns-non-codec.stderr +++ b/crates/lang/tests/ui/contract/fail/message-returns-non-codec.stderr @@ -60,7 +60,7 @@ error[E0599]: the method `fire` exists for struct `ink_env::call::CallBuilder $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! diff --git a/crates/lang/tests/ui/pay_with_call/pass/multiple_args.rs b/crates/lang/tests/ui/pay_with_call/pass/multiple_args.rs new file mode 100644 index 00000000000..a9500cdec9d --- /dev/null +++ b/crates/lang/tests/ui/pay_with_call/pass/multiple_args.rs @@ -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() {} diff --git a/crates/lang/tests/ui/trait_def/fail/message_output_non_codec.stderr b/crates/lang/tests/ui/trait_def/fail/message_output_non_codec.stderr index 76c2ef384b4..e6b9ea797b8 100644 --- a/crates/lang/tests/ui/trait_def/fail/message_output_non_codec.stderr +++ b/crates/lang/tests/ui/trait_def/fail/message_output_non_codec.stderr @@ -34,7 +34,7 @@ error[E0599]: the method `fire` exists for struct `CallBuilder>, = 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! diff --git a/examples/contract-transfer/lib.rs b/examples/contract-transfer/lib.rs index a46da79f193..27e419dadd9 100644 --- a/examples/contract-transfer/lib.rs +++ b/examples/contract-transfer/lib.rs @@ -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::(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]