diff --git a/Cargo.lock b/Cargo.lock index edc1277e..0c62d957 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -117,9 +117,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -132,33 +132,33 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.8" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", "once_cell_polyfill", @@ -735,9 +735,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.7.3" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "beef" @@ -997,9 +997,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" [[package]] name = "byte-slice-cast" @@ -1015,9 +1015,9 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" [[package]] name = "bytemuck" -version = "1.23.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" [[package]] name = "byteorder" @@ -1091,9 +1091,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.25" +version = "1.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951" +checksum = "956a5e21988b87f372569b66183b78babf23ebc2e744b733e4350a752c4dafac" dependencies = [ "jobserver", "libc", @@ -1311,9 +1311,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "combine" @@ -2261,7 +2261,7 @@ dependencies = [ "regex", "syn 2.0.101", "termcolor", - "toml 0.8.22", + "toml 0.8.23", "walkdir", ] @@ -3545,9 +3545,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.3" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" dependencies = [ "allocator-api2", "equivalent", @@ -3904,9 +3904,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.6" +version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a01595e11bdcec50946522c32dde3fc6914743000a68b93000965f2f02406d" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ "http 1.3.1", "hyper 1.6.0", @@ -3922,9 +3922,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8" +checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" dependencies = [ "bytes 1.10.1", "futures-channel", @@ -4246,7 +4246,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.3", + "hashbrown 0.15.4", ] [[package]] @@ -5740,7 +5740,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.3", + "hashbrown 0.15.4", ] [[package]] @@ -6832,8 +6832,8 @@ dependencies = [ "pallet-scheduler", "pallet-timestamp", "parity-scale-codec", + "qp-scheduler", "scale-info", - "sp-common", "sp-core", "sp-io", "sp-runtime", @@ -6850,8 +6850,8 @@ dependencies = [ "log", "pallet-preimage", "parity-scale-codec", + "qp-scheduler", "scale-info", - "sp-common", "sp-core", "sp-io", "sp-runtime", @@ -7003,7 +7003,7 @@ dependencies = [ "log", "pallet-balances 40.0.1", "parity-scale-codec", - "plonky2 1.0.2 (git+https://github.com/Quantus-Network/plonky2)", + "plonky2", "scale-info", "sp-core", "sp-io", @@ -7340,30 +7340,9 @@ dependencies = [ "keccak-hash 0.8.0", "log", "num", - "plonky2_field 1.0.0 (git+https://github.com/Quantus-Network/plonky2)", - "plonky2_maybe_rayon 1.0.0 (git+https://github.com/Quantus-Network/plonky2)", - "plonky2_util 1.0.0 (git+https://github.com/Quantus-Network/plonky2)", - "rand 0.8.5", - "serde", - "static_assertions", - "unroll", -] - -[[package]] -name = "plonky2" -version = "1.0.2" -source = "git+https://github.com/Resonance-Network/plonky2#80a10007d8359b54843acb826097561859a6b621" -dependencies = [ - "ahash", - "anyhow", - "hashbrown 0.14.5", - "itertools 0.11.0", - "keccak-hash 0.8.0", - "log", - "num", - "plonky2_field 1.0.0 (git+https://github.com/Resonance-Network/plonky2)", - "plonky2_maybe_rayon 1.0.0 (git+https://github.com/Resonance-Network/plonky2)", - "plonky2_util 1.0.0 (git+https://github.com/Resonance-Network/plonky2)", + "plonky2_field", + "plonky2_maybe_rayon", + "plonky2_util", "rand 0.8.5", "serde", "static_assertions", @@ -7378,21 +7357,7 @@ dependencies = [ "anyhow", "itertools 0.11.0", "num", - "plonky2_util 1.0.0 (git+https://github.com/Quantus-Network/plonky2)", - "serde", - "static_assertions", - "unroll", -] - -[[package]] -name = "plonky2_field" -version = "1.0.0" -source = "git+https://github.com/Resonance-Network/plonky2#80a10007d8359b54843acb826097561859a6b621" -dependencies = [ - "anyhow", - "itertools 0.11.0", - "num", - "plonky2_util 1.0.0 (git+https://github.com/Resonance-Network/plonky2)", + "plonky2_util", "serde", "static_assertions", "unroll", @@ -7403,21 +7368,11 @@ name = "plonky2_maybe_rayon" version = "1.0.0" source = "git+https://github.com/Quantus-Network/plonky2#80a10007d8359b54843acb826097561859a6b621" -[[package]] -name = "plonky2_maybe_rayon" -version = "1.0.0" -source = "git+https://github.com/Resonance-Network/plonky2#80a10007d8359b54843acb826097561859a6b621" - [[package]] name = "plonky2_util" version = "1.0.0" source = "git+https://github.com/Quantus-Network/plonky2#80a10007d8359b54843acb826097561859a6b621" -[[package]] -name = "plonky2_util" -version = "1.0.0" -source = "git+https://github.com/Resonance-Network/plonky2#80a10007d8359b54843acb826097561859a6b621" - [[package]] name = "polkadot-core-primitives" version = "16.0.0" @@ -7774,9 +7729,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "portable-atomic-util" @@ -7790,11 +7745,11 @@ dependencies = [ [[package]] name = "poseidon-resonance" version = "0.6.0" -source = "git+https://github.com/Quantus-Network/poseidon-resonance#e39fff549e4bef8f97b1b96e0857c47a6a7eb5ab" +source = "git+https://github.com/Quantus-Network/poseidon-resonance#fce507b40c166d9acae1a8885898eea83d0d3780" dependencies = [ "log", "parity-scale-codec", - "plonky2 1.0.2 (git+https://github.com/Resonance-Network/plonky2)", + "plonky2", "scale-info", "serde", "sp-core", @@ -8052,15 +8007,15 @@ dependencies = [ [[package]] name = "proptest" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" +checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f" dependencies = [ "bitflags 2.9.1", "lazy_static", "num-traits", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand 0.9.1", + "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax 0.8.5", "unarray", @@ -8150,6 +8105,16 @@ dependencies = [ "cc", ] +[[package]] +name = "qp-scheduler" +version = "0.1.0" +dependencies = [ + "frame-support", + "parity-scale-codec", + "scale-info", + "sp-runtime", +] + [[package]] name = "qpow-math" version = "0.1.0" @@ -8515,11 +8480,11 @@ dependencies = [ [[package]] name = "rand_xorshift" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ - "rand_core 0.6.4", + "rand_core 0.9.3", ] [[package]] @@ -8803,11 +8768,11 @@ dependencies = [ "parity-scale-codec", "poseidon-resonance", "primitive-types 0.13.1", + "qp-scheduler", "scale-info", "serde_json", "sp-api", "sp-block-builder", - "sp-common", "sp-consensus-qpow", "sp-core", "sp-faucet", @@ -9839,9 +9804,9 @@ dependencies = [ [[package]] name = "sc-network-types" -version = "0.15.3" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff910b7a20f14b1a77b2616de21d509cf51ce1a006e30b2d1f293a8fae72555" +checksum = "149445bdb01a539d50d560468407a9a927938949b115ff5fd0cd04cca9349f42" dependencies = [ "bs58", "bytes 1.10.1", @@ -9870,7 +9835,7 @@ dependencies = [ "futures-timer", "http-body-util", "hyper 1.6.0", - "hyper-rustls 0.27.6", + "hyper-rustls 0.27.7", "hyper-util", "log", "num_cpus", @@ -10609,9 +10574,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] @@ -10791,9 +10756,9 @@ checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "smol" @@ -11087,16 +11052,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "sp-common" -version = "0.1.0" -dependencies = [ - "frame-support", - "parity-scale-codec", - "scale-info", - "sp-runtime", -] - [[package]] name = "sp-consensus" version = "0.41.0" @@ -11991,7 +11946,7 @@ dependencies = [ "sp-version", "strum 0.26.3", "tempfile", - "toml 0.8.22", + "toml 0.8.23", "walkdir", "wasm-opt", ] @@ -12643,9 +12598,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.22" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", @@ -12655,18 +12610,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.26" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap 2.9.0", "serde", @@ -12678,9 +12633,9 @@ dependencies = [ [[package]] name = "toml_write" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "tower" @@ -12739,9 +12694,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662" dependencies = [ "proc-macro2", "quote", @@ -12750,9 +12705,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", diff --git a/Cargo.toml b/Cargo.toml index 021ad3b1..14357f3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,9 +50,9 @@ pallet-conviction-voting = { version = "39.1.0", default-features = false } pallet-ranked-collective = { version = "39.0.0", default-features = false } pallet-preimage = { version = "39.0.0", default-features = false } pallet-referenda = { version = "39.1.0", default-features = false } +pallet-treasury = { version = "38.1.0", default-features = false} pallet-recovery = { version = "39.1.0", default-features = false } pallet-scheduler = { path = "./pallets/scheduler", default-features = false } -pallet-treasury = { version = "38.1.0", default-features = false } pallet-utility = { version = "39.1.0", default-features = false } parking_lot = { version = "0.12.1", default-features = false } pallet-vesting = { version = "39.1.0", default-features = false } @@ -86,7 +86,7 @@ sp-blockchain = { version = "38.0.0", default-features = false } sp-consensus = { version = "0.41.0", default-features = false } sp-consensus-pow = { path = "./primitives/consensus/pow", default-features = false } sp-consensus-qpow = { path = "./primitives/consensus/qpow", default-features = false } -sp-common = { path = "./primitives/common", default-features = false } +qp-scheduler = { path = "./primitives/scheduler", default-features = false } sp-faucet = { path = "./primitives/faucet", default-features = false } sp-core = { version = "35.0.0", default-features = false } sp-genesis-builder = { version = "0.16.0", default-features = false } diff --git a/pallets/reversible-transfers/Cargo.toml b/pallets/reversible-transfers/Cargo.toml index 521e1b27..8d5d4a90 100644 --- a/pallets/reversible-transfers/Cargo.toml +++ b/pallets/reversible-transfers/Cargo.toml @@ -20,6 +20,7 @@ frame-support.workspace = true frame-system.workspace = true sp-runtime.workspace = true pallet-balances = { workspace = true } +qp-scheduler = { workspace = true, default-features = false } [dev-dependencies] frame-support = { workspace = true, features = ["experimental"], default-features = true } @@ -29,7 +30,7 @@ pallet-scheduler = { workspace = true, default-features = false } pallet-timestamp = { workspace = true, default-features = false } pallet-balances = { workspace = true, features = ["std"] } pallet-preimage = { workspace = true, default-features = false } -sp-common = { workspace = true, default-features = false } +qp-scheduler = { workspace = true, default-features = false } [features] default = ["std"] @@ -42,6 +43,8 @@ std = [ "scale-info/std", "sp-runtime/std", "pallet-balances/std", + "qp-scheduler/std", + "pallet-scheduler/std", ] # Enable support for setting the existential deposit to zero. insecure_zero_ed = [] diff --git a/pallets/reversible-transfers/src/lib.rs b/pallets/reversible-transfers/src/lib.rs index 9c785423..833dd501 100644 --- a/pallets/reversible-transfers/src/lib.rs +++ b/pallets/reversible-transfers/src/lib.rs @@ -22,13 +22,18 @@ pub mod weights; pub use weights::WeightInfo; use alloc::vec::Vec; +use frame_support::pallet_prelude::*; use frame_support::traits::tokens::{Fortitude, Restriction}; -use frame_support::{pallet_prelude::*, traits::schedule::DispatchTime}; use frame_system::pallet_prelude::*; +use qp_scheduler::{BlockNumberOrTimestamp, DispatchTime, ScheduleNamed}; use sp_runtime::traits::StaticLookup; +/// Type alias for this config's `BlockNumberOrTimestamp`. +pub type BlockNumberOrTimestampOf = + BlockNumberOrTimestamp, ::Moment>; + /// How to delay transactions -/// - `Explicit`: Only delay transactions explicitly using `schedule_transfer`. +/// - `Explicit`: Only delay transactions explicitly using this pallet's `schedule_transfer` extrinsic. /// - `Intercept`: Intercept and delay transactions at the `TransactionExtension` level. /// /// For example, for a reversible account with `DelayPolicy::Intercept`, the transaction @@ -49,11 +54,11 @@ pub enum DelayPolicy { /// Reversible account details #[derive(Encode, Decode, MaxEncodedLen, Clone, Default, TypeInfo, Debug, PartialEq, Eq)] -pub struct ReversibleAccountData { +pub struct ReversibleAccountData { /// The account that can reverse the transaction. `None` means the account itself. pub explicit_reverser: Option, /// The delay period for the account - pub delay: BlockNumber, + pub delay: Delay, /// The policy for the account pub policy: DelayPolicy, } @@ -78,17 +83,15 @@ type BalanceOf = ::Balance; #[frame_support::pallet] pub mod pallet { use super::*; + use crate::BlockNumberOrTimestampOf; use frame_support::dispatch::PostDispatchInfo; use frame_support::traits::fungible::MutateHold; use frame_support::traits::tokens::Precision; - use frame_support::traits::{Bounded, CallerTrait, QueryPreimage, StorePreimage}; - use frame_support::{ - traits::schedule::v3::{Named, TaskName}, - PalletId, - }; - use sp_runtime::traits::AccountIdConversion; - use sp_runtime::traits::Hash; + use frame_support::traits::{Bounded, CallerTrait, QueryPreimage, StorePreimage, Time}; + use frame_support::{traits::schedule::v3::TaskName, PalletId}; + use sp_runtime::traits::{AccountIdConversion, AtLeast32Bit}; use sp_runtime::traits::{BlockNumberProvider, Dispatchable}; + use sp_runtime::traits::{Hash, Scale}; use sp_runtime::Saturating; #[pallet::pallet] @@ -106,8 +109,9 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Scheduler for the runtime. We use the Named scheduler for cancellability. - type Scheduler: Named< + type Scheduler: ScheduleNamed< BlockNumberFor, + Self::Moment, Self::RuntimeCall, Self::SchedulerOrigin, Hasher = Self::Hashing, @@ -126,12 +130,18 @@ pub mod pallet { type MaxPendingPerAccount: Get; /// The default delay period for reversible transactions if none is specified. + /// + /// NOTE: default delay is always in blocks. + #[pallet::constant] + type DefaultDelay: Get>; + + /// The minimum delay period allowed for reversible transactions, in blocks. #[pallet::constant] - type DefaultDelay: Get>; + type MinDelayPeriodBlocks: Get>; - /// The minimum delay period allowed for reversible transactions. + /// The minimum delay period allowed for reversible transactions, in milliseconds. #[pallet::constant] - type MinDelayPeriod: Get>; + type MinDelayPeriodMoment: Get; /// Pallet Id type PalletId: Get; @@ -144,6 +154,17 @@ pub mod pallet { /// Hold reason for the reversible transactions. type RuntimeHoldReason: From; + + /// Moment type for scheduling. + type Moment: Saturating + + Copy + + Parameter + + AtLeast32Bit + + Scale, Output = Self::Moment> + + MaxEncodedLen; + + /// Time provider for scheduling. + type TimeProvider: Time; } /// Maps accounts to their chosen reversibility delay period (in milliseconds). @@ -154,7 +175,7 @@ pub mod pallet { _, Blake2_128Concat, T::AccountId, - ReversibleAccountData>, + ReversibleAccountData>, OptionQuery, >; @@ -184,14 +205,14 @@ pub mod pallet { /// [who, maybe_delay: None means disabled] ReversibilitySet { who: T::AccountId, - data: ReversibleAccountData>, + data: ReversibleAccountData>, }, /// A transaction has been intercepted and scheduled for delayed execution. /// [who, tx_id, execute_at_moment] TransactionScheduled { who: T::AccountId, tx_id: T::Hash, - execute_at: DispatchTime>, + execute_at: DispatchTime, T::Moment>, }, /// A scheduled transaction has been successfully cancelled by the owner. /// [who, tx_id] @@ -248,24 +269,36 @@ pub mod pallet { #[pallet::weight(::WeightInfo::set_reversibility())] pub fn set_reversibility( origin: OriginFor, - delay: Option>, + delay: Option>, policy: DelayPolicy, reverser: Option, ) -> DispatchResult { let who = ensure_signed(origin)?; - ensure!( - !ReversibleAccounts::::contains_key(&who), - Error::::AccountAlreadyReversible - ); ensure!( reverser != Some(who.clone()), Error::::ExplicitReverserCanNotBeSelf ); - + ensure!( + !ReversibleAccounts::::contains_key(&who), + Error::::AccountAlreadyReversible + ); let delay = delay.unwrap_or(T::DefaultDelay::get()); - ensure!(delay >= T::MinDelayPeriod::get(), Error::::DelayTooShort); + match delay { + BlockNumberOrTimestamp::BlockNumber(x) => { + ensure!( + x > T::MinDelayPeriodBlocks::get(), + Error::::DelayTooShort + ) + } + BlockNumberOrTimestamp::Timestamp(t) => { + ensure!( + t > T::MinDelayPeriodMoment::get(), + Error::::DelayTooShort + ) + } + } let reversible_account_data = ReversibleAccountData { explicit_reverser: reverser, @@ -327,12 +360,16 @@ pub mod pallet { impl Hooks> for Pallet { fn integrity_test() { assert!( - T::MinDelayPeriod::get() > Zero::zero(), - "`T::MinDelayPeriod` must be greater than 0" + !T::MinDelayPeriodBlocks::get().is_zero() + && !T::MinDelayPeriodMoment::get().is_zero(), + "Minimum delay periods must be greater than 0" ); + + // NOTE: default delay is always in blocks assert!( - T::MinDelayPeriod::get() <= T::DefaultDelay::get(), - "`T::MinDelayPeriod` must be less or equal to `T::DefaultDelay`" + BlockNumberOrTimestampOf::::BlockNumber(T::MinDelayPeriodBlocks::get()) + <= T::DefaultDelay::get(), + "Minimum delay periods must be less or equal to `T::DefaultDelay`" ); } } @@ -352,7 +389,7 @@ pub mod pallet { /// Check if an account has reversibility enabled and return its delay. pub fn is_reversible( who: &T::AccountId, - ) -> Option>> { + ) -> Option>> { ReversibleAccounts::::get(who) } @@ -430,18 +467,8 @@ pub mod pallet { amount: BalanceOf, ) -> DispatchResult { let who = ensure_signed(origin)?; - let ReversibleAccountData { - delay, - explicit_reverser, - policy: _, - } = Self::reversible_accounts(&who).ok_or(Error::::AccountNotReversible)?; - - match explicit_reverser { - Some(reverser) => { - ensure!(who != reverser, Error::::InvalidReverser); - } - None => {} - }; + let ReversibleAccountData { delay, .. } = + Self::reversible_accounts(&who).ok_or(Error::::AccountNotReversible)?; let transfer_call: T::RuntimeCall = pallet_balances::Call::::transfer_keep_alive { dest: dest.clone(), @@ -461,9 +488,16 @@ pub mod pallet { Ok(()) })?; - let dispatch_time = DispatchTime::At( - T::BlockNumberProvider::current_block_number().saturating_add(delay), - ); + let dispatch_time = match delay { + BlockNumberOrTimestamp::BlockNumber(blocks) => DispatchTime::At( + T::BlockNumberProvider::current_block_number().saturating_add(blocks), + ), + BlockNumberOrTimestamp::Timestamp(millis) => { + DispatchTime::After(BlockNumberOrTimestamp::Timestamp( + T::TimeProvider::now().saturating_add(millis), + )) + } + }; let call = T::Preimages::bound(transfer_call)?; @@ -592,6 +626,7 @@ pub mod pallet { #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig { /// Configure initial reversible accounts. [AccountId, Delay] + /// NOTE: using `(bool, BlockNumberFor)` where `bool` indicates if the delay is in block numbers pub initial_reversible_accounts: Vec<(T::AccountId, BlockNumberFor)>, } @@ -600,20 +635,22 @@ pub mod pallet { fn build(&self) { for (who, delay) in &self.initial_reversible_accounts { // Basic validation, ensure delay is reasonable if needed - if *delay >= T::MinDelayPeriod::get() { + let wrapped_delay = BlockNumberOrTimestampOf::::BlockNumber(*delay); + + if delay >= &T::MinDelayPeriodBlocks::get() { ReversibleAccounts::::insert( who, ReversibleAccountData { explicit_reverser: None, - delay: *delay, + delay: wrapped_delay, policy: DelayPolicy::Explicit, }, ); } else { // Optionally log a warning during genesis build log::warn!( - "Genesis config for account {:?} has delay {:?} below MinDelayPeriod {:?}, skipping.", - who, delay, T::MinDelayPeriod::get() + "Genesis config for account {:?} has delay {:?} below MinDelayPeriodBlocks {:?}, skipping.", + who, wrapped_delay, T::MinDelayPeriodBlocks::get() ); } } diff --git a/pallets/reversible-transfers/src/mock.rs b/pallets/reversible-transfers/src/mock.rs index 423a4cec..4cc4ea26 100644 --- a/pallets/reversible-transfers/src/mock.rs +++ b/pallets/reversible-transfers/src/mock.rs @@ -1,11 +1,14 @@ +use core::{cell::RefCell, marker::PhantomData}; + use crate as pallet_reversible_transfers; use frame_support::{ derive_impl, ord_parameter_types, parameter_types, - traits::{EitherOfDiverse, EqualPrivilegeOnly, Time}, + traits::{EitherOfDiverse, EqualPrivilegeOnly, OnTimestampSet, Time}, PalletId, }; use frame_system::{limits::BlockWeights, EnsureRoot, EnsureSignedBy}; -use sp_core::{ConstU128, ConstU32, ConstU64}; +use qp_scheduler::BlockNumberOrTimestamp; +use sp_core::{ConstU128, ConstU32}; use sp_runtime::{BuildStorage, Perbill, Weight}; type Block = frame_system::mocking::MockBlock; @@ -64,21 +67,50 @@ impl pallet_balances::Config for Test { type MaxFreezes = MaxReversibleTransfers; } -pub struct MockTimeProvider; +// In memory storage +thread_local! { + static MOCKED_TIME: RefCell = RefCell::new(69420); +} + +type Moment = u64; -impl Time for MockTimeProvider { - type Moment = u64; +/// A mock `TimeProvider` that allows setting the current time for tests. +pub struct MockTimestamp(PhantomData); + +impl MockTimestamp +where + T::Moment: From, +{ + /// Sets the current time for the `MockTimestamp` provider. + pub fn set_timestamp(now: Moment) { + MOCKED_TIME.with(|v| { + *v.borrow_mut() = now; + }); + + pallet_scheduler::Pallet::::on_timestamp_set(now.into()); + } + /// Resets the timestamp to a default value (e.g., 0 or a specific starting time). + /// Good to call at the beginning of tests or `execute_with` blocks if needed. + pub fn reset_timestamp() { + MOCKED_TIME.with(|v| { + *v.borrow_mut() = 69420; + }); + } +} + +impl Time for MockTimestamp { + type Moment = Moment; fn now() -> Self::Moment { - 69420 // Mocked time value + MOCKED_TIME.with(|v| *v.borrow()) } } parameter_types! { pub const ReversibleTransfersPalletIdValue: PalletId = PalletId(*b"rtpallet"); - pub const BlockHashCount: u32 = 250; - pub const DefaultDelay: u64 = 10; - pub const MinDelayPeriod: u64 = 2; + pub const DefaultDelay: BlockNumberOrTimestamp = BlockNumberOrTimestamp::BlockNumber(10); + pub const MinDelayPeriodBlocks: u64 = 2; + pub const MinDelayPeriodMoment: u64 = 2000; pub const MaxReversibleTransfers: u32 = 100; } @@ -90,10 +122,13 @@ impl pallet_reversible_transfers::Config for Test { type BlockNumberProvider = System; type MaxPendingPerAccount = MaxReversibleTransfers; type DefaultDelay = DefaultDelay; - type MinDelayPeriod = MinDelayPeriod; + type MinDelayPeriodBlocks = MinDelayPeriodBlocks; + type MinDelayPeriodMoment = MinDelayPeriodMoment; type PalletId = ReversibleTransfersPalletIdValue; type Preimages = Preimage; type WeightInfo = (); + type Moment = Moment; + type TimeProvider = MockTimestamp; } impl pallet_preimage::Config for Test { @@ -107,7 +142,10 @@ impl pallet_preimage::Config for Test { parameter_types! { pub storage MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * BlockWeights::default().max_block; + + pub const TimestampBucketSize: u64 = 1000; } + ord_parameter_types! { pub const One: u64 = 1; } @@ -123,9 +161,9 @@ impl pallet_scheduler::Config for Test { type MaxScheduledPerBlock = ConstU32<10>; type WeightInfo = (); type Preimages = Preimage; - type Moment = u64; - type TimeProvider = MockTimeProvider; - type TimestampBucketSize = ConstU64<1000>; + type Moment = Moment; + type TimeProvider = MockTimestamp; + type TimestampBucketSize = TimestampBucketSize; } // Build genesis storage according to the mock runtime. @@ -135,7 +173,12 @@ pub fn new_test_ext() -> sp_io::TestExternalities { .unwrap(); pallet_balances::GenesisConfig:: { - balances: vec![(1, 1_000_000_000_000_000), (2, 2), (255, 100_000_000_000)], + balances: vec![ + (1, 1_000_000_000_000_000), + (2, 2), + (255, 100_000_000_000), + (256, 100_000_000_000), + ], } .assimilate_storage(&mut t) .unwrap(); diff --git a/pallets/reversible-transfers/src/tests.rs b/pallets/reversible-transfers/src/tests.rs index eb2eb63a..65f76474 100644 --- a/pallets/reversible-transfers/src/tests.rs +++ b/pallets/reversible-transfers/src/tests.rs @@ -3,10 +3,10 @@ use super::*; // Import items from parent module (lib.rs) use crate::mock::*; // Import mock runtime and types use frame_support::traits::fungible::InspectHold; -use frame_support::traits::StorePreimage; +use frame_support::traits::{StorePreimage, Time}; use frame_support::{assert_err, assert_ok}; use pallet_scheduler::Agenda; -use sp_common::scheduler::BlockNumberOrTimestamp; +use qp_scheduler::BlockNumberOrTimestamp; use sp_core::H256; use sp_runtime::traits::{BadOrigin, BlakeTwo256, Hash}; @@ -55,7 +55,7 @@ fn set_reversibility_works() { // Set the delay let another_user = 3; - let delay = 5u64; + let delay = BlockNumberOrTimestampOf::::BlockNumber(5); assert_ok!(ReversibleTransfers::set_reversibility( RuntimeOrigin::signed(another_user), Some(delay), @@ -122,7 +122,7 @@ fn set_reversibility_works() { ); // Too short delay - let short_delay = MinDelayPeriod::get() - 1; + let short_delay = BlockNumberOrTimestamp::BlockNumber(MinDelayPeriodBlocks::get() - 1); let new_user = 4; assert_err!( @@ -168,11 +168,68 @@ fn set_reversibility_works() { }); } +#[test] +fn set_reversibility_with_timestamp_delay_works() { + new_test_ext().execute_with(|| { + System::set_block_number(1); // Block number still relevant for system events, etc. + MockTimestamp::::set_timestamp(1_000_000); // Initial mock time + + let user = 4; + // Assuming MinDelayPeriod allows for timestamp delays of this magnitude. + // e.g., MinDelayPeriod is Timestamp(1000) and TimestampBucketSize is 1000. + // A delay of 5 * TimestampBucketSize = 5000. + let delay = BlockNumberOrTimestamp::Timestamp(5 * TimestampBucketSize::get()); + + assert_ok!(ReversibleTransfers::set_reversibility( + RuntimeOrigin::signed(user), + Some(delay), + DelayPolicy::Intercept, + None, + )); + assert_eq!( + ReversibleTransfers::is_reversible(&user), + Some(ReversibleAccountData { + delay, + policy: DelayPolicy::Intercept, + explicit_reverser: None, + }) + ); + System::assert_last_event( + Event::ReversibilitySet { + who: user, + data: ReversibleAccountData { + delay, + policy: DelayPolicy::Intercept, + explicit_reverser: None, + }, + } + .into(), + ); + + // Too short timestamp delay + // This requires MinDelayPeriodTimestamp to be set and > 0 for this check to be meaningful + // and not panic due to Ord issues if MinDelayPeriod is BlockNumber. + // Assuming MinDelayPeriodTimestamp is, say, 2 * TimestampBucketSize::get(). + let short_delay_ts = BlockNumberOrTimestamp::Timestamp(TimestampBucketSize::get()); + let another_user = 5; + + assert_err!( + ReversibleTransfers::set_reversibility( + RuntimeOrigin::signed(another_user), + Some(short_delay_ts), + DelayPolicy::Explicit, + None, + ), + Error::::DelayTooShort + ); + }); +} + #[test] fn set_reversibility_fails_delay_too_short() { new_test_ext().execute_with(|| { let user = 2; // User 2 is not reversible initially - let short_delay = MinDelayPeriod::get() - 1; + let short_delay = BlockNumberOrTimestamp::BlockNumber(MinDelayPeriodBlocks::get() - 1); assert_err!( ReversibleTransfers::set_reversibility( @@ -202,7 +259,7 @@ fn schedule_transfer_works() { let ReversibleAccountData { delay: user_delay, .. } = ReversibleTransfers::is_reversible(&user).unwrap(); - let expected_block = System::block_number() + user_delay; + let expected_block = System::block_number() + user_delay.as_block_number().unwrap(); let bounded = Preimage::bound(call.clone()).unwrap(); let expected_block = BlockNumberOrTimestamp::BlockNumber(expected_block); @@ -246,7 +303,144 @@ fn schedule_transfer_works() { // Set reversibility assert_ok!(ReversibleTransfers::set_reversibility( RuntimeOrigin::signed(reversible_account), - Some(10), + Some(BlockNumberOrTimestamp::BlockNumber(10)), + DelayPolicy::Explicit, + Some(explicit_reverser), + )); + + // Schedule transfer + assert_ok!(ReversibleTransfers::schedule_transfer( + RuntimeOrigin::signed(reversible_account), + dest_user, + amount, + )); + + let tx_id = calculate_tx_id(reversible_account, &call); + // Try reversing with original user + assert_err!( + ReversibleTransfers::cancel(RuntimeOrigin::signed(reversible_account), tx_id,), + Error::::InvalidReverser + ); + + let explicit_reverser_balance = Balances::free_balance(explicit_reverser); + let reversible_account_balance = Balances::free_balance(reversible_account); + let explicit_reverser_hold = Balances::balance_on_hold( + &RuntimeHoldReason::ReversibleTransfers(HoldReason::ScheduledTransfer), + &explicit_reverser, + ); + assert_eq!(explicit_reverser_hold, 0); + + // Try reversing with explicit reverser + assert_ok!(ReversibleTransfers::cancel( + RuntimeOrigin::signed(explicit_reverser), + tx_id, + )); + assert!(ReversibleTransfers::pending_dispatches(tx_id).is_none()); + + // Funds should be release as free balance to `explicit_reverser` + assert_eq!( + Balances::balance_on_hold( + &RuntimeHoldReason::ReversibleTransfers(HoldReason::ScheduledTransfer), + &reversible_account + ), + 0 + ); + + assert_eq!( + Balances::free_balance(explicit_reverser), + explicit_reverser_balance + amount + ); + + // Unchanged balance for `reversible_account` + assert_eq!( + Balances::free_balance(reversible_account), + reversible_account_balance + ); + + assert_eq!( + Balances::balance_on_hold( + &RuntimeHoldReason::ReversibleTransfers(HoldReason::ScheduledTransfer), + &explicit_reverser, + ), + 0 + ); + }); +} + +#[test] +fn schedule_transfer_with_timestamp_works() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let user = 255; + let dest_user = 2; + let amount = 100; + let dest_user_balance = Balances::free_balance(dest_user); + let user_balance = Balances::free_balance(user); + + let call = transfer_call(dest_user, amount); + let tx_id = calculate_tx_id(user, &call); + + // Set reversibility + assert_ok!(ReversibleTransfers::set_reversibility( + RuntimeOrigin::signed(user), + Some(BlockNumberOrTimestamp::Timestamp(10_000)), + DelayPolicy::Explicit, + None, + )); + + let timestamp_bucket_size = TimestampBucketSize::get(); + let current_time = MockTimestamp::::now(); + let ReversibleAccountData { + delay: user_delay, .. + } = ReversibleTransfers::is_reversible(&user).unwrap(); + let expected_raw_timestamp = (current_time / timestamp_bucket_size) * timestamp_bucket_size + + user_delay.as_timestamp().unwrap(); + + let bounded = Preimage::bound(call.clone()).unwrap(); + let expected_timestamp = + BlockNumberOrTimestamp::Timestamp(expected_raw_timestamp + TimestampBucketSize::get()); + + assert!(Agenda::::get(expected_timestamp).len() == 0); + + assert_ok!(ReversibleTransfers::schedule_transfer( + RuntimeOrigin::signed(user), + dest_user, + amount, + )); + + // Check storage + assert_eq!( + PendingTransfers::::get(tx_id).unwrap(), + PendingTransfer { + who: user, + call: bounded, + amount, + count: 1, + } + ); + assert_eq!(ReversibleTransfers::account_pending_index(user), 1); + + // Check scheduler + assert!(Agenda::::get(expected_timestamp).len() > 0); + + // Skip to the delay timestamp + MockTimestamp::::set_timestamp(expected_raw_timestamp); + + // Check that the transfer is executed + assert_eq!(Balances::free_balance(user), user_balance - amount); + assert_eq!( + Balances::free_balance(dest_user), + dest_user_balance + amount + ); + + // Use explicit reverser + let reversible_account = 256; + let explicit_reverser = 1; + + // Set reversibility + assert_ok!(ReversibleTransfers::set_reversibility( + RuntimeOrigin::signed(reversible_account), + Some(BlockNumberOrTimestamp::BlockNumber(10)), DelayPolicy::Explicit, Some(explicit_reverser), )); @@ -404,8 +598,9 @@ fn cancel_dispatch_works() { let ReversibleAccountData { delay: user_delay, .. } = ReversibleTransfers::is_reversible(&user).unwrap(); - let execute_block = - BlockNumberOrTimestamp::BlockNumber(System::block_number() + user_delay); + let execute_block = BlockNumberOrTimestamp::BlockNumber( + System::block_number() + user_delay.as_block_number().unwrap(), + ); assert_eq!(Agenda::::get(execute_block).len(), 0); @@ -488,7 +683,9 @@ fn execute_transfer_works() { let tx_id = calculate_tx_id(user, &call); let ReversibleAccountData { delay, .. } = ReversibleTransfers::is_reversible(&user).unwrap(); - let execute_block = System::block_number() + delay; + let execute_block = BlockNumberOrTimestampOf::::BlockNumber( + System::block_number() + delay.as_block_number().unwrap(), + ); assert_ok!(ReversibleTransfers::schedule_transfer( RuntimeOrigin::signed(user), @@ -497,7 +694,7 @@ fn execute_transfer_works() { )); assert!(ReversibleTransfers::pending_dispatches(tx_id).is_some()); - run_to_block(execute_block - 1); + run_to_block(execute_block.as_block_number().unwrap() - 1); // Execute the dispatch as a normal user. This should fail // because the origin should be `Signed(PalletId::into_account())` @@ -517,6 +714,91 @@ fn execute_transfer_works() { }); } +#[test] +fn schedule_transfer_with_timestamp_delay_executes() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let initial_mock_time = MockTimestamp::::now(); + MockTimestamp::::set_timestamp(initial_mock_time); + + let user = 255; + let dest_user = 2; + let amount = 100; + + let bucket_size = TimestampBucketSize::get(); + let user_delay_duration = 5 * bucket_size; // e.g., 5000ms if bucket is 1000ms + let user_timestamp_delay = BlockNumberOrTimestamp::Timestamp(user_delay_duration); + + assert_ok!(ReversibleTransfers::set_reversibility( + RuntimeOrigin::signed(user), + Some(user_timestamp_delay), + DelayPolicy::Explicit, + None, + )); + + let user_balance_before = Balances::free_balance(user); + let dest_balance_before = Balances::free_balance(dest_user); + let call = transfer_call(dest_user, amount); + let tx_id = calculate_tx_id(user, &call); + + assert_ok!(ReversibleTransfers::schedule_transfer( + RuntimeOrigin::signed(user), + dest_user, + amount, + )); + + // The transfer should be scheduled at: current_time + user_delay_duration + let expected_execution_time = + BlockNumberOrTimestamp::Timestamp(initial_mock_time + user_delay_duration) + .normalize(bucket_size); + + assert!( + Agenda::::get(expected_execution_time).len() > 0, + "Task not found in agenda for timestamp" + ); + assert_eq!( + Balances::balance_on_hold( + &RuntimeHoldReason::ReversibleTransfers(HoldReason::ScheduledTransfer), + &user + ), + amount + ); + + // Advance time to just before execution + MockTimestamp::::set_timestamp( + expected_execution_time.as_timestamp().unwrap() - bucket_size - 1, + ); + assert_eq!(Balances::free_balance(user), user_balance_before - amount); + assert_eq!(Balances::free_balance(dest_user), dest_balance_before); + + // Advance time to the exact execution moment + MockTimestamp::::set_timestamp(expected_execution_time.as_timestamp().unwrap() - 1); + + // Check that the transfer is executed + assert_eq!(Balances::free_balance(user), user_balance_before - amount); + assert_eq!( + Balances::free_balance(dest_user), + dest_balance_before + amount + ); + assert_eq!( + Balances::balance_on_hold( + &RuntimeHoldReason::ReversibleTransfers(HoldReason::ScheduledTransfer), + &user + ), + 0 + ); + System::assert_has_event( + Event::TransactionExecuted { + tx_id, + result: Ok(().into()), + } + .into(), + ); + assert!(ReversibleTransfers::pending_dispatches(tx_id).is_none()); + assert_eq!(Agenda::::get(expected_execution_time).len(), 0); // Task removed + }); +} + #[test] fn full_flow_execute_works() { new_test_ext().execute_with(|| { @@ -531,8 +813,8 @@ fn full_flow_execute_works() { let tx_id = calculate_tx_id(user, &call); let ReversibleAccountData { delay, .. } = ReversibleTransfers::is_reversible(&user).unwrap(); - let start_block = System::block_number(); - let execute_block = BlockNumberOrTimestamp::BlockNumber(start_block + delay); + let start_block = BlockNumberOrTimestamp::BlockNumber(System::block_number()); + let execute_block = start_block.saturating_add(&delay).unwrap(); assert_ok!(ReversibleTransfers::schedule_transfer( RuntimeOrigin::signed(user), @@ -566,6 +848,68 @@ fn full_flow_execute_works() { }); } +#[test] +fn full_flow_execute_with_timestamp_delay_works() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let initial_mock_time = 1_000_000; + MockTimestamp::::set_timestamp(initial_mock_time); + + let user = 255; + let dest = 2; + let amount = 50; + + let user_delay_duration = 10 * TimestampBucketSize::get(); // e.g. 10s + let user_timestamp_delay = BlockNumberOrTimestamp::Timestamp(user_delay_duration); + + assert_ok!(ReversibleTransfers::set_reversibility( + RuntimeOrigin::signed(user), + Some(user_timestamp_delay), + DelayPolicy::Explicit, + None + )); + + let initial_user_balance = Balances::free_balance(user); + let initial_dest_balance = Balances::free_balance(dest); + let call = transfer_call(dest, amount); + let tx_id = calculate_tx_id(user, &call); + + assert_ok!(ReversibleTransfers::schedule_transfer( + RuntimeOrigin::signed(user), + dest, + amount + )); + + let expected_execution_time = + BlockNumberOrTimestamp::Timestamp(initial_mock_time + user_delay_duration) + .normalize(TimestampBucketSize::get()); + + assert!(ReversibleTransfers::pending_dispatches(tx_id).is_some()); + assert!(Agenda::::get(expected_execution_time).len() > 0); + assert_eq!(Balances::free_balance(user), initial_user_balance - amount); // On hold + + // Advance time to execution + MockTimestamp::::set_timestamp(expected_execution_time.as_timestamp().unwrap() - 1); + + let expected_event = Event::TransactionExecuted { + tx_id, + result: Ok(().into()), + }; + assert!( + System::events() + .iter() + .any(|rec| rec.event == expected_event.clone().into()), + "Execute event not found" + ); + + assert_eq!(Balances::free_balance(user), initial_user_balance - amount); + assert_eq!(Balances::free_balance(dest), initial_dest_balance + amount); + assert!(ReversibleTransfers::pending_dispatches(tx_id).is_none()); + assert!(ReversibleTransfers::account_pending_index(user).is_zero()); + assert_eq!(Agenda::::get(expected_execution_time).len(), 0); + }); +} + #[test] fn full_flow_cancel_prevents_execution() { new_test_ext().execute_with(|| { @@ -579,7 +923,9 @@ fn full_flow_cancel_prevents_execution() { let ReversibleAccountData { delay, .. } = ReversibleTransfers::is_reversible(&user).unwrap(); let start_block = System::block_number(); - let execute_block = start_block + delay; + let execute_block = BlockNumberOrTimestampOf::::BlockNumber( + start_block + delay.as_block_number().unwrap(), + ); assert_ok!(ReversibleTransfers::schedule_transfer( RuntimeOrigin::signed(user), @@ -603,7 +949,7 @@ fn full_flow_cancel_prevents_execution() { assert!(ReversibleTransfers::account_pending_index(user).is_zero()); // Run past the execution block - run_to_block(execute_block + 1); + run_to_block(execute_block.as_block_number().unwrap() + 1); // State is unchanged, amount is released // Amount is on hold @@ -633,6 +979,84 @@ fn full_flow_cancel_prevents_execution() { }); } +#[test] +fn full_flow_cancel_prevents_execution_with_timestamp_delay() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let initial_mock_time = 1_000_000; + MockTimestamp::::set_timestamp(initial_mock_time); + + let user = 255; + let dest = 2; + let amount = 50; + + let user_delay_duration = 10 * TimestampBucketSize::get(); + let user_timestamp_delay = BlockNumberOrTimestamp::Timestamp(user_delay_duration); + assert_ok!(ReversibleTransfers::set_reversibility( + RuntimeOrigin::signed(user), + Some(user_timestamp_delay), + DelayPolicy::Explicit, + None + )); + + let initial_user_balance = Balances::free_balance(user); + let initial_dest_balance = Balances::free_balance(dest); + let call = transfer_call(dest, amount); + let tx_id = calculate_tx_id(user, &call); + + assert_ok!(ReversibleTransfers::schedule_transfer( + RuntimeOrigin::signed(user), + dest, + amount + )); + assert_eq!( + Balances::balance_on_hold( + &RuntimeHoldReason::ReversibleTransfers(HoldReason::ScheduledTransfer), + &user + ), + amount + ); + + // Cancel before execution time + MockTimestamp::::set_timestamp(initial_mock_time + user_delay_duration / 2); + + assert_ok!(ReversibleTransfers::cancel( + RuntimeOrigin::signed(user), + tx_id + )); + assert!(ReversibleTransfers::pending_dispatches(tx_id).is_none()); + assert!(ReversibleTransfers::account_pending_index(user).is_zero()); + assert_eq!( + Balances::balance_on_hold( + &RuntimeHoldReason::ReversibleTransfers(HoldReason::ScheduledTransfer), + &user + ), + 0 // Hold released + ); + assert_eq!(Balances::free_balance(user), initial_user_balance); // Balance restored + + // Run past the original execution time + let original_execution_time = initial_mock_time + user_delay_duration; + MockTimestamp::::set_timestamp(original_execution_time + TimestampBucketSize::get()); + + assert_eq!(Balances::free_balance(user), initial_user_balance); + assert_eq!(Balances::free_balance(dest), initial_dest_balance); + + let expected_event_pattern = |e: &RuntimeEvent| match e { + RuntimeEvent::ReversibleTransfers(Event::TransactionExecuted { + tx_id: tid, .. + }) if *tid == tx_id => true, + _ => false, + }; + assert!( + !System::events() + .iter() + .any(|rec| expected_event_pattern(&rec.event)), + "TransactionExecuted event should not exist for timestamp delay" + ); + }); +} + /// The case we want to check: /// /// 1. User 1 schedules a transfer to user 2 with amount 100 @@ -655,9 +1079,15 @@ fn freeze_amount_is_consistent_with_multiple_transfers() { let ReversibleAccountData { delay, .. } = ReversibleTransfers::is_reversible(&user).unwrap(); - let execute_block1 = System::block_number() + delay; - let execute_block2 = System::block_number() + delay + 2; - let execute_block3 = System::block_number() + delay + 3; + let delay_blocks = delay.as_block_number().unwrap(); + let execute_block1 = + BlockNumberOrTimestampOf::::BlockNumber(System::block_number() + delay_blocks); + let execute_block2 = BlockNumberOrTimestampOf::::BlockNumber( + System::block_number() + delay_blocks + 2, + ); + let execute_block3 = BlockNumberOrTimestampOf::::BlockNumber( + System::block_number() + delay_blocks + 3, + ); assert_ok!(ReversibleTransfers::schedule_transfer( RuntimeOrigin::signed(user), @@ -695,7 +1125,7 @@ fn freeze_amount_is_consistent_with_multiple_transfers() { user_initial_balance - amount1 - amount2 - amount3 ); - run_to_block(execute_block1); + run_to_block(execute_block1.as_block_number().unwrap()); // Check that the first transfer is executed and the frozen amounts are thawed assert_eq!( @@ -713,7 +1143,7 @@ fn freeze_amount_is_consistent_with_multiple_transfers() { amount2 + amount3 ); - run_to_block(execute_block2); + run_to_block(execute_block2.as_block_number().unwrap()); // Check that the second transfer is executed and the frozen amounts are thawed assert_eq!( Balances::free_balance(user), @@ -733,7 +1163,7 @@ fn freeze_amount_is_consistent_with_multiple_transfers() { ), amount3 ); - run_to_block(execute_block3); + run_to_block(execute_block3.as_block_number().unwrap()); // Check that the third transfer is executed and the held amounts are released assert_eq!( Balances::free_balance(user), diff --git a/pallets/scheduler/Cargo.toml b/pallets/scheduler/Cargo.toml index c248d374..fb6d9a29 100644 --- a/pallets/scheduler/Cargo.toml +++ b/pallets/scheduler/Cargo.toml @@ -20,7 +20,8 @@ sp-io.workspace = true sp-runtime.workspace = true sp-weights.workspace = true docify = { workspace = true } -sp-common = { workspace = true, default-features = false } +qp-scheduler = { workspace = true, default-features = false } + [dev-dependencies] pallet-preimage = { default-features = true, workspace = true } diff --git a/pallets/scheduler/src/lib.rs b/pallets/scheduler/src/lib.rs index 87ea7e52..4612d348 100644 --- a/pallets/scheduler/src/lib.rs +++ b/pallets/scheduler/src/lib.rs @@ -88,10 +88,10 @@ use frame_system::{ pallet_prelude::BlockNumberFor, {self as system}, }; +use qp_scheduler::{BlockNumberOrTimestamp, DispatchTime, Period, ScheduleNamed}; use scale_info::TypeInfo; -use sp_common::scheduler::{BlockNumberOrTimestamp, DispatchTime, Period, ScheduleNamed}; use sp_runtime::{ - traits::{BadOrigin, CheckedDiv, Dispatchable, One, Saturating, Zero}, + traits::{BadOrigin, Dispatchable, One, Saturating}, BoundedVec, DispatchError, RuntimeDebug, }; @@ -642,18 +642,8 @@ impl Pallet { BlockNumberOrTimestamp::BlockNumber(x) => BlockNumberOrTimestamp::BlockNumber( current_block.saturating_add(x).saturating_add(One::one()), ), - BlockNumberOrTimestamp::Timestamp(x) => { - // Simply strip and round to the nearest bucket. - let x = x - .checked_div(&T::TimestampBucketSize::get()) - .unwrap_or(Zero::zero()) - .saturating_mul(T::TimestampBucketSize::get()); - - // Add the bucket size to the current time to ensure that we are - // scheduling in the future. - BlockNumberOrTimestamp::Timestamp( - x.saturating_add(T::TimestampBucketSize::get()), - ) + BlockNumberOrTimestamp::Timestamp(_) => { + x.normalize(T::TimestampBucketSize::get()) } }; res @@ -940,7 +930,9 @@ impl Pallet { return; } - let mut incomplete_since = match now { + let normalized_now = now.normalize(T::TimestampBucketSize::get()); + + let mut incomplete_since = match normalized_now { BlockNumberOrTimestamp::BlockNumber(x) => { BlockNumberOrTimestamp::BlockNumber(x.saturating_add(One::one())) } @@ -948,14 +940,23 @@ impl Pallet { BlockNumberOrTimestamp::Timestamp(x.saturating_add(T::TimestampBucketSize::get())) } }; - let mut when = IncompleteSince::::take().unwrap_or(now); + let mut when = IncompleteSince::::take().unwrap_or(normalized_now); let mut executed = 0; let max_items = T::MaxScheduledPerBlock::get(); let mut count_down = max; let service_agenda_base_weight = T::WeightInfo::service_agenda_base(max_items); - while count_down > 0 && when <= now && weight.can_consume(service_agenda_base_weight) { - if !Self::service_agenda(weight, &mut executed, now, when, u32::max_value()) { + while count_down > 0 + && when <= normalized_now + && weight.can_consume(service_agenda_base_weight) + { + if !Self::service_agenda( + weight, + &mut executed, + normalized_now, + when, + u32::max_value(), + ) { incomplete_since = incomplete_since.min(when); } match when { @@ -971,7 +972,7 @@ impl Pallet { count_down.saturating_dec(); } incomplete_since = incomplete_since.min(when); - if incomplete_since <= now { + if incomplete_since <= normalized_now { IncompleteSince::::put(incomplete_since); } } @@ -1128,7 +1129,7 @@ impl Pallet { task.maybe_periodic = None; } let wake = now.saturating_add(&period); - if let Some(wake) = wake { + if let Ok(wake) = wake { match Self::place_task(wake, task) { Ok(new_address) => { if let Some(retry_config) = maybe_retry_config { @@ -1229,7 +1230,7 @@ impl Pallet { None => return, }; let wake = now.saturating_add(&period); - if let Some(wake) = wake { + if let Ok(wake) = wake { match Self::place_task(wake, task.as_retry()) { Ok(address) => { // Reinsert the retry config to the new address of the task after it was diff --git a/primitives/common/src/lib.rs b/primitives/common/src/lib.rs deleted file mode 100644 index 1be3aa8e..00000000 --- a/primitives/common/src/lib.rs +++ /dev/null @@ -1,136 +0,0 @@ -//! Common primitives for the Quantus blockchain. -#![cfg_attr(not(feature = "std"), no_std)] - -/// Scheduler related traits and types. -pub mod scheduler { - use codec::{Codec, Decode, Encode, EncodeLike, MaxEncodedLen}; - use frame_support::{ - traits::{ - schedule::{self, v3::TaskName, DispatchTime as DispatchBlock}, - Bounded, - }, - Parameter, - }; - use scale_info::TypeInfo; - use sp_runtime::{ - traits::{Hash, One, Saturating, Zero}, - DispatchError, RuntimeDebug, - }; - - /// Information relating to the period of a scheduled task. First item is the length of the - /// period and the second is the number of times it should be executed in total before the task - /// is considered finished and removed. - pub type Period = (BlockNumberOrTimestamp, u32); - - /// Block number or timestamp. - #[derive( - Encode, - Decode, - Copy, - Clone, - PartialEq, - Eq, - RuntimeDebug, - TypeInfo, - MaxEncodedLen, - Ord, - PartialOrd, - )] - pub enum BlockNumberOrTimestamp { - BlockNumber(BlockNumber), - Timestamp(Moment), - } - - impl BlockNumberOrTimestamp - where - BlockNumber: Saturating + Copy + Parameter + One + Zero, - Moment: Saturating + Copy + Parameter + Zero, - { - /// Returns the block number if it is a block number. - pub fn as_block_number(&self) -> Option { - match self { - BlockNumberOrTimestamp::BlockNumber(x) => Some(*x), - BlockNumberOrTimestamp::Timestamp(_) => None, - } - } - - /// Is zero - pub fn is_zero(&self) -> bool { - match self { - BlockNumberOrTimestamp::BlockNumber(x) => x.is_zero(), - BlockNumberOrTimestamp::Timestamp(x) => x.is_zero(), - } - } - - /// Saturating add two `BlockNumberOrTimestamp`. - pub fn saturating_add( - &self, - other: &BlockNumberOrTimestamp, - ) -> Option> { - match (self, other) { - ( - BlockNumberOrTimestamp::BlockNumber(x), - BlockNumberOrTimestamp::BlockNumber(y), - ) => Some(BlockNumberOrTimestamp::BlockNumber(x.saturating_add(*y))), - (BlockNumberOrTimestamp::Timestamp(x), BlockNumberOrTimestamp::Timestamp(y)) => { - Some(BlockNumberOrTimestamp::Timestamp(x.saturating_add(*y))) - } - _ => None, - } - } - } - - /// The dispatch time of a scheduled task. - /// - /// This is an extended version of `frame_support::traits::schedule::DispatchTime` which allows - /// for a task to be scheduled at or close to specific timestamps. This is useful for chains that - /// does not have a fixed block time, such as PoW chains. - #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] - pub enum DispatchTime { - /// At specified block. - At(BlockNumber), - /// After specified number of blocks. - After(BlockNumberOrTimestamp), - } - - impl From> for DispatchTime { - fn from(value: DispatchBlock) -> Self { - match value { - DispatchBlock::At(x) => DispatchTime::At(x), - DispatchBlock::After(x) => { - DispatchTime::After(BlockNumberOrTimestamp::BlockNumber(x)) - } - } - } - } - - /// A trait for scheduling tasks with a name, and with an approximate dispatch time. - pub trait ScheduleNamed { - /// Address type for the scheduled task. - type Address: Codec + MaxEncodedLen + Clone + Eq + EncodeLike + core::fmt::Debug; - /// The type of the hash function used for hashing. - type Hasher: Hash; - - /// Schedule a task with a name, dispatch time, and optional periodicity. - fn schedule_named( - id: TaskName, - when: DispatchTime, - maybe_periodic: Option>, - priority: schedule::Priority, - origin: Origin, - call: Bounded, - ) -> Result; - - /// Schedule a task with a name, dispatch time, and optional periodicity. - fn cancel_named(id: TaskName) -> Result<(), DispatchError>; - - /// Reschedule a task with a name, dispatch time, and optional periodicity. - fn reschedule_named( - id: TaskName, - when: DispatchTime, - ) -> Result; - - /// Get the approximate dispatch block number for a task with a name. - fn next_dispatch_time(id: TaskName) -> Result; - } -} diff --git a/primitives/common/Cargo.toml b/primitives/scheduler/Cargo.toml similarity index 68% rename from primitives/common/Cargo.toml rename to primitives/scheduler/Cargo.toml index e7577bce..0e93dcb1 100644 --- a/primitives/common/Cargo.toml +++ b/primitives/scheduler/Cargo.toml @@ -1,5 +1,6 @@ [package] -name = "sp-common" +name = "qp-scheduler" +description = "Common primitives for the scheduler pallet and its dependencies" version = "0.1.0" license = "Apache-2.0" authors.workspace = true @@ -17,4 +18,8 @@ scale-info = { workspace = true, features = ["derive"] } [features] default = ["std"] std = [ + "codec/std", + "frame-support/std", + "sp-runtime/std", + "scale-info/std" ] diff --git a/primitives/scheduler/src/lib.rs b/primitives/scheduler/src/lib.rs new file mode 100644 index 00000000..1b3b5cea --- /dev/null +++ b/primitives/scheduler/src/lib.rs @@ -0,0 +1,221 @@ +//! Common primitives for the Quantus blockchain. +#![cfg_attr(not(feature = "std"), no_std)] + +/// Scheduler related traits and types. +use codec::{Codec, Decode, Encode, EncodeLike, MaxEncodedLen}; +use frame_support::{ + traits::{ + schedule::{self, v3::TaskName, DispatchTime as DispatchBlock}, + Bounded, + }, + Parameter, +}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{CheckedDiv, Hash, One, Saturating, Zero}, + DispatchError, RuntimeDebug, +}; + +/// Information relating to the period of a scheduled task. First item is the length of the +/// period and the second is the number of times it should be executed in total before the task +/// is considered finished and removed. +pub type Period = (BlockNumberOrTimestamp, u32); + +/// Block number or timestamp. +#[derive( + Encode, + Decode, + Copy, + Clone, + PartialEq, + Eq, + RuntimeDebug, + TypeInfo, + MaxEncodedLen, + Ord, + PartialOrd, +)] +pub enum BlockNumberOrTimestamp { + BlockNumber(BlockNumber), + Timestamp(Moment), +} + +impl BlockNumberOrTimestamp +where + BlockNumber: Saturating + Copy + Parameter + One + Zero, + Moment: Saturating + Copy + Parameter + Zero + CheckedDiv, +{ + /// Normalize timestamp value + pub fn normalize(&self, precision: Moment) -> Self { + match self { + BlockNumberOrTimestamp::BlockNumber(_) => self.clone(), + BlockNumberOrTimestamp::Timestamp(t) => { + let stripped_t = t + .checked_div(&precision) + .unwrap_or(Zero::zero()) + .saturating_mul(precision); + + BlockNumberOrTimestamp::Timestamp(stripped_t.saturating_add(precision)) + } + } + } + + /// Returns the block number if it is a block number. + pub fn as_block_number(&self) -> Option { + match self { + BlockNumberOrTimestamp::BlockNumber(x) => Some(*x), + BlockNumberOrTimestamp::Timestamp(_) => None, + } + } + + /// Returns the timestamp if it is a timestamp + pub fn as_timestamp(&self) -> Option { + match self { + BlockNumberOrTimestamp::BlockNumber(_) => None, + BlockNumberOrTimestamp::Timestamp(x) => Some(*x), + } + } + + /// Is zero + pub fn is_zero(&self) -> bool { + match self { + BlockNumberOrTimestamp::BlockNumber(x) => x.is_zero(), + BlockNumberOrTimestamp::Timestamp(x) => x.is_zero(), + } + } + + /// Saturating add two `BlockNumberOrTimestamp`. + pub fn saturating_add( + &self, + other: &BlockNumberOrTimestamp, + ) -> Result, ()> { + match (self, other) { + (BlockNumberOrTimestamp::BlockNumber(x), BlockNumberOrTimestamp::BlockNumber(y)) => { + Ok(BlockNumberOrTimestamp::BlockNumber(x.saturating_add(*y))) + } + (BlockNumberOrTimestamp::Timestamp(x), BlockNumberOrTimestamp::Timestamp(y)) => { + Ok(BlockNumberOrTimestamp::Timestamp(x.saturating_add(*y))) + } + _ => Err(()), + } + } +} + +/// The dispatch time of a scheduled task. +/// +/// This is an extended version of `frame_support::traits::schedule::DispatchTime` which allows +/// for a task to be scheduled at or close to specific timestamps. This is useful for chains that +/// does not have a fixed block time, such as PoW chains. +#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub enum DispatchTime { + /// At specified block. + At(BlockNumber), + /// After specified number of blocks. + After(BlockNumberOrTimestamp), +} + +impl From> for DispatchTime { + fn from(value: DispatchBlock) -> Self { + match value { + DispatchBlock::At(x) => DispatchTime::At(x), + DispatchBlock::After(x) => DispatchTime::After(BlockNumberOrTimestamp::BlockNumber(x)), + } + } +} + +/// A trait for scheduling tasks with a name, and with an approximate dispatch time. +pub trait ScheduleNamed { + /// Address type for the scheduled task. + type Address: Codec + MaxEncodedLen + Clone + Eq + EncodeLike + core::fmt::Debug; + /// The type of the hash function used for hashing. + type Hasher: Hash; + + /// Schedule a task with a name, dispatch time, and optional periodicity. + fn schedule_named( + id: TaskName, + when: DispatchTime, + maybe_periodic: Option>, + priority: schedule::Priority, + origin: Origin, + call: Bounded, + ) -> Result; + + /// Schedule a task with a name, dispatch time, and optional periodicity. + fn cancel_named(id: TaskName) -> Result<(), DispatchError>; + + /// Reschedule a task with a name, dispatch time, and optional periodicity. + fn reschedule_named( + id: TaskName, + when: DispatchTime, + ) -> Result; + + /// Get the approximate dispatch block number for a task with a name. + fn next_dispatch_time(id: TaskName) -> Result; +} + +#[cfg(test)] +mod tests { + use super::BlockNumberOrTimestamp; // Adjust path as needed + + type DefaultBlockNumberOrTimestamp = BlockNumberOrTimestamp; + + #[test] + fn normalize_block_number_is_unchanged() { + let bn = DefaultBlockNumberOrTimestamp::BlockNumber(123u64); + assert_eq!( + bn.normalize(10u64), + DefaultBlockNumberOrTimestamp::BlockNumber(123u64) + ); + } + + #[test] + fn normalize_timestamp_mid_bucket() { + // Tests the common case: timestamp within a bucket. + // Expected: start of the *next* bucket. + let ts = DefaultBlockNumberOrTimestamp::Timestamp(15500u64); // Bucket [14000, 15999] + // Calculation: (15500 / 2000) * 2000 + 2000 = 14000 + 2000 = 16000 + assert_eq!( + ts.normalize(2000u64), + DefaultBlockNumberOrTimestamp::Timestamp(16000u64) + ); + } + + #[test] + fn normalize_timestamp_at_bucket_start_boundary() { + // Tests behavior when timestamp is exactly at a bucket start. + // Expected: start of the *next* bucket. + let ts = DefaultBlockNumberOrTimestamp::Timestamp(14000u64); // Exactly at start of bucket [14000, 15999] + let precision = 2000u64; + // Calculation: (14000 / 2000) * 2000 + 2000 = 14000 + 2000 = 16000 + assert_eq!( + ts.normalize(precision), + DefaultBlockNumberOrTimestamp::Timestamp(16000u64) + ); + } + + #[test] + fn normalize_timestamp_zero_value() { + // Tests the zero timestamp edge case. + // Expected: 0 + precision. + let ts = DefaultBlockNumberOrTimestamp::Timestamp(0u64); + let precision = 2000u64; + // Calculation: (0 / 2000) * 2000 + 2000 = 0 + 2000 = 2000 + assert_eq!( + ts.normalize(precision), + DefaultBlockNumberOrTimestamp::Timestamp(2000u64) + ); + } + + #[test] + fn normalize_timestamp_less_than_precision() { + // Tests when timestamp is smaller than the precision (falls into the first bucket [0, precision-1]). + // Expected: 0 + precision. + let ts = DefaultBlockNumberOrTimestamp::Timestamp(500u64); + let precision = 2000u64; + // Calculation: (500 / 2000) * 2000 + 2000 = 0 + 2000 = 2000 + assert_eq!( + ts.normalize(precision), + DefaultBlockNumberOrTimestamp::Timestamp(2000u64) + ); + } +} diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index aa24c3ad..e9a33f0f 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -57,8 +57,9 @@ pallet-qpow = { workspace = true, default-features = false } pallet-wormhole = { workspace = true, default-features = false } pallet-reversible-transfers = { workspace = true, default-features = false } pallet-vesting = { workspace = true, default-features = false } -sp-consensus-qpow = { workspace = true, default-features = false } -sp-faucet = { workspace = true, default-features = false } +qp-scheduler = { workspace = true, default-features = false } +sp-consensus-qpow = { workspace = true, default-features = false} +sp-faucet = {workspace = true, default-features = false} log = { workspace = true } pallet-merkle-airdrop = { workspace = true, default-features = false } @@ -78,7 +79,6 @@ hdwallet = { path = "../dilithium-crypto/hdwallet", default-features = true } env_logger = "0.11.5" sp-keyring = { workspace = true, features = ["std"] } sp-io = { workspace = true, default-features = true } -sp-common = { workspace = true, default-features = false } [features] default = ["std"] @@ -133,9 +133,9 @@ std = [ "dilithium-crypto/full_crypto", "poseidon-resonance/std", "pallet-reversible-transfers/std", + "qp-scheduler/std", ] - runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", diff --git a/runtime/src/configs/mod.rs b/runtime/src/configs/mod.rs index c036c9c7..b1b9cba7 100644 --- a/runtime/src/configs/mod.rs +++ b/runtime/src/configs/mod.rs @@ -46,6 +46,7 @@ use pallet_ranked_collective::Linear; use pallet_referenda::impl_tracksinfo_get; use pallet_transaction_payment::{ConstFeeMultiplier, FungibleAdapter, Multiplier}; use poseidon_resonance::PoseidonHasher; +use qp_scheduler::BlockNumberOrTimestamp; use sp_runtime::traits::ConvertInto; use sp_runtime::{traits::One, Perbill, Permill}; use sp_version::RuntimeVersion; @@ -133,13 +134,18 @@ impl pallet_mining_rewards::Config for Runtime { type FeesToTreasuryPermill = MiningRewardsFeesToTreasury; } +parameter_types! { + /// Target block time + pub const TargetBlockTime: u64 = 10000; +} + impl pallet_qpow::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = pallet_qpow::DefaultWeightInfo; // NOTE: InitialDistance will be shifted left by this amount: higher is easier type InitialDistanceThresholdExponent = ConstU32<502>; type DifficultyAdjustPercentClamp = ConstU8<10>; - type TargetBlockTime = ConstU64<10000>; + type TargetBlockTime = TargetBlockTime; type AdjustmentPeriod = ConstU32<1>; type BlockTimeHistorySize = ConstU32<10>; type MaxReorgDepth = ConstU32<10>; @@ -150,12 +156,15 @@ impl pallet_wormhole::Config for Runtime { type WeightInfo = pallet_wormhole::DefaultWeightInfo; } +type Moment = u64; + parameter_types! { pub const MinimumPeriod: u64 = 100; } + impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. - type Moment = u64; + type Moment = Moment; type OnTimestampSet = Scheduler; type MinimumPeriod = MinimumPeriod; type WeightInfo = (); @@ -446,8 +455,8 @@ impl pallet_recovery::Config for Runtime { parameter_types! { pub const ReversibleTransfersPalletIdValue: PalletId = PalletId(*b"rtpallet"); - pub const DefaultDelay: BlockNumber = 10; - pub const MinDelayPeriod: BlockNumber = 2; + pub const DefaultDelay: BlockNumberOrTimestamp = BlockNumberOrTimestamp::BlockNumber(DAYS); + pub const MinDelayPeriodBlocks: BlockNumber = 2; pub const MaxReversibleTransfers: u32 = 10; } @@ -458,11 +467,14 @@ impl pallet_reversible_transfers::Config for Runtime { type BlockNumberProvider = System; type MaxPendingPerAccount = MaxReversibleTransfers; type DefaultDelay = DefaultDelay; - type MinDelayPeriod = MinDelayPeriod; + type MinDelayPeriodBlocks = MinDelayPeriodBlocks; + type MinDelayPeriodMoment = TargetBlockTime; type PalletId = ReversibleTransfersPalletIdValue; type Preimages = Preimage; type WeightInfo = pallet_reversible_transfers::weights::SubstrateWeight; type RuntimeHoldReason = RuntimeHoldReason; + type Moment = Moment; + type TimeProvider = Timestamp; } impl pallet_merkle_airdrop::Config for Runtime { diff --git a/runtime/tests/governance/treasury.rs b/runtime/tests/governance/treasury.rs index c7b4bac7..f39d8748 100644 --- a/runtime/tests/governance/treasury.rs +++ b/runtime/tests/governance/treasury.rs @@ -1074,10 +1074,7 @@ mod tests { matches!( event_record.event, RuntimeEvent::Scheduler(pallet_scheduler::Event::Dispatched { - task: ( - sp_common::scheduler::BlockNumberOrTimestamp::BlockNumber(86402), - 0 - ), + task: (qp_scheduler::BlockNumberOrTimestamp::BlockNumber(86402), 0), id: _, result: Ok(()) })