1use super::{
16 Keypair,
17 log_info,
18 sr25519,
19};
20use ink_env::Environment;
21
22use crate::contract_results::{
23 ContractExecResultFor,
24 ContractInstantiateResultFor,
25};
26use core::marker::PhantomData;
27use funty::Fundamental;
28use ink_primitives::{
29 Address,
30 DepositLimit,
31};
32use pallet_revive::{
33 CodeUploadResult,
34 evm::{
35 CallTrace,
36 CallTracerConfig,
37 Trace,
38 TracerType,
39 },
40};
41use sp_core::H256;
42use sp_runtime::OpaqueExtrinsic;
43use subxt::{
44 OnlineClient,
45 backend::{
46 legacy::LegacyRpcMethods,
47 rpc::RpcClient,
48 },
49 blocks::ExtrinsicEvents,
50 config::{
51 DefaultExtrinsicParams,
52 DefaultExtrinsicParamsBuilder,
53 ExtrinsicParams,
54 HashFor,
55 Header,
56 },
57 ext::{
58 scale_encode,
59 subxt_core::tx::Transaction,
60 },
61 tx::{
62 Signer,
63 SubmittableTransaction,
64 TxStatus,
65 },
66};
67
68#[derive(
70 Copy,
71 Clone,
72 Eq,
73 PartialEq,
74 Debug,
75 Default,
76 scale::Encode,
77 scale::Decode,
78 scale::MaxEncodedLen,
79 scale_encode::EncodeAsType,
80)]
81#[encode_as_type(crate_path = "subxt::ext::scale_encode")]
82pub struct Weight {
83 #[codec(compact)]
84 ref_time: u64,
86 #[codec(compact)]
87 proof_size: u64,
89}
90
91impl From<sp_weights::Weight> for Weight {
92 fn from(weight: sp_weights::Weight) -> Self {
93 Self {
94 ref_time: weight.ref_time(),
95 proof_size: weight.proof_size(),
96 }
97 }
98}
99
100impl From<Weight> for sp_weights::Weight {
101 fn from(weight: Weight) -> Self {
102 sp_weights::Weight::from_parts(weight.ref_time, weight.proof_size)
103 }
104}
105
106#[derive(Debug, scale::Encode, scale::Decode, scale_encode::EncodeAsType)]
110#[encode_as_type(trait_bounds = "", crate_path = "subxt::ext::scale_encode")]
111pub struct InstantiateWithCode<E: Environment> {
112 #[codec(compact)]
113 value: E::Balance,
114 gas_limit: Weight,
115 #[codec(compact)]
116 storage_deposit_limit: E::Balance,
117 code: Vec<u8>,
118 data: Vec<u8>,
119 salt: Option<[u8; 32]>,
120}
121
122#[derive(Debug, scale::Decode, scale::Encode, scale_encode::EncodeAsType)]
126#[encode_as_type(trait_bounds = "", crate_path = "subxt::ext::scale_encode")]
127pub struct Call<E: Environment> {
128 dest: Address,
129 #[codec(compact)]
130 value: E::Balance,
131 gas_limit: Weight,
132 #[codec(compact)]
133 storage_deposit_limit: E::Balance,
134 data: Vec<u8>,
135}
136
137#[derive(Debug, scale::Decode, scale::Encode, scale_encode::EncodeAsType)]
141#[encode_as_type(trait_bounds = "", crate_path = "subxt::ext::scale_encode")]
142pub struct MapAccount {}
143
144#[derive(Debug, scale::Decode, scale::Encode, scale_encode::EncodeAsType)]
146#[encode_as_type(trait_bounds = "", crate_path = "subxt::ext::scale_encode")]
147pub struct Transfer<E: Environment, C: subxt::Config> {
148 dest: subxt::utils::Static<C::Address>,
149 #[codec(compact)]
150 value: E::Balance,
151}
152
153#[derive(Debug, scale::Encode, scale::Decode, scale_encode::EncodeAsType)]
157#[encode_as_type(trait_bounds = "", crate_path = "subxt::ext::scale_encode")]
158pub struct RemoveCode {
159 code_hash: H256,
160}
161
162#[derive(Debug, scale::Encode, scale::Decode, scale_encode::EncodeAsType)]
166#[encode_as_type(trait_bounds = "", crate_path = "subxt::ext::scale_encode")]
167pub struct UploadCode<E: Environment> {
168 code: Vec<u8>,
169 #[codec(compact)]
170 storage_deposit_limit: E::Balance,
171}
172
173#[derive(scale::Encode)]
175struct RpcInstantiateRequest<C: subxt::Config, E: Environment> {
176 origin: C::AccountId,
177 value: E::Balance,
178 gas_limit: Option<Weight>,
179 storage_deposit_limit: Option<E::Balance>,
180 code: Code,
181 data: Vec<u8>,
182 salt: Option<[u8; 32]>,
183}
184
185#[derive(scale::Encode)]
187struct RpcCodeUploadRequest<C: subxt::Config, E: Environment>
188where
189 E::Balance: serde::Serialize,
190{
191 origin: C::AccountId,
192 code: Vec<u8>,
193 storage_deposit_limit: Option<E::Balance>,
194}
195
196#[derive(scale::Encode)]
198struct RpcCallRequest<C: subxt::Config, E: Environment> {
199 origin: C::AccountId,
200 dest: Address,
201 value: E::Balance,
202 gas_limit: Option<Weight>,
203 storage_deposit_limit: DepositLimit<E::Balance>,
204 input_data: Vec<u8>,
205}
206
207#[derive(scale::Encode)]
209enum Code {
210 Upload(Vec<u8>),
212 #[allow(unused)]
213 Existing(H256),
215}
216
217pub struct ReviveApi<C: subxt::Config, E: Environment> {
219 pub rpc: LegacyRpcMethods<C>,
220 pub client: OnlineClient<C>,
221 _phantom: PhantomData<fn() -> (C, E)>,
222}
223
224impl<C, E> ReviveApi<C, E>
225where
226 C: subxt::Config,
227 C::AccountId: From<sr25519::PublicKey> + serde::de::DeserializeOwned + scale::Codec,
228 C::Address: From<sr25519::PublicKey>,
229 C::Signature: From<sr25519::Signature>,
230 <C::ExtrinsicParams as ExtrinsicParams<C>>::Params:
231 From<<DefaultExtrinsicParams<C> as ExtrinsicParams<C>>::Params>,
232 E: Environment,
233 E::Balance: scale::HasCompact + serde::Serialize + std::fmt::Debug,
234{
235 pub async fn new(rpc: RpcClient) -> Result<Self, subxt::Error> {
237 let client = OnlineClient::<C>::from_rpc_client(rpc.clone()).await?;
238 let rpc = LegacyRpcMethods::<C>::new(rpc);
239 Ok(Self {
240 rpc,
241 client,
242 _phantom: Default::default(),
243 })
244 }
245
246 pub async fn transfer_allow_death(
251 &self,
252 origin: &Keypair,
253 dest: C::AccountId,
254 value: E::Balance,
255 ) -> Result<(), subxt::Error> {
256 let call = subxt::tx::DefaultPayload::new(
257 "Balances",
258 "transfer_allow_death",
259 Transfer::<E, C> {
260 dest: subxt::utils::Static(dest.into()),
261 value,
262 },
263 )
264 .unvalidated();
265
266 let _ = self.submit_extrinsic(&call, origin).await;
267
268 Ok(())
269 }
270
271 pub async fn instantiate_with_code_dry_run(
273 &self,
274 value: E::Balance,
275 storage_deposit_limit: DepositLimit<E::Balance>,
276 code: Vec<u8>,
277 data: Vec<u8>,
278 salt: Option<[u8; 32]>,
279 signer: &Keypair,
280 ) -> ContractInstantiateResultFor<E> {
281 let code = Code::Upload(code);
282 let storage_deposit_limit = match storage_deposit_limit {
283 DepositLimit::Balance(v) => Some(v),
284 DepositLimit::UnsafeOnlyForDryRun => None,
285 };
286 let call_request = RpcInstantiateRequest::<C, E> {
287 origin: Signer::<C>::account_id(signer),
288 value,
289 gas_limit: None,
290 storage_deposit_limit,
291 code,
292 data,
293 salt,
294 };
295 let func = "ReviveApi_instantiate";
296 let params = scale::Encode::encode(&call_request);
297 let bytes = self
298 .rpc
299 .state_call(func, Some(¶ms), None)
300 .await
301 .unwrap_or_else(|err| {
302 panic!("error on ws request `revive_instantiate`: {err:?}");
303 });
304 scale::Decode::decode(&mut bytes.as_ref()).unwrap_or_else(|err| {
305 panic!("decoding `ContractInstantiateResult` failed: {err}")
306 })
307 }
308
309 pub async fn create_extrinsic<Call>(
311 &self,
312 call: &Call,
313 signer: &Keypair,
314 ) -> SubmittableTransaction<C, OnlineClient<C>>
315 where
316 Call: subxt::tx::Payload,
317 {
318 let account_id = <Keypair as Signer<C>>::account_id(signer);
319 let account_nonce =
320 self.get_account_nonce(&account_id)
321 .await
322 .unwrap_or_else(|err| {
323 panic!("error calling `get_account_nonce`: {err:?}");
324 });
325
326 let params = DefaultExtrinsicParamsBuilder::new()
327 .nonce(account_nonce)
328 .build();
329 self.client
330 .tx()
331 .create_partial_offline(call, params.into())
332 .unwrap_or_else(|err| {
333 panic!("error on call `create_signed_with_nonce`: {err:?}");
334 })
335 .sign(signer)
336 }
337
338 pub async fn submit_extrinsic<Call>(
340 &self,
341 call: &Call,
342 signer: &Keypair,
343 ) -> (ExtrinsicEvents<C>, Option<CallTrace>)
344 where
345 Call: subxt::tx::Payload,
346 {
347 let parent_hash = self.best_block().await;
352
353 let mut tx = self
354 .create_extrinsic(call, signer)
355 .await
356 .submit_and_watch()
357 .await
358 .inspect(|tx_progress| {
359 log_info(&format!(
360 "signed and submitted tx with hash {:?}",
361 tx_progress.extrinsic_hash()
362 ));
363 })
364 .unwrap_or_else(|err| {
365 panic!("error on call `submit_and_watch`: {err:?}");
366 });
367
368 while let Some(status) = tx.next().await {
375 match status.unwrap_or_else(|err| {
376 panic!("error subscribing to tx status: {err:?}");
377 }) {
378 TxStatus::InBestBlock(tx_in_block)
379 | TxStatus::InFinalizedBlock(tx_in_block) => {
380 let events = tx_in_block.fetch_events().await.unwrap_or_else(|err| {
381 panic!("error on call `fetch_events`: {err:?}");
382 });
383 let trace = self
384 .trace(
385 tx_in_block.block_hash(),
386 Some(tx_in_block.extrinsic_hash()),
387 parent_hash,
388 None,
389 )
390 .await;
391 return (events, trace)
392 }
393 TxStatus::Error { message } => {
394 panic!("TxStatus::Error: {message:?}");
395 }
396 TxStatus::Invalid { message } => {
397 panic!("TxStatus::Invalid: {message:?}");
398 }
399 TxStatus::Dropped { message } => {
400 panic!("TxStatus::Dropped: {message:?}");
401 }
402 _ => continue,
403 }
404 }
405 panic!("Error waiting for tx status")
406 }
407
408 pub async fn trace(
410 &self,
411 block_hash: HashFor<C>,
412 extrinsic_hash: Option<HashFor<C>>,
413 parent_hash: HashFor<C>,
414 extrinsic: Option<Vec<u8>>,
415 ) -> Option<CallTrace> {
416 let block_details = self
418 .rpc
419 .chain_get_block(Some(block_hash))
420 .await
421 .expect("no block found")
422 .expect("no block details found");
423 let header = block_details.block.header;
424 let mut exts: Vec<OpaqueExtrinsic> = block_details
425 .block
426 .extrinsics
427 .clone()
428 .into_iter()
429 .filter_map(|e| scale::Decode::decode(&mut &e[..]).ok())
430 .collect::<Vec<_>>();
431
432 let tx_index: usize = match (extrinsic_hash, extrinsic) {
434 (Some(hash), None) => {
435 block_details
436 .block
437 .extrinsics
438 .iter()
439 .cloned()
440 .enumerate()
441 .find_map(|(index, ext)| {
442 let hash_ext = Transaction::<C>::from_bytes(ext.0)
443 .hash_with(self.client.hasher());
444 if hash_ext == hash {
445 return Some(index);
446 }
447 None
448 })
449 .expect("the extrinsic hash was not found in the block")
450 }
451 (None, Some(extrinsic)) => {
452 exts.push(
453 OpaqueExtrinsic::from_bytes(&extrinsic[..])
454 .expect("OpaqueExtrinsic cannot be created"),
455 );
456 exts.len() - 1
457 }
458 _ => panic!("pattern error"),
459 };
460
461 let tracer_type = TracerType::CallTracer(Some(CallTracerConfig::default()));
462 let func = "ReviveApi_trace_tx";
463
464 let params =
465 scale::Encode::encode(&((header, exts), tx_index.as_u32(), tracer_type));
466
467 let bytes = self
468 .rpc
469 .state_call(func, Some(¶ms), Some(parent_hash))
470 .await
471 .unwrap_or_else(|err| {
472 panic!(
473 "error on ws request `trace_tx`: {err:?}\n\n{:#}",
474 format!("{err}").trim_start_matches("RPC error: ")
475 );
476 });
477
478 let trace: Option<Trace> = scale::Decode::decode(&mut bytes.as_ref())
479 .unwrap_or_else(|err| panic!("decoding `trace_tx` result failed: {err}"));
480
481 match trace {
482 Some(Trace::Call(trace)) => Some(trace),
483 _ => None,
484 }
485 }
486
487 pub async fn best_block(&self) -> HashFor<C> {
489 self.rpc
490 .chain_get_block_hash(None)
491 .await
492 .unwrap_or_else(|err| {
493 panic!("error on call `chain_get_block_hash`: {err:?}");
494 })
495 .unwrap_or_else(|| {
496 panic!("error on call `chain_get_block_hash`: no best block found");
497 })
498 }
499
500 async fn get_account_nonce(
502 &self,
503 account_id: &C::AccountId,
504 ) -> Result<u64, subxt::Error> {
505 let best_block = self.best_block().await;
506 let account_nonce = self
507 .client
508 .blocks()
509 .at(best_block)
510 .await?
511 .account_nonce(account_id)
512 .await?;
513 Ok(account_nonce)
514 }
515
516 #[allow(clippy::too_many_arguments)]
521 pub async fn instantiate_with_code(
522 &self,
523 value: E::Balance,
524 gas_limit: Weight,
525 storage_deposit_limit: E::Balance,
526 code: Vec<u8>,
527 data: Vec<u8>,
528 salt: Option<[u8; 32]>,
529 signer: &Keypair,
530 ) -> (ExtrinsicEvents<C>, Option<CallTrace>) {
531 let call = subxt::tx::DefaultPayload::new(
532 "Revive",
533 "instantiate_with_code",
534 InstantiateWithCode::<E> {
535 value,
536 gas_limit,
537 storage_deposit_limit,
538 code,
539 data,
540 salt,
541 },
542 )
543 .unvalidated();
544
545 self.submit_extrinsic(&call, signer).await
546 }
547
548 pub async fn upload_dry_run(
550 &self,
551 signer: &Keypair,
552 code: Vec<u8>,
553 storage_deposit_limit: Option<E::Balance>,
554 ) -> CodeUploadResult<E::Balance> {
555 let call_request = RpcCodeUploadRequest::<C, E> {
556 origin: Signer::<C>::account_id(signer),
557 code,
558 storage_deposit_limit,
559 };
560 let func = "ReviveApi_upload_code";
561 let params = scale::Encode::encode(&call_request);
562 let bytes = self
563 .rpc
564 .state_call(func, Some(¶ms), None)
565 .await
566 .unwrap_or_else(|err| {
567 panic!("error on ws request `upload_code`: {err:?}");
568 });
569 scale::Decode::decode(&mut bytes.as_ref())
570 .unwrap_or_else(|err| panic!("decoding CodeUploadResult failed: {err}"))
571 }
572
573 pub async fn upload(
578 &self,
579 signer: &Keypair,
580 code: Vec<u8>,
581 storage_deposit_limit: E::Balance,
582 ) -> (ExtrinsicEvents<C>, Option<CallTrace>) {
583 let call = subxt::tx::DefaultPayload::new(
584 "Revive",
585 "upload_code",
586 UploadCode::<E> {
587 code,
588 storage_deposit_limit,
589 },
590 )
591 .unvalidated();
592
593 self.submit_extrinsic(&call, signer).await
594 }
595
596 pub async fn remove_code(
601 &self,
602 signer: &Keypair,
603 code_hash: H256,
604 ) -> (ExtrinsicEvents<C>, Option<CallTrace>) {
605 let call = subxt::tx::DefaultPayload::new(
606 "Revive",
607 "remove_code",
608 RemoveCode { code_hash },
609 )
610 .unvalidated();
611
612 self.submit_extrinsic(&call, signer).await
613 }
614
615 pub async fn call_dry_run(
617 &self,
618 dest: Address,
619 data: Vec<u8>,
620 value: E::Balance,
621 storage_deposit_limit: DepositLimit<E::Balance>,
622 signer: &Keypair,
623 ) -> (ContractExecResultFor<E>, Option<CallTrace>) {
624 let origin = Signer::<C>::account_id(signer);
625 let call_request = RpcCallRequest::<C, E> {
626 origin,
627 dest,
628 value,
629 gas_limit: Some(Weight {
630 ref_time: u64::MAX,
631 proof_size: u64::MAX,
632 }),
633 storage_deposit_limit: storage_deposit_limit.clone(),
634 input_data: data.clone(),
635 };
636 let func = "ReviveApi_call";
637 let params = scale::Encode::encode(&call_request);
638 let bytes = self
639 .rpc
640 .state_call(func, Some(¶ms), None)
641 .await
642 .unwrap_or_else(|err| {
643 panic!("error on ws request `ReviveApi_call`: {err:?}");
644 });
645 let dry_run_result: ContractExecResultFor<E> =
646 scale::Decode::decode(&mut bytes.as_ref()).unwrap_or_else(|err| {
647 panic!("decoding `ContractExecResult` failed: {err}")
648 });
649
650 let storage_deposit_limit = match storage_deposit_limit {
654 DepositLimit::UnsafeOnlyForDryRun => {
655 dry_run_result.storage_deposit.charge_or_zero()
656 }
657 DepositLimit::Balance(limit) => limit,
658 };
659
660 let call = subxt::tx::DefaultPayload::new(
661 "Revive",
662 "call",
663 crate::xts::Call::<E> {
664 dest,
665 value,
666 gas_limit: Weight {
667 ref_time: dry_run_result.gas_required.ref_time(),
668 proof_size: dry_run_result.gas_required.proof_size(),
669 },
670 storage_deposit_limit,
671 data,
672 },
673 )
674 .unvalidated();
675 let xt = self.create_extrinsic(&call, signer).await;
676 let block_hash = self.best_block().await;
677 let block_details = self
678 .rpc
679 .chain_get_block(Some(block_hash))
680 .await
681 .expect("no block found")
682 .expect("no block details found");
683 let block_number: u64 = block_details.block.header.number().into();
684 let parent_hash = self
685 .rpc
686 .chain_get_block_hash(Some((block_number - 1u64).into()))
687 .await
688 .expect("no block hash found")
689 .expect("no block details found");
690
691 let trace = self
692 .trace(block_hash, None, parent_hash, Some(xt.into_encoded()))
693 .await;
694
695 (dry_run_result, trace)
696 }
697
698 pub async fn call(
704 &self,
705 contract: Address,
706 value: E::Balance,
707 gas_limit: Weight,
708 storage_deposit_limit: E::Balance,
709 data: Vec<u8>,
710 signer: &Keypair,
711 ) -> (ExtrinsicEvents<C>, Option<CallTrace>) {
712 let call = subxt::tx::DefaultPayload::new(
713 "Revive",
714 "call",
715 Call::<E> {
716 dest: contract,
717 value,
718 gas_limit,
719 storage_deposit_limit,
720 data,
721 },
722 )
723 .unvalidated();
724 self.submit_extrinsic(&call, signer).await
725 }
726
727 pub async fn map_account(
734 &self,
735 signer: &Keypair,
736 ) -> (ExtrinsicEvents<C>, Option<CallTrace>) {
737 let call = subxt::tx::DefaultPayload::new("Revive", "map_account", MapAccount {})
740 .unvalidated();
741
742 self.submit_extrinsic(&call, signer).await
743 }
744
745 pub async fn runtime_call<'a>(
752 &self,
753 signer: &Keypair,
754 pallet_name: &'a str,
755 call_name: &'a str,
756 call_data: Vec<subxt::dynamic::Value>,
757 ) -> (ExtrinsicEvents<C>, Option<CallTrace>) {
758 let call = subxt::dynamic::tx(pallet_name, call_name, call_data);
759 self.submit_extrinsic(&call, signer).await
760 }
761}