ink_env/engine/off_chain/
test_api.rs

1// Copyright (C) Use Ink (UK) Ltd.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Operations on the off-chain testing environment.
16
17use super::{
18    EnvInstance,
19    OnInstance,
20};
21use crate::{
22    Result,
23    types::Environment,
24};
25use core::fmt::Debug;
26use std::panic::UnwindSafe;
27
28pub use super::call_data::CallData;
29pub use ink_engine::ext::ChainSpec;
30use ink_primitives::{
31    AccountIdMapper,
32    Address,
33    H256,
34    U256,
35};
36
37/// Record for an emitted event.
38#[derive(Clone)]
39pub struct EmittedEvent {
40    /// Recorded topics of the emitted event.
41    pub topics: Vec<[u8; 32]>,
42    /// Recorded encoding of the emitted event.
43    pub data: Vec<u8>,
44}
45
46/// Sets the balance of a contract to the given balance.
47///
48/// # Note
49///
50/// If a 0 balance is set, this would not fail. This is useful for
51/// reaping an account.
52///
53/// # Errors
54///
55/// - If `addr` does not exist.
56/// - If the underlying `new_balance` type does not match.
57/// - If the `new_balance` is less than the existential minimum.
58pub fn set_contract_balance(addr: Address, new_balance: U256) {
59    let min = ChainSpec::default().minimum_balance;
60    if new_balance < min && new_balance != U256::zero() {
61        panic!("Balance must be at least [{min}]. Use 0 as balance to reap the account.");
62    }
63
64    <EnvInstance as OnInstance>::on_instance(|instance| {
65        instance.engine.set_balance(addr, new_balance);
66    })
67}
68
69/// Returns the balance of a contract.
70///
71/// # Note
72///
73/// This returns the same as `env::api::balance` if given the contract
74/// address of the currently executed smart contract.
75///
76/// # Errors
77///
78/// - If `contract` does not exist.
79pub fn get_contract_balance<T>(addr: Address) -> Result<U256> {
80    <EnvInstance as OnInstance>::on_instance(|instance| {
81        instance.engine.get_balance(addr).map_err(Into::into)
82    })
83}
84
85/// Set to true to disable clearing storage
86///
87/// # Note
88///
89/// Useful for benchmarks because it ensures the initialized storage is maintained across
90/// runs, because lazy storage structures automatically clear their associated cells when
91/// they are dropped.
92pub fn set_clear_storage_disabled(_disable: bool) {
93    unimplemented!(
94        "off-chain environment does not yet support `set_clear_storage_disabled`"
95    );
96}
97
98/// Advances the chain by a single block.
99pub fn advance_block<T>()
100where
101    T: Environment,
102{
103    <EnvInstance as OnInstance>::on_instance(|instance| {
104        instance.engine.advance_block();
105    })
106}
107
108/// Sets a caller for the next call.
109pub fn set_caller(caller: Address) {
110    <EnvInstance as OnInstance>::on_instance(|instance| {
111        instance.engine.set_caller(caller);
112    })
113}
114
115/// Sets the callee for the next call.
116pub fn set_callee(callee: Address) {
117    <EnvInstance as OnInstance>::on_instance(|instance| {
118        instance.engine.set_callee(callee);
119    })
120}
121
122/// Sets an account as a contract
123pub fn set_contract(contract: Address) {
124    <EnvInstance as OnInstance>::on_instance(|instance| {
125        instance.engine.set_contract(contract);
126    })
127}
128
129/// Returns a boolean to indicate whether an account is a contract
130#[cfg(feature = "unstable-hostfn")]
131pub fn is_contract(contract: Address) -> bool {
132    <EnvInstance as OnInstance>::on_instance(|instance| {
133        instance.engine.is_contract(&contract)
134    })
135}
136
137/// Gets the currently set callee.
138///
139/// This is the address of the currently executing contract.
140pub fn callee() -> Address {
141    <EnvInstance as OnInstance>::on_instance(|instance| {
142        let callee = instance.engine.get_callee();
143        scale::Decode::decode(&mut &callee[..])
144            .unwrap_or_else(|err| panic!("encoding failed: {err}"))
145    })
146}
147
148/// Returns the total number of reads and writes of the contract's storage.
149pub fn get_contract_storage_rw(addr: Address) -> (usize, usize) {
150    <EnvInstance as OnInstance>::on_instance(|instance| {
151        instance.engine.get_contract_storage_rw(addr)
152    })
153}
154
155/// Sets the value transferred from the caller to the callee as part of the call.
156///
157/// Please note that the acting accounts should be set with [`set_caller()`] and
158/// [`set_callee()`] beforehand.
159pub fn set_value_transferred(value: U256) {
160    <EnvInstance as OnInstance>::on_instance(|instance| {
161        instance.engine.set_value_transferred(value);
162    })
163}
164
165/// Transfers value from the caller account to the contract.
166///
167/// Please note that the acting accounts should be set with [`set_caller()`] and
168/// [`set_callee()`] beforehand.
169#[allow(clippy::arithmetic_side_effects)] // todo
170pub fn transfer_in(value: U256) {
171    <EnvInstance as OnInstance>::on_instance(|instance| {
172        let caller = instance.engine.exec_context.caller;
173
174        let caller_old_balance = instance.engine.get_balance(caller).unwrap_or_default();
175
176        let callee = instance.engine.get_callee();
177        let contract_old_balance =
178            instance.engine.get_balance(callee).unwrap_or_default();
179
180        instance
181            .engine
182            .set_balance(caller, caller_old_balance - value);
183        instance
184            .engine
185            .set_balance(callee, contract_old_balance + value);
186        instance.engine.set_value_transferred(value);
187    });
188}
189
190/// Returns the amount of storage cells used by the contract `addr`.
191///
192/// Returns `None` if the contract at `addr` is non-existent.
193pub fn count_used_storage_cells<T>(addr: Address) -> Result<usize>
194where
195    T: Environment,
196{
197    <EnvInstance as OnInstance>::on_instance(|instance| {
198        instance
199            .engine
200            .count_used_storage_cells(&addr)
201            .map_err(Into::into)
202    })
203}
204
205/// Sets the block timestamp for the next [`advance_block`] invocation.
206pub fn set_block_timestamp<T>(value: T::Timestamp)
207where
208    T: Environment<Timestamp = u64>,
209{
210    <EnvInstance as OnInstance>::on_instance(|instance| {
211        instance.engine.set_block_timestamp(value);
212    })
213}
214
215/// Sets the block number for the next [`advance_block`] invocation.
216pub fn set_block_number<T>(value: T::BlockNumber)
217where
218    T: Environment<BlockNumber = u32>,
219{
220    <EnvInstance as OnInstance>::on_instance(|instance| {
221        instance.engine.set_block_number(value);
222    })
223}
224
225/// Runs the given closure test function with the default configuration
226/// for the off-chain environment.
227pub fn run_test<T, F>(f: F) -> Result<()>
228where
229    T: Environment,
230    F: FnOnce(DefaultAccounts) -> Result<()>,
231{
232    let default_accounts = default_accounts();
233    <EnvInstance as OnInstance>::on_instance(|instance| {
234        instance.engine.initialize_or_reset();
235
236        let alice = default_accounts.alice;
237        // instance.engine.set_caller(alice.clone()); // todo
238        instance.engine.set_callee(alice);
239
240        // set up the funds for the default accounts
241        let substantial = 1_000_000.into();
242        let some = 1_000.into();
243        instance.engine.set_balance(alice, substantial);
244        instance.engine.set_balance(default_accounts.bob, some);
245        instance.engine.set_balance(default_accounts.charlie, some);
246        instance
247            .engine
248            .set_balance(default_accounts.django, 0.into());
249        instance.engine.set_balance(default_accounts.eve, 0.into());
250        instance
251            .engine
252            .set_balance(default_accounts.frank, 0.into());
253    });
254    f(default_accounts)
255}
256
257/// Returns the `H160` addresses of default accounts, for testing
258/// purposes: Alice, Bob, Charlie, Django, Eve and Frank.
259pub fn default_accounts() -> DefaultAccounts {
260    DefaultAccounts {
261        alice: AccountIdMapper::to_address(&[0x01; 32]),
262        bob: AccountIdMapper::to_address(&[0x02; 32]),
263        charlie: AccountIdMapper::to_address(&[0x03; 32]),
264        django: AccountIdMapper::to_address(&[0x04; 32]),
265        eve: AccountIdMapper::to_address(&[0x05; 32]),
266        frank: AccountIdMapper::to_address(&[0x06; 32]),
267    }
268}
269
270/// Addresses of the default accounts.
271pub struct DefaultAccounts {
272    /// The predefined `ALICE` address holding substantial amounts of value.
273    pub alice: Address,
274    /// The predefined `BOB` address holding some amounts of value.
275    pub bob: Address,
276    /// The predefined `CHARLIE` address holding some amounts of value.
277    pub charlie: Address,
278    /// The predefined `DJANGO` address holding no value.
279    pub django: Address,
280    /// The predefined `EVE` address holding no value.
281    pub eve: Address,
282    /// The predefined `FRANK` address holding no value.
283    pub frank: Address,
284}
285
286/// Returns the recorded emitted events in order.
287pub fn recorded_events() -> Vec<EmittedEvent> {
288    <EnvInstance as OnInstance>::on_instance(|instance| {
289        instance
290            .engine
291            .get_emitted_events()
292            .map(|evt: ink_engine::test_api::EmittedEvent| evt.into())
293            .collect()
294    })
295}
296
297/// Tests if a contract terminates successfully after `self.env().terminate()`
298/// has been called.
299///
300/// The arguments denote:
301///
302/// * `should_terminate`: A closure in which the function supposed to terminate is called.
303/// * `expected_beneficiary`: The beneficiary account who should have received the
304///   remaining value in the contract
305/// * `expected_value_transferred_to_beneficiary`: The value which should have been
306///   transferred to the `expected_beneficiary`.
307///
308/// # Usage
309///
310/// ```no_compile
311/// let should_terminate = move || your_contract.fn_which_should_terminate();
312/// ink_env::test::assert_contract_termination::<ink_env::DefaultEnvironment, _>(
313///     should_terminate,
314///     expected_beneficiary,
315///     expected_value_transferred_to_beneficiary
316/// );
317/// ```
318///
319/// See our [`contract-terminate`](https://github.com/use-ink/ink-examples/tree/v5.x.x/contract-terminate)
320/// example for a complete usage exemplification.
321pub fn assert_contract_termination<T, F>(
322    should_terminate: F,
323    expected_beneficiary: Address,
324    expected_value_transferred_to_beneficiary: U256,
325) where
326    T: Environment,
327    F: FnMut() + UnwindSafe,
328    <T as Environment>::AccountId: Debug,
329    <T as Environment>::Balance: Debug,
330{
331    let value_any = ::std::panic::catch_unwind(should_terminate)
332        .expect_err("contract did not terminate");
333    let encoded_input = value_any
334        .downcast_ref::<Vec<u8>>()
335        .expect("panic object can not be cast");
336    let (value_transferred, beneficiary): (U256, Address) =
337        scale::Decode::decode(&mut &encoded_input[..])
338            .unwrap_or_else(|err| panic!("input can not be decoded: {err}"));
339    assert_eq!(value_transferred, expected_value_transferred_to_beneficiary);
340    assert_eq!(beneficiary, expected_beneficiary);
341}
342
343/// Prepend contract message call with value transfer. Used for tests in off-chain
344/// environment.
345#[macro_export]
346macro_rules! pay_with_call {
347    ($contract:ident . $message:ident ( $( $params:expr ),* ) , $amount:expr) => {{
348        $crate::test::transfer_in($amount);
349        $contract.$message($ ($params) ,*)
350    }}
351}
352
353/// Retrieves the value stored by `return_value()`.
354pub fn get_return_value() -> Vec<u8> {
355    <EnvInstance as OnInstance>::on_instance(|instance| instance.get_return_value())
356}
357
358/// Gets a pseudo code hash for a contract ref.
359pub fn upload_code<E, ContractRef>() -> H256
360where
361    E: Environment,
362    ContractRef: crate::ContractReverseReference,
363    <ContractRef as crate::ContractReverseReference>::Type:
364        crate::reflect::ContractMessageDecoder,
365{
366    <EnvInstance as OnInstance>::on_instance(|instance| {
367        instance.upload_code::<ContractRef>()
368    })
369}