use core::marker::PhantomData; use codec::{Decode, Encode}; use fp_evm::{ExitError, PrecompileFailure}; use pallet_evm::PrecompileHandle; use precompile_utils::EvmResult; use sp_core::H256; use crate::{PrecompileExt, PrecompileHandleExt}; type ScheduledCallMetadata = (bool, bool, H256, u8, H256, bool, u32, bool, u64, u32); pub struct SchedulerPrecompile(PhantomData); impl PrecompileExt for SchedulerPrecompile where R: frame_system::Config + pallet_evm::Config + pallet_scheduler::Config, R::AccountId: From<[u8; 32]>, R::Hash: AsRef<[u8]>, pallet_scheduler::BlockNumberFor: TryFrom + TryInto, { const INDEX: u64 = 2063; } #[precompile_utils::precompile] impl SchedulerPrecompile where R: frame_system::Config + pallet_evm::Config + pallet_scheduler::Config, R::AccountId: From<[u8; 32]>, R::Hash: AsRef<[u8]>, pallet_scheduler::BlockNumberFor: TryFrom + TryInto, { #[precompile::public("getIncompleteSince()")] #[precompile::view] fn get_incomplete_since(handle: &mut impl PrecompileHandle) -> EvmResult<(bool, u64)> { handle.record_db_reads::(1)?; match pallet_scheduler::IncompleteSince::::get() { Some(block) => Ok((true, block_number_to_u64::(block)?)), None => Ok((false, 0)), } } #[precompile::public("getScheduledCallCount(uint64)")] #[precompile::view] fn get_scheduled_call_count(handle: &mut impl PrecompileHandle, when: u64) -> EvmResult { handle.record_db_reads::(1)?; let agenda = pallet_scheduler::Agenda::::get(block_number_from_u64::(when)?); u32::try_from(agenda.len()).map_err(|_| conversion_error("scheduler agenda length")) } #[precompile::public("getScheduledCall(uint64,uint32)")] #[precompile::view] fn get_scheduled_call( handle: &mut impl PrecompileHandle, when: u64, index: u32, ) -> EvmResult { handle.record_db_reads::(1)?; let agenda = pallet_scheduler::Agenda::::get(block_number_from_u64::(when)?); let Some(Some(scheduled)) = agenda .get(usize::try_from(index).map_err(|_| conversion_error("scheduler agenda index"))?) else { return Ok(( false, false, H256::zero(), 0, H256::zero(), false, 0, false, 0, 0, )); }; let (has_task_id, task_id) = scheduled .maybe_id .map(|id| (true, H256::from(id))) .unwrap_or((false, H256::zero())); let call_hash = <[u8; 32]>::try_from(scheduled.call.hash().as_ref()) .map(H256::from) .map_err(|_| conversion_error("scheduler call hash"))?; let (has_call_length, call_length) = scheduled .call .len() .map(|length| (true, length)) .unwrap_or((false, 0)); let (is_periodic, period, remaining) = match scheduled.maybe_periodic { Some((period, remaining)) => (true, block_number_to_u64::(period)?, remaining), None => (false, 0, 0), }; Ok(( true, has_task_id, task_id, scheduled.priority, call_hash, has_call_length, call_length, is_periodic, period, remaining, )) } #[precompile::public("getRetry(uint64,uint32)")] #[precompile::view] fn get_retry( handle: &mut impl PrecompileHandle, when: u64, index: u32, ) -> EvmResult<(bool, u8, u8, u64)> { handle.record_db_reads::(1)?; let address = (block_number_from_u64::(when)?, index); match pallet_scheduler::Retries::::get(address) { Some(retry) => { let encoded = retry.encode(); let (total_retries, remaining, period) = <(u8, u8, pallet_scheduler::BlockNumberFor)>::decode( &mut encoded.as_slice(), ) .map_err(|_| conversion_error("scheduler retry metadata"))?; Ok(( true, total_retries, remaining, block_number_to_u64::(period)?, )) } None => Ok((false, 0, 0, 0)), } } #[precompile::public("getTaskAddress(bytes32)")] #[precompile::view] fn get_task_address( handle: &mut impl PrecompileHandle, task_id: H256, ) -> EvmResult<(bool, u64, u32)> { handle.record_db_reads::(1)?; match pallet_scheduler::Lookup::::get(task_id.0) { Some((when, index)) => Ok((true, block_number_to_u64::(when)?, index)), None => Ok((false, 0, 0)), } } } fn block_number_from_u64(block: u64) -> EvmResult> where R: pallet_scheduler::Config, pallet_scheduler::BlockNumberFor: TryFrom, { block .try_into() .map_err(|_| conversion_error("scheduler block number")) } fn block_number_to_u64(block: pallet_scheduler::BlockNumberFor) -> EvmResult where R: pallet_scheduler::Config, pallet_scheduler::BlockNumberFor: TryInto, { block .try_into() .map_err(|_| conversion_error("scheduler block number")) } fn conversion_error(field: &'static str) -> PrecompileFailure { PrecompileFailure::Error { exit_status: ExitError::Other(field.into()), } } #[cfg(test)] mod tests { use super::*; use crate::mock::{Runtime, addr_from_index, new_test_ext, precompiles, selector_u32}; use precompile_utils::{ prelude::RuntimeHelper, solidity::{encode_return_value, encode_with_selector}, testing::PrecompileTesterExt, }; #[test] fn address_selectors_and_empty_metadata_are_stable() { new_test_ext().execute_with(|| { assert_eq!(SchedulerPrecompile::::INDEX, 2063); let precompiles = precompiles::>(); let caller = addr_from_index(1); let address = addr_from_index(2063); let read_cost = RuntimeHelper::::db_read_gas_cost(); precompiles .prepare_test( caller, address, encode_with_selector(selector_u32("getIncompleteSince()"), ()), ) .with_static_call(true) .expect_cost(read_cost) .execute_returns_raw(encode_return_value((false, 0u64))); precompiles .prepare_test( caller, address, encode_with_selector(selector_u32("getScheduledCallCount(uint64)"), (10u64,)), ) .with_static_call(true) .expect_cost(read_cost) .execute_returns_raw(encode_return_value(0u32)); precompiles .prepare_test( caller, address, encode_with_selector( selector_u32("getScheduledCall(uint64,uint32)"), (10u64, 0u32), ), ) .with_static_call(true) .expect_cost(read_cost) .execute_returns_raw(encode_return_value(( false, false, H256::zero(), 0u8, H256::zero(), false, 0u32, false, 0u64, 0u32, ))); precompiles .prepare_test( caller, address, encode_with_selector(selector_u32("getRetry(uint64,uint32)"), (10u64, 0u32)), ) .with_static_call(true) .expect_cost(read_cost) .execute_returns_raw(encode_return_value((false, 0u8, 0u8, 0u64))); precompiles .prepare_test( caller, address, encode_with_selector(selector_u32("getTaskAddress(bytes32)"), (H256::zero(),)), ) .with_static_call(true) .expect_cost(read_cost) .execute_returns_raw(encode_return_value((false, 0u64, 0u32))); }); } }