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