aurora_engine_precompiles/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2#![forbid(unsafe_code)]
3
4pub mod account_ids;
5pub mod alt_bn256;
6pub mod blake2;
7pub mod hash;
8pub mod identity;
9pub mod modexp;
10pub mod native;
11mod prelude;
12pub mod prepaid_gas;
13pub mod promise_result;
14pub mod random;
15pub mod secp256k1;
16mod utils;
17pub mod xcc;
18
19use crate::account_ids::{predecessor_account, CurrentAccount, PredecessorAccount};
20use crate::alt_bn256::{Bn256Add, Bn256Mul, Bn256Pair};
21use crate::blake2::Blake2F;
22use crate::hash::{RIPEMD160, SHA256};
23use crate::identity::Identity;
24use crate::modexp::ModExp;
25use crate::native::{exit_to_ethereum, exit_to_near, ExitToEthereum, ExitToNear};
26use crate::prelude::types::EthGas;
27use crate::prelude::{Vec, H256};
28use crate::prepaid_gas::PrepaidGas;
29use crate::random::RandomSeed;
30use crate::secp256k1::ECRecover;
31use crate::xcc::CrossContractCall;
32use aurora_engine_modexp::ModExpAlgorithm;
33use aurora_engine_sdk::env::Env;
34use aurora_engine_sdk::io::IO;
35use aurora_engine_sdk::promise::ReadOnlyPromiseHandler;
36use aurora_engine_types::{account_id::AccountId, types::Address, vec, BTreeMap, BTreeSet, Box};
37use aurora_evm::backend::Log;
38use aurora_evm::executor::{
39    self,
40    stack::{PrecompileFailure, PrecompileHandle},
41};
42use aurora_evm::{Context, ExitError, ExitFatal, ExitSucceed};
43use promise_result::PromiseResult;
44use xcc::cross_contract_call;
45
46#[derive(Debug, Default, PartialEq, Eq)]
47pub struct PrecompileOutput {
48    pub cost: EthGas,
49    pub output: Vec<u8>,
50    pub logs: Vec<Log>,
51}
52
53impl PrecompileOutput {
54    #[must_use]
55    pub const fn without_logs(cost: EthGas, output: Vec<u8>) -> Self {
56        Self {
57            cost,
58            output,
59            logs: Vec::new(),
60        }
61    }
62}
63
64type EvmPrecompileResult = Result<PrecompileOutput, ExitError>;
65
66/// A precompiled function for use in the EVM.
67pub trait Precompile {
68    /// The required gas in order to run the precompile function.
69    fn required_gas(input: &[u8]) -> Result<EthGas, ExitError>
70    where
71        Self: Sized;
72
73    /// Runs the precompile function.
74    fn run(
75        &self,
76        input: &[u8],
77        target_gas: Option<EthGas>,
78        context: &Context,
79        is_static: bool,
80    ) -> EvmPrecompileResult;
81}
82
83pub trait HandleBasedPrecompile {
84    fn run_with_handle(
85        &self,
86        handle: &mut impl PrecompileHandle,
87    ) -> Result<PrecompileOutput, PrecompileFailure>;
88}
89
90/// Hard fork marker.
91pub trait HardFork {}
92
93/// Homestead hard fork marker.
94pub struct Homestead;
95
96/// Homestead hard fork marker.
97pub struct Byzantium;
98
99/// Homestead hard fork marker.
100pub struct Istanbul;
101
102/// Homestead hard fork marker.
103pub struct Berlin;
104
105impl HardFork for Homestead {}
106
107impl HardFork for Byzantium {}
108
109impl HardFork for Istanbul {}
110
111impl HardFork for Berlin {}
112
113pub struct Precompiles<'a, I, E, H> {
114    pub all_precompiles: BTreeMap<Address, AllPrecompiles<'a, I, E, H>>,
115    pub paused_precompiles: BTreeSet<Address>,
116}
117
118impl<I, E, H> Precompiles<'_, I, E, H> {
119    fn is_paused(&self, address: &Address) -> bool {
120        self.paused_precompiles.contains(address)
121    }
122}
123
124impl<I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> executor::stack::PrecompileSet
125    for Precompiles<'_, I, E, H>
126{
127    fn execute(
128        &self,
129        handle: &mut impl PrecompileHandle,
130    ) -> Option<Result<executor::stack::PrecompileOutput, PrecompileFailure>> {
131        let address = Address::new(handle.code_address());
132
133        if self.is_paused(&address) {
134            return Some(Err(PrecompileFailure::Fatal {
135                exit_status: ExitFatal::Other(prelude::Cow::Borrowed("ERR_PAUSED")),
136            }));
137        }
138
139        let result = match self.all_precompiles.get(&address)? {
140            AllPrecompiles::ExitToNear(p) => process_precompile(p, handle),
141            AllPrecompiles::ExitToEthereum(p) => process_precompile(p, handle),
142            AllPrecompiles::PredecessorAccount(p) => process_precompile(p, handle),
143            AllPrecompiles::PrepaidGas(p) => process_precompile(p, handle),
144            AllPrecompiles::PromiseResult(p) => process_precompile(p, handle),
145            AllPrecompiles::CrossContractCall(p) => process_handle_based_precompile(p, handle),
146            AllPrecompiles::Generic(p) => process_precompile(p.as_ref(), handle),
147        };
148
149        Some(result.and_then(|output| post_process(output, handle)))
150    }
151
152    fn is_precompile(&self, address: prelude::H160) -> bool {
153        self.all_precompiles.contains_key(&Address::new(address))
154    }
155}
156
157fn process_precompile(
158    p: &dyn Precompile,
159    handle: &impl PrecompileHandle,
160) -> Result<PrecompileOutput, PrecompileFailure> {
161    let input = handle.input();
162    let gas_limit = handle.gas_limit();
163    let context = handle.context();
164    let is_static = handle.is_static();
165
166    p.run(input, gas_limit.map(EthGas::new), context, is_static)
167        .map_err(|exit_status| PrecompileFailure::Error { exit_status })
168}
169
170fn process_handle_based_precompile(
171    p: &impl HandleBasedPrecompile,
172    handle: &mut impl PrecompileHandle,
173) -> Result<PrecompileOutput, PrecompileFailure> {
174    p.run_with_handle(handle)
175}
176
177fn post_process(
178    output: PrecompileOutput,
179    handle: &mut impl PrecompileHandle,
180) -> Result<executor::stack::PrecompileOutput, PrecompileFailure> {
181    handle.record_cost(output.cost.as_u64())?;
182    for log in output.logs {
183        handle.log(log.address, log.topics, log.data)?;
184    }
185    Ok(executor::stack::PrecompileOutput {
186        exit_status: ExitSucceed::Returned,
187        output: output.output,
188    })
189}
190
191pub struct PrecompileConstructorContext<'a, I, E, H, M> {
192    pub current_account_id: AccountId,
193    pub random_seed: H256,
194    pub io: I,
195    pub env: &'a E,
196    pub promise_handler: H,
197    pub mod_exp_algorithm: prelude::PhantomData<M>,
198}
199
200impl<'a, I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> Precompiles<'a, I, E, H> {
201    #[allow(dead_code)]
202    pub fn new_homestead<M: ModExpAlgorithm + 'static>(
203        ctx: PrecompileConstructorContext<'a, I, E, H, M>,
204    ) -> Self {
205        let addresses = vec![
206            ECRecover::ADDRESS,
207            SHA256::ADDRESS,
208            RIPEMD160::ADDRESS,
209            RandomSeed::ADDRESS,
210            CurrentAccount::ADDRESS,
211        ];
212        let fun: Vec<Box<dyn Precompile>> = vec![
213            Box::new(ECRecover),
214            Box::new(SHA256),
215            Box::new(RIPEMD160),
216            Box::new(RandomSeed::new(ctx.random_seed)),
217            Box::new(CurrentAccount::new(ctx.current_account_id.clone())),
218        ];
219        let map = addresses
220            .into_iter()
221            .zip(fun)
222            .map(|(a, f)| (a, AllPrecompiles::Generic(f)))
223            .collect();
224        Self::with_generic_precompiles(map, ctx)
225    }
226
227    #[allow(dead_code)]
228    pub fn new_byzantium<M: ModExpAlgorithm + 'static>(
229        ctx: PrecompileConstructorContext<'a, I, E, H, M>,
230    ) -> Self {
231        let addresses = vec![
232            ECRecover::ADDRESS,
233            SHA256::ADDRESS,
234            RIPEMD160::ADDRESS,
235            Identity::ADDRESS,
236            ModExp::<Byzantium, M>::ADDRESS,
237            Bn256Add::<Byzantium>::ADDRESS,
238            Bn256Mul::<Byzantium>::ADDRESS,
239            Bn256Pair::<Byzantium>::ADDRESS,
240            RandomSeed::ADDRESS,
241            CurrentAccount::ADDRESS,
242        ];
243        let fun: Vec<Box<dyn Precompile>> = vec![
244            Box::new(ECRecover),
245            Box::new(SHA256),
246            Box::new(RIPEMD160),
247            Box::new(Identity),
248            Box::new(ModExp::<Byzantium, M>::new()),
249            Box::new(Bn256Add::<Byzantium>::new()),
250            Box::new(Bn256Mul::<Byzantium>::new()),
251            Box::new(Bn256Pair::<Byzantium>::new()),
252            Box::new(RandomSeed::new(ctx.random_seed)),
253            Box::new(CurrentAccount::new(ctx.current_account_id.clone())),
254        ];
255        let map = addresses
256            .into_iter()
257            .zip(fun)
258            .map(|(a, f)| (a, AllPrecompiles::Generic(f)))
259            .collect();
260
261        Self::with_generic_precompiles(map, ctx)
262    }
263
264    pub fn new_istanbul<M: ModExpAlgorithm + 'static>(
265        ctx: PrecompileConstructorContext<'a, I, E, H, M>,
266    ) -> Self {
267        let addresses = vec![
268            ECRecover::ADDRESS,
269            SHA256::ADDRESS,
270            RIPEMD160::ADDRESS,
271            Identity::ADDRESS,
272            ModExp::<Byzantium, M>::ADDRESS,
273            Bn256Add::<Istanbul>::ADDRESS,
274            Bn256Mul::<Istanbul>::ADDRESS,
275            Bn256Pair::<Istanbul>::ADDRESS,
276            Blake2F::ADDRESS,
277            RandomSeed::ADDRESS,
278            CurrentAccount::ADDRESS,
279        ];
280        let fun: Vec<Box<dyn Precompile>> = vec![
281            Box::new(ECRecover),
282            Box::new(SHA256),
283            Box::new(RIPEMD160),
284            Box::new(Identity),
285            Box::new(ModExp::<Byzantium, M>::new()),
286            Box::new(Bn256Add::<Istanbul>::new()),
287            Box::new(Bn256Mul::<Istanbul>::new()),
288            Box::new(Bn256Pair::<Istanbul>::new()),
289            Box::new(Blake2F),
290            Box::new(RandomSeed::new(ctx.random_seed)),
291            Box::new(CurrentAccount::new(ctx.current_account_id.clone())),
292        ];
293        let map = addresses
294            .into_iter()
295            .zip(fun)
296            .map(|(a, f)| (a, AllPrecompiles::Generic(f)))
297            .collect();
298
299        Self::with_generic_precompiles(map, ctx)
300    }
301
302    pub fn new_berlin<M: ModExpAlgorithm + 'static>(
303        ctx: PrecompileConstructorContext<'a, I, E, H, M>,
304    ) -> Self {
305        let addresses = vec![
306            ECRecover::ADDRESS,
307            SHA256::ADDRESS,
308            RIPEMD160::ADDRESS,
309            Identity::ADDRESS,
310            ModExp::<Berlin, M>::ADDRESS,
311            Bn256Add::<Istanbul>::ADDRESS,
312            Bn256Mul::<Istanbul>::ADDRESS,
313            Bn256Pair::<Istanbul>::ADDRESS,
314            Blake2F::ADDRESS,
315            RandomSeed::ADDRESS,
316            CurrentAccount::ADDRESS,
317        ];
318        let fun: Vec<Box<dyn Precompile>> = vec![
319            Box::new(ECRecover),
320            Box::new(SHA256),
321            Box::new(RIPEMD160),
322            Box::new(Identity),
323            Box::new(ModExp::<Berlin, M>::new()),
324            Box::new(Bn256Add::<Istanbul>::new()),
325            Box::new(Bn256Mul::<Istanbul>::new()),
326            Box::new(Bn256Pair::<Istanbul>::new()),
327            Box::new(Blake2F),
328            Box::new(RandomSeed::new(ctx.random_seed)),
329            Box::new(CurrentAccount::new(ctx.current_account_id.clone())),
330        ];
331        let map = addresses
332            .into_iter()
333            .zip(fun)
334            .map(|(a, f)| (a, AllPrecompiles::Generic(f)))
335            .collect();
336
337        Self::with_generic_precompiles(map, ctx)
338    }
339
340    pub fn new_london<M: ModExpAlgorithm + 'static>(
341        ctx: PrecompileConstructorContext<'a, I, E, H, M>,
342    ) -> Self {
343        // no precompile changes in London HF
344        Self::new_berlin(ctx)
345    }
346
347    fn with_generic_precompiles<M: ModExpAlgorithm + 'static>(
348        mut generic_precompiles: BTreeMap<Address, AllPrecompiles<'a, I, E, H>>,
349        ctx: PrecompileConstructorContext<'a, I, E, H, M>,
350    ) -> Self {
351        let near_exit = ExitToNear::new(ctx.current_account_id.clone(), ctx.io);
352        let ethereum_exit = ExitToEthereum::new(ctx.current_account_id.clone(), ctx.io);
353        let cross_contract_call = CrossContractCall::new(ctx.current_account_id, ctx.io);
354        let predecessor_account_id = PredecessorAccount::new(ctx.env);
355        let prepaid_gas = PrepaidGas::new(ctx.env);
356        let promise_results = PromiseResult::new(ctx.promise_handler);
357
358        generic_precompiles.insert(exit_to_near::ADDRESS, AllPrecompiles::ExitToNear(near_exit));
359        generic_precompiles.insert(
360            exit_to_ethereum::ADDRESS,
361            AllPrecompiles::ExitToEthereum(ethereum_exit),
362        );
363        generic_precompiles.insert(
364            cross_contract_call::ADDRESS,
365            AllPrecompiles::CrossContractCall(cross_contract_call),
366        );
367        generic_precompiles.insert(
368            predecessor_account::ADDRESS,
369            AllPrecompiles::PredecessorAccount(predecessor_account_id),
370        );
371        generic_precompiles.insert(
372            prepaid_gas::ADDRESS,
373            AllPrecompiles::PrepaidGas(prepaid_gas),
374        );
375        generic_precompiles.insert(
376            promise_result::ADDRESS,
377            AllPrecompiles::PromiseResult(promise_results),
378        );
379
380        Self {
381            all_precompiles: generic_precompiles,
382            paused_precompiles: BTreeSet::new(),
383        }
384    }
385}
386
387pub enum AllPrecompiles<'a, I, E, H> {
388    ExitToNear(ExitToNear<I>),
389    ExitToEthereum(ExitToEthereum<I>),
390    CrossContractCall(CrossContractCall<I>),
391    PredecessorAccount(PredecessorAccount<'a, E>),
392    PrepaidGas(PrepaidGas<'a, E>),
393    PromiseResult(PromiseResult<H>),
394    Generic(Box<dyn Precompile>),
395}
396
397const fn make_h256(x: u128, y: u128) -> H256 {
398    let x_bytes = x.to_be_bytes();
399    let y_bytes = y.to_be_bytes();
400    H256([
401        x_bytes[0],
402        x_bytes[1],
403        x_bytes[2],
404        x_bytes[3],
405        x_bytes[4],
406        x_bytes[5],
407        x_bytes[6],
408        x_bytes[7],
409        x_bytes[8],
410        x_bytes[9],
411        x_bytes[10],
412        x_bytes[11],
413        x_bytes[12],
414        x_bytes[13],
415        x_bytes[14],
416        x_bytes[15],
417        y_bytes[0],
418        y_bytes[1],
419        y_bytes[2],
420        y_bytes[3],
421        y_bytes[4],
422        y_bytes[5],
423        y_bytes[6],
424        y_bytes[7],
425        y_bytes[8],
426        y_bytes[9],
427        y_bytes[10],
428        y_bytes[11],
429        y_bytes[12],
430        y_bytes[13],
431        y_bytes[14],
432        y_bytes[15],
433    ])
434}
435
436#[cfg(test)]
437mod tests {
438    use crate::prelude::H160;
439    use crate::{prelude, Byzantium, Istanbul};
440    use prelude::types::Address;
441
442    #[test]
443    fn test_precompile_addresses() {
444        assert_eq!(super::secp256k1::ECRecover::ADDRESS, u8_to_address(1));
445        assert_eq!(super::hash::SHA256::ADDRESS, u8_to_address(2));
446        assert_eq!(super::hash::RIPEMD160::ADDRESS, u8_to_address(3));
447        assert_eq!(super::identity::Identity::ADDRESS, u8_to_address(4));
448        assert_eq!(super::ModExp::<Byzantium>::ADDRESS, u8_to_address(5));
449        assert_eq!(super::Bn256Add::<Istanbul>::ADDRESS, u8_to_address(6));
450        assert_eq!(super::Bn256Mul::<Istanbul>::ADDRESS, u8_to_address(7));
451        assert_eq!(super::Bn256Pair::<Istanbul>::ADDRESS, u8_to_address(8));
452        assert_eq!(super::blake2::Blake2F::ADDRESS, u8_to_address(9));
453    }
454
455    #[test]
456    #[allow(clippy::too_many_lines)]
457    fn test_paused_precompiles_throws_error() {
458        use crate::{
459            AllPrecompiles, Context, EvmPrecompileResult, ExitError, Precompile, PrecompileOutput,
460            Precompiles,
461        };
462        use aurora_engine_sdk::env::Fixed;
463        use aurora_engine_sdk::promise::Noop;
464        use aurora_engine_test_doubles::io::StoragePointer;
465        use aurora_engine_types::types::EthGas;
466        use aurora_evm::executor::stack::{PrecompileFailure, PrecompileHandle, PrecompileSet};
467        use aurora_evm::{ExitFatal, ExitReason, Transfer};
468
469        struct MockPrecompile;
470
471        impl Precompile for MockPrecompile {
472            fn required_gas(_input: &[u8]) -> Result<EthGas, ExitError>
473            where
474                Self: Sized,
475            {
476                Ok(EthGas::new(0))
477            }
478
479            fn run(
480                &self,
481                _input: &[u8],
482                _target_gas: Option<EthGas>,
483                _context: &Context,
484                _is_static: bool,
485            ) -> EvmPrecompileResult {
486                Ok(PrecompileOutput::default())
487            }
488        }
489
490        struct MockPrecompileHandle {
491            code_address: H160,
492        }
493
494        impl MockPrecompileHandle {
495            pub const fn new(code_address: H160) -> Self {
496                Self { code_address }
497            }
498        }
499
500        impl PrecompileHandle for MockPrecompileHandle {
501            fn call(
502                &mut self,
503                _to: H160,
504                _transfer: Option<Transfer>,
505                _input: Vec<u8>,
506                _gas_limit: Option<u64>,
507                _is_static: bool,
508                _context: &Context,
509            ) -> (ExitReason, Vec<u8>) {
510                unimplemented!()
511            }
512
513            fn record_cost(&mut self, _cost: u64) -> Result<(), ExitError> {
514                unimplemented!()
515            }
516
517            fn record_external_cost(
518                &mut self,
519                _ref_time: Option<u64>,
520                _proof_size: Option<u64>,
521                _storage_growth: Option<u64>,
522            ) -> Result<(), ExitError> {
523                unimplemented!()
524            }
525
526            fn refund_external_cost(&mut self, _ref_time: Option<u64>, _proof_size: Option<u64>) {
527                unimplemented!()
528            }
529
530            fn remaining_gas(&self) -> u64 {
531                unimplemented!()
532            }
533
534            fn log(
535                &mut self,
536                _address: H160,
537                _topics: Vec<aurora_engine_types::H256>,
538                _data: Vec<u8>,
539            ) -> Result<(), ExitError> {
540                unimplemented!()
541            }
542
543            fn code_address(&self) -> H160 {
544                self.code_address
545            }
546
547            fn input(&self) -> &[u8] {
548                unimplemented!()
549            }
550
551            fn context(&self) -> &Context {
552                unimplemented!()
553            }
554
555            fn is_static(&self) -> bool {
556                unimplemented!()
557            }
558
559            fn gas_limit(&self) -> Option<u64> {
560                unimplemented!()
561            }
562        }
563
564        let precompile_address = Address::default();
565        let precompile: AllPrecompiles<StoragePointer, Fixed, Noop> =
566            AllPrecompiles::Generic(Box::new(MockPrecompile));
567
568        let precompiles: Precompiles<StoragePointer, Fixed, Noop> = Precompiles {
569            all_precompiles: {
570                let mut map = prelude::BTreeMap::new();
571                map.insert(precompile_address, precompile);
572                map
573            },
574            paused_precompiles: {
575                let mut set = prelude::BTreeSet::new();
576                set.insert(precompile_address);
577                set
578            },
579        };
580        let mut precompile_handle = MockPrecompileHandle::new(precompile_address.raw());
581
582        let result = precompiles
583            .execute(&mut precompile_handle)
584            .expect("result must contain error but is empty");
585        let actual_failure = result.expect_err("result must contain failure but is successful");
586        let expected_failure = PrecompileFailure::Fatal {
587            exit_status: ExitFatal::Other(prelude::Cow::Borrowed("ERR_PAUSED")),
588        };
589
590        assert_eq!(expected_failure, actual_failure);
591    }
592
593    const fn u8_to_address(x: u8) -> Address {
594        let mut bytes = [0u8; 20];
595        bytes[19] = x;
596        Address::new(H160(bytes))
597    }
598}