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