#![cfg_attr(not(feature = "std"), no_std)]
mod benchmarking;
pub mod weights;
use tetcore_std::{result, cmp};
use tp_inherents::{ProvideInherent, InherentData, InherentIdentifier};
#[cfg(feature = "std")]
use fabric_support::debug;
use fabric_support::{
Parameter, decl_storage, decl_module,
traits::{Time, UnixTime, Get},
weights::{DispatchClass, Weight},
};
use tp_runtime::{
RuntimeString,
traits::{
AtLeast32Bit, Zero, SaturatedConversion, Scale,
}
};
use fabric_system::ensure_none;
use tp_timestamp::{
InherentError, INHERENT_IDENTIFIER, InherentType,
OnTimestampSet,
};
pub use weights::WeightInfo;
pub trait Config: fabric_system::Config {
type Moment: Parameter + Default + AtLeast32Bit
+ Scale<Self::BlockNumber, Output = Self::Moment> + Copy;
type OnTimestampSet: OnTimestampSet<Self::Moment>;
type MinimumPeriod: Get<Self::Moment>;
type WeightInfo: WeightInfo;
}
decl_module! {
pub struct Module<T: Config> for enum Call where origin: T::Origin {
const MinimumPeriod: T::Moment = T::MinimumPeriod::get();
#[weight = (
T::WeightInfo::set(),
DispatchClass::Mandatory
)]
fn set(origin, #[compact] now: T::Moment) {
ensure_none(origin)?;
assert!(!<Self as Store>::DidUpdate::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"
);
<Self as Store>::Now::put(now);
<Self as Store>::DidUpdate::put(true);
<T::OnTimestampSet as OnTimestampSet<_>>::on_timestamp_set(now);
}
fn on_initialize() -> Weight {
T::WeightInfo::on_finalize()
}
fn on_finalize() {
assert!(<Self as Store>::DidUpdate::take(), "Timestamp must be updated once in the block");
}
}
}
decl_storage! {
trait Store for Module<T: Config> as Timestamp {
pub Now get(fn now): T::Moment;
DidUpdate: bool;
}
}
impl<T: Config> Module<T> {
pub fn get() -> T::Moment {
Self::now()
}
#[cfg(feature = "std")]
pub fn set_timestamp(now: T::Moment) {
<Self as Store>::Now::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> ProvideInherent for Module<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> Time for Module<T> {
type Moment = T::Moment;
fn now() -> Self::Moment {
Self::now()
}
}
impl<T: Config> UnixTime for Module<T> {
fn now() -> core::time::Duration {
let now = Self::now();
tetcore_std::if_std! {
if now == T::Moment::zero() {
debug::error!(
"`noble_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 noble_timestamp;
use super::*;
use fabric_support::{assert_ok, parameter_types};
use tet_io::TestExternalities;
use tet_core::H256;
use tp_runtime::{traits::{BlakeTwo256, IdentityLookup}, testing::Header};
pub fn new_test_ext() -> TestExternalities {
let t = fabric_system::GenesisConfig::default().build_storage::<Test>().unwrap();
TestExternalities::new(t)
}
type UncheckedExtrinsic = fabric_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = fabric_system::mocking::MockBlock<Test>;
fabric_support::construct_runtime!(
pub enum Test where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: fabric_system::{Module, Call, Config, Storage, Event<T>},
Timestamp: noble_timestamp::{Module, Call, Storage, Inherent},
}
);
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub BlockWeights: fabric_system::limits::BlockWeights =
fabric_system::limits::BlockWeights::simple_max(1024);
}
impl fabric_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 NobleInfo = NobleInfo;
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);
});
}
}