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