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