op_revm/
precompiles.rs

1//! Contains Optimism specific precompiles.
2use crate::OpSpecId;
3use revm::{
4    context::Cfg,
5    context_interface::ContextTr,
6    handler::{EthPrecompiles, PrecompileProvider},
7    interpreter::{InputsImpl, InterpreterResult},
8    precompile::{
9        self, bn254, secp256r1, Precompile, PrecompileError, PrecompileId, PrecompileResult,
10        Precompiles,
11    },
12    primitives::{hardfork::SpecId, Address, OnceLock},
13};
14use std::boxed::Box;
15use std::string::String;
16
17/// Optimism precompile provider
18#[derive(Debug, Clone)]
19pub struct OpPrecompiles {
20    /// Inner precompile provider is same as Ethereums.
21    inner: EthPrecompiles,
22    /// Spec id of the precompile provider.
23    spec: OpSpecId,
24}
25
26impl OpPrecompiles {
27    /// Create a new precompile provider with the given OpSpec.
28    #[inline]
29    pub fn new_with_spec(spec: OpSpecId) -> Self {
30        let precompiles = match spec {
31            spec @ (OpSpecId::BEDROCK
32            | OpSpecId::REGOLITH
33            | OpSpecId::CANYON
34            | OpSpecId::ECOTONE) => Precompiles::new(spec.into_eth_spec().into()),
35            OpSpecId::FJORD => fjord(),
36            OpSpecId::GRANITE | OpSpecId::HOLOCENE => granite(),
37            OpSpecId::ISTHMUS | OpSpecId::INTEROP | OpSpecId::OSAKA => isthmus(),
38        };
39
40        Self {
41            inner: EthPrecompiles {
42                precompiles,
43                spec: SpecId::default(),
44            },
45            spec,
46        }
47    }
48
49    /// Precompiles getter.
50    #[inline]
51    pub fn precompiles(&self) -> &'static Precompiles {
52        self.inner.precompiles
53    }
54}
55
56/// Returns precompiles for Fjord spec.
57pub fn fjord() -> &'static Precompiles {
58    static INSTANCE: OnceLock<Precompiles> = OnceLock::new();
59    INSTANCE.get_or_init(|| {
60        let mut precompiles = Precompiles::cancun().clone();
61        // RIP-7212: secp256r1 P256verify
62        precompiles.extend([secp256r1::P256VERIFY]);
63        precompiles
64    })
65}
66
67/// Returns precompiles for Granite spec.
68pub fn granite() -> &'static Precompiles {
69    static INSTANCE: OnceLock<Precompiles> = OnceLock::new();
70    INSTANCE.get_or_init(|| {
71        let mut precompiles = fjord().clone();
72        // Restrict bn254Pairing input size
73        precompiles.extend([bn254_pair::GRANITE]);
74        precompiles
75    })
76}
77
78/// Returns precompiles for isthumus spec.
79pub fn isthmus() -> &'static Precompiles {
80    static INSTANCE: OnceLock<Precompiles> = OnceLock::new();
81    INSTANCE.get_or_init(|| {
82        let mut precompiles = granite().clone();
83        // Prague bls12 precompiles
84        precompiles.extend(precompile::bls12_381::precompiles());
85        // Isthmus bls12 precompile modifications
86        precompiles.extend([
87            bls12_381::ISTHMUS_G1_MSM,
88            bls12_381::ISTHMUS_G2_MSM,
89            bls12_381::ISTHMUS_PAIRING,
90        ]);
91        precompiles
92    })
93}
94
95impl<CTX> PrecompileProvider<CTX> for OpPrecompiles
96where
97    CTX: ContextTr<Cfg: Cfg<Spec = OpSpecId>>,
98{
99    type Output = InterpreterResult;
100
101    #[inline]
102    fn set_spec(&mut self, spec: <CTX::Cfg as Cfg>::Spec) -> bool {
103        if spec == self.spec {
104            return false;
105        }
106        *self = Self::new_with_spec(spec);
107        true
108    }
109
110    #[inline]
111    fn run(
112        &mut self,
113        context: &mut CTX,
114        address: &Address,
115        inputs: &InputsImpl,
116        is_static: bool,
117        gas_limit: u64,
118    ) -> Result<Option<Self::Output>, String> {
119        self.inner
120            .run(context, address, inputs, is_static, gas_limit)
121    }
122
123    #[inline]
124    fn warm_addresses(&self) -> Box<impl Iterator<Item = Address>> {
125        self.inner.warm_addresses()
126    }
127
128    #[inline]
129    fn contains(&self, address: &Address) -> bool {
130        self.inner.contains(address)
131    }
132}
133
134impl Default for OpPrecompiles {
135    fn default() -> Self {
136        Self::new_with_spec(OpSpecId::ISTHMUS)
137    }
138}
139
140/// Bn254 pair precompile.
141pub mod bn254_pair {
142    use super::*;
143
144    /// Max input size for the bn254 pair precompile.
145    pub const GRANITE_MAX_INPUT_SIZE: usize = 112687;
146    /// Bn254 pair precompile.
147    pub const GRANITE: Precompile =
148        Precompile::new(PrecompileId::Bn254Pairing, bn254::pair::ADDRESS, run_pair);
149
150    /// Run the bn254 pair precompile with Optimism input limit.
151    pub fn run_pair(input: &[u8], gas_limit: u64) -> PrecompileResult {
152        if input.len() > GRANITE_MAX_INPUT_SIZE {
153            return Err(PrecompileError::Bn254PairLength);
154        }
155        bn254::run_pair(
156            input,
157            bn254::pair::ISTANBUL_PAIR_PER_POINT,
158            bn254::pair::ISTANBUL_PAIR_BASE,
159            gas_limit,
160        )
161    }
162}
163
164/// Bls12_381 precompile.
165pub mod bls12_381 {
166    use super::*;
167    use revm::precompile::bls12_381_const::{G1_MSM_ADDRESS, G2_MSM_ADDRESS, PAIRING_ADDRESS};
168
169    #[cfg(not(feature = "std"))]
170    use crate::std::string::ToString;
171
172    /// Max input size for the g1 msm precompile.
173    pub const ISTHMUS_G1_MSM_MAX_INPUT_SIZE: usize = 513760;
174    /// Max input size for the g2 msm precompile.
175    pub const ISTHMUS_G2_MSM_MAX_INPUT_SIZE: usize = 488448;
176    /// Max input size for the pairing precompile.
177    pub const ISTHMUS_PAIRING_MAX_INPUT_SIZE: usize = 235008;
178
179    /// G1 msm precompile.
180    pub const ISTHMUS_G1_MSM: Precompile =
181        Precompile::new(PrecompileId::Bls12G1Msm, G1_MSM_ADDRESS, run_g1_msm);
182    /// G2 msm precompile.
183    pub const ISTHMUS_G2_MSM: Precompile =
184        Precompile::new(PrecompileId::Bls12G2Msm, G2_MSM_ADDRESS, run_g2_msm);
185    /// Pairing precompile.
186    pub const ISTHMUS_PAIRING: Precompile =
187        Precompile::new(PrecompileId::Bls12Pairing, PAIRING_ADDRESS, run_pair);
188
189    /// Run the g1 msm precompile with Optimism input limit.
190    pub fn run_g1_msm(input: &[u8], gas_limit: u64) -> PrecompileResult {
191        if input.len() > ISTHMUS_G1_MSM_MAX_INPUT_SIZE {
192            return Err(PrecompileError::Other(
193                "G1MSM input length too long for OP Stack input size limitation".to_string(),
194            ));
195        }
196        precompile::bls12_381::g1_msm::g1_msm(input, gas_limit)
197    }
198
199    /// Run the g2 msm precompile with Optimism input limit.
200    pub fn run_g2_msm(input: &[u8], gas_limit: u64) -> PrecompileResult {
201        if input.len() > ISTHMUS_G2_MSM_MAX_INPUT_SIZE {
202            return Err(PrecompileError::Other(
203                "G2MSM input length too long for OP Stack input size limitation".to_string(),
204            ));
205        }
206        precompile::bls12_381::g2_msm::g2_msm(input, gas_limit)
207    }
208
209    /// Run the pairing precompile with Optimism input limit.
210    pub fn run_pair(input: &[u8], gas_limit: u64) -> PrecompileResult {
211        if input.len() > ISTHMUS_PAIRING_MAX_INPUT_SIZE {
212            return Err(PrecompileError::Other(
213                "Pairing input length too long for OP Stack input size limitation".to_string(),
214            ));
215        }
216        precompile::bls12_381::pairing::pairing(input, gas_limit)
217    }
218}
219
220#[cfg(test)]
221mod tests {
222    use crate::precompiles::bls12_381::{
223        run_g1_msm, run_g2_msm, ISTHMUS_G1_MSM_MAX_INPUT_SIZE, ISTHMUS_G2_MSM_MAX_INPUT_SIZE,
224        ISTHMUS_PAIRING_MAX_INPUT_SIZE,
225    };
226
227    use super::*;
228    use revm::{
229        precompile::PrecompileError,
230        primitives::{hex, Bytes},
231    };
232    use std::vec;
233
234    #[test]
235    fn test_bn254_pair() {
236        let input = hex::decode(
237            "\
238      1c76476f4def4bb94541d57ebba1193381ffa7aa76ada664dd31c16024c43f59\
239      3034dd2920f673e204fee2811c678745fc819b55d3e9d294e45c9b03a76aef41\
240      209dd15ebff5d46c4bd888e51a93cf99a7329636c63514396b4a452003a35bf7\
241      04bf11ca01483bfa8b34b43561848d28905960114c8ac04049af4b6315a41678\
242      2bb8324af6cfc93537a2ad1a445cfd0ca2a71acd7ac41fadbf933c2a51be344d\
243      120a2a4cf30c1bf9845f20c6fe39e07ea2cce61f0c9bb048165fe5e4de877550\
244      111e129f1cf1097710d41c4ac70fcdfa5ba2023c6ff1cbeac322de49d1b6df7c\
245      2032c61a830e3c17286de9462bf242fca2883585b93870a73853face6a6bf411\
246      198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2\
247      1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed\
248      090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b\
249      12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa",
250        )
251        .unwrap();
252        let expected =
253            hex::decode("0000000000000000000000000000000000000000000000000000000000000001")
254                .unwrap();
255        let outcome = bn254_pair::run_pair(&input, 260_000).unwrap();
256        assert_eq!(outcome.bytes, expected);
257
258        // Invalid input length
259        let input = hex::decode(
260            "\
261          1111111111111111111111111111111111111111111111111111111111111111\
262          1111111111111111111111111111111111111111111111111111111111111111\
263          111111111111111111111111111111\
264      ",
265        )
266        .unwrap();
267
268        let res = bn254_pair::run_pair(&input, 260_000);
269        assert!(matches!(res, Err(PrecompileError::Bn254PairLength)));
270
271        // Valid input length shorter than 112687
272        let input = vec![1u8; 586 * bn254::PAIR_ELEMENT_LEN];
273        let res = bn254_pair::run_pair(&input, 260_000);
274        assert!(matches!(res, Err(PrecompileError::OutOfGas)));
275
276        // Input length longer than 112687
277        let input = vec![1u8; 587 * bn254::PAIR_ELEMENT_LEN];
278        let res = bn254_pair::run_pair(&input, 260_000);
279        assert!(matches!(res, Err(PrecompileError::Bn254PairLength)));
280    }
281
282    #[test]
283    fn test_cancun_precompiles_in_fjord() {
284        // additional to cancun, fjord has p256verify
285        assert_eq!(fjord().difference(Precompiles::cancun()).len(), 1)
286    }
287
288    #[test]
289    fn test_cancun_precompiles_in_granite() {
290        // granite has p256verify (fjord)
291        // granite has modification of cancun's bn254 pair (doesn't count as new precompile)
292        assert_eq!(granite().difference(Precompiles::cancun()).len(), 1)
293    }
294
295    #[test]
296    fn test_prague_precompiles_in_isthmus() {
297        let new_prague_precompiles = Precompiles::prague().difference(Precompiles::cancun());
298
299        // isthmus contains all precompiles that were new in prague, without modifications
300        assert!(new_prague_precompiles.difference(isthmus()).is_empty())
301    }
302
303    #[test]
304    fn test_default_precompiles_is_latest() {
305        let latest = OpPrecompiles::new_with_spec(OpSpecId::default())
306            .inner
307            .precompiles;
308        let default = OpPrecompiles::default().inner.precompiles;
309        assert_eq!(latest.len(), default.len());
310
311        let intersection = default.intersection(latest);
312        assert_eq!(intersection.len(), latest.len())
313    }
314
315    #[test]
316    fn test_g1_isthmus_max_size() {
317        let oversized_input = vec![0u8; ISTHMUS_G1_MSM_MAX_INPUT_SIZE + 1];
318        let input = Bytes::from(oversized_input);
319
320        let res = run_g1_msm(&input, 260_000);
321
322        assert!(
323            matches!(res, Err(PrecompileError::Other(msg)) if msg.contains("input length too long"))
324        );
325    }
326    #[test]
327    fn test_g2_isthmus_max_size() {
328        let oversized_input = vec![0u8; ISTHMUS_G2_MSM_MAX_INPUT_SIZE + 1];
329        let input = Bytes::from(oversized_input);
330
331        let res = run_g2_msm(&input, 260_000);
332
333        assert!(
334            matches!(res, Err(PrecompileError::Other(msg)) if msg.contains("input length too long"))
335        );
336    }
337    #[test]
338    fn test_pair_isthmus_max_size() {
339        let oversized_input = vec![0u8; ISTHMUS_PAIRING_MAX_INPUT_SIZE + 1];
340        let input = Bytes::from(oversized_input);
341
342        let res = bls12_381::run_pair(&input, 260_000);
343
344        assert!(
345            matches!(res, Err(PrecompileError::Other(msg)) if msg.contains("input length too long"))
346        );
347    }
348}