Skip to content

Commit f06c961

Browse files
agryaznovHCastanoascjones
authored
Backport #1379 #1401 #1418 into v3.x.x and fix tests (#1439)
* Add `ink_env::pay_with_call!` helper macro for off-chain emulation of sending payments with contract msg calls (#1379) * first ver.: transfer_in api function implememted but we can't have it in on-chain env * transfer_in moved to test_api * doc + example updated * Update examples/contract-transfer/lib.rs Co-authored-by: Hernando Castano <[email protected]> * use stmt moved to macro * docs and nitty gritties * moved the macro to the test mod * spell fix * next review round suggestions applied * Use four spaces for macro indentation Co-authored-by: Hernando Castano <[email protected]> * Allow `pay_with_call` to take multiple arguments (#1401) * Moved constants from `match` to a separate constants (#1418) * fix tests Co-authored-by: Hernando Castano <[email protected]> Co-authored-by: Andrew Jones <[email protected]>
1 parent c131bf1 commit f06c961

File tree

7 files changed

+138
-24
lines changed

7 files changed

+138
-24
lines changed

crates/env/src/engine/off_chain/test_api.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,8 @@ where
200200
}
201201

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

214+
/// Transfers value from the caller account to the contract.
215+
///
216+
/// Please note that the acting accounts should be set with [`set_caller()`] and [`set_callee()`] beforehand.
217+
pub fn transfer_in<T>(value: T::Balance)
218+
where
219+
T: Environment<Balance = u128>, // Just temporary for the MVP!
220+
{
221+
<EnvInstance as OnInstance>::on_instance(|instance| {
222+
let caller = instance
223+
.engine
224+
.exec_context
225+
.caller
226+
.as_ref()
227+
.expect("no caller has been set")
228+
.as_bytes()
229+
.to_vec();
230+
231+
let caller_old_balance = instance
232+
.engine
233+
.get_balance(caller.clone())
234+
.unwrap_or_default();
235+
236+
let callee = instance.engine.get_callee();
237+
let contract_old_balance = instance
238+
.engine
239+
.get_balance(callee.clone())
240+
.unwrap_or_default();
241+
242+
instance
243+
.engine
244+
.set_balance(caller, caller_old_balance - value);
245+
instance
246+
.engine
247+
.set_balance(callee, contract_old_balance + value);
248+
instance.engine.set_value_transferred(value);
249+
});
250+
}
251+
212252
/// Returns the amount of storage cells used by the account `account_id`.
213253
///
214254
/// Returns `None` if the `account_id` is non-existent.
@@ -355,3 +395,12 @@ pub fn assert_contract_termination<T, F>(
355395
assert_eq!(value_transferred, expected_value_transferred_to_beneficiary);
356396
assert_eq!(beneficiary, expected_beneficiary);
357397
}
398+
399+
/// Prepend contract message call with value transfer. Used for tests in off-chain environment.
400+
#[macro_export]
401+
macro_rules! pay_with_call {
402+
($contract:ident . $message:ident ( $( $params:expr ),* ) , $amount:expr) => {{
403+
$crate::test::transfer_in::<Environment>($amount);
404+
$contract.$message($ ($params) ,*)
405+
}}
406+
}

