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