use core::marker::PhantomData; use alloc::vec::Vec; use fp_evm::{ExitError, PrecompileFailure}; use frame_support::BoundedVec; use pallet_evm::PrecompileHandle; use precompile_utils::{ EvmResult, prelude::{BoundedBytes, UnboundedBytes}, }; use sp_core::ConstU32; use crate::{PrecompileExt, PrecompileHandleExt}; type BeaconConfiguration = ( UnboundedBytes, u32, u32, UnboundedBytes, UnboundedBytes, UnboundedBytes, UnboundedBytes, ); pub struct DrandPrecompile(PhantomData); impl PrecompileExt for DrandPrecompile where R: frame_system::Config + pallet_evm::Config + pallet_drand::Config, R::AccountId: From<[u8; 32]>, frame_system::pallet_prelude::BlockNumberFor: TryInto, { const INDEX: u64 = 2064; } #[precompile_utils::precompile] impl DrandPrecompile where R: frame_system::Config + pallet_evm::Config + pallet_drand::Config, R::AccountId: From<[u8; 32]>, frame_system::pallet_prelude::BlockNumberFor: TryInto, { #[precompile::public("getBeaconConfig()")] #[precompile::view] fn get_beacon_config(handle: &mut impl PrecompileHandle) -> EvmResult { handle.record_db_reads::(1)?; let config = pallet_drand::BeaconConfig::::get(); Ok(( config.public_key.into_inner().into(), config.period, config.genesis_time, config.hash.into_inner().into(), config.group_hash.into_inner().into(), config.scheme_id.into_inner().into(), config.metadata.beacon_id.into_inner().into(), )) } #[precompile::public("getPulse(uint64)")] #[precompile::view] fn get_pulse( handle: &mut impl PrecompileHandle, round: u64, ) -> EvmResult<(bool, u64, UnboundedBytes, UnboundedBytes)> { handle.record_db_reads::(1)?; match pallet_drand::Pulses::::get(round) { Some(pulse) => Ok(( true, pulse.round, pulse.randomness.into_inner().into(), pulse.signature.into_inner().into(), )), None => Ok(( false, round, UnboundedBytes::default(), UnboundedBytes::default(), )), } } #[precompile::public("getStoredRoundRange()")] #[precompile::view] fn get_stored_round_range(handle: &mut impl PrecompileHandle) -> EvmResult<(u64, u64)> { handle.record_db_reads::(2)?; Ok(( pallet_drand::OldestStoredRound::::get(), pallet_drand::LastStoredRound::::get(), )) } #[precompile::public("getNextUnsignedAt()")] #[precompile::view] fn get_next_unsigned_at(handle: &mut impl PrecompileHandle) -> EvmResult { handle.record_db_reads::(1)?; pallet_drand::Pallet::::next_unsigned_at() .try_into() .map_err(|_| conversion_error("drand next unsigned block")) } #[precompile::public("hasMigrationRun(bytes)")] #[precompile::view] fn has_migration_run( handle: &mut impl PrecompileHandle, key: BoundedBytes>, ) -> EvmResult { handle.record_db_reads::(1)?; let key = BoundedVec::>::try_from(Vec::::from(key)) .map_err(|_| conversion_error("drand migration key"))?; Ok(pallet_drand::HasMigrationRun::::get(key)) } } 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_state_are_stable() { new_test_ext().execute_with(|| { assert_eq!(DrandPrecompile::::INDEX, 2064); let precompiles = precompiles::>(); let caller = addr_from_index(1); let address = addr_from_index(2064); let read_cost = RuntimeHelper::::db_read_gas_cost(); precompiles .prepare_test( caller, address, encode_with_selector(selector_u32("getPulse(uint64)"), (42u64,)), ) .with_static_call(true) .expect_cost(read_cost) .execute_returns_raw(encode_return_value(( false, 42u64, UnboundedBytes::default(), UnboundedBytes::default(), ))); precompiles .prepare_test( caller, address, encode_with_selector(selector_u32("getStoredRoundRange()"), ()), ) .with_static_call(true) .expect_cost(read_cost.saturating_mul(2)) .execute_returns_raw(encode_return_value((0u64, 0u64))); precompiles .prepare_test( caller, address, encode_with_selector(selector_u32("getNextUnsignedAt()"), ()), ) .with_static_call(true) .expect_cost(read_cost) .execute_returns_raw(encode_return_value(0u64)); precompiles .prepare_test( caller, address, encode_with_selector( selector_u32("hasMigrationRun(bytes)"), (BoundedBytes::>::from(Vec::::new()),), ), ) .with_static_call(true) .expect_cost(read_cost) .execute_returns_raw(encode_return_value(false)); }); } }