op_revm/
precompiles.rs

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