Skip to content

Commit

Permalink
Backport #1379 #1401 #1418 into v3.x.x and fix tests (#1439)
Browse files Browse the repository at this point in the history
* 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 <HCastano@users.noreply.github.com>

* 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 <HCastano@users.noreply.github.com>

* 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 <HCastano@users.noreply.github.com>
Co-authored-by: Andrew Jones <ascjones@gmail.com>
  • Loading branch information
3 people authored Oct 20, 2022
1 parent c131bf1 commit f06c961
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 24 deletions.
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

0 comments on commit f06c961

Please sign in to comment.