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 H160,
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: 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
80pub 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
98pub 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
111pub fn set_clear_storage_disabled(_disable: bool) {
119 unimplemented!(
120 "off-chain environment does not yet support `set_clear_storage_disabled`"
121 );
122}
123
124pub fn advance_block<T>()
126where
127 T: Environment,
128{
129 <EnvInstance as OnInstance>::on_instance(|instance| {
130 instance.engine.advance_block();
131 })
132}
133
134pub fn set_caller(caller: H160) {
136 <EnvInstance as OnInstance>::on_instance(|instance| {
137 instance.engine.set_caller(caller);
138 })
139}
140
141pub fn set_callee(callee: H160) {
143 <EnvInstance as OnInstance>::on_instance(|instance| {
144 instance.engine.set_callee(callee);
145 })
146}
147
148pub fn set_contract(contract: H160) {
150 <EnvInstance as OnInstance>::on_instance(|instance| {
151 instance.engine.set_contract(contract);
152 })
153}
154
155#[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
163pub 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
174pub 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
181pub fn set_value_transferred(value: U256) {
186 <EnvInstance as OnInstance>::on_instance(|instance| {
187 instance.engine.set_value_transferred(value);
188 })
189}
190
191#[allow(clippy::arithmetic_side_effects)] pub 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
216pub 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
231pub 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
241pub 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
251pub 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_callee(alice);
265
266 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
283pub 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
297pub struct DefaultAccounts {
299 pub alice: H160,
301 pub bob: H160,
303 pub charlie: H160,
305 pub django: H160,
307 pub eve: H160,
309 pub frank: H160,
311}
312
313pub 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
323pub 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#[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
379pub fn get_return_value() -> Vec<u8> {
381 <EnvInstance as OnInstance>::on_instance(|instance| instance.get_return_value())
382}
383
384pub 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}