op_revm/
precompiles.rs

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