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
66pub trait Precompile {
68 fn required_gas(input: &[u8]) -> Result<EthGas, ExitError>
70 where
71 Self: Sized;
72
73 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
90pub trait HardFork {}
92
93pub struct Homestead;
95
96pub struct Byzantium;
98
99pub struct Istanbul;
101
102pub 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 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}