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