1use crate::{
2 AccountIdFor,
3 ContractExecResultFor,
4 ContractResultInstantiate,
5 H256,
6 Sandbox,
7 balance_to_evm_value,
8};
9use frame_support::{
10 pallet_prelude::DispatchError,
11 sp_runtime::traits::Bounded,
12 traits::{
13 Time,
14 fungible::{
15 Inspect,
16 Mutate,
17 },
18 },
19 weights::Weight,
20};
21use frame_system::pallet_prelude::OriginFor;
22use ink_primitives::Address;
23use pallet_revive::{
24 Code,
25 CodeUploadResult,
26 ExecConfig,
27 evm::{
28 Tracer,
29 TracerType,
30 },
31};
32use sp_core::U256;
33use std::ops::Not;
34
35type BalanceOf<R> =
36 <<R as pallet_revive::Config>::Currency as Inspect<AccountIdFor<R>>>::Balance;
37
38type MomentOf<T> = <<T as pallet_revive::Config>::Time as Time>::Moment;
39
40pub trait ContractAPI {
42 type T: pallet_revive::Config;
44
45 #[allow(clippy::type_complexity, clippy::too_many_arguments)]
58 fn map_account(
59 &mut self,
60 account: impl crate::IntoAccountId<AccountIdFor<Self::T>>,
61 ) -> Result<(), DispatchError>;
62
63 fn warm_up(&mut self);
70
71 #[allow(clippy::type_complexity, clippy::too_many_arguments)]
84 fn deploy_contract(
85 &mut self,
86 contract_bytes: Vec<u8>,
87 value: BalanceOf<Self::T>,
88 data: Vec<u8>,
89 salt: Option<[u8; 32]>,
90 origin: OriginFor<Self::T>,
91 gas_limit: Weight,
92 storage_deposit_limit: BalanceOf<Self::T>,
93 ) -> ContractResultInstantiate<Self::T>;
94
95 #[allow(clippy::type_complexity, clippy::too_many_arguments)]
108 fn instantiate_contract(
109 &mut self,
110 code_hash: H256,
111 value: BalanceOf<Self::T>,
112 data: Vec<u8>,
113 salt: Option<[u8; 32]>,
114 origin: OriginFor<Self::T>,
115 gas_limit: Weight,
116 storage_deposit_limit: BalanceOf<Self::T>,
117 ) -> ContractResultInstantiate<Self::T>;
118
119 fn upload_contract(
127 &mut self,
128 contract_bytes: Vec<u8>,
129 origin: OriginFor<Self::T>,
130 storage_deposit_limit: BalanceOf<Self::T>,
131 ) -> CodeUploadResult<BalanceOf<Self::T>>;
132
133 #[allow(clippy::type_complexity, clippy::too_many_arguments)]
144 fn call_contract(
145 &mut self,
146 address: Address,
147 value: BalanceOf<Self::T>,
148 data: Vec<u8>,
149 origin: OriginFor<Self::T>,
150 gas_limit: Weight,
151 storage_deposit_limit: BalanceOf<Self::T>,
152 ) -> ContractExecResultFor<Self::T>;
153
154 fn evm_tracer(&mut self, tracer_type: TracerType) -> Tracer<Self::T>;
155}
156
157impl<T> ContractAPI for T
158where
159 T: Sandbox,
160 T::Runtime: pallet_balances::Config + pallet_revive::Config,
161 BalanceOf<T::Runtime>: Into<U256> + TryFrom<U256> + Bounded,
162 MomentOf<T::Runtime>: Into<U256>,
163 <<T as Sandbox>::Runtime as frame_system::Config>::Nonce: Into<u32>,
164 <<T as Sandbox>::Runtime as frame_system::Config>::Hash:
166 frame_support::traits::IsType<sp_core::H256>,
167{
168 type T = T::Runtime;
169
170 fn map_account(
171 &mut self,
172 account: impl crate::IntoAccountId<AccountIdFor<Self::T>>,
173 ) -> Result<(), DispatchError> {
174 let account_id = account.into_account_id();
175 let origin = Self::convert_account_to_origin(account_id);
176 self.execute_with(|| pallet_revive::Pallet::<Self::T>::map_account(origin))
177 }
178
179 fn warm_up(&mut self) {
186 self.execute_with(|| {
187 let acc = pallet_revive::Pallet::<Self::T>::account_id();
188 let ed = pallet_balances::Pallet::<Self::T>::minimum_balance();
189
190 if pallet_balances::Pallet::<Self::T>::free_balance(&acc) < ed {
192 let _ = pallet_balances::Pallet::<Self::T>::mint_into(&acc, ed);
193 }
194 });
195 }
196
197 fn deploy_contract(
198 &mut self,
199 contract_bytes: Vec<u8>,
200 value: BalanceOf<Self::T>,
201 data: Vec<u8>,
202 salt: Option<[u8; 32]>,
203 origin: OriginFor<Self::T>,
204 gas_limit: Weight,
205 storage_deposit_limit: BalanceOf<Self::T>,
206 ) -> ContractResultInstantiate<Self::T> {
207 self.warm_up();
208 self.execute_with(|| {
209 pallet_revive::Pallet::<Self::T>::bare_instantiate(
210 origin,
211 balance_to_evm_value::<Self::T>(value),
212 gas_limit,
213 storage_deposit_limit,
214 Code::Upload(contract_bytes),
215 data,
216 salt,
217 ExecConfig {
218 bump_nonce: true,
219 collect_deposit_from_hold: None,
220 effective_gas_price: None,
221 },
222 )
223 })
224 }
225
226 fn instantiate_contract(
227 &mut self,
228 code_hash: H256,
229 value: BalanceOf<Self::T>,
230 data: Vec<u8>,
231 salt: Option<[u8; 32]>,
232 origin: OriginFor<Self::T>,
233 gas_limit: Weight,
234 storage_deposit_limit: BalanceOf<Self::T>,
235 ) -> ContractResultInstantiate<Self::T> {
236 self.execute_with(|| {
237 pallet_revive::Pallet::<Self::T>::bare_instantiate(
238 origin,
239 balance_to_evm_value::<Self::T>(value),
240 gas_limit,
241 storage_deposit_limit,
242 Code::Existing(code_hash),
243 data,
244 salt,
245 ExecConfig {
246 bump_nonce: true,
247 collect_deposit_from_hold: None,
248 effective_gas_price: None,
249 },
250 )
251 })
252 }
253
254 fn upload_contract(
255 &mut self,
256 contract_bytes: Vec<u8>,
257 origin: OriginFor<Self::T>,
258 storage_deposit_limit: BalanceOf<Self::T>,
259 ) -> CodeUploadResult<BalanceOf<Self::T>> {
260 self.execute_with(|| {
261 pallet_revive::Pallet::<Self::T>::bare_upload_code(
262 origin,
263 contract_bytes,
264 storage_deposit_limit,
265 )
266 })
267 }
268
269 fn call_contract(
270 &mut self,
271 address: Address,
272 value: BalanceOf<Self::T>,
273 data: Vec<u8>,
274 origin: OriginFor<Self::T>,
275 gas_limit: Weight,
276 storage_deposit_limit: BalanceOf<Self::T>,
277 ) -> ContractExecResultFor<Self::T> {
278 self.execute_with(|| {
279 pallet_revive::Pallet::<Self::T>::bare_call(
280 origin,
281 address,
282 balance_to_evm_value::<Self::T>(value),
283 gas_limit,
284 storage_deposit_limit,
285 data,
286 ExecConfig {
287 bump_nonce: true,
288 collect_deposit_from_hold: None,
289 effective_gas_price: None,
290 },
291 )
292 })
293 }
294
295 fn evm_tracer(&mut self, tracer_type: TracerType) -> Tracer<Self::T> {
296 self.execute_with(|| pallet_revive::Pallet::<Self::T>::evm_tracer(tracer_type))
297 }
298}
299
300pub fn decode_debug_buffer(buffer: &[u8]) -> Vec<String> {
303 let decoded = buffer.iter().map(|b| *b as char).collect::<String>();
304 decoded
305 .split('\n')
306 .filter_map(|s| s.is_empty().not().then_some(s.to_string()))
307 .collect()
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313 use crate::{
314 DefaultSandbox,
315 RuntimeEventOf,
316 api::prelude::*,
317 };
318
319 const STORAGE_DEPOSIT_LIMIT: u128 = u128::MAX;
320
321 fn compile_module(contract_name: &str) -> Vec<u8> {
322 let path = [
324 std::env::var("CARGO_MANIFEST_DIR").as_deref().unwrap(),
325 "/test-resources/",
326 contract_name,
327 ".polkavm",
328 ]
329 .concat();
330 std::fs::read(std::path::Path::new(&path)).unwrap()
331 }
332
333 fn warm_up<T>(sandbox: &mut T)
340 where
341 <T as Sandbox>::Runtime: pallet_revive::Config + pallet_balances::Config,
342 T: BalanceAPI<T> + Sandbox,
343 {
344 let acc = pallet_revive::Pallet::<<T as Sandbox>::Runtime>::account_id();
345 let ed = pallet_balances::Pallet::<<T as Sandbox>::Runtime>::minimum_balance();
346 sandbox.mint_into(&acc, ed).unwrap_or_else(|_| {
347 panic!("Failed to mint existential balance into `pallet-revive` account")
348 });
349 }
350
351 #[test]
352 fn can_upload_code() {
353 let mut sandbox = DefaultSandbox::default();
354 let contract_binary = compile_module("dummy");
355 warm_up::<DefaultSandbox>(&mut sandbox);
356
357 use sha3::{
358 Digest,
359 Keccak256,
360 };
361 let hash = Keccak256::digest(contract_binary.as_slice());
362 let hash = H256::from_slice(hash.as_slice());
363
364 let origin =
365 DefaultSandbox::convert_account_to_origin(DefaultSandbox::default_actor());
366 let result = sandbox.upload_contract(contract_binary, origin, 100000000000000);
367
368 assert!(result.is_ok());
369 assert_eq!(hash, result.unwrap().code_hash);
370 }
371
372 #[test]
373 fn can_deploy_contract() {
374 let mut sandbox = DefaultSandbox::default();
375 let contract_binary = compile_module("dummy");
376
377 let events_before = sandbox.events();
378 assert!(events_before.is_empty());
379
380 warm_up::<DefaultSandbox>(&mut sandbox);
381
382 let default_actor = DefaultSandbox::default_actor();
383 sandbox.map_account(&default_actor).expect("cannot map");
384 let origin = DefaultSandbox::convert_account_to_origin(default_actor);
385 let result = sandbox.deploy_contract(
386 contract_binary.clone(),
387 0,
388 vec![],
389 None,
390 origin.clone(),
391 DefaultSandbox::default_gas_limit(),
392 100000000000000,
393 );
394 assert!(result.result.is_ok());
395 assert!(!result.result.unwrap().result.did_revert());
396
397 let result = sandbox.deploy_contract(
399 contract_binary,
400 0,
401 vec![],
402 None,
403 origin,
404 DefaultSandbox::default_gas_limit(),
405 100000000000000,
406 );
407 assert!(result.result.is_err());
408 let dispatch_err = result.result.unwrap_err();
409 assert!(format!("{dispatch_err:?}").contains("DuplicateContract"));
410 }
411
412 #[test]
413 fn can_call_contract() {
414 let mut sandbox = DefaultSandbox::default();
415 let _actor = DefaultSandbox::default_actor();
416 let contract_binary = compile_module("dummy");
417 warm_up::<DefaultSandbox>(&mut sandbox);
418
419 let default_actor = DefaultSandbox::default_actor();
420 sandbox.map_account(&default_actor).expect("unable to map");
421 let origin = DefaultSandbox::convert_account_to_origin(default_actor);
422 let result = sandbox.deploy_contract(
423 contract_binary,
424 0,
425 vec![],
426 None,
427 origin.clone(),
428 DefaultSandbox::default_gas_limit(),
429 STORAGE_DEPOSIT_LIMIT,
430 );
431 assert!(!result.result.clone().unwrap().result.did_revert());
432
433 let contract_address = result.result.expect("Contract should be deployed").addr;
434
435 sandbox.reset_events();
436
437 let result = sandbox.call_contract(
438 contract_address,
439 0,
440 vec![],
441 origin.clone(),
442 DefaultSandbox::default_gas_limit(),
443 STORAGE_DEPOSIT_LIMIT,
444 );
445 assert!(result.result.is_ok());
446 assert!(!result.result.unwrap().did_revert());
447
448 let events = sandbox.events();
449 assert_eq!(events.len(), 1);
450 assert_eq!(
451 events[0].event,
452 RuntimeEventOf::<DefaultSandbox>::Revive(
453 pallet_revive::Event::ContractEmitted {
454 contract: contract_address,
455 topics: vec![H256::from([42u8; 32])],
456 data: vec![1, 2, 3, 4],
457 }
458 )
459 );
460 }
461}