1use alloc::vec::Vec;
2use objects::*;
3use pink::chain_extension::signing;
4use primitive_types::H256;
5use scale::{Decode, Encode};
6
7use crate::contracts::objects::{ContractCall, WeightV2};
8
9pub mod objects;
10
11pub type ContractId = [u8; 32];
12pub type Balance = u128;
13
14#[derive(Encode, Decode, Debug)]
15#[repr(u8)]
16#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
17pub enum Error {
18 FailedToDryRunContract(crate::Error),
19 FailedToQueryContract(crate::Error),
20 FailedToCreateTransaction(crate::Error),
21 FailedToSendTransaction(crate::Error),
22 FailedToDecode,
23 InvalidAddressLength,
24 NoResult,
25 FailedToReadResult,
26 ContractError(Vec<u8>), ContractUnknownError, ContractTrapped(u32), }
30
31#[derive(Encode, Decode, Debug)]
32#[repr(u8)]
33#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
34pub enum ContractError {
35 Error,
36}
37
38pub type Result<T> = core::result::Result<T, Error>;
39
40pub struct InkContract<'a> {
41 rpc: &'a str,
42 pallet_id: u8,
43 call_id: u8,
44 contract_id: &'a ContractId,
45}
46
47impl<'a> InkContract<'a> {
48 pub fn new(rpc: &'a str, pallet_id: u8, call_id: u8, contract_id: &'a ContractId) -> Self {
49 InkContract {
50 rpc,
51 pallet_id,
52 call_id,
53 contract_id,
54 }
55 }
56
57 pub fn query<A: Encode, R: Decode>(
58 &self,
59 origin: [u8; 32],
60 contract_method: [u8; 4],
61 contract_args: Option<&A>,
62 value: Balance,
63 ) -> Result<R> {
64 self.query_at(origin, contract_method, contract_args, value, None)
65 }
66
67 pub fn query_at<A: Encode, R: Decode>(
68 &self,
69 origin: [u8; 32],
70 contract_method: [u8; 4],
71 contract_args: Option<&A>,
72 value: Balance,
73 at: Option<H256>,
74 ) -> Result<R> {
75 let call = build_contract_query(
76 origin,
77 *self.contract_id,
78 contract_method,
79 contract_args,
80 value,
81 );
82
83 let encoded_call = Encode::encode(&call);
84
85 let contract_query_result: ContractQueryResult<ContractError, Balance> =
86 crate::query_contract(self.rpc, &encoded_call, at)
87 .map_err(Error::FailedToQueryContract)?;
88
89 self.handle_error(&contract_query_result)?;
91
92 let result = contract_query_result
94 .result
95 .data
96 .map_err(|_| Error::NoResult)?;
97 let result = <core::result::Result<R, Error>>::decode(&mut result.as_slice())
98 .map_err(|_| Error::FailedToDecode)?;
99 let result = result.map_err(|_| Error::FailedToReadResult)?;
100
101 Ok(result)
102 }
103
104 pub fn send_transaction<A: Encode>(
105 &self,
106 contract_method: [u8; 4],
107 contract_args: Option<&A>,
108 value: Balance,
109 gas_limit: WeightV2,
110 signer: &[u8; 32],
111 ) -> Result<Vec<u8>> {
112 let call: ContractCall<ContractId, u32, Balance> = build_contract_call(
113 *self.contract_id,
114 contract_method,
115 contract_args,
116 value,
117 gas_limit,
118 );
119
120 let signed_tx = crate::create_transaction(
121 signer,
122 "astar",
123 self.rpc,
124 self.pallet_id,
125 self.call_id,
126 call,
127 crate::ExtraParam::default(),
128 )
129 .map_err(Error::FailedToCreateTransaction)?;
130
131 let result = crate::send_transaction(self.rpc, &signed_tx)
132 .map_err(Error::FailedToSendTransaction)?;
133
134 Ok(result)
135 }
136
137 pub fn dry_run_and_send_transaction<A: Encode>(
138 &self,
139 contract_method: [u8; 4],
140 contract_args: Option<&A>,
141 value: Balance,
142 signer: &[u8; 32],
143 ) -> Result<Vec<u8>> {
144 let origin: [u8; 32] = signing::get_public_key(signer, signing::SigType::Sr25519)
145 .try_into()
146 .map_err(|_| Error::InvalidAddressLength)?;
147
148 let call = build_contract_query(
149 origin,
150 *self.contract_id,
151 contract_method,
152 contract_args,
153 value,
154 );
155
156 let encoded_call = Encode::encode(&call);
157
158 let contract_query_result: ContractQueryResult<ContractError, Balance> =
159 crate::query_contract(self.rpc, &encoded_call, None)
160 .map_err(Error::FailedToQueryContract)?;
161
162 self.handle_error(&contract_query_result)?;
164
165 self.send_transaction(
167 contract_method,
168 contract_args,
169 value,
170 contract_query_result.gas_required,
171 signer,
172 )
173 }
174
175 fn handle_error(
176 &self,
177 contract_query_result: &ContractQueryResult<ContractError, Balance>,
178 ) -> Result<()> {
179 if contract_query_result.result.flags == 0 {
181 return Ok(());
182 }
183
184 if contract_query_result.result.flags == 256 {
186 let error = match &contract_query_result.result.data {
187 Ok(v) => Error::ContractError(v.clone()),
188 _ => Error::ContractUnknownError,
189 };
190 return Err(error);
191 }
192 Err(Error::ContractTrapped(contract_query_result.result.flags))
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 struct EnvVars {
271 rpc: String,
273 pallet_id: u8,
274 call_id: u8,
275 contract_id: ContractId,
276 }
277
278 fn env() -> EnvVars {
279 EnvVars {
290 rpc: "https://shibuya.public.blastapi.io".to_string(),
291 pallet_id: 70u8,
292 call_id: 6u8,
293 contract_id: hex_literal::hex!(
294 "d0859843adc542e9439152c9a17e8cf5260c644346334b1a38dd624a7fb24af6"
295 ),
296 }
297 }
298
299 #[test]
300 #[ignore = "this is expensive so we don't test it often"]
301 fn test_query_with_primitive_result() {
302 pink_chain_extension::mock_ext::mock_all_ext();
303
304 let EnvVars {
306 rpc,
307 pallet_id,
308 call_id,
309 contract_id,
310 } = env();
311
312 let contract = InkContract::new(&rpc, pallet_id, call_id, &contract_id);
314
315 let origin =
317 hex_literal::hex!("189dac29296d31814dc8c56cf3d36a0543372bba7538fa322a4aebfebc39e056");
318 let method_get = hex_literal::hex!("2f865bd9");
322 let params: Option<&()> = None;
324
325 let value: i32 = contract
327 .query(origin, method_get, params, 0)
328 .expect("Error when call the method 'get'");
329 println!("Query the method get, result : {}", value);
331 assert!(value > 0);
332 }
333
334 #[test]
335 #[ignore = "this is expensive so we don't test it often"]
336 fn test_query_with_object_result() {
337 pink_chain_extension::mock_ext::mock_all_ext();
338
339 let EnvVars {
341 rpc,
342 pallet_id,
343 call_id,
344 contract_id,
345 } = env();
346
347 let contract = InkContract::new(&rpc, pallet_id, call_id, &contract_id);
349
350 let origin =
352 hex_literal::hex!("189dac29296d31814dc8c56cf3d36a0543372bba7538fa322a4aebfebc39e056");
353 let method_get_with_result = hex_literal::hex!("f21dd3cb");
357 let params: Option<&()> = None;
359
360 type Result = core::result::Result<i32, Error>;
362 let value: Result = contract
364 .query(origin, method_get_with_result, params, 0)
365 .expect("Error when call the method 'get_with_result'");
366
367 println!("Query the method get, result : {:?}", value);
369 assert!(value.unwrap() > 0);
370 }
371
372 #[test]
373 #[ignore = "this is expensive so we don't test it often"]
374 fn test_call_without_params() {
375 pink_chain_extension::mock_ext::mock_all_ext();
376
377 let EnvVars {
379 rpc,
380 pallet_id,
381 call_id,
382 contract_id,
383 } = env();
384
385 let contract = InkContract::new(&rpc, pallet_id, call_id, &contract_id);
387
388 let alice_pk: [u8; 32] =
390 hex_literal::hex!("e5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a");
391
392 let method_inc_by_1 = hex_literal::hex!("b5d14a10");
396 let params: Option<&()> = None;
398
399 contract
401 .dry_run_and_send_transaction(method_inc_by_1, params, 0, &alice_pk)
402 .expect("Error when call the method 'inc'");
403 }
404
405 #[test]
406 #[ignore = "this is expensive so we don't test it often"]
407 fn test_call_with_params() {
408 pink_chain_extension::mock_ext::mock_all_ext();
409
410 let EnvVars {
412 rpc,
413 pallet_id,
414 call_id,
415 contract_id,
416 } = env();
417
418 let contract = InkContract::new(&rpc, pallet_id, call_id, &contract_id);
420
421 let alice_pk: [u8; 32] =
423 hex_literal::hex!("e5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a");
424
425 let method_inc = hex_literal::hex!("1d32619f");
429 let params: Option<&i32> = Some(&3);
431
432 contract
434 .dry_run_and_send_transaction(method_inc, params, 0, &alice_pk)
435 .expect("Error when call the method 'inc'");
436 }
437
438 #[test]
439 #[ignore = "this is expensive so we don't test it often"]
440 fn test_query_with_error() {
441 pink_chain_extension::mock_ext::mock_all_ext();
442
443 let EnvVars {
445 rpc,
446 pallet_id,
447 call_id,
448 contract_id,
449 } = env();
450
451 let contract = InkContract::new(&rpc, pallet_id, call_id, &contract_id);
453
454 let origin =
456 hex_literal::hex!("189dac29296d31814dc8c56cf3d36a0543372bba7538fa322a4aebfebc39e056");
457 let method_get_error = hex_literal::hex!("6baa1eed");
461 let params: Option<&()> = None;
463 type Result = core::result::Result<i32, Error>;
465 let result: Result = contract.query(origin, method_get_error, params, 0);
467 match result {
468 Err(Error::ContractError(e)) => println!("Expected contract error {:?}", e),
469 Err(e) => {
470 println!(
471 "We expect to receive a contract error but we receive this error {:?}",
472 e
473 );
474 panic!("we expect to receive a contract error");
475 }
476 r => {
477 println!("We expect to receive an error but we receive that {:?}", r);
478 panic!("we expect to receive an error");
479 }
480 }
481 }
482
483 #[test]
484 #[ignore = "this is expensive so we don't test it often"]
485 fn test_query_with_trapped_error() {
486 pink_chain_extension::mock_ext::mock_all_ext();
487
488 let EnvVars {
490 rpc,
491 pallet_id,
492 call_id,
493 contract_id,
494 } = env();
495
496 let contract = InkContract::new(&rpc, pallet_id, call_id, &contract_id);
498
499 let origin =
501 hex_literal::hex!("189dac29296d31814dc8c56cf3d36a0543372bba7538fa322a4aebfebc39e056");
502 let method_get_error = hex_literal::hex!("fb02c510");
506 let params: Option<&()> = None;
508 type Result = core::result::Result<i32, Error>;
510 let result: Result = contract.query(origin, method_get_error, params, 0);
512 match result {
513 Err(Error::ContractTrapped(e)) => println!("Expected contract trapped error {:?}", e),
514 Err(e) => {
515 println!(
516 "We expect to receive a contract trapped error but we receive this error {:?}",
517 e
518 );
519 panic!("we expect to receive a contract trapped error");
520 }
521 r => {
522 println!("We expect to receive an error but we receive that {:?}", r);
523 panic!("we expect to receive an error");
524 }
525 }
526 }
527
528 #[test]
529 #[ignore = "this is expensive so we don't test it often"]
530 fn test_call_with_error() {
531 pink_chain_extension::mock_ext::mock_all_ext();
532
533 let EnvVars {
535 rpc,
536 pallet_id,
537 call_id,
538 contract_id,
539 } = env();
540
541 let contract = InkContract::new(&rpc, pallet_id, call_id, &contract_id);
543
544 let alice_pk: [u8; 32] =
546 hex_literal::hex!("e5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a");
547 let method_get_error = hex_literal::hex!("721bc303");
551 let params: Option<&()> = None;
553
554 let result = contract.dry_run_and_send_transaction(method_get_error, params, 0, &alice_pk);
556 match result {
557 Err(e) => println!("Expected error {:?}", e),
558 r => {
559 println!("We expect to receive an error but we receive that {:?}", r);
560 panic!("we expect to receive an error");
561 }
562 }
563 }
564}