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