referrerpolicy=no-referrer-when-downgrade

pallet_revive_eth_rpc/
example.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17//! Example utilities
18use crate::{EthRpcClient, ReceiptInfo};
19use anyhow::Context;
20use pallet_revive::evm::*;
21use std::sync::Arc;
22
23/// Transaction builder.
24pub struct TransactionBuilder<Client: EthRpcClient + Sync + Send> {
25	client: Arc<Client>,
26	signer: Account,
27	value: U256,
28	input: Bytes,
29	to: Option<H160>,
30	nonce: Option<U256>,
31	mutate: Box<dyn FnOnce(&mut TransactionLegacyUnsigned)>,
32}
33
34#[derive(Debug)]
35pub struct SubmittedTransaction<Client: EthRpcClient + Sync + Send> {
36	tx: GenericTransaction,
37	hash: H256,
38	client: Arc<Client>,
39}
40
41impl<Client: EthRpcClient + Sync + Send> SubmittedTransaction<Client> {
42	/// Get the hash of the transaction.
43	pub fn hash(&self) -> H256 {
44		self.hash
45	}
46
47	/// The gas sent with the transaction.
48	pub fn gas(&self) -> U256 {
49		self.tx.gas.unwrap()
50	}
51
52	pub fn generic_transaction(&self) -> GenericTransaction {
53		self.tx.clone()
54	}
55
56	/// Wait for the receipt of the transaction.
57	pub async fn wait_for_receipt(&self) -> anyhow::Result<ReceiptInfo> {
58		let hash = self.hash();
59		for _ in 0..30 {
60			tokio::time::sleep(std::time::Duration::from_secs(2)).await;
61			let receipt = self.client.get_transaction_receipt(hash).await?;
62			if let Some(receipt) = receipt {
63				if receipt.is_success() {
64					assert!(
65						self.gas() > receipt.gas_used,
66						"Gas used should be less than gas estimated."
67					);
68					return Ok(receipt);
69				} else {
70					anyhow::bail!("Transaction failed receipt: {receipt:?}")
71				}
72			}
73		}
74
75		anyhow::bail!("Timeout, failed to get receipt")
76	}
77}
78
79impl<Client: EthRpcClient + Send + Sync> TransactionBuilder<Client> {
80	pub fn new(client: &Arc<Client>) -> Self {
81		Self {
82			client: Arc::clone(client),
83			signer: Account::default(),
84			value: U256::zero(),
85			input: Bytes::default(),
86			to: None,
87			nonce: None,
88			mutate: Box::new(|_| {}),
89		}
90	}
91	/// Set the signer.
92	pub fn signer(mut self, signer: Account) -> Self {
93		self.signer = signer;
94		self
95	}
96
97	/// Set the value.
98	pub fn value(mut self, value: U256) -> Self {
99		self.value = value;
100		self
101	}
102
103	/// Set the input.
104	pub fn input(mut self, input: Vec<u8>) -> Self {
105		self.input = Bytes(input);
106		self
107	}
108
109	/// Set the destination.
110	pub fn to(mut self, to: H160) -> Self {
111		self.to = Some(to);
112		self
113	}
114
115	/// Set the nonce.
116	pub fn nonce(mut self, nonce: U256) -> Self {
117		self.nonce = Some(nonce);
118		self
119	}
120
121	/// Set a mutation function, that mutates the transaction before sending.
122	pub fn mutate(mut self, mutate: impl FnOnce(&mut TransactionLegacyUnsigned) + 'static) -> Self {
123		self.mutate = Box::new(mutate);
124		self
125	}
126
127	/// Call eth_call to get the result of a view function
128	pub async fn eth_call(self) -> anyhow::Result<Vec<u8>> {
129		let TransactionBuilder { client, signer, value, input, to, .. } = self;
130
131		let from = signer.address();
132		let result = client
133			.call(
134				GenericTransaction {
135					from: Some(from),
136					input: input.into(),
137					value: Some(value),
138					to,
139					..Default::default()
140				},
141				None,
142			)
143			.await
144			.with_context(|| "eth_call failed")?;
145		Ok(result.0)
146	}
147
148	/// Send the transaction.
149	pub async fn send(self) -> anyhow::Result<SubmittedTransaction<Client>> {
150		let TransactionBuilder { client, signer, value, input, to, nonce, mutate } = self;
151
152		let from = signer.address();
153		let chain_id = Some(client.chain_id().await?);
154		let gas_price = client.gas_price().await?;
155		let nonce = if let Some(nonce) = nonce {
156			nonce
157		} else {
158			client
159				.get_transaction_count(from, BlockTag::Latest.into())
160				.await
161				.with_context(|| "Failed to fetch account nonce")?
162		};
163
164		let gas = client
165			.estimate_gas(
166				GenericTransaction {
167					from: Some(from),
168					input: input.clone().into(),
169					value: Some(value),
170					gas_price: Some(gas_price),
171					to,
172					..Default::default()
173				},
174				None,
175			)
176			.await
177			.with_context(|| "Failed to fetch gas estimate")?;
178
179		let mut unsigned_tx = TransactionLegacyUnsigned {
180			gas,
181			nonce,
182			to,
183			value,
184			input,
185			gas_price,
186			chain_id,
187			..Default::default()
188		};
189
190		mutate(&mut unsigned_tx);
191
192		let signed_tx = signer.sign_transaction(unsigned_tx.into());
193		let bytes = signed_tx.signed_payload();
194
195		let hash = client
196			.send_raw_transaction(bytes.into())
197			.await
198			.with_context(|| "send_raw_transaction failed")?;
199
200		Ok(SubmittedTransaction {
201			tx: GenericTransaction::from_signed(signed_tx, gas_price, Some(from)),
202			hash,
203			client,
204		})
205	}
206}
207
208#[test]
209fn test_dummy_payload_has_correct_len() {
210	let signer = Account::from(subxt_signer::eth::dev::ethan());
211	let unsigned_tx: TransactionUnsigned =
212		TransactionLegacyUnsigned { input: vec![42u8; 100].into(), ..Default::default() }.into();
213
214	let signed_tx = signer.sign_transaction(unsigned_tx.clone());
215	let signed_payload = signed_tx.signed_payload();
216	let unsigned_tx = signed_tx.unsigned();
217
218	let dummy_payload = unsigned_tx.dummy_signed_payload();
219	assert_eq!(dummy_payload.len(), signed_payload.len());
220}