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: false,
215 effective_gas_price: None,
216 },
217 )
218 })
219 }
220
221 fn instantiate_contract(
222 &mut self,
223 code_hash: H256,
224 value: BalanceOf<Self::T>,
225 data: Vec<u8>,
226 salt: Option<[u8; 32]>,
227 origin: OriginFor<Self::T>,
228 gas_limit: Weight,
229 storage_deposit_limit: BalanceOf<Self::T>,
230 ) -> ContractResultInstantiate<Self::T> {
231 self.execute_with(|| {
232 pallet_revive::Pallet::<Self::T>::bare_instantiate(
233 origin,
234 balance_to_evm_value::<Self::T>(value),
235 gas_limit,
236 storage_deposit_limit,
237 Code::Existing(code_hash),
238 data,
239 salt,
240 ExecConfig {
241 bump_nonce: true,
242 collect_deposit_from_hold: false,
243 effective_gas_price: None,
244 },
245 )
246 })
247 }
248
249 fn upload_contract(
250 &mut self,
251 contract_bytes: Vec<u8>,
252 origin: OriginFor<Self::T>,
253 storage_deposit_limit: BalanceOf<Self::T>,
254 ) -> CodeUploadResult<BalanceOf<Self::T>> {
255 self.execute_with(|| {
256 pallet_revive::Pallet::<Self::T>::bare_upload_code(
257 origin,
258 contract_bytes,
259 storage_deposit_limit,
260 )
261 })
262 }
263
264 fn call_contract(
265 &mut self,
266 address: Address,
267 value: BalanceOf<Self::T>,
268 data: Vec<u8>,
269 origin: OriginFor<Self::T>,
270 gas_limit: Weight,
271 storage_deposit_limit: BalanceOf<Self::T>,
272 ) -> ContractExecResultFor<Self::T> {
273 self.execute_with(|| {
274 pallet_revive::Pallet::<Self::T>::bare_call(
275 origin,
276 address,
277 balance_to_evm_value::<Self::T>(value),
278 gas_limit,
279 storage_deposit_limit,
280 data,
281 ExecConfig {
282 bump_nonce: true,
283 collect_deposit_from_hold: false,
284 effective_gas_price: None,
285 },
286 )
287 })
288 }
289
290 fn evm_tracer(&mut self, tracer_type: TracerType) -> Tracer<Self::T> {
291 self.execute_with(|| pallet_revive::Pallet::<Self::T>::evm_tracer(tracer_type))
292 }
293}
294
295pub fn decode_debug_buffer(buffer: &[u8]) -> Vec<String> {
298 let decoded = buffer.iter().map(|b| *b as char).collect::<String>();
299 decoded
300 .split('\n')
301 .filter_map(|s| s.is_empty().not().then_some(s.to_string()))
302 .collect()
303}
304
305#[cfg(test)]
306mod tests {
307 use super::*;
308 use crate::{
309 DefaultSandbox,
310 RuntimeEventOf,
311 api::prelude::*,
312 };
313
314 const STORAGE_DEPOSIT_LIMIT: u128 = u128::MAX;
315
316 fn compile_module(contract_name: &str) -> Vec<u8> {
317 let path = [
319 std::env::var("CARGO_MANIFEST_DIR").as_deref().unwrap(),
320 "/test-resources/",
321 contract_name,
322 ".polkavm",
323 ]
324 .concat();
325 std::fs::read(std::path::Path::new(&path)).unwrap()
326 }
327
328 fn warm_up<T>(sandbox: &mut T)
335 where
336 <T as Sandbox>::Runtime: pallet_revive::Config + pallet_balances::Config,
337 T: BalanceAPI<T> + Sandbox,
338 {
339 let acc = pallet_revive::Pallet::<<T as Sandbox>::Runtime>::account_id();
340 let ed = pallet_balances::Pallet::<<T as Sandbox>::Runtime>::minimum_balance();
341 sandbox.mint_into(&acc, ed).unwrap_or_else(|_| {
342 panic!("Failed to mint existential balance into `pallet-revive` account")
343 });
344 }
345
346 #[test]
347 fn can_upload_code() {
348 let mut sandbox = DefaultSandbox::default();
349 let contract_binary = compile_module("dummy");
350 warm_up::<DefaultSandbox>(&mut sandbox);
351
352 use sha3::{
353 Digest,
354 Keccak256,
355 };
356 let hash = Keccak256::digest(contract_binary.as_slice());
357 let hash = H256::from_slice(hash.as_slice());
358
359 let origin =
360 DefaultSandbox::convert_account_to_origin(DefaultSandbox::default_actor());
361 let result = sandbox.upload_contract(contract_binary, origin, 100000000000000);
362
363 assert!(result.is_ok());
364 assert_eq!(hash, result.unwrap().code_hash);
365 }
366
367 #[test]
368 fn can_deploy_contract() {
369 let mut sandbox = DefaultSandbox::default();
370 let contract_binary = compile_module("dummy");
371
372 let events_before = sandbox.events();
373 assert!(events_before.is_empty());
374
375 warm_up::<DefaultSandbox>(&mut sandbox);
376
377 let origin =
378 DefaultSandbox::convert_account_to_origin(DefaultSandbox::default_actor());
379 sandbox.map_account(origin.clone()).expect("cannot map");
380 let result = sandbox.deploy_contract(
381 contract_binary.clone(),
382 0,
383 vec![],
384 None,
385 origin.clone(),
386 DefaultSandbox::default_gas_limit(),
387 100000000000000,
388 );
389 assert!(result.result.is_ok());
390 assert!(!result.result.unwrap().result.did_revert());
391
392 let result = sandbox.deploy_contract(
394 contract_binary,
395 0,
396 vec![],
397 None,
398 origin,
399 DefaultSandbox::default_gas_limit(),
400 100000000000000,
401 );
402 assert!(result.result.is_err());
403 let dispatch_err = result.result.unwrap_err();
404 assert!(format!("{dispatch_err:?}").contains("DuplicateContract"));
405 }
406
407 #[test]
408 fn can_call_contract() {
409 let mut sandbox = DefaultSandbox::default();
410 let _actor = DefaultSandbox::default_actor();
411 let contract_binary = compile_module("dummy");
412 warm_up::<DefaultSandbox>(&mut sandbox);
413
414 let origin =
415 DefaultSandbox::convert_account_to_origin(DefaultSandbox::default_actor());
416 sandbox.map_account(origin.clone()).expect("unable to map");
417 let result = sandbox.deploy_contract(
418 contract_binary,
419 0,
420 vec![],
421 None,
422 origin.clone(),
423 DefaultSandbox::default_gas_limit(),
424 STORAGE_DEPOSIT_LIMIT,
425 );
426 assert!(!result.result.clone().unwrap().result.did_revert());
427
428 let contract_address = result.result.expect("Contract should be deployed").addr;
429
430 sandbox.reset_events();
431
432 let result = sandbox.call_contract(
433 contract_address,
434 0,
435 vec![],
436 origin.clone(),
437 DefaultSandbox::default_gas_limit(),
438 STORAGE_DEPOSIT_LIMIT,
439 );
440 assert!(result.result.is_ok());
441 assert!(!result.result.unwrap().did_revert());
442
443 let events = sandbox.events();
444 assert_eq!(events.len(), 1);
445 assert_eq!(
446 events[0].event,
447 RuntimeEventOf::<DefaultSandbox>::Revive(
448 pallet_revive::Event::ContractEmitted {
449 contract: contract_address,
450 topics: vec![H256::from([42u8; 32])],
451 data: vec![1, 2, 3, 4],
452 }
453 )
454 );
455 }
456}