1#![cfg_attr(not(test), warn(unused_crate_dependencies))]
3#![cfg_attr(docsrs, feature(doc_cfg))]
4
5pub mod error;
6pub use error::Result;
7use tempo_chainspec::hardfork::TempoHardfork;
8pub mod account_keychain;
9pub mod nonce;
10pub mod path_usd;
11pub mod stablecoin_exchange;
12pub mod storage;
13pub mod tip20;
14pub mod tip20_factory;
15pub mod tip20_rewards_registry;
16pub mod tip403_registry;
17pub mod tip_account_registrar;
18pub mod tip_fee_manager;
19pub mod validator_config;
20
21#[cfg(test)]
22pub mod test_util;
23
24use crate::{
25 account_keychain::AccountKeychain,
26 nonce::NonceManager,
27 path_usd::PathUSD,
28 stablecoin_exchange::StablecoinExchange,
29 storage::{PrecompileStorageProvider, evm::EvmPrecompileStorageProvider},
30 tip_account_registrar::TipAccountRegistrar,
31 tip_fee_manager::TipFeeManager,
32 tip20::{TIP20Token, address_to_token_id_unchecked, is_tip20_prefix},
33 tip20_factory::TIP20Factory,
34 tip20_rewards_registry::TIP20RewardsRegistry,
35 tip403_registry::TIP403Registry,
36 validator_config::ValidatorConfig,
37};
38pub use error::IntoPrecompileResult;
39
40#[cfg(test)]
41use alloy::sol_types::SolInterface;
42use alloy::{
43 primitives::{Address, Bytes},
44 sol,
45 sol_types::{SolCall, SolError},
46};
47use alloy_evm::precompiles::{DynPrecompile, PrecompilesMap};
48use revm::{
49 context::CfgEnv,
50 precompile::{PrecompileError, PrecompileId, PrecompileOutput, PrecompileResult},
51};
52
53pub use tempo_contracts::precompiles::{
54 ACCOUNT_KEYCHAIN_ADDRESS, DEFAULT_FEE_TOKEN_POST_ALLEGRETTO, DEFAULT_FEE_TOKEN_PRE_ALLEGRETTO,
55 NONCE_PRECOMPILE_ADDRESS, PATH_USD_ADDRESS, STABLECOIN_EXCHANGE_ADDRESS, TIP_ACCOUNT_REGISTRAR,
56 TIP_FEE_MANAGER_ADDRESS, TIP20_FACTORY_ADDRESS, TIP20_REWARDS_REGISTRY_ADDRESS,
57 TIP403_REGISTRY_ADDRESS, VALIDATOR_CONFIG_ADDRESS,
58};
59
60pub use account_keychain::{AuthorizedKey, compute_keys_slot};
62
63pub const INPUT_PER_WORD_COST: u64 = 6;
67
68#[inline]
69pub fn input_cost(calldata_len: usize) -> u64 {
70 revm::interpreter::gas::cost_per_word(calldata_len, INPUT_PER_WORD_COST).unwrap_or(u64::MAX)
71}
72
73pub trait Precompile {
74 fn call(&mut self, calldata: &[u8], msg_sender: Address) -> PrecompileResult;
75}
76
77pub fn extend_tempo_precompiles(precompiles: &mut PrecompilesMap, cfg: &CfgEnv<TempoHardfork>) {
78 let chain_id = cfg.chain_id;
79 let spec = cfg.spec;
80 precompiles.set_precompile_lookup(move |address: &Address| {
81 if is_tip20_prefix(*address) {
82 let token_id = address_to_token_id_unchecked(*address);
83 if token_id == 0 {
84 Some(PathUSDPrecompile::create(chain_id, spec))
85 } else {
86 Some(TIP20Precompile::create(*address, chain_id, spec))
87 }
88 } else if *address == TIP20_FACTORY_ADDRESS {
89 Some(TIP20FactoryPrecompile::create(chain_id, spec))
90 } else if *address == TIP20_REWARDS_REGISTRY_ADDRESS {
91 Some(TIP20RewardsRegistryPrecompile::create(chain_id, spec))
92 } else if *address == TIP403_REGISTRY_ADDRESS {
93 Some(TIP403RegistryPrecompile::create(chain_id, spec))
94 } else if *address == TIP_FEE_MANAGER_ADDRESS {
95 Some(TipFeeManagerPrecompile::create(chain_id, spec))
96 } else if *address == TIP_ACCOUNT_REGISTRAR {
97 Some(TipAccountRegistrarPrecompile::create(chain_id, spec))
98 } else if *address == STABLECOIN_EXCHANGE_ADDRESS {
99 Some(StablecoinExchangePrecompile::create(chain_id, spec))
100 } else if *address == NONCE_PRECOMPILE_ADDRESS {
101 Some(NoncePrecompile::create(chain_id, spec))
102 } else if *address == VALIDATOR_CONFIG_ADDRESS {
103 Some(ValidatorConfigPrecompile::create(chain_id, spec))
104 } else if *address == ACCOUNT_KEYCHAIN_ADDRESS && spec.is_allegretto() {
105 Some(AccountKeychainPrecompile::create(chain_id, spec))
107 } else {
108 None
109 }
110 });
111}
112
113sol! {
114 error DelegateCallNotAllowed();
115}
116
117macro_rules! tempo_precompile {
118 ($id:expr, |$input:ident| $impl:expr) => {
119 DynPrecompile::new_stateful(PrecompileId::Custom($id.into()), move |$input| {
120 if !$input.is_direct_call() {
121 return Ok(PrecompileOutput::new_reverted(
122 0,
123 DelegateCallNotAllowed {}.abi_encode().into(),
124 ));
125 }
126 $impl.call($input.data, $input.caller)
127 })
128 };
129}
130
131pub struct TipFeeManagerPrecompile;
132impl TipFeeManagerPrecompile {
133 pub fn create(chain_id: u64, spec: TempoHardfork) -> DynPrecompile {
134 tempo_precompile!("TipFeeManager", |input| TipFeeManager::new(
135 &mut EvmPrecompileStorageProvider::new(input.internals, input.gas, chain_id, spec)
136 ))
137 }
138}
139
140pub struct TipAccountRegistrarPrecompile;
141impl TipAccountRegistrarPrecompile {
142 pub fn create(chain_id: u64, spec: TempoHardfork) -> DynPrecompile {
143 tempo_precompile!("TipAccountRegistrar", |input| TipAccountRegistrar::new(
144 &mut crate::storage::evm::EvmPrecompileStorageProvider::new(
145 input.internals,
146 input.gas,
147 chain_id,
148 spec
149 ),
150 ))
151 }
152}
153
154pub struct TIP20RewardsRegistryPrecompile;
155impl TIP20RewardsRegistryPrecompile {
156 pub fn create(chain_id: u64, spec: TempoHardfork) -> DynPrecompile {
157 tempo_precompile!("TIP20RewardsRegistry", |input| TIP20RewardsRegistry::new(
158 &mut EvmPrecompileStorageProvider::new(input.internals, input.gas, chain_id, spec),
159 ))
160 }
161}
162
163pub struct TIP403RegistryPrecompile;
164impl TIP403RegistryPrecompile {
165 pub fn create(chain_id: u64, spec: TempoHardfork) -> DynPrecompile {
166 tempo_precompile!("TIP403Registry", |input| TIP403Registry::new(
167 &mut crate::storage::evm::EvmPrecompileStorageProvider::new(
168 input.internals,
169 input.gas,
170 chain_id,
171 spec
172 ),
173 ))
174 }
175}
176
177pub struct TIP20FactoryPrecompile;
178impl TIP20FactoryPrecompile {
179 pub fn create(chain_id: u64, spec: TempoHardfork) -> DynPrecompile {
180 tempo_precompile!("TIP20Factory", |input| TIP20Factory::new(
181 &mut EvmPrecompileStorageProvider::new(input.internals, input.gas, chain_id, spec)
182 ))
183 }
184}
185
186pub struct TIP20Precompile;
187impl TIP20Precompile {
188 pub fn create(address: Address, chain_id: u64, spec: TempoHardfork) -> DynPrecompile {
189 let token_id = address_to_token_id_unchecked(address);
190 tempo_precompile!("TIP20Token", |input| TIP20Token::new(
191 token_id,
192 &mut EvmPrecompileStorageProvider::new(input.internals, input.gas, chain_id, spec),
193 ))
194 }
195}
196
197pub struct StablecoinExchangePrecompile;
198impl StablecoinExchangePrecompile {
199 pub fn create(chain_id: u64, spec: TempoHardfork) -> DynPrecompile {
200 tempo_precompile!("StablecoinExchange", |input| StablecoinExchange::new(
201 &mut EvmPrecompileStorageProvider::new(input.internals, input.gas, chain_id, spec)
202 ))
203 }
204}
205
206pub struct NoncePrecompile;
207impl NoncePrecompile {
208 pub fn create(chain_id: u64, spec: TempoHardfork) -> DynPrecompile {
209 tempo_precompile!("NonceManager", |input| NonceManager::new(
210 &mut EvmPrecompileStorageProvider::new(input.internals, input.gas, chain_id, spec)
211 ))
212 }
213}
214
215pub struct AccountKeychainPrecompile;
216impl AccountKeychainPrecompile {
217 pub fn create(chain_id: u64, spec: TempoHardfork) -> DynPrecompile {
218 tempo_precompile!("AccountKeychain", |input| AccountKeychain::new(
219 &mut EvmPrecompileStorageProvider::new(input.internals, input.gas, chain_id, spec)
220 ))
221 }
222}
223
224pub struct PathUSDPrecompile;
225impl PathUSDPrecompile {
226 pub fn create(chain_id: u64, spec: TempoHardfork) -> DynPrecompile {
227 tempo_precompile!("PathUSD", |input| PathUSD::new(
228 &mut EvmPrecompileStorageProvider::new(input.internals, input.gas, chain_id, spec),
229 ))
230 }
231}
232
233pub struct ValidatorConfigPrecompile;
234impl ValidatorConfigPrecompile {
235 pub fn create(chain_id: u64, spec: TempoHardfork) -> DynPrecompile {
236 tempo_precompile!("ValidatorConfig", |input| ValidatorConfig::new(
237 &mut EvmPrecompileStorageProvider::new(input.internals, input.gas, chain_id, spec),
238 ))
239 }
240}
241
242#[inline]
243fn metadata<T: SolCall>(f: impl FnOnce() -> Result<T::Return>) -> PrecompileResult {
244 f().into_precompile_result(0, |ret| T::abi_encode_returns(&ret).into())
245}
246
247#[inline]
248fn view<T: SolCall>(calldata: &[u8], f: impl FnOnce(T) -> Result<T::Return>) -> PrecompileResult {
249 let Ok(call) = T::abi_decode(calldata) else {
250 return Ok(PrecompileOutput::new_reverted(0, Bytes::new()));
252 };
253 f(call).into_precompile_result(0, |ret| T::abi_encode_returns(&ret).into())
254}
255
256#[inline]
257pub fn mutate<T: SolCall>(
258 calldata: &[u8],
259 sender: Address,
260 f: impl FnOnce(Address, T) -> Result<T::Return>,
261) -> PrecompileResult {
262 let Ok(call) = T::abi_decode(calldata) else {
263 return Ok(PrecompileOutput::new_reverted(0, Bytes::new()));
264 };
265 f(sender, call).into_precompile_result(0, |ret| T::abi_encode_returns(&ret).into())
266}
267
268#[inline]
269fn mutate_void<T: SolCall>(
270 calldata: &[u8],
271 sender: Address,
272 f: impl FnOnce(Address, T) -> Result<()>,
273) -> PrecompileResult {
274 let Ok(call) = T::abi_decode(calldata) else {
275 return Ok(PrecompileOutput::new_reverted(0, Bytes::new()));
276 };
277 f(sender, call).into_precompile_result(0, |()| Bytes::new())
278}
279
280#[inline]
281fn fill_precompile_output(
282 mut output: PrecompileOutput,
283 storage: &mut impl PrecompileStorageProvider,
284) -> PrecompileOutput {
285 output.gas_used = storage.gas_used();
286
287 if !output.reverted && storage.spec().is_allegretto() {
289 output.gas_refunded = storage.gas_refunded();
290 }
291 output
292}
293
294#[inline]
299pub fn unknown_selector(selector: [u8; 4], gas: u64, spec: TempoHardfork) -> PrecompileResult {
300 if spec.is_moderato() {
301 error::TempoPrecompileError::UnknownFunctionSelector(selector)
302 .into_precompile_result(gas, |_: ()| Bytes::new())
303 } else {
304 Err(PrecompileError::Other("Unknown function selector".into()))
305 }
306}
307
308#[cfg(test)]
309pub fn expect_precompile_revert<E>(result: &PrecompileResult, expected_error: E)
310where
311 E: SolInterface + PartialEq + std::fmt::Debug,
312{
313 match result {
314 Ok(result) => {
315 assert!(result.reverted);
316 let decoded = E::abi_decode(&result.bytes).unwrap();
317 assert_eq!(decoded, expected_error);
318 }
319 Err(other) => {
320 panic!("expected reverted output, got: {other:?}");
321 }
322 }
323}
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328 use crate::{storage::evm::EvmPrecompileStorageProvider, tip20::TIP20Token};
329 use alloy::primitives::{Address, Bytes, U256};
330 use alloy_evm::{
331 EthEvmFactory, EvmEnv, EvmFactory, EvmInternals,
332 precompiles::{Precompile as AlloyEvmPrecompile, PrecompileInput},
333 };
334 use revm::{
335 context::ContextTr,
336 database::{CacheDB, EmptyDB},
337 };
338
339 #[test]
340 fn test_precompile_delegatecall() {
341 let precompile = tempo_precompile!("TIP20Token", |input| TIP20Token::new(
342 1,
343 &mut EvmPrecompileStorageProvider::new(
344 input.internals,
345 input.gas,
346 1,
347 Default::default()
348 ),
349 ));
350
351 let db = CacheDB::new(EmptyDB::new());
352 let mut evm = EthEvmFactory::default().create_evm(db, EvmEnv::default());
353 let block = evm.block.clone();
354 let evm_internals = EvmInternals::new(evm.journal_mut(), &block);
355
356 let target_address = Address::random();
357 let bytecode_address = Address::random();
358 let input = PrecompileInput {
359 data: &Bytes::new(),
360 caller: Address::ZERO,
361 internals: evm_internals,
362 gas: 0,
363 value: U256::ZERO,
364 target_address,
365 bytecode_address,
366 };
367
368 let result = AlloyEvmPrecompile::call(&precompile, input);
369
370 match result {
371 Ok(output) => {
372 assert!(output.reverted);
373 let decoded = DelegateCallNotAllowed::abi_decode(&output.bytes).unwrap();
374 assert!(matches!(decoded, DelegateCallNotAllowed {}));
375 }
376 Err(_) => panic!("expected reverted output"),
377 }
378 }
379}