ink_env/engine/off_chain/
test_api.rs1use 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#[derive(Clone)]
39pub struct EmittedEvent {
40 pub topics: Vec<[u8; 32]>,
42 pub data: Vec<u8>,
44}
45
46pub 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
69pub 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
85pub fn set_clear_storage_disabled(_disable: bool) {
93 unimplemented!(
94 "off-chain environment does not yet support `set_clear_storage_disabled`"
95 );
96}
97
98pub fn advance_block<T>()
100where
101 T: Environment,
102{
103 <EnvInstance as OnInstance>::on_instance(|instance| {
104 instance.engine.advance_block();
105 })
106}
107
108pub fn set_caller(caller: Address) {
110 <EnvInstance as OnInstance>::on_instance(|instance| {
111 instance.engine.set_caller(caller);
112 })
113}
114
115pub fn set_callee(callee: Address) {
117 <EnvInstance as OnInstance>::on_instance(|instance| {
118 instance.engine.set_callee(callee);
119 })
120}
121
122pub fn set_contract(contract: Address) {
124 <EnvInstance as OnInstance>::on_instance(|instance| {
125 instance.engine.set_contract(contract);
126 })
127}
128
129#[cfg(feature = "unstable-hostfn")]
131pub fn is_contract(contract: Address) -> bool {
132 <EnvInstance as OnInstance>::on_instance(|instance| {
133 instance.engine.is_contract(&contract)
134 })
135}
136
137pub fn callee() -> Address {
141 <EnvInstance as OnInstance>::on_instance(|instance| {
142 let callee = instance.engine.get_callee();
143 scale::Decode::decode(&mut &callee[..])
144 .unwrap_or_else(|err| panic!("encoding failed: {err}"))
145 })
146}
147
148pub fn get_contract_storage_rw(addr: Address) -> (usize, usize) {
150 <EnvInstance as OnInstance>::on_instance(|instance| {
151 instance.engine.get_contract_storage_rw(addr)
152 })
153}
154
155pub fn set_value_transferred(value: U256) {
160 <EnvInstance as OnInstance>::on_instance(|instance| {
161 instance.engine.set_value_transferred(value);
162 })
163}
164
165#[allow(clippy::arithmetic_side_effects)] pub fn transfer_in(value: U256) {
171 <EnvInstance as OnInstance>::on_instance(|instance| {
172 let caller = instance.engine.exec_context.caller;
173
174 let caller_old_balance = instance.engine.get_balance(caller).unwrap_or_default();
175
176 let callee = instance.engine.get_callee();
177 let contract_old_balance =
178 instance.engine.get_balance(callee).unwrap_or_default();
179
180 instance
181 .engine
182 .set_balance(caller, caller_old_balance - value);
183 instance
184 .engine
185 .set_balance(callee, contract_old_balance + value);
186 instance.engine.set_value_transferred(value);
187 });
188}
189
190pub fn count_used_storage_cells<T>(addr: Address) -> Result<usize>
194where
195 T: Environment,
196{
197 <EnvInstance as OnInstance>::on_instance(|instance| {
198 instance
199 .engine
200 .count_used_storage_cells(&addr)
201 .map_err(Into::into)
202 })
203}
204
205pub fn set_block_timestamp<T>(value: T::Timestamp)
207where
208 T: Environment<Timestamp = u64>,
209{
210 <EnvInstance as OnInstance>::on_instance(|instance| {
211 instance.engine.set_block_timestamp(value);
212 })
213}
214
215pub fn set_block_number<T>(value: T::BlockNumber)
217where
218 T: Environment<BlockNumber = u32>,
219{
220 <EnvInstance as OnInstance>::on_instance(|instance| {
221 instance.engine.set_block_number(value);
222 })
223}
224
225pub fn run_test<T, F>(f: F) -> Result<()>
228where
229 T: Environment,
230 F: FnOnce(DefaultAccounts) -> Result<()>,
231{
232 let default_accounts = default_accounts();
233 <EnvInstance as OnInstance>::on_instance(|instance| {
234 instance.engine.initialize_or_reset();
235
236 let alice = default_accounts.alice;
237 instance.engine.set_callee(alice);
239
240 let substantial = 1_000_000.into();
242 let some = 1_000.into();
243 instance.engine.set_balance(alice, substantial);
244 instance.engine.set_balance(default_accounts.bob, some);
245 instance.engine.set_balance(default_accounts.charlie, some);
246 instance
247 .engine
248 .set_balance(default_accounts.django, 0.into());
249 instance.engine.set_balance(default_accounts.eve, 0.into());
250 instance
251 .engine
252 .set_balance(default_accounts.frank, 0.into());
253 });
254 f(default_accounts)
255}
256
257pub fn default_accounts() -> DefaultAccounts {
260 DefaultAccounts {
261 alice: AccountIdMapper::to_address(&[0x01; 32]),
262 bob: AccountIdMapper::to_address(&[0x02; 32]),
263 charlie: AccountIdMapper::to_address(&[0x03; 32]),
264 django: AccountIdMapper::to_address(&[0x04; 32]),
265 eve: AccountIdMapper::to_address(&[0x05; 32]),
266 frank: AccountIdMapper::to_address(&[0x06; 32]),
267 }
268}
269
270pub struct DefaultAccounts {
272 pub alice: Address,
274 pub bob: Address,
276 pub charlie: Address,
278 pub django: Address,
280 pub eve: Address,
282 pub frank: Address,
284}
285
286pub fn recorded_events() -> Vec<EmittedEvent> {
288 <EnvInstance as OnInstance>::on_instance(|instance| {
289 instance
290 .engine
291 .get_emitted_events()
292 .map(|evt: ink_engine::test_api::EmittedEvent| evt.into())
293 .collect()
294 })
295}
296
297pub fn assert_contract_termination<T, F>(
322 should_terminate: F,
323 expected_beneficiary: Address,
324 expected_value_transferred_to_beneficiary: U256,
325) where
326 T: Environment,
327 F: FnMut() + UnwindSafe,
328 <T as Environment>::AccountId: Debug,
329 <T as Environment>::Balance: Debug,
330{
331 let value_any = ::std::panic::catch_unwind(should_terminate)
332 .expect_err("contract did not terminate");
333 let encoded_input = value_any
334 .downcast_ref::<Vec<u8>>()
335 .expect("panic object can not be cast");
336 let (value_transferred, beneficiary): (U256, Address) =
337 scale::Decode::decode(&mut &encoded_input[..])
338 .unwrap_or_else(|err| panic!("input can not be decoded: {err}"));
339 assert_eq!(value_transferred, expected_value_transferred_to_beneficiary);
340 assert_eq!(beneficiary, expected_beneficiary);
341}
342
343#[macro_export]
346macro_rules! pay_with_call {
347 ($contract:ident . $message:ident ( $( $params:expr ),* ) , $amount:expr) => {{
348 $crate::test::transfer_in($amount);
349 $contract.$message($ ($params) ,*)
350 }}
351}
352
353pub fn get_return_value() -> Vec<u8> {
355 <EnvInstance as OnInstance>::on_instance(|instance| instance.get_return_value())
356}
357
358pub fn upload_code<E, ContractRef>() -> H256
360where
361 E: Environment,
362 ContractRef: crate::ContractReverseReference,
363 <ContractRef as crate::ContractReverseReference>::Type:
364 crate::reflect::ContractMessageDecoder,
365{
366 <EnvInstance as OnInstance>::on_instance(|instance| {
367 instance.upload_code::<ContractRef>()
368 })
369}