From 2c10b43e718a0db0369ab8c76720f986409dccd3 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 12 Feb 2024 17:49:35 +0100 Subject: [PATCH 1/3] Draft `sp-time` Signed-off-by: Oliver Tale-Yazdi --- Cargo.lock | 11 +++ Cargo.toml | 1 + substrate/primitives/time/Cargo.toml | 32 +++++++ substrate/primitives/time/src/lib.rs | 125 +++++++++++++++++++++++++++ 4 files changed, 169 insertions(+) create mode 100644 substrate/primitives/time/Cargo.toml create mode 100644 substrate/primitives/time/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index f5b272d11756..7129b8e3486e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18921,6 +18921,17 @@ dependencies = [ "sp-std 14.0.0", ] +[[package]] +name = "sp-time" +version = "0.1.0" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-arithmetic", + "sp-runtime", + "sp-std 14.0.0", +] + [[package]] name = "sp-timestamp" version = "26.0.0" diff --git a/Cargo.toml b/Cargo.toml index 0a70bb03756b..f7c09a8ed1e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -470,6 +470,7 @@ members = [ "substrate/primitives/std", "substrate/primitives/storage", "substrate/primitives/test-primitives", + "substrate/primitives/time", "substrate/primitives/timestamp", "substrate/primitives/tracing", "substrate/primitives/transaction-pool", diff --git a/substrate/primitives/time/Cargo.toml b/substrate/primitives/time/Cargo.toml new file mode 100644 index 000000000000..acff68b5e1e8 --- /dev/null +++ b/substrate/primitives/time/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "sp-time" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +repository.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } +sp-arithmetic = { path = "../arithmetic", default-features = false } +sp-runtime = { path = "../runtime", default-features = false } +sp-std = { path = "../std", default-features = false } + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[features] +default = ["std"] +std = [ + "sp-runtime/std", + "sp-std/std", + "codec/std", + "scale-info/std", + "sp-arithmetic/std" +] diff --git a/substrate/primitives/time/src/lib.rs b/substrate/primitives/time/src/lib.rs new file mode 100644 index 000000000000..c0f8fb93d81a --- /dev/null +++ b/substrate/primitives/time/src/lib.rs @@ -0,0 +1,125 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg_attr(not(feature = "std"), no_std)] + +//! Substrate time and duration types. +//! +//! Main design goal of this API is to KISS (Keep It Simple and Stupid) and to still fulfill our +//! needs. This means to rely on already existing conventions as much as possible. + +use codec::{EncodeLike, FullCodec}; +use core::fmt; +use sp_arithmetic::traits::{CheckedAdd, CheckedSub, Saturating, Zero}; +use sp_runtime::traits::Member; + +/// Provides the current time. +pub trait InstantProvider { + /// Monotonicity assumptions about `now()`. + /// + /// Some pallets may restrict themselves to only work with monotonic clocks. + const MONOTIC: Monotonicity; + + /// Returns the current time. + fn now() -> I; +} + +/// What to assume about two directly consecutive calls to a function `f`. +pub enum Monotonicity { + /// No implied correlation between two calls to `f`. + None, + /// `f() <= f()` always holds. + MonotonicIncrease, + /// `f() < f()` always holds. + StrictMonotonicIncrease, +} + +/// An instant or "time point". +pub trait Instant: + Member + + FullCodec + + EncodeLike + + fmt::Debug + + scale_info::TypeInfo + + core::cmp::PartialOrd + + core::cmp::Eq + + Since<::Delta> + + Until<::Delta> + // If it turns out that our API is bad, then people can still use UNIX formats: + + TryFrom + + TryInto +{ + type Delta: Duration; + + /// Dial time forward by `delta`. + fn checked_forward(&self, delta: &Self::Delta) -> Option; + + /// Dial time backward by `delta`. + fn checked_rewind(&self, delta: &Self::Delta) -> Option; +} + +/// A duration or "time interval". +/// +/// Durations MUST always be positive. +pub trait Duration: + Member + + FullCodec + + EncodeLike + + fmt::Debug + + scale_info::TypeInfo + + core::cmp::PartialOrd + + core::cmp::Eq + + CheckedAdd + + CheckedSub + + Saturating + + Zero + + TryFrom + + TryInto +{ + /// Scale the duration by a factor. + fn checked_scale(&self, other: u32) -> Option; +} + +/// Calculates the time since a given instant. +pub trait Since { + /// How long it has been since `past`. + /// + /// `None` is returned if the time is in the future. + fn since(&self, past: &Self) -> Option; +} + +/// Calculates the time until a given instant. +pub trait Until { + /// How long it is until `future`. + /// + /// `None` is returned if the time is in the past. + fn until(&self, future: &Self) -> Option; +} + +/// A UNIX duration. +pub struct UnixDuration { + /// Nano seconds. + pub ns: u128, +} + +/// A UNIX compatible instant. +/// +/// Note that UNIX often uses seconds or milliseconds instead of nanoseconds. +pub struct UnixInstant { + /// Time since 00:00:00 UTC on 1 January 1970. + pub since_epoch: UnixDuration, +} From 0b4f7572da63ef811da90b19c8575342ec525a77 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 15 Feb 2024 12:41:09 +0100 Subject: [PATCH 2/3] Use marker traits Signed-off-by: Oliver Tale-Yazdi --- substrate/primitives/time/src/lib.rs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/substrate/primitives/time/src/lib.rs b/substrate/primitives/time/src/lib.rs index c0f8fb93d81a..8bafe7d99d34 100644 --- a/substrate/primitives/time/src/lib.rs +++ b/substrate/primitives/time/src/lib.rs @@ -29,24 +29,15 @@ use sp_runtime::traits::Member; /// Provides the current time. pub trait InstantProvider { - /// Monotonicity assumptions about `now()`. - /// - /// Some pallets may restrict themselves to only work with monotonic clocks. - const MONOTIC: Monotonicity; - /// Returns the current time. fn now() -> I; } -/// What to assume about two directly consecutive calls to a function `f`. -pub enum Monotonicity { - /// No implied correlation between two calls to `f`. - None, - /// `f() <= f()` always holds. - MonotonicIncrease, - /// `f() < f()` always holds. - StrictMonotonicIncrease, -} +/// Marker trait for `InstantProvider`s where `now() <= now()` always holds. +pub trait MonotonicIncrease {} + +/// Marker trait for `InstantProvider`s where `now() < now()` always holds. +pub trait StrictMonotonicIncrease: MonotonicIncrease {} /// An instant or "time point". pub trait Instant: From 99c513088195238bd6115dbcb0bcefbd49a61e4c Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 26 Feb 2024 15:25:29 +0100 Subject: [PATCH 3/3] Try out the traits Signed-off-by: Oliver Tale-Yazdi --- Cargo.lock | 18 +- substrate/frame/timestamp/Cargo.toml | 2 + substrate/frame/timestamp/src/lib.rs | 9 + substrate/frame/timestamp/src/tests.rs | 21 ++- substrate/primitives/time/Cargo.toml | 6 +- substrate/primitives/time/src/lib.rs | 134 +++++++++------ substrate/primitives/time/src/mock.rs | 52 ++++++ .../primitives/time/src/provider/adapter.rs | 51 ++++++ .../primitives/time/src/provider/linear.rs | 100 +++++++++++ substrate/primitives/time/src/provider/mod.rs | 19 ++ .../primitives/time/src/provider/parachain.rs | 28 +++ substrate/primitives/time/src/unix.rs | 162 ++++++++++++++++++ 12 files changed, 543 insertions(+), 59 deletions(-) create mode 100644 substrate/primitives/time/src/mock.rs create mode 100644 substrate/primitives/time/src/provider/adapter.rs create mode 100644 substrate/primitives/time/src/provider/linear.rs create mode 100644 substrate/primitives/time/src/provider/mod.rs create mode 100644 substrate/primitives/time/src/provider/parachain.rs create mode 100644 substrate/primitives/time/src/unix.rs diff --git a/Cargo.lock b/Cargo.lock index 7129b8e3486e..da81821ed3d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -341,6 +341,9 @@ name = "arbitrary" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] [[package]] name = "ark-bls12-377" @@ -4558,6 +4561,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -10910,6 +10924,7 @@ dependencies = [ "sp-runtime", "sp-std 14.0.0", "sp-storage 19.0.0", + "sp-time", "sp-timestamp", ] @@ -18925,11 +18940,12 @@ dependencies = [ name = "sp-time" version = "0.1.0" dependencies = [ + "arbitrary", "parity-scale-codec", "scale-info", "sp-arithmetic", + "sp-core", "sp-runtime", - "sp-std 14.0.0", ] [[package]] diff --git a/substrate/frame/timestamp/Cargo.toml b/substrate/frame/timestamp/Cargo.toml index cd0737c6bb8f..bd3c29c3e197 100644 --- a/substrate/frame/timestamp/Cargo.toml +++ b/substrate/frame/timestamp/Cargo.toml @@ -25,6 +25,7 @@ frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } sp-inherents = { path = "../../primitives/inherents", default-features = false } sp-io = { path = "../../primitives/io", default-features = false, optional = true } +sp-time = { path = "../../primitives/time", default-features = false } sp-runtime = { path = "../../primitives/runtime", default-features = false } sp-std = { path = "../../primitives/std", default-features = false } sp-storage = { path = "../../primitives/storage", default-features = false } @@ -47,6 +48,7 @@ std = [ "scale-info/std", "sp-core/std", "sp-inherents/std", + "sp-time/std", "sp-io?/std", "sp-runtime/std", "sp-std/std", diff --git a/substrate/frame/timestamp/src/lib.rs b/substrate/frame/timestamp/src/lib.rs index a62ac6d633d0..552cf03495bd 100644 --- a/substrate/frame/timestamp/src/lib.rs +++ b/substrate/frame/timestamp/src/lib.rs @@ -136,6 +136,7 @@ pub mod weights; use frame_support::traits::{OnTimestampSet, Time, UnixTime}; use sp_runtime::traits::{AtLeast32Bit, SaturatedConversion, Scale, Zero}; use sp_std::{cmp, result}; +use sp_time::{InstantProvider, UnixDuration, UnixInstant}; use sp_timestamp::{InherentError, InherentType, INHERENT_IDENTIFIER}; pub use weights::WeightInfo; @@ -379,3 +380,11 @@ impl UnixTime for Pallet { core::time::Duration::from_millis(now.saturated_into::()) } } + +impl InstantProvider for Pallet { + fn now() -> UnixInstant { + let ms_since_epoch: u128 = Now::::get().saturated_into(); + + UnixInstant::from_epoch_start(UnixDuration::from_millis(ms_since_epoch)) + } +} diff --git a/substrate/frame/timestamp/src/tests.rs b/substrate/frame/timestamp/src/tests.rs index cc49d8a3296e..9cb930e13f9a 100644 --- a/substrate/frame/timestamp/src/tests.rs +++ b/substrate/frame/timestamp/src/tests.rs @@ -17,13 +17,14 @@ //! Tests for the Timestamp module. -use crate::mock::*; +use crate::{mock::*, Now}; use frame_support::assert_ok; +use sp_time::InstantProvider; #[test] fn timestamp_works() { new_test_ext().execute_with(|| { - crate::Now::::put(46); + Now::::put(46); assert_ok!(Timestamp::set(RuntimeOrigin::none(), 69)); assert_eq!(Timestamp::now(), 69); assert_eq!(Some(69), get_captured_moment()); @@ -47,7 +48,21 @@ fn double_timestamp_should_fail() { )] fn block_period_minimum_enforced() { new_test_ext().execute_with(|| { - crate::Now::::put(44); + Now::::put(44); let _ = Timestamp::set(RuntimeOrigin::none(), 46); }); } + +#[test] +fn instant_provider_works() { + new_test_ext().execute_with(|| { + for i in 0..10 { + Now::::put(i * 1_000); + + let instant = >::now(); + let ms = Now::::get(); + + assert_eq!(ms as u128, instant.since_epoch.ns / 1_000_000); + } + }); +} diff --git a/substrate/primitives/time/Cargo.toml b/substrate/primitives/time/Cargo.toml index acff68b5e1e8..af59ba6afc31 100644 --- a/substrate/primitives/time/Cargo.toml +++ b/substrate/primitives/time/Cargo.toml @@ -13,7 +13,8 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } sp-arithmetic = { path = "../arithmetic", default-features = false } sp-runtime = { path = "../runtime", default-features = false } -sp-std = { path = "../std", default-features = false } +sp-core = { path = "../core", optional = true } +arbitrary = { version = "1.3.2", features = ["derive"], optional = true } [lints] workspace = true @@ -24,8 +25,9 @@ targets = ["x86_64-unknown-linux-gnu"] [features] default = ["std"] std = [ + "dep:arbitrary", + "sp-core/std", "sp-runtime/std", - "sp-std/std", "codec/std", "scale-info/std", "sp-arithmetic/std" diff --git a/substrate/primitives/time/src/lib.rs b/substrate/primitives/time/src/lib.rs index 8bafe7d99d34..18c763c6d053 100644 --- a/substrate/primitives/time/src/lib.rs +++ b/substrate/primitives/time/src/lib.rs @@ -15,52 +15,103 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![cfg_attr(not(feature = "std"), no_std)] - //! Substrate time and duration types. //! //! Main design goal of this API is to KISS (Keep It Simple and Stupid) and to still fulfill our //! needs. This means to rely on already existing conventions as much as possible. -use codec::{EncodeLike, FullCodec}; -use core::fmt; -use sp_arithmetic::traits::{CheckedAdd, CheckedSub, Saturating, Zero}; +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(feature = "std")] +pub mod mock; +pub mod provider; +pub mod unix; + +pub use unix::{UnixDuration, UnixInstant}; + +use codec::FullCodec; +use core::{ + cmp::{Eq, PartialOrd}, + fmt::Debug, +}; +use scale_info::TypeInfo; +use sp_arithmetic::traits::{Bounded, CheckedAdd, CheckedSub, Zero}; use sp_runtime::traits::Member; /// Provides the current time. +// NOTE: we cannot use an associated tye here because it would result in the `Instant cannot be made +// into an object` error. pub trait InstantProvider { /// Returns the current time. fn now() -> I; } +/// Provide the time at genesis of this chain. +/// +/// Can be used to calculate relative times since the inception of the chain. This can be useful for +/// things like vesting or other timed locks. +/// +/// It is decoupled from the the normal `InstantProvider` because there can be pallets that only +/// need to know the absolute time. +pub trait GenesisInstantProvider: InstantProvider { + /// Returns the time at genesis. + /// + /// The exact value of this is defined by the runtime. + fn genesis() -> I; +} + /// Marker trait for `InstantProvider`s where `now() <= now()` always holds. +/// +/// `InstantProvider`s must saturate in the overflow case. pub trait MonotonicIncrease {} /// Marker trait for `InstantProvider`s where `now() < now()` always holds. +/// +/// Note this may not hold in the saturating case. pub trait StrictMonotonicIncrease: MonotonicIncrease {} /// An instant or "time point". pub trait Instant: Member + FullCodec - + EncodeLike - + fmt::Debug - + scale_info::TypeInfo - + core::cmp::PartialOrd - + core::cmp::Eq - + Since<::Delta> - + Until<::Delta> - // If it turns out that our API is bad, then people can still use UNIX formats: + + TypeInfo + + PartialOrd + + Eq + + Bounded + + Debug + // If it turns out that our API is bad, then devs can still use UNIX formats: + TryFrom + TryInto { - type Delta: Duration; + /// A difference (aka. Delta) between two `Instant`s. + type Duration: Duration; + + /// Try to increase `self` by `delta`. + /// + /// This does not use the standard `CheckedAdd` trait since that would require the argument to + /// be of type `Self`. + fn checked_add(&self, delta: &Self::Duration) -> Option; + + fn saturating_add(&self, delta: &Self::Duration) -> Self { + self.checked_add(delta).unwrap_or_else(|| Self::max_value()) + } + + /// Try to decrease `self` by `delta`. + fn checked_sub(&self, delta: &Self::Duration) -> Option; - /// Dial time forward by `delta`. - fn checked_forward(&self, delta: &Self::Delta) -> Option; + fn saturating_sub(&self, delta: &Self::Duration) -> Self { + self.checked_sub(delta).unwrap_or_else(|| Self::min_value()) + } - /// Dial time backward by `delta`. - fn checked_rewind(&self, delta: &Self::Delta) -> Option; + /// How long it has been since `past`. + /// + /// `None` is returned if the time is in the future. Note that this function glues together the `Self::Duration` and `Self` types. + fn since(&self, past: &Self) -> Option; + + /// How long it is until `future`. + /// + /// `None` is returned if the time is in the past. Note that this function glues together the `Self::Duration` and `Self` types. + fn until(&self, future: &Self) -> Option; } /// A duration or "time interval". @@ -69,48 +120,25 @@ pub trait Instant: pub trait Duration: Member + FullCodec - + EncodeLike - + fmt::Debug - + scale_info::TypeInfo - + core::cmp::PartialOrd - + core::cmp::Eq + + TypeInfo + + PartialOrd + + Eq + + Debug + + Bounded + CheckedAdd + CheckedSub - + Saturating + Zero + // If it turns out that our API is bad, then devs can still use UNIX formats: + TryFrom + TryInto { /// Scale the duration by a factor. - fn checked_scale(&self, other: u32) -> Option; -} - -/// Calculates the time since a given instant. -pub trait Since { - /// How long it has been since `past`. - /// - /// `None` is returned if the time is in the future. - fn since(&self, past: &Self) -> Option; -} - -/// Calculates the time until a given instant. -pub trait Until { - /// How long it is until `future`. - /// - /// `None` is returned if the time is in the past. - fn until(&self, future: &Self) -> Option; -} + fn checked_mul_int(&self, other: u128) -> Option; -/// A UNIX duration. -pub struct UnixDuration { - /// Nano seconds. - pub ns: u128, -} + fn saturating_mul_int(&self, other: u128) -> Self { + self.checked_mul_int(other).unwrap_or_else(|| Self::max_value()) + } -/// A UNIX compatible instant. -/// -/// Note that UNIX often uses seconds or milliseconds instead of nanoseconds. -pub struct UnixInstant { - /// Time since 00:00:00 UTC on 1 January 1970. - pub since_epoch: UnixDuration, + /// Divide the duration by a factor. + fn checked_div_int(&self, other: u128) -> Option; } diff --git a/substrate/primitives/time/src/mock.rs b/substrate/primitives/time/src/mock.rs new file mode 100644 index 000000000000..548aaa60e6f0 --- /dev/null +++ b/substrate/primitives/time/src/mock.rs @@ -0,0 +1,52 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Testing helpers that can be re-used by external crates. + +use crate::Instant; +use arbitrary::{Arbitrary, Unstructured}; +use sp_runtime::traits::Zero; + +pub struct InstantFuzzer(core::marker::PhantomData); + +impl InstantFuzzer +where + for<'a> I: Instant + Arbitrary<'a>, + for<'a> I::Duration: Arbitrary<'a>, +{ + pub fn fuzz() { + Self::prop_duration_is_not_negative(); + } + + /// Ensure that a `Duration` is never negative. + fn prop_duration_is_not_negative() { + Self::with_durations(1_000_000, |d| assert!(d >= I::Duration::zero())); + } + + fn with_durations(reps: u32, f: impl Fn(I::Duration)) { + for _ in 0..reps { + let seed = u32::arbitrary(&mut Unstructured::new(&[0; 4])).unwrap(); + f(Self::duration(seed)); + } + } + + fn duration(seed: u32) -> I::Duration { + let seed = sp_core::blake2_256(&seed.to_le_bytes()); + let mut unstructured = Unstructured::new(&seed); + I::Duration::arbitrary(&mut unstructured).unwrap() + } +} diff --git a/substrate/primitives/time/src/provider/adapter.rs b/substrate/primitives/time/src/provider/adapter.rs new file mode 100644 index 000000000000..ad5d01d16305 --- /dev/null +++ b/substrate/primitives/time/src/provider/adapter.rs @@ -0,0 +1,51 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{Duration, Instant, InstantProvider}; +use sp_runtime::traits::{BlockNumberProvider, Get}; + +/// Adapter for converting a `Get` into an `InstantProvider`. +pub struct StaticInstantProvider(core::marker::PhantomData<(I, N)>); + +impl> InstantProvider for StaticInstantProvider { + fn now() -> I { + N::get() + } +} + +/// Uses a `BlockNumberProvider` to calculate the current time with respect to offset and slope. +/// +/// This can be used by solo/relay chains to convert the `System` pallet into an `InstantProvider` +/// or by a parachain by using the `cumulus_pallet_parachain_system` pallet. +pub struct BlockNumberInstantProvider(core::marker::PhantomData<(I, B, O, S)>); + +impl InstantProvider for BlockNumberInstantProvider +where + I: Instant, + B: BlockNumberProvider, + ::BlockNumber: Into, + O: Get, + S: Get, +{ + fn now() -> I { + let block: u128 = B::current_block_number().into(); + let slope = S::get().saturating_mul_int(block); + let offset = O::get(); + + offset.saturating_add(&slope) + } +} diff --git a/substrate/primitives/time/src/provider/linear.rs b/substrate/primitives/time/src/provider/linear.rs new file mode 100644 index 000000000000..7f86233fc066 --- /dev/null +++ b/substrate/primitives/time/src/provider/linear.rs @@ -0,0 +1,100 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{Duration, Instant, InstantProvider}; +use sp_runtime::traits::Get; + +/// An `InstantProvider` that follows a linear equation on the form of `now() = s * N::get() + o`. +/// +/// This can be used by any chain that has constant block times. Mostly relay- and solo-chains. +pub struct LinearInstantProvider(core::marker::PhantomData<(I, T, N, O, S)>); + +impl InstantProvider for LinearInstantProvider +where + I: Instant, + T: Into, + N: Get, + O: Get, + S: Get, +{ + fn now() -> I { + let slope = S::get().saturating_mul_int(N::get().into()); + let offset = O::get(); + + offset.saturating_add(&slope) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{UnixDuration, UnixInstant}; + + #[test] + fn linear_instant_provider_works() { + struct BlockNumber; + impl Get for BlockNumber { + fn get() -> u32 { + 42 + } + } + + struct Offset; + impl Get for Offset { + fn get() -> UnixInstant { + UnixInstant { since_epoch: UnixDuration { ns: 654 } } + } + } + + struct Slope; + impl Get for Slope { + fn get() -> UnixDuration { + UnixDuration { ns: 123 } + } + } + + type Time = LinearInstantProvider; + assert_eq!(Time::now(), UnixInstant { since_epoch: UnixDuration { ns: 654 + 42 * 123 } }); + } + + #[test] + fn linear_instant_overflow_saturates_works() { + struct BlockNumber; + impl Get for BlockNumber { + fn get() -> u32 { + 42 + } + } + + struct Offset; + impl Get for Offset { + fn get() -> UnixInstant { + UnixInstant { since_epoch: UnixDuration { ns: 2 } } + } + } + + struct Slope; + impl Get for Slope { + fn get() -> UnixDuration { + UnixDuration { ns: u128::MAX / 2 } + } + } + + type Time = LinearInstantProvider; + assert_eq!(Time::now(), UnixInstant { since_epoch: UnixDuration { ns: u128::MAX } }); + } +} diff --git a/substrate/primitives/time/src/provider/mod.rs b/substrate/primitives/time/src/provider/mod.rs new file mode 100644 index 000000000000..1e17085cd0f0 --- /dev/null +++ b/substrate/primitives/time/src/provider/mod.rs @@ -0,0 +1,19 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod adapter; +pub mod linear; diff --git a/substrate/primitives/time/src/provider/parachain.rs b/substrate/primitives/time/src/provider/parachain.rs new file mode 100644 index 000000000000..40e04a139fd9 --- /dev/null +++ b/substrate/primitives/time/src/provider/parachain.rs @@ -0,0 +1,28 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{Instant, InstantProvider}; +use sp_runtime::traits::Get; + +/// Adapter for converting a `Get` into an `InstantProvider`. +pub struct TimestampInstantProvider(core::marker::PhantomData<(I, N)>); + +impl> InstantProvider for TimestampInstantProvider { + fn now() -> I { + N::get() + } +} diff --git a/substrate/primitives/time/src/unix.rs b/substrate/primitives/time/src/unix.rs new file mode 100644 index 000000000000..1f2b8d5640c6 --- /dev/null +++ b/substrate/primitives/time/src/unix.rs @@ -0,0 +1,162 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; +use codec::{Decode, Encode}; +use core::ops::{Add, Sub}; +use scale_info::TypeInfo; + +/// A UNIX duration. +#[derive(Encode, Decode, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, TypeInfo)] +#[cfg_attr(feature = "std", derive(arbitrary::Arbitrary))] +pub struct UnixDuration { + /// Nano seconds. + pub ns: u128, +} + +impl UnixDuration { + pub fn from_millis>(ms: T) -> Self { + Self { ns: ms.into().saturating_mul(1_000_000) } + } +} + +/// A UNIX compatible instant. +/// +/// Note that UNIX often uses seconds or milliseconds instead of nanoseconds. +#[derive(Encode, Decode, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, TypeInfo)] +#[cfg_attr(feature = "std", derive(arbitrary::Arbitrary))] +pub struct UnixInstant { + /// Time since 00:00:00 UTC on 1 January 1970. + pub since_epoch: UnixDuration, +} + +impl UnixInstant { + pub fn from_epoch_start>(d: D) -> Self { + Self { since_epoch: d.into() } + } +} + +impl Instant for UnixInstant { + type Duration = UnixDuration; + + fn checked_add(&self, other: &Self::Duration) -> Option { + self.since_epoch + .ns + .checked_add(other.ns) + .map(|ns| UnixInstant { since_epoch: UnixDuration { ns } }) + } + + fn checked_sub(&self, other: &Self::Duration) -> Option { + self.since_epoch + .ns + .checked_sub(other.ns) + .map(|ns| UnixInstant { since_epoch: UnixDuration { ns } }) + } + + fn since(&self, past: &Self) -> Option { + self.since_epoch + .ns + .checked_sub(past.since_epoch.ns) + .map(|ns| UnixDuration { ns }) + } + + fn until(&self, future: &Self) -> Option { + future + .since_epoch + .ns + .checked_sub(self.since_epoch.ns) + .map(|ns| UnixDuration { ns }) + } +} + +impl Bounded for UnixInstant { + fn min_value() -> Self { + Self { since_epoch: Bounded::min_value() } + } + + fn max_value() -> Self { + Self { since_epoch: Bounded::max_value() } + } +} + +impl Bounded for UnixDuration { + fn min_value() -> Self { + Self { ns: 0 } + } + + fn max_value() -> Self { + Self { ns: u128::max_value() } + } +} + +impl Zero for UnixDuration { + fn is_zero(&self) -> bool { + self == &Self::default() + } + + fn zero() -> Self { + Self::default() + } +} + +impl Add for UnixDuration { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self { ns: self.ns + rhs.ns } + } +} + +impl Sub for UnixDuration { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self { ns: self.ns - rhs.ns } + } +} + +impl CheckedAdd for UnixDuration { + fn checked_add(&self, rhs: &Self) -> Option { + self.ns.checked_add(rhs.ns).map(|ns| UnixDuration { ns }) + } +} + +impl CheckedSub for UnixDuration { + fn checked_sub(&self, rhs: &Self) -> Option { + self.ns.checked_sub(rhs.ns).map(|ns| UnixDuration { ns }) + } +} + +impl Duration for UnixDuration { + fn checked_mul_int(&self, scale: u128) -> Option { + self.ns.checked_mul(scale.into()).map(|ns| UnixDuration { ns }) + } + + fn checked_div_int(&self, scale: u128) -> Option { + self.ns.checked_div(scale.into()).map(|ns| UnixDuration { ns }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn fuzz() { + crate::mock::InstantFuzzer::::fuzz(); + } +}