#![cfg_attr(not(feature = "std"), no_std)]
mod benchmarking;
pub mod weights;
use sp_std::{result, cmp};
use sp_inherents::InherentData;
#[cfg(feature = "std")]
use frame_support::debug;
use frame_support::traits::{Time, UnixTime};
use sp_runtime::{
RuntimeString,
traits::{
AtLeast32Bit, Zero, SaturatedConversion, Scale,
}
};
use sp_timestamp::{
InherentError, INHERENT_IDENTIFIER, InherentType,
OnTimestampSet,
};
pub use weights::WeightInfo;
pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use super::*;
#[pallet::config]
pub trait Config: frame_system::Config {
type Moment: Parameter + Default + AtLeast32Bit
+ Scale<Self::BlockNumber, Output = Self::Moment> + Copy;
type OnTimestampSet: OnTimestampSet<Self::Moment>;
#[pallet::constant]
type MinimumPeriod: Get<Self::Moment>;
type WeightInfo: WeightInfo;
}
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(PhantomData<T>);
#[pallet::storage]
#[pallet::getter(fn now)]
pub type Now<T: Config> = StorageValue<_, T::Moment, ValueQuery>;
#[pallet::storage]
pub(super) type DidUpdate<T: Config> = StorageValue<_, bool, ValueQuery>;
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
T::WeightInfo::on_finalize()
}
fn on_finalize(_n: BlockNumberFor<T>) {
assert!(DidUpdate::<T>::take(), "Timestamp must be updated once in the block");
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight((
T::WeightInfo::set(),
DispatchClass::Mandatory
))]
pub(super) fn set(origin: OriginFor<T>, #[pallet::compact] now: T::Moment) -> DispatchResultWithPostInfo {
ensure_none(origin)?;
assert!(!DidUpdate::<T>::exists(), "Timestamp must be updated only once in the block");
let prev = Self::now();
assert!(
prev.is_zero() || now >= prev + T::MinimumPeriod::get(),
"Timestamp must increment by at least <MinimumPeriod> between sequential blocks"
);
Now::<T>::put(now);
DidUpdate::<T>::put(true);
<T::OnTimestampSet as OnTimestampSet<_>>::on_timestamp_set(now);
Ok(().into())
}
}
#[pallet::inherent]
impl<T: Config> ProvideInherent for Pallet<T> {
type Call = Call<T>;
type Error = InherentError;
const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
fn create_inherent(data: &InherentData) -> Option<Self::Call> {
let data: T::Moment = extract_inherent_data(data)
.expect("Gets and decodes timestamp inherent data")
.saturated_into();
let next_time = cmp::max(data, Self::now() + T::MinimumPeriod::get());
Some(Call::set(next_time.into()))
}
fn check_inherent(call: &Self::Call, data: &InherentData) -> result::Result<(), Self::Error> {
const MAX_TIMESTAMP_DRIFT_MILLIS: u64 = 30 * 1000;
let t: u64 = match call {
Call::set(ref t) => t.clone().saturated_into::<u64>(),
_ => return Ok(()),
};
let data = extract_inherent_data(data).map_err(|e| InherentError::Other(e))?;
let minimum = (Self::now() + T::MinimumPeriod::get()).saturated_into::<u64>();
if t > data + MAX_TIMESTAMP_DRIFT_MILLIS {
Err(InherentError::Other("Timestamp too far in future to accept".into()))
} else if t < minimum {
Err(InherentError::ValidAtTimestamp(minimum))
} else {
Ok(())
}
}
}
}
impl<T: Config> Pallet<T> {
pub fn get() -> T::Moment {
Self::now()
}
#[cfg(feature = "std")]
pub fn set_timestamp(now: T::Moment) {
Now::<T>::put(now);
}
}
fn extract_inherent_data(data: &InherentData) -> Result<InherentType, RuntimeString> {
data.get_data::<InherentType>(&INHERENT_IDENTIFIER)
.map_err(|_| RuntimeString::from("Invalid timestamp inherent data encoding."))?
.ok_or_else(|| "Timestamp inherent data is not provided.".into())
}
impl<T: Config> Time for Pallet<T> {
type Moment = T::Moment;
fn now() -> Self::Moment {
Self::now()
}
}
impl<T: Config> UnixTime for Pallet<T> {
fn now() -> core::time::Duration {
let now = Self::now();
sp_std::if_std! {
if now == T::Moment::zero() {
debug::error!(
"`pallet_timestamp::UnixTime::now` is called at genesis, invalid value returned: 0"
);
}
}
core::time::Duration::from_millis(now.saturated_into::<u64>())
}
}
#[cfg(test)]
mod tests {
use crate as pallet_timestamp;
use super::*;
use frame_support::{assert_ok, parameter_types};
use sp_io::TestExternalities;
use sp_core::H256;
use sp_runtime::{traits::{BlakeTwo256, IdentityLookup}, testing::Header};
pub fn new_test_ext() -> TestExternalities {
let t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
TestExternalities::new(t)
}
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>;
frame_support::construct_runtime!(
pub enum Test where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: frame_system::{Module, Call, Config, Storage, Event<T>},
Timestamp: pallet_timestamp::{Module, Call, Storage, Inherent},
}
);
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub BlockWeights: frame_system::limits::BlockWeights =
frame_system::limits::BlockWeights::simple_max(1024);
}
impl frame_system::Config for Test {
type BaseCallFilter = ();
type BlockWeights = ();
type BlockLength = ();
type DbWeight = ();
type Origin = Origin;
type Index = u64;
type BlockNumber = u64;
type Call = Call;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type Event = Event;
type BlockHashCount = BlockHashCount;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = ();
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
}
parameter_types! {
pub const MinimumPeriod: u64 = 5;
}
impl Config for Test {
type Moment = u64;
type OnTimestampSet = ();
type MinimumPeriod = MinimumPeriod;
type WeightInfo = ();
}
#[test]
fn timestamp_works() {
new_test_ext().execute_with(|| {
Timestamp::set_timestamp(42);
assert_ok!(Timestamp::set(Origin::none(), 69));
assert_eq!(Timestamp::now(), 69);
});
}
#[test]
#[should_panic(expected = "Timestamp must be updated only once in the block")]
fn double_timestamp_should_fail() {
new_test_ext().execute_with(|| {
Timestamp::set_timestamp(42);
assert_ok!(Timestamp::set(Origin::none(), 69));
let _ = Timestamp::set(Origin::none(), 70);
});
}
#[test]
#[should_panic(expected = "Timestamp must increment by at least <MinimumPeriod> between sequential blocks")]
fn block_period_minimum_enforced() {
new_test_ext().execute_with(|| {
Timestamp::set_timestamp(42);
let _ = Timestamp::set(Origin::none(), 46);
});
}
}