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::Inspect,
15 },
16 weights::Weight,
17};
18use frame_system::pallet_prelude::OriginFor;
19use ink_primitives::{
20 Address,
21 DepositLimit,
22};
23use pallet_revive::{
24 BumpNonce,
25 Code,
26 CodeUploadResult,
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 #[allow(clippy::type_complexity, clippy::too_many_arguments)]
73 fn deploy_contract(
74 &mut self,
75 contract_bytes: Vec<u8>,
76 value: BalanceOf<Self::T>,
77 data: Vec<u8>,
78 salt: Option<[u8; 32]>,
79 origin: OriginFor<Self::T>,
80 gas_limit: Weight,
81 storage_deposit_limit: DepositLimit<BalanceOf<Self::T>>,
82 ) -> ContractResultInstantiate<Self::T>;
83
84 #[allow(clippy::type_complexity, clippy::too_many_arguments)]
97 fn instantiate_contract(
98 &mut self,
99 code_hash: H256,
100 value: BalanceOf<Self::T>,
101 data: Vec<u8>,
102 salt: Option<[u8; 32]>,
103 origin: OriginFor<Self::T>,
104 gas_limit: Weight,
105 storage_deposit_limit: DepositLimit<BalanceOf<Self::T>>,
106 ) -> ContractResultInstantiate<Self::T>;
107
108 fn upload_contract(
116 &mut self,
117 contract_bytes: Vec<u8>,
118 origin: OriginFor<Self::T>,
119 storage_deposit_limit: BalanceOf<Self::T>,
120 ) -> CodeUploadResult<BalanceOf<Self::T>>;
121
122 #[allow(clippy::type_complexity, clippy::too_many_arguments)]
133 fn call_contract(
134 &mut self,
135 address: Address,
136 value: BalanceOf<Self::T>,
137 data: Vec<u8>,
138 origin: OriginFor<Self::T>,
139 gas_limit: Weight,
140 storage_deposit_limit: DepositLimit<BalanceOf<Self::T>>,
141 ) -> ContractExecResultFor<Self::T>;
142
143 fn evm_tracer(&mut self, tracer_type: TracerType) -> Tracer<Self::T>;
144}
145
146impl<T> ContractAPI for T
147where
148 T: Sandbox,
149 T::Runtime: pallet_revive::Config,
150 BalanceOf<T::Runtime>: Into<U256> + TryFrom<U256> + Bounded,
151 MomentOf<T::Runtime>: Into<U256>,
152 <<T as Sandbox>::Runtime as frame_system::Config>::Nonce: Into<u32>,
153 <<T as Sandbox>::Runtime as frame_system::Config>::Hash:
155 frame_support::traits::IsType<sp_core::H256>,
156{
157 type T = T::Runtime;
158
159 fn map_account(
160 &mut self,
161 account_id: OriginFor<Self::T>,
162 ) -> Result<(), DispatchError> {
163 self.execute_with(|| pallet_revive::Pallet::<Self::T>::map_account(account_id))
164 }
165
166 fn deploy_contract(
167 &mut self,
168 contract_bytes: Vec<u8>,
169 value: BalanceOf<Self::T>,
170 data: Vec<u8>,
171 salt: Option<[u8; 32]>,
172 origin: OriginFor<Self::T>,
173 gas_limit: Weight,
174 storage_deposit_limit: DepositLimit<BalanceOf<Self::T>>,
175 ) -> ContractResultInstantiate<Self::T> {
176 let storage_deposit_limit = storage_deposit_limit_fn(storage_deposit_limit);
177 self.execute_with(|| {
178 pallet_revive::Pallet::<Self::T>::bare_instantiate(
179 origin,
180 balance_to_evm_value::<Self::T>(value),
181 gas_limit,
182 storage_deposit_limit,
183 Code::Upload(contract_bytes),
184 data,
185 salt,
186 BumpNonce::Yes,
187 )
188 })
189 }
190
191 fn instantiate_contract(
192 &mut self,
193 code_hash: H256,
194 value: BalanceOf<Self::T>,
195 data: Vec<u8>,
196 salt: Option<[u8; 32]>,
197 origin: OriginFor<Self::T>,
198 gas_limit: Weight,
199 storage_deposit_limit: DepositLimit<BalanceOf<Self::T>>,
200 ) -> ContractResultInstantiate<Self::T> {
201 let storage_deposit_limit = storage_deposit_limit_fn(storage_deposit_limit);
202 self.execute_with(|| {
203 pallet_revive::Pallet::<Self::T>::bare_instantiate(
204 origin,
205 balance_to_evm_value::<Self::T>(value),
206 gas_limit,
207 storage_deposit_limit,
208 Code::Existing(code_hash),
209 data,
210 salt,
211 BumpNonce::Yes,
212 )
213 })
214 }
215
216 fn upload_contract(
217 &mut self,
218 contract_bytes: Vec<u8>,
219 origin: OriginFor<Self::T>,
220 storage_deposit_limit: BalanceOf<Self::T>,
221 ) -> CodeUploadResult<BalanceOf<Self::T>> {
222 self.execute_with(|| {
223 pallet_revive::Pallet::<Self::T>::bare_upload_code(
224 origin,
225 contract_bytes,
226 storage_deposit_limit,
227 )
228 })
229 }
230
231 fn call_contract(
232 &mut self,
233 address: Address,
234 value: BalanceOf<Self::T>,
235 data: Vec<u8>,
236 origin: OriginFor<Self::T>,
237 gas_limit: Weight,
238 storage_deposit_limit: DepositLimit<BalanceOf<Self::T>>,
239 ) -> ContractExecResultFor<Self::T> {
240 let storage_deposit_limit = storage_deposit_limit_fn(storage_deposit_limit);
241 self.execute_with(|| {
242 pallet_revive::Pallet::<Self::T>::bare_call(
243 origin,
244 address,
245 balance_to_evm_value::<Self::T>(value),
246 gas_limit,
247 storage_deposit_limit,
248 data,
249 )
250 })
251 }
252
253 fn evm_tracer(&mut self, tracer_type: TracerType) -> Tracer<Self::T> {
254 self.execute_with(|| pallet_revive::Pallet::<Self::T>::evm_tracer(tracer_type))
255 }
256}
257
258fn storage_deposit_limit_fn<Balance>(
260 limit: DepositLimit<Balance>,
261) -> pallet_revive::DepositLimit<Balance> {
262 match limit {
263 DepositLimit::UnsafeOnlyForDryRun => {
264 pallet_revive::DepositLimit::UnsafeOnlyForDryRun
265 }
266 DepositLimit::Balance(v) => pallet_revive::DepositLimit::Balance(v),
267 }
268}
269
270pub fn decode_debug_buffer(buffer: &[u8]) -> Vec<String> {
273 let decoded = buffer.iter().map(|b| *b as char).collect::<String>();
274 decoded
275 .split('\n')
276 .filter_map(|s| s.is_empty().not().then_some(s.to_string()))
277 .collect()
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283 use crate::{
284 DefaultSandbox,
285 RuntimeEventOf,
286 api::prelude::*,
287 };
288
289 const STORAGE_DEPOSIT_LIMIT: DepositLimit<u128> = DepositLimit::UnsafeOnlyForDryRun;
290
291 fn compile_module(contract_name: &str) -> Vec<u8> {
292 let path = [
294 std::env::var("CARGO_MANIFEST_DIR").as_deref().unwrap(),
295 "/test-resources/",
296 contract_name,
297 ".polkavm",
298 ]
299 .concat();
300 std::fs::read(std::path::Path::new(&path)).unwrap()
301 }
302
303 fn warm_up<T>(sandbox: &mut T)
310 where
311 <T as Sandbox>::Runtime: pallet_revive::Config + pallet_balances::Config,
312 T: BalanceAPI<T> + Sandbox,
313 {
314 let acc = pallet_revive::Pallet::<<T as Sandbox>::Runtime>::account_id();
315 let ed = pallet_balances::Pallet::<<T as Sandbox>::Runtime>::minimum_balance();
316 sandbox.mint_into(&acc, ed).unwrap_or_else(|_| {
317 panic!("Failed to mint existential balance into `pallet-revive` account")
318 });
319 }
320
321 #[test]
322 fn can_upload_code() {
323 let mut sandbox = DefaultSandbox::default();
324 let contract_binary = compile_module("dummy");
325 warm_up::<DefaultSandbox>(&mut sandbox);
326
327 use sha3::{
328 Digest,
329 Keccak256,
330 };
331 let hash = Keccak256::digest(contract_binary.as_slice());
332 let hash = H256::from_slice(hash.as_slice());
333
334 let origin =
335 DefaultSandbox::convert_account_to_origin(DefaultSandbox::default_actor());
336 let result = sandbox.upload_contract(contract_binary, origin, 100000000000000);
337
338 assert!(result.is_ok());
339 assert_eq!(hash, result.unwrap().code_hash);
340 }
341
342 #[test]
343 fn can_deploy_contract() {
344 let mut sandbox = DefaultSandbox::default();
345 let contract_binary = compile_module("dummy");
346
347 let events_before = sandbox.events();
348 assert!(events_before.is_empty());
349
350 warm_up::<DefaultSandbox>(&mut sandbox);
351
352 let origin =
353 DefaultSandbox::convert_account_to_origin(DefaultSandbox::default_actor());
354 sandbox.map_account(origin.clone()).expect("cannot map");
355 let result = sandbox.deploy_contract(
356 contract_binary.clone(),
357 0,
358 vec![],
359 None,
360 origin.clone(),
361 DefaultSandbox::default_gas_limit(),
362 DepositLimit::Balance(100000000000000),
363 );
364 assert!(result.result.is_ok());
365 assert!(!result.result.unwrap().result.did_revert());
366
367 let result = sandbox.deploy_contract(
369 contract_binary,
370 0,
371 vec![],
372 None,
373 origin,
374 DefaultSandbox::default_gas_limit(),
375 DepositLimit::Balance(100000000000000),
376 );
377 assert!(result.result.is_err());
378 let dispatch_err = result.result.unwrap_err();
379 assert!(format!("{dispatch_err:?}").contains("DuplicateContract"));
380 }
381
382 #[test]
383 fn can_call_contract() {
384 let mut sandbox = DefaultSandbox::default();
385 let _actor = DefaultSandbox::default_actor();
386 let contract_binary = compile_module("dummy");
387 warm_up::<DefaultSandbox>(&mut sandbox);
388
389 let origin =
390 DefaultSandbox::convert_account_to_origin(DefaultSandbox::default_actor());
391 sandbox.map_account(origin.clone()).expect("unable to map");
392 let result = sandbox.deploy_contract(
393 contract_binary,
394 0,
395 vec![],
396 None,
397 origin.clone(),
398 DefaultSandbox::default_gas_limit(),
399 STORAGE_DEPOSIT_LIMIT,
400 );
401 assert!(!result.result.clone().unwrap().result.did_revert());
402
403 let contract_address = result.result.expect("Contract should be deployed").addr;
404
405 sandbox.reset_events();
406
407 let result = sandbox.call_contract(
408 contract_address,
409 0,
410 vec![],
411 origin.clone(),
412 DefaultSandbox::default_gas_limit(),
413 STORAGE_DEPOSIT_LIMIT,
414 );
415 assert!(result.result.is_ok());
416 assert!(!result.result.unwrap().did_revert());
417
418 let events = sandbox.events();
419 assert_eq!(events.len(), 1);
420 assert_eq!(
421 events[0].event,
422 RuntimeEventOf::<DefaultSandbox>::Revive(
423 pallet_revive::Event::ContractEmitted {
424 contract: contract_address,
425 topics: vec![H256::from([42u8; 32])],
426 data: vec![1, 2, 3, 4],
427 }
428 )
429 );
430 }
431}