1use super::{
16 log_info,
17 sr25519,
18 Keypair,
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 evm::{
34 CallTrace,
35 CallTracerConfig,
36 Trace,
37 TracerType,
38 },
39 CodeUploadResult,
40};
41use sp_core::H256;
42use sp_runtime::OpaqueExtrinsic;
43use subxt::{
44 backend::{
45 legacy::LegacyRpcMethods,
46 rpc::RpcClient,
47 },
48 blocks::ExtrinsicEvents,
49 config::{
50 DefaultExtrinsicParams,
51 DefaultExtrinsicParamsBuilder,
52 ExtrinsicParams,
53 HashFor,
54 Header,
55 },
56 ext::{
57 scale_encode,
58 subxt_core::tx::Transaction,
59 },
60 tx::{
61 Signer,
62 SubmittableTransaction,
63 TxStatus,
64 },
65 OnlineClient,
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 serde::Serialize,
81 serde::Deserialize,
82)]
83#[encode_as_type(crate_path = "subxt::ext::scale_encode")]
84pub struct Weight {
85 #[codec(compact)]
86 ref_time: u64,
88 #[codec(compact)]
89 proof_size: u64,
91}
92
93impl From<sp_weights::Weight> for Weight {
94 fn from(weight: sp_weights::Weight) -> Self {
95 Self {
96 ref_time: weight.ref_time(),
97 proof_size: weight.proof_size(),
98 }
99 }
100}
101
102impl From<Weight> for sp_weights::Weight {
103 fn from(weight: Weight) -> Self {
104 sp_weights::Weight::from_parts(weight.ref_time, weight.proof_size)
105 }
106}
107
108#[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)]
124#[encode_as_type(trait_bounds = "", crate_path = "subxt::ext::scale_encode")]
125pub struct Call<E: Environment> {
126 dest: Address,
127 #[codec(compact)]
128 value: E::Balance,
129 gas_limit: Weight,
130 #[codec(compact)]
131 storage_deposit_limit: E::Balance,
132 data: Vec<u8>,
133}
134
135#[derive(Debug, scale::Decode, scale::Encode, scale_encode::EncodeAsType)]
137#[encode_as_type(trait_bounds = "", crate_path = "subxt::ext::scale_encode")]
138pub struct MapAccount {}
139
140#[derive(Debug, scale::Decode, scale::Encode, scale_encode::EncodeAsType)]
142#[encode_as_type(trait_bounds = "", crate_path = "subxt::ext::scale_encode")]
143pub struct Transfer<E: Environment, C: subxt::Config> {
144 dest: subxt::utils::Static<C::Address>,
145 #[codec(compact)]
146 value: E::Balance,
147}
148
149#[derive(Debug, scale::Encode, scale::Decode, scale_encode::EncodeAsType)]
151#[encode_as_type(trait_bounds = "", crate_path = "subxt::ext::scale_encode")]
152pub struct RemoveCode {
153 code_hash: H256,
154}
155
156#[derive(Debug, scale::Encode, scale::Decode, scale_encode::EncodeAsType)]
158#[encode_as_type(trait_bounds = "", crate_path = "subxt::ext::scale_encode")]
159pub struct UploadCode<E: Environment> {
160 code: Vec<u8>,
161 #[codec(compact)]
162 storage_deposit_limit: E::Balance,
163}
164
165#[derive(scale::Encode)]
167struct RpcInstantiateRequest<C: subxt::Config, E: Environment> {
170 origin: C::AccountId,
171 value: E::Balance,
172 gas_limit: Option<Weight>,
173 storage_deposit_limit: Option<E::Balance>,
174 code: Code,
175 data: Vec<u8>,
176 salt: Option<[u8; 32]>,
177}
178
179#[derive(scale::Encode)]
181struct RpcCodeUploadRequest<C: subxt::Config, E: Environment>
184where
185 E::Balance: serde::Serialize,
186{
187 origin: C::AccountId,
188 code: Vec<u8>,
189 storage_deposit_limit: Option<E::Balance>,
190}
191
192#[derive(scale::Encode)]
194struct RpcCallRequest<C: subxt::Config, E: Environment> {
197 origin: C::AccountId,
198 dest: Address,
199 value: E::Balance,
200 gas_limit: Option<Weight>,
201 storage_deposit_limit: Option<E::Balance>,
202 input_data: Vec<u8>,
203}
204
205#[derive(serde::Serialize, scale::Encode)]
207#[serde(rename_all = "camelCase")]
208enum Code {
209 Upload(Vec<u8>),
211 #[allow(unused)]
212 Existing(H256),
214}
215
216pub struct ReviveApi<C: subxt::Config, E: Environment> {
218 pub rpc: LegacyRpcMethods<C>,
219 pub client: OnlineClient<C>,
220 _phantom: PhantomData<fn() -> (C, E)>,
221}
222
223impl<C, E> ReviveApi<C, E>
224where
225 C: subxt::Config,
226 C::AccountId: From<sr25519::PublicKey> + serde::de::DeserializeOwned + scale::Codec,
227 C::Address: From<sr25519::PublicKey>,
228 C::Signature: From<sr25519::Signature>,
229 <C::ExtrinsicParams as ExtrinsicParams<C>>::Params:
230 From<<DefaultExtrinsicParams<C> as ExtrinsicParams<C>>::Params>,
231
232 E: Environment,
233 E::Balance: scale::HasCompact + serde::Serialize,
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 try_transfer_balance(
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 let index = 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 index
451 }
452 (None, Some(extrinsic)) => {
453 exts.push(
454 OpaqueExtrinsic::from_bytes(&extrinsic[..])
455 .expect("OpaqueExtrinsic cannot be created"),
456 );
457 exts.len() - 1
458 }
459 _ => panic!("pattern error"),
460 };
461
462 let tracer_type = TracerType::CallTracer(Some(CallTracerConfig::default()));
463 let func = "ReviveApi_trace_tx";
464
465 let params =
466 scale::Encode::encode(&((header, exts), tx_index.as_u32(), tracer_type));
467
468 let bytes = self
469 .rpc
470 .state_call(func, Some(¶ms), Some(parent_hash))
471 .await
472 .unwrap_or_else(|err| {
473 panic!(
474 "error on ws request `trace_tx`: {err:?}\n\n{:#}",
475 format!("{err}").trim_start_matches("RPC error: ")
476 );
477 });
478
479 let trace: Option<Trace> = scale::Decode::decode(&mut bytes.as_ref())
480 .unwrap_or_else(|err| panic!("decoding `trace_tx` result failed: {err}"));
481
482 match trace {
483 Some(Trace::Call(trace)) => Some(trace),
484 _ => None,
485 }
486 }
487
488 pub async fn best_block(&self) -> HashFor<C> {
490 self.rpc
491 .chain_get_block_hash(None)
492 .await
493 .unwrap_or_else(|err| {
494 panic!("error on call `chain_get_block_hash`: {err:?}");
495 })
496 .unwrap_or_else(|| {
497 panic!("error on call `chain_get_block_hash`: no best block found");
498 })
499 }
500
501 async fn get_account_nonce(
503 &self,
504 account_id: &C::AccountId,
505 ) -> Result<u64, subxt::Error> {
506 let best_block = self.best_block().await;
507 let account_nonce = self
508 .client
509 .blocks()
510 .at(best_block)
511 .await?
512 .account_nonce(account_id)
513 .await?;
514 Ok(account_nonce)
515 }
516
517 #[allow(clippy::too_many_arguments)]
522 pub async fn instantiate_with_code(
523 &self,
524 value: E::Balance,
525 gas_limit: Weight,
526 storage_deposit_limit: E::Balance,
527 code: Vec<u8>,
528 data: Vec<u8>,
529 salt: Option<[u8; 32]>,
530 signer: &Keypair,
531 ) -> (ExtrinsicEvents<C>, Option<CallTrace>) {
532 let call = subxt::tx::DefaultPayload::new(
533 "Revive",
534 "instantiate_with_code",
535 InstantiateWithCode::<E> {
536 value,
537 gas_limit,
538 storage_deposit_limit, code,
540 data,
541 salt,
542 },
543 )
544 .unvalidated();
545
546 self.submit_extrinsic(&call, signer).await
547 }
548
549 pub async fn upload_dry_run(
551 &self,
552 signer: &Keypair,
553 code: Vec<u8>,
554 _storage_deposit_limit: E::Balance,
556 ) -> CodeUploadResult<E::Balance> {
557 let call_request = RpcCodeUploadRequest::<C, E> {
558 origin: Signer::<C>::account_id(signer),
559 code,
560 storage_deposit_limit: None,
562 };
563 let func = "ReviveApi_upload_code";
564 let params = scale::Encode::encode(&call_request);
565 let bytes = self
566 .rpc
567 .state_call(func, Some(¶ms), None)
568 .await
569 .unwrap_or_else(|err| {
570 panic!("error on ws request `upload_code`: {err:?}");
571 });
572 scale::Decode::decode(&mut bytes.as_ref())
573 .unwrap_or_else(|err| panic!("decoding CodeUploadResult failed: {err}"))
574 }
575
576 pub async fn upload(
581 &self,
582 signer: &Keypair,
583 code: Vec<u8>,
584 storage_deposit_limit: E::Balance,
585 ) -> ExtrinsicEvents<C> {
586 let call = subxt::tx::DefaultPayload::new(
587 "Revive",
588 "upload_code",
589 UploadCode::<E> {
590 code,
591 storage_deposit_limit,
592 },
594 )
595 .unvalidated();
596
597 self.submit_extrinsic(&call, signer).await.0
598 }
599
600 pub async fn remove_code(
605 &self,
606 signer: &Keypair,
607 code_hash: H256,
608 ) -> ExtrinsicEvents<C> {
609 let call = subxt::tx::DefaultPayload::new(
610 "Revive",
611 "remove_code",
612 RemoveCode { code_hash },
613 )
614 .unvalidated();
615
616 self.submit_extrinsic(&call, signer).await.0
617 }
618
619 pub async fn call_dry_run(
621 &self,
622 origin: C::AccountId,
623 dest: Address,
624 input_data: Vec<u8>,
625 value: E::Balance,
626 _storage_deposit_limit: E::Balance, signer: &Keypair,
628 ) -> (ContractExecResultFor<E>, Option<CallTrace>) {
629 let call_request = RpcCallRequest::<C, E> {
630 origin,
631 dest,
632 value,
633 gas_limit: Some(Weight {
634 ref_time: u64::MAX,
635 proof_size: u64::MAX,
636 }),
637 storage_deposit_limit: None,
638 input_data: input_data.clone(),
639 };
640 let func = "ReviveApi_call";
641 let params = scale::Encode::encode(&call_request);
642 let bytes = self
643 .rpc
644 .state_call(func, Some(¶ms), None)
645 .await
646 .unwrap_or_else(|err| {
647 panic!("error on ws request `contracts_call`: {err:?}");
648 });
649 let res: ContractExecResultFor<E> = scale::Decode::decode(&mut bytes.as_ref())
650 .unwrap_or_else(|err| panic!("decoding ContractExecResult failed: {err}"));
651
652 let call = subxt::tx::DefaultPayload::new(
657 "Revive",
658 "call",
659 crate::xts::Call::<E> {
660 dest,
661 value,
662 gas_limit: Weight {
663 ref_time: u64::MAX,
664 proof_size: u64::MAX,
665 },
666 storage_deposit_limit: E::Balance::from(u32::MAX), data: input_data,
668 },
669 )
670 .unvalidated();
671 let xt = self.create_extrinsic(&call, signer).await;
672
673 let block_hash = self.best_block().await;
674
675 let block_details = self
676 .rpc
677 .chain_get_block(Some(block_hash))
678 .await
679 .expect("no block found")
680 .expect("no block details found");
681 let block_number: u64 = block_details.block.header.number().into();
683 let parent_hash = self
684 .rpc
685 .chain_get_block_hash(Some((block_number - 1u64).into()))
686 .await
687 .expect("no block hash found")
688 .expect("no block details found");
689
690 let trace = self
691 .trace(block_hash, None, parent_hash, Some(xt.into_encoded()))
692 .await;
693
694 (res, trace)
695 }
696
697 pub async fn call(
703 &self,
704 contract: Address,
705 value: E::Balance,
706 gas_limit: Weight,
707 storage_deposit_limit: E::Balance,
708 data: Vec<u8>,
709 signer: &Keypair,
710 ) -> (ExtrinsicEvents<C>, Option<CallTrace>) {
711 let call = subxt::tx::DefaultPayload::new(
712 "Revive",
713 "call",
714 Call::<E> {
715 dest: contract,
716 value,
717 gas_limit,
718 storage_deposit_limit,
719 data,
720 },
721 )
722 .unvalidated();
723
724 self.submit_extrinsic(&call, signer).await
725 }
726
727 pub async fn map_account(&self, signer: &Keypair) -> ExtrinsicEvents<C> {
733 let call = subxt::tx::DefaultPayload::new("Revive", "map_account", MapAccount {})
734 .unvalidated();
735
736 self.submit_extrinsic(&call, signer).await.0
737 }
738
739 pub async fn runtime_call<'a>(
746 &self,
747 signer: &Keypair,
748 pallet_name: &'a str,
749 call_name: &'a str,
750 call_data: Vec<subxt::dynamic::Value>,
751 ) -> ExtrinsicEvents<C> {
752 let call = subxt::dynamic::tx(pallet_name, call_name, call_data);
753
754 self.submit_extrinsic(&call, signer).await.0
755 }
756}