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