ink_env/engine/off_chain/
test_api.rs1use 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#[derive(Clone)]
42pub struct EmittedEvent {
43 pub topics: Vec<Vec<u8>>,
45 pub data: Vec<u8>,
47}
48
49#[cfg(feature = "unstable-hostfn")] pub 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
77pub 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
95pub 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
108pub fn set_clear_storage_disabled(_disable: bool) {
116 unimplemented!(
117 "off-chain environment does not yet support `set_clear_storage_disabled`"
118 );
119}
120
121pub fn advance_block<T>()
123where
124 T: Environment,
125{
126 <EnvInstance as OnInstance>::on_instance(|instance| {
127 instance.engine.advance_block();
128 })
129}
130
131pub fn set_caller(caller: Address) {
133 <EnvInstance as OnInstance>::on_instance(|instance| {
134 instance.engine.set_caller(caller);
135 })
136}
137
138pub fn set_callee(callee: Address) {
140 <EnvInstance as OnInstance>::on_instance(|instance| {
141 instance.engine.set_callee(callee);
142 })
143}
144
145pub fn set_contract(contract: Address) {
147 <EnvInstance as OnInstance>::on_instance(|instance| {
148 instance.engine.set_contract(contract);
149 })
150}
151
152#[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
160pub 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
171pub 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
178pub fn set_value_transferred(value: U256) {
183 <EnvInstance as OnInstance>::on_instance(|instance| {
184 instance.engine.set_value_transferred(value);
185 })
186}
187
188#[allow(clippy::arithmetic_side_effects)] pub 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
213pub 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
228pub 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
238pub 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
248pub 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_callee(alice);
262
263 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
280pub 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
294pub struct DefaultAccounts {
296 pub alice: Address,
298 pub bob: Address,
300 pub charlie: Address,
302 pub django: Address,
304 pub eve: Address,
306 pub frank: Address,
308}
309
310pub 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
320pub 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#[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
376pub fn get_return_value() -> Vec<u8> {
378 <EnvInstance as OnInstance>::on_instance(|instance| instance.get_return_value())
379}
380
381pub 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}