Skip to main content

yevm_core/
call.rs

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// TODO: proper blob handling
258/*
259/// EIP-4844 BLOBBASEFEE calculation (fake_exponential)
260/// (see: https://eips.ethereum.org/EIPS/eip-4844)
261pub fn blob_base_fee(excess_blob_gas: u64) -> u128 {
262    let d = 5_007_716u128;
263    let mut i = 1u64;
264    let mut out = 0;
265    let mut acc = d;
266    while acc > 0 {
267        out += acc;
268        acc = (acc * excess_blob_gas) / (d * i);
269        i += 1;
270    }
271    out / d
272}
273*/
274
275#[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}