1use serde::{Deserialize, Serialize};
2use yevm_base::{Acc, Int};
3use yevm_misc::{buf::Buf, hex::Hex, keccak256};
4
5#[derive(Clone, Debug, Deserialize, Serialize)]
6pub struct Call {
7 pub by: Acc,
8 #[serde(default)]
9 #[serde(skip_serializing_if = "Option::is_none")]
10 pub to: Option<Acc>,
11 pub gas: u64,
12 pub eth: Int,
13 pub data: Buf,
14}
15
16impl Call {
17 pub fn is_create(&self) -> bool {
18 self.to.is_none()
19 }
20
21 pub fn builder() -> Builder {
22 Builder::default()
23 }
24}
25
26#[derive(Clone, Debug, Default)]
27pub struct Builder {
28 by: Option<Acc>,
29 to: Option<Acc>,
30 gas: Option<u64>,
31 eth: Option<Int>,
32 data: Buf,
33}
34
35impl Builder {
36 pub fn build(self) -> Call {
37 Call {
38 by: self.by.unwrap_or_default(),
39 to: self.to,
40 gas: self.gas.unwrap_or_default(),
41 eth: self.eth.unwrap_or_default(),
42 data: self.data,
43 }
44 }
45
46 pub fn call(self, abi: &str, args: &[&[u8]]) -> Self {
47 let len = 4 + args.iter().map(|arg| arg.len()).sum::<usize>();
48 let selector = keccak256(abi.as_bytes());
49 let selector = &selector.as_ref()[..4];
50 let mut vec = Vec::with_capacity(len);
51 vec.extend_from_slice(selector);
52 for arg in args {
53 vec.extend_from_slice(arg);
54 }
55 Self {
56 data: vec.into(),
57 ..self
58 }
59 }
60
61 pub fn create(self, mut code: Vec<u8>, args: &[&[u8]]) -> Self {
62 let len = args.iter().map(|arg| arg.len()).sum::<usize>();
63 code.reserve(len);
64 for arg in args {
65 code.extend_from_slice(arg);
66 }
67 Self {
68 data: code.into(),
69 ..self
70 }
71 }
72
73 pub fn by(self, by: Acc) -> Self {
74 Self {
75 by: Some(by),
76 ..self
77 }
78 }
79
80 pub fn to(self, to: Acc) -> Self {
81 Self {
82 to: Some(to),
83 ..self
84 }
85 }
86
87 pub fn gas(self, gas: u64) -> Self {
88 Self {
89 gas: Some(gas),
90 ..self
91 }
92 }
93
94 pub fn eth(self, eth: Int) -> Self {
95 Self {
96 eth: Some(eth),
97 ..self
98 }
99 }
100}
101
102#[derive(Clone, Debug, Default, Deserialize, Serialize)]
103#[serde(rename_all = "camelCase")]
104pub struct Head {
105 pub number: Hex<8>,
106 pub hash: Int,
107 pub gas_limit: Int,
108 #[serde(alias = "miner")]
109 pub coinbase: Acc,
110 pub timestamp: Int,
111 #[serde(alias = "baseFeePerGas")]
112 pub base_fee: Int,
113 #[serde(default)]
114 #[serde(skip_serializing_if = "Option::is_none")]
115 pub excess_blob_gas: Option<Int>,
116 #[serde(default)]
117 #[serde(skip_serializing_if = "Option::is_none")]
118 pub blobhash: Option<Int>,
119 #[serde(alias = "mixHash")]
120 #[serde(alias = "prevRandao")]
121 pub prevrandao: Int,
122 pub parent_hash: Int,
123 #[serde(default)]
124 #[serde(skip_serializing_if = "Option::is_none")]
125 pub parent_beacon_block_root: Option<Int>,
126}
127
128#[derive(Clone, Debug, Deserialize, Serialize)]
129#[serde(rename_all = "camelCase")]
130pub struct Tx {
131 #[serde(default)]
132 #[serde(skip_serializing_if = "Hex::is_zero")]
133 pub chain_id: Hex<8>,
134 pub nonce: Int,
135 pub gas_price: Int,
136 #[serde(default)]
137 #[serde(skip_serializing_if = "Int::is_zero")]
138 pub max_fee_per_gas: Int,
139 #[serde(default)]
140 #[serde(skip_serializing_if = "Int::is_zero")]
141 pub max_priority_fee_per_gas: Int,
142 #[serde(default)]
143 #[serde(skip_serializing_if = "Vec::is_empty")]
144 pub access_list: Vec<AccessListItem>,
145 #[serde(default)]
146 #[serde(skip_serializing_if = "Vec::is_empty")]
147 pub authorization_list: Vec<AuthorizationListItem>,
148 #[serde(default)]
149 #[serde(skip_serializing_if = "Vec::is_empty")]
150 pub blob_versioned_hashes: Vec<Int>,
151 #[serde(default)]
152 #[serde(skip_serializing_if = "Option::is_none")]
153 pub max_fee_per_blob_gas: Option<Int>,
154 pub hash: Int,
155 #[serde(rename = "transactionIndex")]
156 pub index: Int,
157}
158
159#[derive(Clone, Debug, Deserialize, Serialize)]
160#[serde(rename_all = "camelCase")]
161pub struct AccessListItem {
162 pub address: Acc,
163 pub storage_keys: Vec<Int>,
164}
165
166#[derive(Clone, Debug, Deserialize, Serialize)]
167#[serde(rename_all = "camelCase")]
168pub struct AuthorizationListItem {
169 pub address: Acc,
170 pub chain_id: Int,
171 pub nonce: Int,
172 pub r: Int,
173 pub s: Int,
174 pub y_parity: Int,
175}
176
177#[derive(Clone, Debug, Deserialize, Serialize)]
178pub struct TxCall {
179 #[serde(default)]
180 #[serde(skip_serializing_if = "Option::is_none")]
181 pub to: Option<Acc>,
182 pub from: Acc,
183 pub input: Buf,
184 pub value: Int,
185 pub gas: Int,
186}
187
188impl From<TxCall> for Call {
189 fn from(call: TxCall) -> Self {
190 Call {
191 by: call.from,
192 to: call.to,
193 gas: call.gas.as_u64(),
194 eth: call.value,
195 data: call.input,
196 }
197 }
198}
199
200#[derive(Clone, Debug, Deserialize, Serialize)]
201pub struct TxFull {
202 #[serde(flatten)]
203 pub call: TxCall,
204 #[serde(flatten)]
205 pub tx: Tx,
206}
207
208pub type TxHash = Int;
209
210#[derive(Clone, Debug, Deserialize, Serialize)]
211pub struct Block {
212 #[serde(flatten)]
213 pub head: Head,
214 #[serde(rename = "transactions")]
215 pub txs: Vec<TxFull>,
216}
217
218#[derive(Clone, Debug, Deserialize, Serialize)]
219#[serde(rename_all = "camelCase")]
220pub struct Receipt {
221 pub block_hash: Int,
222 pub block_number: Int,
223 #[serde(default)]
224 #[serde(skip_serializing_if = "Option::is_none")]
225 pub contract_address: Option<Acc>,
226 pub cumulative_gas_used: Int,
227 pub effective_gas_price: Int,
228 pub gas_used: Int,
229 pub status: Int,
230 #[serde(default)]
231 #[serde(skip_serializing_if = "Option::is_none")]
232 pub to: Option<Acc>,
233 pub r#type: Int,
234 pub transaction_hash: Int,
235 pub transaction_index: Int,
236 pub logs: Vec<Logged>,
237 pub logs_bloom: Buf,
238}
239
240#[derive(Clone, Debug, Deserialize, Serialize)]
241#[serde(rename_all = "camelCase")]
242pub struct Logged {
243 pub address: Acc,
244 pub block_hash: Int,
245 pub block_number: Int,
246 #[serde(default)]
247 #[serde(skip_serializing_if = "Option::is_none")]
248 pub block_timestamp: Option<Int>,
249 pub data: Buf,
250 pub log_index: Int,
251 pub removed: bool,
252 pub topics: Vec<Int>,
253 pub transaction_hash: Int,
254 pub transaction_index: Int,
255}
256
257#[cfg(test)]
276mod tests {
277 use super::*;
278 #[test]
279 fn test_block() {
280 const JSON: &str = r#"{
281 "baseFeePerGas": "0x246edf3",
282 "blobGasUsed": "0x40000",
283 "difficulty": "0x0",
284 "excessBlobGas": "0xa2a720e",
285 "extraData": "0x4275696c6465724e6574202842656176657229",
286 "gasLimit": "0x3938700",
287 "gasUsed": "0x1a4ed69",
288 "hash": "0x6c4c6ea047f0ca4bcb0b0fbbcc2e50061d8dd24ba1ab8a22a6d3583a497c3c82",
289 "logsBloom": "0xfffffef97ffdf7dfb75fbbfeffffdfffffbeefffffbbffff6ffffdffbffffaf9df7fff7f7fff6ffbffffdfdffbffffff76fffffcbf7ffbfffffeefffffff7df7bffffffdefffff8fdfdf7f7ceffdffff7ff77affe7ef5ff5fbfff57ff7eeadfffffffabfff7bdffbbfbffcfffdefffeffefffffffff5fffefdffdffbffffdfdffbffe7fffffffffdffebf7e37fdfe977f7fbdfefdffddffffffffff5f97ffffdfbffdf7fffffbff9feff77afffefffeef77ffff7fff6fffeffd77ffffeefefdb7bffffff7f7ffdfbff7ffffffffefdffdffddffffffefffffffefdfbf7fdffef7fffff3bdf7fff6cefffdfffdf7ffffeffbfbffffdafffdfbf7efdbeffff7fff",
290 "miner": "0xdadb0d80178819f2319190d340ce9a924f783711",
291 "mixHash": "0xf824cbc0bffaa4862fffafa96c2cce90898dda8b97fc3eaa6a85ac38075991e5",
292 "nonce": "0x0000000000000000",
293 "number": "0x177d354",
294 "parentBeaconBlockRoot": "0xd9b957562f8477151f61eddc77bcad7451ef62eab8f4fe470402073a794e8bba",
295 "parentHash": "0x63ce16602b2afe4089fa8e0a2c16be1a2dea1c4d30b65bd717d9398ddd5fba23",
296 "receiptsRoot": "0x52fc280ab6b1db61781dae4cc5f81b7ddd484eae78320bdd8161d5b2fe8e7719",
297 "requestsHash": "0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
298 "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
299 "size": "0x2a8a9",
300 "stateRoot": "0x41ebb402f36d0ce15799e98d1aca83bbb5d4b08d1b6572e73b388e4ece192916",
301 "timestamp": "0x69b09d6b",
302 "transactions": [
303 {
304 "accessList": [
305 {
306 "address": "0xf6e72db5454dd049d0788e411b06cfaf16853042",
307 "storageKeys": [
308 "0x0000000000000000000000000000000000000000000000000000000000000004"
309 ]
310 }
311 ],
312 "authorizationList": [
313 {
314 "address": "0x66e9d4bc80c1d04d045573c2f4631342fe6fc87d",
315 "chainId": "0x1",
316 "nonce": "0xa28",
317 "r": "0xedba2225e5a2a8958d7530ef3fdd941ba5996c8a92ddb60d649f1ccabb9d686d",
318 "s": "0x2a058bc60a7674c234739fefcbcf40bf846a7c4cf89ed850b86cb871033e7502",
319 "yParity": "0x0"
320 }
321 ],
322 "blockHash": "0x1f5b98ca47015bbfa9bff1b6282fd7753a0b79053876677b22ac178df53d76c7",
323 "blockNumber": "0x177e232",
324 "blockTimestamp": "0x69b1509f",
325 "chainId": "0x1",
326 "from": "0xdadb0d80178819f2319190d340ce9a924f783711",
327 "gas": "0x5208",
328 "gasPrice": "0x4b2f976",
329 "hash": "0xb478179e8e7f2fadfd42103007e9dfb5ea882667b1dd6e65142ef6b2a54b5668",
330 "input": "0x",
331 "maxFeePerGas": "0x4b2f976",
332 "maxPriorityFeePerGas": "0x0",
333 "nonce": "0x22792c",
334 "r": "0x51a8855fd9176012c328891ee0027959fd6480ef7ca09a2aac2e287c1c311566",
335 "s": "0x3e4b0c0cf854f42a0999c2ce6a9dc88b16f1b5f4e201dd61a36ed3a769719e6c",
336 "to": "0x08ec37e2eb451ab6fb29fc14d215b0aeef170040",
337 "transactionIndex": "0x201",
338 "type": "0x2",
339 "v": "0x1",
340 "value": "0x1f92fa85e3ea2a",
341 "yParity": "0x1"
342 }
343 ],
344 "transactionsRoot": "0x3b889eb9fa0713d2e1d9e90725d45fd04ec4a550a7a390639c243c5f3b2c6215",
345 "uncles": [],
346 "withdrawals": [
347 {
348 "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f",
349 "amount": "0x344741",
350 "index": "0x73e482b",
351 "validatorIndex": "0x21e64c"
352 }
353 ],
354 "withdrawalsRoot": "0x76abcbef0846a5bd065d0207581dacc3cf8df4ec2ba0a8e4d4fbbe97864b82ee"
355 }"#;
356 assert!(serde_json::from_str::<Block>(JSON).is_ok());
357 }
358
359 #[test]
360 fn test_receipt() {
361 const JSON: &str = r#" {
362 "blockHash": "0xe80afa4e302440b3f13974819d24027d1c5c7663a9688c5f03caa685660a28c7",
363 "blockNumber": "0x178f9f8",
364 "contractAddress": null,
365 "cumulativeGasUsed": "0x216cc",
366 "effectiveGasPrice": "0x1ef66a0",
367 "from": "0xe4613af9ecdf8db83707546af9d4bd9da76ce504",
368 "gasUsed": "0x216cc",
369 "logs": [
370 {
371 "address": "0xe0f63a424a4439cbe457d80e4f4b51ad25b2c56c",
372 "blockHash": "0xe80afa4e302440b3f13974819d24027d1c5c7663a9688c5f03caa685660a28c7",
373 "blockNumber": "0x178f9f8",
374 "blockTimestamp": "0x69be7e43",
375 "data": "0x000000000000000000000000000000000000000000000000000000261a04825d",
376 "logIndex": "0x0",
377 "removed": false,
378 "topics": [
379 "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
380 "0x00000000000000000000000051c72848c68a965f66fa7a88855f9f7784502a7f",
381 "0x00000000000000000000000052c77b0cb827afbad022e6d6caf2c44452edbc39"
382 ],
383 "transactionHash": "0xd3aafbde18d85a863399c94ffec80af928bb3ebecc3685f1c784245deff04c04",
384 "transactionIndex": "0x0"
385 }
386 ],
387 "logsBloom": "0x002000000000000002000000800000010000000000000000000000000000000000000000000000000000000000000000020000000800000000000400000000000000000000000000000000080000002000000000000400000000000000000000000000000000000000000000000000000000000000000000000000100000000000000040000000000000000000000000000000000000000c0000004000000000000000000000000000000000000000004000000000000000000000000000000000000002000000000000000000000000000000000400001000000000000000000000200000000000000000000000000000400000000000000000000040006000",
388 "status": "0x1",
389 "to": "0x51c72848c68a965f66fa7a88855f9f7784502a7f",
390 "transactionHash": "0xd3aafbde18d85a863399c94ffec80af928bb3ebecc3685f1c784245deff04c04",
391 "transactionIndex": "0x0",
392 "type": "0x2"
393 }"#;
394 assert!(serde_json::from_str::<Receipt>(JSON).is_ok());
395 }
396}