crates/lang/codegen/src/generator/dispatch.rs

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use ir::{
2525
};
2626
use proc_macro2::TokenStream as TokenStream2;
2727
use quote::{
28+
format_ident,
2829
quote,
2930
quote_spanned,
3031
};
@@ -488,19 +489,25 @@ impl Dispatch<'_> {
488489
#constructor_ident(#constructor_input)
489490
)
490491
});
491-
let constructor_match = (0..count_constructors).map(|index| {
492-
let constructor_span = constructor_spans[index];
493-
let constructor_ident = constructor_variant_ident(index);
494-
let constructor_selector = quote_spanned!(span=>
495-
<#storage_ident as ::ink_lang::reflect::DispatchableConstructorInfo<{
492+
493+
let constructor_selector = (0..count_constructors).map(|index| {
494+
let const_ident = format_ident!("CONSTRUCTOR_{}", index);
495+
quote_spanned!(span=>
496+
const #const_ident: [::core::primitive::u8; 4usize] = <#storage_ident as ::ink_lang::reflect::DispatchableConstructorInfo<{
496497
<#storage_ident as ::ink_lang::reflect::ContractDispatchableConstructors<{
497498
<#storage_ident as ::ink_lang::reflect::ContractAmountDispatchables>::CONSTRUCTORS
498499
}>>::IDS[#index]
499-
}>>::SELECTOR
500-
);
500+
}>>::SELECTOR;
501+
)
502+
});
503+
504+
let constructor_match = (0..count_constructors).map(|index| {
505+
let constructor_span = constructor_spans[index];
506+
let constructor_ident = constructor_variant_ident(index);
507+
let const_ident = format_ident!("CONSTRUCTOR_{}", index);
501508
let constructor_input = expand_constructor_input(constructor_span, storage_ident, index);
502509
quote_spanned!(constructor_span=>
503-
#constructor_selector => {
510+
#const_ident => {
504511
::core::result::Result::Ok(Self::#constructor_ident(
505512
<#constructor_input as ::scale::Decode>::decode(input)
506513
.map_err(|_| ::ink_lang::reflect::DispatchError::InvalidParameters)?
@@ -576,6 +583,9 @@ impl Dispatch<'_> {
576583
where
577584
I: ::scale::Input,
578585
{
586+
#(
587+
#constructor_selector
588+
)*
579589
match <[::core::primitive::u8; 4usize] as ::scale::Decode>::decode(input)
580590
.map_err(|_| ::ink_lang::reflect::DispatchError::InvalidSelector)?
581591
{
@@ -653,19 +663,25 @@ impl Dispatch<'_> {
653663
#message_ident(#message_input)
654664
)
655665
});
656-
let message_match = (0..count_messages).map(|index| {
657-
let message_span = message_spans[index];
658-
let message_ident = message_variant_ident(index);
659-
let message_selector = quote_spanned!(span=>
660-
<#storage_ident as ::ink_lang::reflect::DispatchableMessageInfo<{
666+
667+
let message_selector = (0..count_messages).map(|index| {
668+
let const_ident = format_ident!("MESSAGE_{}", index);
669+
quote_spanned!(span=>
670+
const #const_ident: [::core::primitive::u8; 4usize] = <#storage_ident as ::ink_lang::reflect::DispatchableMessageInfo<{
661671
<#storage_ident as ::ink_lang::reflect::ContractDispatchableMessages<{
662672
<#storage_ident as ::ink_lang::reflect::ContractAmountDispatchables>::MESSAGES
663673
}>>::IDS[#index]
664-
}>>::SELECTOR
665-
);
674+
}>>::SELECTOR;
675+
)
676+
});
677+
678+
let message_match = (0..count_messages).map(|index| {
679+
let message_span = message_spans[index];
680+
let message_ident = message_variant_ident(index);
681+
let const_ident = format_ident!("MESSAGE_{}", index);
666682
let message_input = expand_message_input(message_span, storage_ident, index);
667683
quote_spanned!(message_span=>
668-
#message_selector => {
684+
#const_ident => {
669685
::core::result::Result::Ok(Self::#message_ident(
670686
<#message_input as ::scale::Decode>::decode(input)
671687
.map_err(|_| ::ink_lang::reflect::DispatchError::InvalidParameters)?
@@ -772,6 +788,9 @@ impl Dispatch<'_> {
772788
where
773789
I: ::scale::Input,
774790
{
791+
#(
792+
#message_selector
793+
)*
775794
match <[::core::primitive::u8; 4usize] as ::scale::Decode>::decode(input)
776795
.map_err(|_| ::ink_lang::reflect::DispatchError::InvalidSelector)?
777796
{

crates/lang/tests/compile_tests.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,6 @@ fn ui_tests() {
3232
t.compile_fail("tests/ui/trait_def/fail/*.rs");
3333

3434
t.pass("tests/ui/chain_extension/E-01-simple.rs");
35+
36+
t.pass("tests/ui/pay_with_call/pass/multiple_args.rs");
3537
}

crates/lang/tests/ui/contract/fail/message-returns-non-codec.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ error[E0599]: the method `fire` exists for struct `ink_env::call::CallBuilder<De
6060
= note: the following trait bounds were not satisfied:
6161
`NonCodecType: parity_scale_codec::Decode`
6262
note: the following trait must be implemented
63-
--> $CARGO/parity-scale-codec-3.1.5/src/codec.rs
63+
--> $CARGO/parity-scale-codec-3.2.1/src/codec.rs
6464
|
6565
| / pub trait Decode: Sized {
6666
| | // !INTERNAL USE ONLY!
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use ink_lang as ink;
2+
3+
#[ink::contract]
4+
mod contract {
5+
use ink_env as env;
6+
7+
#[ink(storage)]
8+
pub struct Contract {}
9+
10+
impl Contract {
11+
#[ink(constructor)]
12+
pub fn new() -> Self {
13+
Self {}
14+
}
15+
16+
#[ink(message)]
17+
pub fn message0(&self) {}
18+
19+
#[ink(message)]
20+
pub fn message1(&self, _arg1: u8) {}
21+
22+
#[ink(message)]
23+
pub fn message2(&self, _arg1: u8, _arg2: (u8, AccountId)) {}
24+
25+
fn check_compiles(&self) {
26+
env::pay_with_call!(self.message0(), 0);
27+
env::pay_with_call!(self.message1(0), 0);
28+
env::pay_with_call!(self.message2(0, (0, Self::env().account_id())), 0);
29+
}
30+
}
31+
}
32+
33+
fn main() {}

crates/lang/tests/ui/trait_def/fail/message_output_non_codec.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ error[E0599]: the method `fire` exists for struct `CallBuilder<E, Set<Call<E>>,
3434
= note: the following trait bounds were not satisfied:
3535
`NonCodec: parity_scale_codec::Decode`
3636
note: the following trait must be implemented
37-
--> $CARGO/parity-scale-codec-3.1.5/src/codec.rs
37+
--> $CARGO/parity-scale-codec-3.2.1/src/codec.rs
3838
|
3939
| / pub trait Decode: Sized {
4040
| | // !INTERNAL USE ONLY!

examples/contract-transfer/lib.rs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,20 +101,31 @@ pub mod give_me {
101101

102102
#[ink::test]
103103
fn test_transferred_value() {
104+
use ink_lang::codegen::Env;
104105
// given
105106
let accounts = default_accounts();
106107
let give_me = create_contract(100);
108+
let contract_account = give_me.env().account_id();
107109

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

115117
// then
116-
// there must be no panic
117-
give_me.was_it_ten();
118+
// we use helper macro to emulate method invocation coming with payment,
119+
// and there must be no panic
120+
ink_env::pay_with_call!(give_me.was_it_ten(), 10);
121+
122+
// and
123+
// balances should be changed properly
124+
let contract_new_balance = get_balance(contract_account);
125+
let caller_new_balance = get_balance(accounts.eve);
126+
127+
assert_eq!(caller_new_balance, 100 - 10);
128+
assert_eq!(contract_new_balance, 10);
118129
}
119130

120131
#[ink::test]

0 commit comments

Comments
 (0)