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	mutate: Box<dyn FnOnce(&mut TransactionLegacyUnsigned)>,
31}
32
33#[derive(Debug)]
34pub struct SubmittedTransaction<Client: EthRpcClient + Sync + Send> {
35	tx: GenericTransaction,
36	hash: H256,
37	client: Arc<Client>,
38}
39
40impl<Client: EthRpcClient + Sync + Send> SubmittedTransaction<Client> {
41	/// Get the hash of the transaction.
42	pub fn hash(&self) -> H256 {
43		self.hash
44	}
45
46	/// The gas sent with the transaction.
47	pub fn gas(&self) -> U256 {
48		self.tx.gas.unwrap()
49	}
50
51	/// Wait for the receipt of the transaction.
52	pub async fn wait_for_receipt(&self) -> anyhow::Result<ReceiptInfo> {
53		let hash = self.hash();
54		for _ in 0..30 {
55			tokio::time::sleep(std::time::Duration::from_secs(2)).await;
56			let receipt = self.client.get_transaction_receipt(hash).await?;
57			if let Some(receipt) = receipt {
58				if receipt.is_success() {
59					assert!(
60						self.gas() > receipt.gas_used,
61						"Gas used should be less than gas estimated."
62					);
63					return Ok(receipt)
64				} else {
65					anyhow::bail!("Transaction failed")
66				}
67			}
68		}
69
70		anyhow::bail!("Timeout, failed to get receipt")
71	}
72}
73
74impl<Client: EthRpcClient + Send + Sync> TransactionBuilder<Client> {
75	pub fn new(client: &Arc<Client>) -> Self {
76		Self {
77			client: Arc::clone(client),
78			signer: Account::default(),
79			value: U256::zero(),
80			input: Bytes::default(),
81			to: None,
82			mutate: Box::new(|_| {}),
83		}
84	}
85	/// Set the signer.
86	pub fn signer(mut self, signer: Account) -> Self {
87		self.signer = signer;
88		self
89	}
90
91	/// Set the value.
92	pub fn value(mut self, value: U256) -> Self {
93		self.value = value;
94		self
95	}
96
97	/// Set the input.
98	pub fn input(mut self, input: Vec<u8>) -> Self {
99		self.input = Bytes(input);
100		self
101	}
102
103	/// Set the destination.
104	pub fn to(mut self, to: H160) -> Self {
105		self.to = Some(to);
106		self
107	}
108
109	/// Set a mutation function, that mutates the transaction before sending.
110	pub fn mutate(mut self, mutate: impl FnOnce(&mut TransactionLegacyUnsigned) + 'static) -> Self {
111		self.mutate = Box::new(mutate);
112		self
113	}
114
115	/// Call eth_call to get the result of a view function
116	pub async fn eth_call(self) -> anyhow::Result<Vec<u8>> {
117		let TransactionBuilder { client, signer, value, input, to, .. } = self;
118
119		let from = signer.address();
120		let result = client
121			.call(
122				GenericTransaction {
123					from: Some(from),
124					input: input.into(),
125					value: Some(value),
126					to,
127					..Default::default()
128				},
129				None,
130			)
131			.await
132			.with_context(|| "eth_call failed")?;
133		Ok(result.0)
134	}
135
136	/// Send the transaction.
137	pub async fn send(self) -> anyhow::Result<SubmittedTransaction<Client>> {
138		let TransactionBuilder { client, signer, value, input, to, mutate } = self;
139
140		let from = signer.address();
141		let chain_id = Some(client.chain_id().await?);
142		let gas_price = client.gas_price().await?;
143		let nonce = client
144			.get_transaction_count(from, BlockTag::Latest.into())
145			.await
146			.with_context(|| "Failed to fetch account nonce")?;
147
148		let gas = client
149			.estimate_gas(
150				GenericTransaction {
151					from: Some(from),
152					input: input.clone().into(),
153					value: Some(value),
154					gas_price: Some(gas_price),
155					to,
156					..Default::default()
157				},
158				None,
159			)
160			.await
161			.with_context(|| "Failed to fetch gas estimate")?;
162
163		println!("Gas estimate: {gas:?}");
164		let mut unsigned_tx = TransactionLegacyUnsigned {
165			gas,
166			nonce,
167			to,
168			value,
169			input,
170			gas_price,
171			chain_id,
172			..Default::default()
173		};
174
175		mutate(&mut unsigned_tx);
176
177		let signed_tx = signer.sign_transaction(unsigned_tx.into());
178		let bytes = signed_tx.signed_payload();
179
180		let hash = client
181			.send_raw_transaction(bytes.into())
182			.await
183			.with_context(|| "transaction failed")?;
184
185		Ok(SubmittedTransaction {
186			tx: GenericTransaction::from_signed(signed_tx, gas_price, Some(from)),
187			hash,
188			client,
189		})
190	}
191}
192
193#[test]
194fn test_dummy_payload_has_correct_len() {
195	let signer = Account::from(subxt_signer::eth::dev::ethan());
196	let unsigned_tx: TransactionUnsigned =
197		TransactionLegacyUnsigned { input: vec![42u8; 100].into(), ..Default::default() }.into();
198
199	let signed_tx = signer.sign_transaction(unsigned_tx.clone());
200	let signed_payload = signed_tx.signed_payload();
201	let unsigned_tx = signed_tx.unsigned();
202
203	let dummy_payload = unsigned_tx.dummy_signed_payload();
204	assert_eq!(dummy_payload.len(), signed_payload.len());
205}