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
130pub fn is_contract(contract: Address) -> bool {
131    <EnvInstance as OnInstance>::on_instance(|instance| {
132        instance.engine.is_contract(&contract)
133    })
134}
135
136/// Gets the currently set callee.
137///
138/// This is the address of the currently executing contract.
139pub fn callee() -> Address {
140    <EnvInstance as OnInstance>::on_instance(|instance| {
141        let callee = instance.engine.get_callee();
142        scale::Decode::decode(&mut &callee[..])
143            .unwrap_or_else(|err| panic!("encoding failed: {err}"))
144    })
145}
146
147/// Returns the total number of reads and writes of the contract's storage.
148pub fn get_contract_storage_rw(addr: Address) -> (usize, usize) {
149    <EnvInstance as OnInstance>::on_instance(|instance| {
150        instance.engine.get_contract_storage_rw(addr)
151    })
152}
153
154/// Sets the value transferred from the caller to the callee as part of the call.
155///
156/// Please note that the acting accounts should be set with [`set_caller()`] and
157/// [`set_callee()`] beforehand.
158pub fn set_value_transferred(value: U256) {
159    <EnvInstance as OnInstance>::on_instance(|instance| {
160        instance.engine.set_value_transferred(value);
161    })
162}
163
164/// Transfers value from the caller account to the contract.
165///
166/// Please note that the acting accounts should be set with [`set_caller()`] and
167/// [`set_callee()`] beforehand.
168#[allow(clippy::arithmetic_side_effects)] // todo
169pub fn transfer_in(value: U256) {
170    <EnvInstance as OnInstance>::on_instance(|instance| {
171        let caller = instance.engine.exec_context.caller;
172
173        let caller_old_balance = instance.engine.get_balance(caller).unwrap_or_default();
174
175        let callee = instance.engine.get_callee();
176        let contract_old_balance =
177            instance.engine.get_balance(callee).unwrap_or_default();
178
179        instance
180            .engine
181            .set_balance(caller, caller_old_balance - value);
182        instance
183            .engine
184            .set_balance(callee, contract_old_balance + value);
185        instance.engine.set_value_transferred(value);
186    });
187}
188
189/// Returns the amount of storage cells used by the contract `addr`.
190///
191/// Returns `None` if the contract at `addr` is non-existent.
192pub fn count_used_storage_cells<T>(addr: Address) -> Result<usize>
193where
194    T: Environment,
195{
196    <EnvInstance as OnInstance>::on_instance(|instance| {
197        instance
198            .engine
199            .count_used_storage_cells(&addr)
200            .map_err(Into::into)
201    })
202}
203
204/// Sets the block timestamp for the next [`advance_block`] invocation.
205pub fn set_block_timestamp<T>(value: T::Timestamp)
206where
207    T: Environment<Timestamp = u64>,
208{
209    <EnvInstance as OnInstance>::on_instance(|instance| {
210        instance.engine.set_block_timestamp(value);
211    })
212}
213
214/// Sets the block number for the next [`advance_block`] invocation.
215pub fn set_block_number<T>(value: T::BlockNumber)
216where
217    T: Environment<BlockNumber = u32>,
218{
219    <EnvInstance as OnInstance>::on_instance(|instance| {
220        instance.engine.set_block_number(value);
221    })
222}
223
224/// Runs the given closure test function with the default configuration
225/// for the off-chain environment.
226pub fn run_test<T, F>(f: F) -> Result<()>
227where
228    T: Environment,
229    F: FnOnce(DefaultAccounts) -> Result<()>,
230{
231    let default_accounts = default_accounts();
232    <EnvInstance as OnInstance>::on_instance(|instance| {
233        instance.engine.initialize_or_reset();
234
235        let alice = default_accounts.alice;
236        // instance.engine.set_caller(alice.clone()); // todo
237        instance.engine.set_callee(alice);
238
239        // set up the funds for the default accounts
240        let substantial = 1_000_000.into();
241        let some = 1_000.into();
242        instance.engine.set_balance(alice, substantial);
243        instance.engine.set_balance(default_accounts.bob, some);
244        instance.engine.set_balance(default_accounts.charlie, some);
245        instance
246            .engine
247            .set_balance(default_accounts.django, 0.into());
248        instance.engine.set_balance(default_accounts.eve, 0.into());
249        instance
250            .engine
251            .set_balance(default_accounts.frank, 0.into());
252    });
253    f(default_accounts)
254}
255
256/// Returns the `H160` addresses of default accounts, for testing
257/// purposes: Alice, Bob, Charlie, Django, Eve and Frank.
258pub fn default_accounts() -> DefaultAccounts {
259    DefaultAccounts {
260        alice: AccountIdMapper::to_address(&[0x01; 32]),
261        bob: AccountIdMapper::to_address(&[0x02; 32]),
262        charlie: AccountIdMapper::to_address(&[0x03; 32]),
263        django: AccountIdMapper::to_address(&[0x04; 32]),
264        eve: AccountIdMapper::to_address(&[0x05; 32]),
265        frank: AccountIdMapper::to_address(&[0x06; 32]),
266    }
267}
268
269/// Addresses of the default accounts.
270pub struct DefaultAccounts {
271    /// The predefined `ALICE` address holding substantial amounts of value.
272    pub alice: Address,
273    /// The predefined `BOB` address holding some amounts of value.
274    pub bob: Address,
275    /// The predefined `CHARLIE` address holding some amounts of value.
276    pub charlie: Address,
277    /// The predefined `DJANGO` address holding no value.
278    pub django: Address,
279    /// The predefined `EVE` address holding no value.
280    pub eve: Address,
281    /// The predefined `FRANK` address holding no value.
282    pub frank: Address,
283}
284
285/// Returns the recorded emitted events in order.
286pub fn recorded_events() -> Vec<EmittedEvent> {
287    <EnvInstance as OnInstance>::on_instance(|instance| {
288        instance
289            .engine
290            .get_emitted_events()
291            .map(|evt: ink_engine::test_api::EmittedEvent| evt.into())
292            .collect()
293    })
294}
295
296/// Tests if a contract terminates successfully after `self.env().terminate()`
297/// has been called.
298///
299/// The arguments denote:
300///
301/// * `should_terminate`: A closure in which the function supposed to terminate is called.
302/// * `expected_beneficiary`: The beneficiary account who should have received the
303///   remaining value in the contract
304/// * `expected_value_transferred_to_beneficiary`: The value which should have been
305///   transferred to the `expected_beneficiary`.
306///
307/// # Usage
308///
309/// ```no_compile
310/// let should_terminate = move || your_contract.fn_which_should_terminate();
311/// ink_env::test::assert_contract_termination::<ink_env::DefaultEnvironment, _>(
312///     should_terminate,
313///     expected_beneficiary,
314///     expected_value_transferred_to_beneficiary
315/// );
316/// ```
317///
318/// See our [`contract-terminate`](https://github.com/use-ink/ink-examples/tree/v5.x.x/contract-terminate)
319/// example for a complete usage exemplification.
320pub fn assert_contract_termination<T, F>(
321    should_terminate: F,
322    expected_beneficiary: Address,
323    expected_value_transferred_to_beneficiary: U256,
324) where
325    T: Environment,
326    F: FnMut() + UnwindSafe,
327    <T as Environment>::AccountId: Debug,
328    <T as Environment>::Balance: Debug,
329{
330    let value_any = ::std::panic::catch_unwind(should_terminate)
331        .expect_err("contract did not terminate");
332    let encoded_input = value_any
333        .downcast_ref::<Vec<u8>>()
334        .expect("panic object can not be cast");
335    let (value_transferred, beneficiary): (U256, Address) =
336        scale::Decode::decode(&mut &encoded_input[..])
337            .unwrap_or_else(|err| panic!("input can not be decoded: {err}"));
338    assert_eq!(value_transferred, expected_value_transferred_to_beneficiary);
339    assert_eq!(beneficiary, expected_beneficiary);
340}
341
342/// Prepend contract message call with value transfer. Used for tests in off-chain
343/// environment.
344#[macro_export]
345macro_rules! pay_with_call {
346    ($contract:ident . $message:ident ( $( $params:expr ),* ) , $amount:expr) => {{
347        $crate::test::transfer_in($amount);
348        $contract.$message($ ($params) ,*)
349    }}
350}
351
352/// Retrieves the value stored by `return_value()`.
353pub fn get_return_value() -> Vec<u8> {
354    <EnvInstance as OnInstance>::on_instance(|instance| instance.get_return_value())
355}
356
357/// Gets a pseudo code hash for a contract ref.
358pub fn upload_code<E, ContractRef>() -> H256
359where
360    E: Environment,
361    ContractRef: crate::ContractReverseReference,
362    <ContractRef as crate::ContractReverseReference>::Type:
363        crate::reflect::ContractMessageDecoder,
364{
365    <EnvInstance as OnInstance>::on_instance(|instance| {
366        instance.upload_code::<ContractRef>()
367    })
368}