foundry_evm_traces/decoder/
precompiles.rs

1use crate::{CallTrace, DecodedCallData};
2use alloy_primitives::{hex, Address, B256, U256};
3use alloy_sol_types::{abi, sol, SolCall};
4use foundry_evm_core::precompiles::{
5    BLAKE_2F, EC_ADD, EC_MUL, EC_PAIRING, EC_RECOVER, IDENTITY, MOD_EXP, POINT_EVALUATION,
6    RIPEMD_160, SHA_256,
7};
8use itertools::Itertools;
9use revm_inspectors::tracing::types::DecodedCallTrace;
10
11sol! {
12/// EVM precompiles interface. For illustration purposes only, as precompiles don't follow the
13/// Solidity ABI codec.
14///
15/// Parameter names and types are taken from [evm.codes](https://www.evm.codes/precompiled).
16interface Precompiles {
17    struct EcPairingInput {
18        uint256 x1;
19        uint256 y1;
20        uint256 x2;
21        uint256 y2;
22        uint256 x3;
23        uint256 y3;
24    }
25
26    /* 0x01 */ function ecrecover(bytes32 hash, uint8 v, uint256 r, uint256 s) returns (address publicAddress);
27    /* 0x02 */ function sha256(bytes data) returns (bytes32 hash);
28    /* 0x03 */ function ripemd(bytes data) returns (bytes20 hash);
29    /* 0x04 */ function identity(bytes data) returns (bytes data);
30    /* 0x05 */ function modexp(uint256 Bsize, uint256 Esize, uint256 Msize, bytes B, bytes E, bytes M) returns (bytes value);
31    /* 0x06 */ function ecadd(uint256 x1, uint256 y1, uint256 x2, uint256 y2) returns (uint256 x, uint256 y);
32    /* 0x07 */ function ecmul(uint256 x1, uint256 y1, uint256 s) returns (uint256 x, uint256 y);
33    /* 0x08 */ function ecpairing(EcPairingInput[] input) returns (bool success);
34    /* 0x09 */ function blake2f(uint32 rounds, uint64[8] h, uint64[16] m, uint64[2] t, bool f) returns (uint64[8] h);
35    /* 0x0a */ function pointEvaluation(bytes32 versionedHash, bytes32 z, bytes32 y, bytes1[48] commitment, bytes1[48] proof) returns (bytes value);
36}
37}
38use Precompiles::*;
39
40macro_rules! tri {
41    ($e:expr) => {
42        match $e {
43            Ok(x) => x,
44            Err(_) => return None,
45        }
46    };
47}
48
49pub(super) fn is_known_precompile(address: Address, _chain_id: u64) -> bool {
50    address[..19].iter().all(|&x| x == 0) &&
51        matches!(
52            address,
53            EC_RECOVER |
54                SHA_256 |
55                RIPEMD_160 |
56                IDENTITY |
57                MOD_EXP |
58                EC_ADD |
59                EC_MUL |
60                EC_PAIRING |
61                BLAKE_2F |
62                POINT_EVALUATION
63        )
64}
65
66/// Tries to decode a precompile call. Returns `Some` if successful.
67pub(super) fn decode(trace: &CallTrace, _chain_id: u64) -> Option<DecodedCallTrace> {
68    if !is_known_precompile(trace.address, _chain_id) {
69        return None;
70    }
71
72    let data = &trace.data;
73
74    let (signature, args) = match trace.address {
75        EC_RECOVER => {
76            let (sig, ecrecoverCall { hash, v, r, s }) = tri!(abi_decode_call(data));
77            (sig, vec![hash.to_string(), v.to_string(), r.to_string(), s.to_string()])
78        }
79        SHA_256 => (sha256Call::SIGNATURE, vec![data.to_string()]),
80        RIPEMD_160 => (ripemdCall::SIGNATURE, vec![data.to_string()]),
81        IDENTITY => (identityCall::SIGNATURE, vec![data.to_string()]),
82        MOD_EXP => (modexpCall::SIGNATURE, tri!(decode_modexp(data))),
83        EC_ADD => {
84            let (sig, ecaddCall { x1, y1, x2, y2 }) = tri!(abi_decode_call(data));
85            (sig, vec![x1.to_string(), y1.to_string(), x2.to_string(), y2.to_string()])
86        }
87        EC_MUL => {
88            let (sig, ecmulCall { x1, y1, s }) = tri!(abi_decode_call(data));
89            (sig, vec![x1.to_string(), y1.to_string(), s.to_string()])
90        }
91        EC_PAIRING => (ecpairingCall::SIGNATURE, tri!(decode_ecpairing(data))),
92        BLAKE_2F => (blake2fCall::SIGNATURE, tri!(decode_blake2f(data))),
93        POINT_EVALUATION => (pointEvaluationCall::SIGNATURE, tri!(decode_kzg(data))),
94        _ => return None,
95    };
96
97    Some(DecodedCallTrace {
98        label: Some("PRECOMPILES".to_string()),
99        call_data: Some(DecodedCallData { signature: signature.to_string(), args }),
100        // TODO: Decode return data too.
101        return_data: None,
102    })
103}
104
105// Note: we use the ABI decoder, but this is not necessarily ABI-encoded data. It's just a
106// convenient way to decode the data.
107
108fn decode_modexp(data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
109    let mut decoder = abi::Decoder::new(data, false);
110    let b_size = decoder.take_offset()?;
111    let e_size = decoder.take_offset()?;
112    let m_size = decoder.take_offset()?;
113    let b = decoder.take_slice_unchecked(b_size)?;
114    let e = decoder.take_slice_unchecked(e_size)?;
115    let m = decoder.take_slice_unchecked(m_size)?;
116    Ok(vec![
117        b_size.to_string(),
118        e_size.to_string(),
119        m_size.to_string(),
120        hex::encode_prefixed(b),
121        hex::encode_prefixed(e),
122        hex::encode_prefixed(m),
123    ])
124}
125
126fn decode_ecpairing(data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
127    let mut decoder = abi::Decoder::new(data, false);
128    let mut values = Vec::new();
129    // input must be either empty or a multiple of 6 32-byte values
130    let mut tmp = <[&B256; 6]>::default();
131    while !decoder.is_empty() {
132        for tmp in &mut tmp {
133            *tmp = decoder.take_word()?;
134        }
135        values.push(iter_to_string(tmp.iter().map(|x| U256::from_be_bytes(x.0))));
136    }
137    Ok(values)
138}
139
140fn decode_blake2f<'a>(data: &'a [u8]) -> alloy_sol_types::Result<Vec<String>> {
141    let mut decoder = abi::Decoder::new(data, false);
142    let rounds = u32::from_be_bytes(decoder.take_slice_unchecked(4)?.try_into().unwrap());
143    let u64_le_list =
144        |x: &'a [u8]| x.chunks_exact(8).map(|x| u64::from_le_bytes(x.try_into().unwrap()));
145    let h = u64_le_list(decoder.take_slice_unchecked(64)?);
146    let m = u64_le_list(decoder.take_slice_unchecked(128)?);
147    let t = u64_le_list(decoder.take_slice_unchecked(16)?);
148    let f = decoder.take_slice_unchecked(1)?[0];
149    Ok(vec![
150        rounds.to_string(),
151        iter_to_string(h),
152        iter_to_string(m),
153        iter_to_string(t),
154        f.to_string(),
155    ])
156}
157
158fn decode_kzg(data: &[u8]) -> alloy_sol_types::Result<Vec<String>> {
159    let mut decoder = abi::Decoder::new(data, false);
160    let versioned_hash = decoder.take_word()?;
161    let z = decoder.take_word()?;
162    let y = decoder.take_word()?;
163    let commitment = decoder.take_slice_unchecked(48)?;
164    let proof = decoder.take_slice_unchecked(48)?;
165    Ok(vec![
166        versioned_hash.to_string(),
167        z.to_string(),
168        y.to_string(),
169        hex::encode_prefixed(commitment),
170        hex::encode_prefixed(proof),
171    ])
172}
173
174fn abi_decode_call<T: SolCall>(data: &[u8]) -> alloy_sol_types::Result<(&'static str, T)> {
175    // raw because there are no selectors here
176    Ok((T::SIGNATURE, T::abi_decode_raw(data, false)?))
177}
178
179fn iter_to_string<I: Iterator<Item = T>, T: std::fmt::Display>(iter: I) -> String {
180    format!("[{}]", iter.format(", "))
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186    use alloy_primitives::hex;
187
188    #[test]
189    fn ecpairing() {
190        // https://github.com/foundry-rs/foundry/issues/5337#issuecomment-1627384480
191        let data = hex!(
192            "
193            26bbb723f965460ca7282cd75f0e3e7c67b15817f7cee60856b394936ed02917
194            0fbe873ac672168143a91535450bab6c412dce8dc8b66a88f2da6e245f9282df
195            13cd4f0451538ece5014fe6688b197aefcc611a5c6a7c319f834f2188ba04b08
196            126ff07e81490a1b6ae92b2d9e700c8e23e9d5c7f6ab857027213819a6c9ae7d
197            04183624c9858a56c54deb237c26cb4355bc2551312004e65fc5b299440b15a3
198            2e4b11aa549ad6c667057b18be4f4437fda92f018a59430ebb992fa3462c9ca1
199            2d4d9aa7e302d9df41749d5507949d05dbea33fbb16c643b22f599a2be6df2e2
200            14bedd503c37ceb061d8ec60209fe345ce89830a19230301f076caff004d1926
201            0967032fcbf776d1afc985f88877f182d38480a653f2decaa9794cbc3bf3060c
202            0e187847ad4c798374d0d6732bf501847dd68bc0e071241e0213bc7fc13db7ab
203            304cfbd1e08a704a99f5e847d93f8c3caafddec46b7a0d379da69a4d112346a7
204            1739c1b1a457a8c7313123d24d2f9192f896b7c63eea05a9d57f06547ad0cec8
205            001d6fedb032f70e377635238e0563f131670001f6abf439adb3a9d5d52073c6
206            1889afe91e4e367f898a7fcd6464e5ca4e822fe169bccb624f6aeb87e4d060bc
207            198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2
208            1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed
209            090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b
210            12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa
211            2dde6d7baf0bfa09329ec8d44c38282f5bf7f9ead1914edd7dcaebb498c84519
212            0c359f868a85c6e6c1ea819cfab4a867501a3688324d74df1fe76556558b1937
213            29f41c6e0e30802e2749bfb0729810876f3423e6f24829ad3e30adb1934f1c8a
214            030e7a5f70bb5daa6e18d80d6d447e772efb0bb7fb9d0ffcd54fc5a48af1286d
215            0ea726b117e48cda8bce2349405f006a84cdd3dcfba12efc990df25970a27b6d
216            30364cd4f8a293b1a04f0153548d3e01baad091c69097ca4e9f26be63e4095b5
217        "
218        );
219        let decoded = decode_ecpairing(&data).unwrap();
220        // 4 arrays of 6 32-byte values
221        assert_eq!(decoded.len(), 4);
222    }
223}