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