1use serde::de::DeserializeOwned;
2use serde_json::{Value, json};
3use yevm_base::{Acc, Int};
4use yevm_misc::{buf::Buf, http::Http};
5
6use crate::{
7 call::{Block, Head, Receipt, TxFull},
8 chain::Chain,
9 state::Account,
10};
11
12pub struct Rpc {
13 url: String,
14 http: Http,
15 pub block_number: u64,
16 pub block_hash: Int,
17}
18
19impl Rpc {
20 pub async fn latest(url: String) -> eyre::Result<Self> {
21 let http = Http::new();
22 let head = head(&http, &url, "latest".into()).await?;
23 Ok(Self {
24 url,
25 http,
26 block_number: head.number.as_u64(),
27 block_hash: head.hash,
28 })
29 }
30
31 pub async fn number(url: String, number: u64) -> eyre::Result<Self> {
32 let http = Http::new();
33 let head = head(&http, &url, format!("0x{:x}", number)).await?;
34 Ok(Self {
35 url,
36 http,
37 block_number: head.number.as_u64(),
38 block_hash: head.hash,
39 })
40 }
41
42 pub fn offline() -> Self {
43 Self {
44 url: "".to_string(),
45 http: Http::new(),
46 block_number: 0,
47 block_hash: Int::zero(),
48 }
49 }
50
51 pub fn reset(&mut self, number: u64, hash: Int) {
52 self.block_number = number;
53 self.block_hash = hash;
54 }
55
56 pub async fn chain_id(&self) -> eyre::Result<u64> {
57 let chain_id: Int = call(&self.http, &self.url, "eth_chainId", &[]).await?;
58 Ok(chain_id.as_u64())
59 }
60
61 pub async fn lookup(&self, block: u64, index: u64) -> eyre::Result<TxFull> {
62 let tx = call::<TxFull>(
63 &self.http,
64 &self.url,
65 "eth_getTransactionByBlockNumberAndIndex",
66 &[
67 Value::String(format!("0x{block:x}")),
68 Value::String(format!("0x{index:x}")),
69 ],
70 )
71 .await?;
72 Ok(tx)
73 }
74
75 pub async fn receipt(&self, hash: Int) -> eyre::Result<Receipt> {
76 receipt(&self.http, &self.url, hash).await
77 }
78}
79
80#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
81#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
82impl Chain for Rpc {
83 async fn get(&self, acc: &Acc, key: &Int) -> eyre::Result<Int> {
84 let value = call(
85 &self.http,
86 &self.url,
87 "eth_getStorageAt",
88 &[
89 Value::String(acc.to_string()),
90 Value::String(key.to_string()),
91 Value::String(self.block_hash.to_string()),
92 ],
93 )
94 .await?;
95 Ok(value)
96 }
97
98 async fn acc(&self, acc: &Acc) -> eyre::Result<Account> {
99 Ok(Account {
101 value: self.balance(acc).await?,
102 nonce: self.nonce(acc).await?.into(),
103 code: self.code(acc).await?,
104 })
105 }
106
107 async fn code(&self, acc: &Acc) -> eyre::Result<(Buf, Int)> {
108 let code: Buf = call(
109 &self.http,
110 &self.url,
111 "eth_getCode",
112 &[
113 Value::String(acc.to_string()),
114 Value::String(self.block_hash.to_string()),
115 ],
116 )
117 .await?;
118 let hash = yevm_misc::keccak256(code.as_slice());
119 Ok((code, hash))
120 }
121
122 async fn nonce(&self, acc: &Acc) -> eyre::Result<u64> {
123 let nonce: Int = call(
124 &self.http,
125 &self.url,
126 "eth_getTransactionCount",
127 &[
128 Value::String(acc.to_string()),
129 Value::String(self.block_hash.to_string()),
130 ],
131 )
132 .await?;
133 Ok(nonce.as_u64())
134 }
135
136 async fn balance(&self, acc: &Acc) -> eyre::Result<Int> {
137 let balance = call(
138 &self.http,
139 &self.url,
140 "eth_getBalance",
141 &[
142 Value::String(acc.to_string()),
143 Value::String(self.block_hash.to_string()),
144 ],
145 )
146 .await?;
147 Ok(balance)
148 }
149
150 async fn head(&self, number: u64) -> eyre::Result<Head> {
151 let head = call(
152 &self.http,
153 &self.url,
154 "eth_getBlockByNumber",
155 &[Value::String(format!("0x{:x}", number)), Value::Bool(false)],
156 )
157 .await?;
158 Ok(head)
159 }
160
161 async fn block(&self, number: u64) -> eyre::Result<Block> {
162 let block = call(
163 &self.http,
164 &self.url,
165 "eth_getBlockByNumber",
166 &[Value::String(format!("0x{:x}", number)), Value::Bool(true)],
167 )
168 .await?;
169 Ok(block)
170 }
171
172 async fn chain_id(&self) -> eyre::Result<u64> {
173 let chain_id: Int = call(&self.http, &self.url, "eth_chainId", &[]).await?;
174 Ok(chain_id.as_u64())
175 }
176}
177
178async fn head(http: &Http, url: &str, arg: String) -> eyre::Result<Head> {
179 let head = call(
180 http,
181 url,
182 "eth_getBlockByNumber",
183 &[Value::String(arg), Value::Bool(false)],
184 )
185 .await?;
186 Ok(head)
187}
188
189async fn receipt(http: &Http, url: &str, hash: Int) -> eyre::Result<Receipt> {
190 let head = call(
191 http,
192 url,
193 "eth_getTransactionReceipt",
194 &[Value::String(hash.to_string())],
195 )
196 .await?;
197 Ok(head)
198}
199
200async fn call<R: DeserializeOwned>(
201 http: &Http,
202 url: &str,
203 method: &str,
204 params: &[Value],
205) -> eyre::Result<R> {
206 let body = json!({
207 "jsonrpc": "2.0",
208 "method": method,
209 "params": params,
210 "id": 1,
211 });
212 let json: Value = http.post(url, &body).await?;
213 if std::env::var("DEBUG").is_ok() {
214 let body = serde_json::to_string_pretty(&body).unwrap();
216 let json = serde_json::to_string_pretty(&json).unwrap();
217 println!("{} -> {}", body, json);
218 }
219 if let Some(error) = json.get("error") {
220 if let Some(message) = error.as_str() {
221 eyre::bail!(message.to_owned());
222 }
223 if let Some(message) = error.get("message") {
224 eyre::bail!(message.to_string());
225 }
226 let message = serde_json::to_string(error)?;
227 eyre::bail!(message);
228 }
229 if let Some(result) = json.get("result") {
230 if result.is_null() {
231 eyre::bail!("result is null");
232 } else {
233 let ret = serde_json::from_value::<R>(result.to_owned())?;
234 return Ok(ret);
235 }
236 }
237 eprint!("JSON: {:#?}", json);
238 eyre::bail!("missing: result & error")
239}
240
241#[cfg(test)]
242mod tests {
243 use yevm_base::int;
244
245 use super::*;
246
247 #[tokio::test]
248 #[ignore = "makes RPC call to a public node"]
249 async fn test_latest() -> eyre::Result<()> {
250 let http = Http::new();
251 let head = head(
252 &http,
253 "https://ethereum-rpc.publicnode.com",
254 "latest".to_string(),
255 )
256 .await?;
257 let (number, hash) = (head.number.as_u64(), head.hash);
258 assert_ne!(hash, Int::ZERO);
259 assert!(number > 24697386);
260 Ok(())
261 }
262
263 #[tokio::test]
264 #[ignore = "makes RPC call to a public node"]
265 async fn test_receipt() -> eyre::Result<()> {
266 let hash = int("0xd3aafbde18d85a863399c94ffec80af928bb3ebecc3685f1c784245deff04c04");
267 let http = Http::new();
268 let _ = receipt(&http, "https://ethereum-rpc.publicnode.com", hash).await?;
269 Ok(())
270 }
271}