diff --git a/Cargo.toml b/Cargo.toml
index 3eb8697f..4728cee5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,6 +3,7 @@ members = [
"chain-extensions/dapps-staking",
"chain-extensions/pallet-assets",
"chain-extensions/xvm",
+ "chain-extensions/pallet-scheduler",
"chain-extensions/types/*",
"frame/block-reward",
"frame/collator-selection",
@@ -97,6 +98,7 @@ sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0
frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39", default-features = false }
sp-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39", default-features = false }
sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39", default-features = false }
+sp-keystore = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39", default-features = false }
# (native)
sp-consensus-aura = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" }
@@ -120,6 +122,8 @@ pallet-authorship = { git = "https://github.com/paritytech/substrate", branch =
pallet-session = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39", default-features = false }
pallet-aura = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39", default-features = false }
pallet-assets = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39", default-features = false }
+pallet-scheduler = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39", default-features = false }
+pallet-preimage = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39", default-features = false }
# Polkadot
# (wasm)
@@ -154,6 +158,7 @@ pallet-xc-asset-config = { path = "./frame/xc-asset-config", default-features =
dapps-staking-chain-extension-types = { path = "./chain-extensions/types/dapps-staking", default-features = false }
xvm-chain-extension-types = { path = "./chain-extensions/types/xvm", default-features = false }
assets-chain-extension-types = { path = "./chain-extensions/types/assets", default-features = false }
+scheduler-chain-extension-types = { path = "./chain-extensions/types/scheduler", default-features = false }
pallet-evm-precompile-assets-erc20 = { path = "./precompiles/assets-erc20", default-features = false }
precompile-utils = { path = "./precompiles/utils", default-features = false }
diff --git a/chain-extensions/pallet-scheduler/Cargo.toml b/chain-extensions/pallet-scheduler/Cargo.toml
new file mode 100644
index 00000000..13fe3666
--- /dev/null
+++ b/chain-extensions/pallet-scheduler/Cargo.toml
@@ -0,0 +1,50 @@
+[package]
+name = "pallet-chain-extension-scheduler"
+version = "0.1.0"
+license = "Apache-2.0"
+description = "Scheduler chain extension for WASM contracts"
+authors.workspace = true
+edition.workspace = true
+homepage.workspace = true
+repository.workspace = true
+
+[dependencies]
+frame-support = { workspace = true }
+frame-system = { workspace = true }
+log = { workspace = true }
+num-traits = { workspace = true }
+pallet-contracts = { workspace = true }
+pallet-contracts-primitives = { workspace = true }
+pallet-scheduler = { workspace = true }
+parity-scale-codec = { workspace = true }
+scale-info = { workspace = true }
+scheduler-chain-extension-types = { workspace = true }
+sp-core = { workspace = true }
+sp-runtime = { workspace = true }
+sp-std = { workspace = true }
+
+[dev-dependencies]
+env_logger = "0.9"
+pallet-balances = { workspace = true }
+pallet-preimage = { workspace = true }
+pallet-timestamp = { workspace = true }
+sp-io = { workspace = true }
+sp-keystore = { workspace = true }
+
+[features]
+default = ["std"]
+std = [
+ "parity-scale-codec/std",
+ "frame-support/std",
+ "frame-system/std",
+ "num-traits/std",
+ "pallet-contracts/std",
+ "pallet-contracts-primitives/std",
+ "scale-info/std",
+ "sp-std/std",
+ "sp-core/std",
+ "sp-runtime/std",
+ "pallet-scheduler/std",
+ "scheduler-chain-extension-types/std",
+ "pallet-balances/std",
+]
diff --git a/chain-extensions/pallet-scheduler/src/lib.rs b/chain-extensions/pallet-scheduler/src/lib.rs
new file mode 100644
index 00000000..e91a5d80
--- /dev/null
+++ b/chain-extensions/pallet-scheduler/src/lib.rs
@@ -0,0 +1,167 @@
+// This file is part of Astar.
+
+// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd.
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+// Astar is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Astar is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Astar. If not, see .
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+#[cfg(test)]
+mod mock;
+#[cfg(test)]
+pub mod tests;
+pub mod weights;
+
+use frame_support::dispatch::Weight;
+use frame_support::traits::{schedule, Currency};
+use frame_system::RawOrigin;
+use pallet_contracts::{
+ chain_extension::{ChainExtension, Environment, Ext, InitState, RetVal, SysConfig},
+ Call as PalletContractCall,
+};
+use pallet_scheduler::WeightInfo;
+use parity_scale_codec::{Encode, HasCompact};
+use scale_info::TypeInfo;
+use scheduler_chain_extension_types::*;
+use sp_core::Get;
+use sp_runtime::traits::StaticLookup;
+use sp_runtime::DispatchError;
+use sp_std::boxed::Box;
+use sp_std::fmt::Debug;
+use sp_std::marker::PhantomData;
+
+enum SchedulerFunc {
+ Schedule,
+ Cancel,
+}
+
+impl TryFrom for SchedulerFunc {
+ type Error = DispatchError;
+
+ fn try_from(value: u16) -> Result {
+ match value {
+ 1 => Ok(SchedulerFunc::Schedule),
+ 2 => Ok(SchedulerFunc::Cancel),
+ _ => Err(DispatchError::Other(
+ "PalletSchedulerExtension: Unimplemented func_id",
+ )),
+ }
+ }
+}
+
+type BalanceOf = <::Currency as Currency<
+ ::AccountId,
+>>::Balance;
+
+/// Pallet Scheduler chain extension.
+pub struct SchedulerExtension(PhantomData<(T, W)>);
+
+impl Default for SchedulerExtension {
+ fn default() -> Self {
+ SchedulerExtension(PhantomData)
+ }
+}
+
+impl ChainExtension for SchedulerExtension
+where
+ T: pallet_scheduler::Config + pallet_contracts::Config,
+ <::Lookup as StaticLookup>::Source: From<::AccountId>,
+ ::AccountId: From<[u8; 32]>,
+ <<::Currency as Currency<::AccountId>>::Balance as HasCompact>::Type: Clone + Encode + TypeInfo + Debug + Eq,
+ ::RuntimeCall: From> + Encode,
+ ::RuntimeCall: From>,
+ W: weights::WeightInfo,
+{
+ fn call(&mut self, env: Environment) -> Result
+ where
+ E: Ext,
+ {
+ let func_id = env.func_id().try_into()?;
+ let mut env = env.buf_in_buf_out();
+
+ match func_id {
+ SchedulerFunc::Schedule => {
+ let (origin, when, maybe_periodic, priority, call_input): (
+ Origin,
+ T::BlockNumber,
+ Option>,
+ schedule::Priority,
+ ContractCallInput>,
+ ) = env.read_as_unbounded(env.in_len())?;
+
+ let read_weight = ::read_as_unbounded(env.in_len());
+ env.charge_weight(read_weight)?;
+
+ let base_weight = ::WeightInfo::schedule(
+ T::MaxScheduledPerBlock::get(),
+ );
+ env.charge_weight(base_weight)?;
+
+ let raw_origin = select_origin!(&origin, env.ext().address().clone());
+
+ let call: ::RuntimeCall =
+ PalletContractCall::::call {
+ dest: call_input.dest.into(),
+ value: call_input.value,
+ gas_limit: Weight::from_parts(call_input.gas_limit.0, call_input.gas_limit.1),
+ data: call_input.data.into(),
+ storage_deposit_limit: None,
+ }.into();
+
+ let call_result = pallet_scheduler::Pallet::::schedule(
+ raw_origin.into(),
+ when,
+ maybe_periodic,
+ priority,
+ Box::new(call),
+ );
+ return match call_result {
+ Err(e) => {
+ let mapped_error = Outcome::from(e);
+ Ok(RetVal::Converging(mapped_error as u32))
+ }
+ Ok(_) => Ok(RetVal::Converging(Outcome::Success as u32)),
+ };
+ },
+ SchedulerFunc::Cancel => {
+ let (origin, when, index): (
+ Origin,
+ T::BlockNumber,
+ u32,
+ ) = env.read_as()?;
+
+ let base_weight = ::WeightInfo::cancel(
+ T::MaxScheduledPerBlock::get(),
+ );
+ env.charge_weight(base_weight)?;
+
+ let raw_origin = select_origin!(&origin, env.ext().address().clone());
+
+ let call_result = pallet_scheduler::Pallet::::cancel(
+ raw_origin.into(),
+ when,
+ index,
+ );
+ return match call_result {
+ Err(e) => {
+ let mapped_error = Outcome::from(e);
+ Ok(RetVal::Converging(mapped_error as u32))
+ }
+ Ok(_) => Ok(RetVal::Converging(Outcome::Success as u32)),
+ };
+ }
+ }
+ }
+}
diff --git a/chain-extensions/pallet-scheduler/src/mock.rs b/chain-extensions/pallet-scheduler/src/mock.rs
new file mode 100644
index 00000000..49f43f66
--- /dev/null
+++ b/chain-extensions/pallet-scheduler/src/mock.rs
@@ -0,0 +1,242 @@
+// This file is part of Astar.
+
+// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd.
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+// Astar is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Astar is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Astar. If not, see .
+
+use crate::weights::SubstrateWeight;
+use crate::BalanceOf;
+use crate::{weights, SchedulerExtension};
+use frame_support::traits::Randomness;
+use frame_support::{
+ ord_parameter_types, parameter_types, sp_io,
+ traits::{ConstU32, ConstU64, EqualPrivilegeOnly, Nothing, OnFinalize, OnInitialize},
+ weights::Weight,
+};
+use frame_system::{EnsureRoot, EnsureSigned};
+use pallet_contracts::chain_extension::RegisteredChainExtension;
+use pallet_contracts::{DefaultAddressGenerator, Frame};
+use sp_core::crypto::AccountId32;
+use sp_keystore::{testing::KeyStore, KeystoreExt};
+use sp_runtime::testing::H256;
+use sp_runtime::traits::{BlakeTwo256, Convert, IdentityLookup, Zero};
+use sp_runtime::{generic, Perbill};
+use std::sync::Arc;
+
+pub type BlockNumber = u32;
+pub type Balance = u128;
+
+parameter_types! {
+ pub const BlockHashCount: BlockNumber = 250;
+ pub BlockWeights: frame_system::limits::BlockWeights =
+ frame_system::limits::BlockWeights::simple_max(
+ Weight::from_ref_time(2_000_000_000_000).set_proof_size(u64::MAX),
+ );
+}
+impl frame_system::Config for Test {
+ type BaseCallFilter = frame_support::traits::Everything;
+ type BlockWeights = BlockWeights;
+ type BlockLength = ();
+ type DbWeight = ();
+ type RuntimeOrigin = RuntimeOrigin;
+ type Index = u32;
+ type BlockNumber = BlockNumber;
+ type Hash = H256;
+ type RuntimeCall = RuntimeCall;
+ type Hashing = BlakeTwo256;
+ type AccountId = AccountId32;
+ type Lookup = IdentityLookup;
+ type Header = generic::Header;
+ type RuntimeEvent = RuntimeEvent;
+ type BlockHashCount = BlockHashCount;
+ type Version = ();
+ type PalletInfo = PalletInfo;
+ type AccountData = pallet_balances::AccountData;
+ type OnNewAccount = ();
+ type OnKilledAccount = ();
+ type SystemWeightInfo = ();
+ type SS58Prefix = ();
+ type OnSetCode = ();
+ type MaxConsumers = frame_support::traits::ConstU32<16>;
+}
+
+impl pallet_preimage::Config for Test {
+ type RuntimeEvent = RuntimeEvent;
+ type WeightInfo = ();
+ type Currency = ();
+ type ManagerOrigin = EnsureRoot;
+ type BaseDeposit = ();
+ type ByteDeposit = ();
+}
+
+parameter_types! {
+ pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) *
+ BlockWeights::get().max_block;
+}
+
+ord_parameter_types! {
+ pub const One: u64 = 1;
+}
+
+impl pallet_scheduler::Config for Test {
+ type RuntimeEvent = RuntimeEvent;
+ type RuntimeOrigin = RuntimeOrigin;
+ type PalletsOrigin = OriginCaller;
+ type RuntimeCall = RuntimeCall;
+ type MaximumWeight = MaximumSchedulerWeight;
+ type ScheduleOrigin = EnsureSigned;
+ type MaxScheduledPerBlock = ConstU32<10>;
+ type WeightInfo = ();
+ type OriginPrivilegeCmp = EqualPrivilegeOnly;
+ type Preimages = Preimage;
+}
+
+parameter_types! {
+ pub const DeletionWeightLimit: Weight = Weight::from_ref_time(500_000_000_000);
+ pub static UnstableInterface: bool = true;
+ pub Schedule: pallet_contracts::Schedule = Default::default();
+ pub static DepositPerByte: BalanceOf = 1;
+ pub const DepositPerItem: BalanceOf = 1;
+}
+
+pub struct DummyDeprecatedRandomness;
+impl Randomness for DummyDeprecatedRandomness {
+ fn random(_: &[u8]) -> (H256, BlockNumber) {
+ (Default::default(), Zero::zero())
+ }
+}
+
+impl pallet_contracts::Config for Test {
+ type Time = Timestamp;
+ type Randomness = DummyDeprecatedRandomness;
+ type Currency = Balances;
+ type RuntimeEvent = RuntimeEvent;
+ type RuntimeCall = RuntimeCall;
+ type CallFilter = Nothing;
+ type CallStack = [Frame; 5];
+ type WeightPrice = Self;
+ type WeightInfo = ();
+ type ChainExtension = SchedulerExtension>;
+ type DeletionQueueDepth = ConstU32<128>;
+ type DeletionWeightLimit = DeletionWeightLimit;
+ type Schedule = Schedule;
+ type DepositPerByte = DepositPerByte;
+ type DepositPerItem = DepositPerItem;
+ type AddressGenerator = DefaultAddressGenerator;
+ type MaxCodeLen = ConstU32<{ 123 * 1024 }>;
+ type MaxStorageKeyLen = ConstU32<128>;
+ type UnsafeUnstableInterface = UnstableInterface;
+ type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>;
+}
+
+impl RegisteredChainExtension for SchedulerExtension {
+ const ID: u16 = 03;
+}
+
+parameter_types! {
+ pub static ExistentialDeposit: u64 = 1;
+}
+
+impl pallet_balances::Config for Test {
+ type MaxLocks = ();
+ type MaxReserves = ();
+ type ReserveIdentifier = [u8; 8];
+ type Balance = Balance;
+ type RuntimeEvent = RuntimeEvent;
+ type DustRemoval = ();
+ type ExistentialDeposit = ExistentialDeposit;
+ type AccountStore = System;
+ type WeightInfo = ();
+}
+
+impl pallet_timestamp::Config for Test {
+ type Moment = u64;
+ type OnTimestampSet = ();
+ type MinimumPeriod = ConstU64<1>;
+ type WeightInfo = ();
+}
+
+type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic;
+type Block = frame_system::mocking::MockBlock;
+
+frame_support::construct_runtime!(
+ pub enum Test where
+ Block = Block,
+ NodeBlock = Block,
+ UncheckedExtrinsic = UncheckedExtrinsic,
+ {
+ System: frame_system::{Pallet, Call, Config, Storage, Event},
+ Balances: pallet_balances::{Pallet, Call, Storage, Config, Event},
+ Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent},
+ Contracts: pallet_contracts::{Pallet, Call, Storage, Event},
+ Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event},
+ Preimage: pallet_preimage::{Pallet, Call, Storage, Event},
+ }
+);
+
+pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]);
+pub const GAS_LIMIT: Weight = Weight::from_ref_time(100_000_000_000).set_proof_size(700000u64);
+
+impl Convert> for Test {
+ fn convert(w: Weight) -> BalanceOf {
+ w.ref_time().into()
+ }
+}
+
+pub struct ExtBuilder {
+ existential_deposit: u64,
+}
+
+impl Default for ExtBuilder {
+ fn default() -> Self {
+ Self {
+ existential_deposit: ExistentialDeposit::get(),
+ }
+ }
+}
+
+impl ExtBuilder {
+ pub fn existential_deposit(mut self, existential_deposit: u64) -> Self {
+ self.existential_deposit = existential_deposit;
+ self
+ }
+ pub fn set_associated_consts(&self) {
+ EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit);
+ }
+ pub fn build(self) -> sp_io::TestExternalities {
+ use env_logger::{Builder, Env};
+ let env = Env::new().default_filter_or("runtime=debug");
+ let _ = Builder::from_env(env).is_test(true).try_init();
+ self.set_associated_consts();
+ let mut t = frame_system::GenesisConfig::default()
+ .build_storage::()
+ .unwrap();
+ pallet_balances::GenesisConfig:: { balances: vec![] }
+ .assimilate_storage(&mut t)
+ .unwrap();
+ let mut ext = sp_io::TestExternalities::new(t);
+ ext.register_extension(KeystoreExt(Arc::new(KeyStore::new())));
+ ext.execute_with(|| System::set_block_number(1));
+ ext
+ }
+}
+
+pub fn run_to_block(n: u32) {
+ while System::block_number() < n {
+ Scheduler::on_finalize(System::block_number());
+ System::set_block_number(System::block_number() + 1);
+ Scheduler::on_initialize(System::block_number());
+ }
+}
diff --git a/chain-extensions/pallet-scheduler/src/tests.rs b/chain-extensions/pallet-scheduler/src/tests.rs
new file mode 100644
index 00000000..1e9a54fe
--- /dev/null
+++ b/chain-extensions/pallet-scheduler/src/tests.rs
@@ -0,0 +1,196 @@
+// This file is part of Astar.
+
+// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd.
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+// Astar is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Astar is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Astar. If not, see .
+
+use crate::mock::{Test, *};
+use frame_support::assert_ok;
+use frame_support::traits::Currency;
+use pallet_contracts::Determinism;
+use pallet_contracts_primitives::{Code, ExecReturnValue};
+use pallet_scheduler::Agenda;
+use parity_scale_codec::Encode;
+use sp_runtime::{AccountId32, DispatchError};
+use std::fs;
+
+// Those tests use the contract scheduler_example avilable here:
+// https://github.com/swanky-dapps/chain-extension-contracts/blob/feature/scheduler/examples/scheduler/lib.rs
+// It stores a u32 value in storage (default value: 0):
+// #[ink(storage)]
+// #[derive(Default)]
+// pub struct Scheduler {
+// value: u32,
+// }
+//
+// and `schedule()` will schedule a call to `increase_value()` that will increment value by 10
+// #[ink(message)]
+// pub fn increase_value(&mut self) {
+// self.value += 10;
+// }
+
+#[test]
+fn schedule_call_works() {
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ let addr = instantiate();
+
+ // Schedule a call to `increase_value()` that will increment value by 10
+ // params
+ // when: BlockNumber = 5
+ // maybe_periodic: None
+ let result = schedule(addr.clone(), 5, None);
+ assert_ok!(result);
+
+ // Assert that the scheduled call is part of Scheduler Agenda
+ assert!(Agenda::::get(5).len() == 1);
+
+ // Run tu block 5 add assert value has been incremented
+ run_to_block(5);
+ let result = get_value(addr);
+ assert_eq!(result.data[1], 10);
+ });
+}
+
+#[test]
+fn schedule_periodic_call() {
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ let addr = instantiate();
+
+ // Schedule a call to `increase_value()` that will increment value by 10
+ // params
+ // when: BlockNumber = 5
+ // maybe_periodic: every 2 blocks, 3 times
+ let result = schedule(addr.clone(), 5, Some((2, 3)));
+ assert_ok!(result);
+
+ // Assert that the scheduled call is part of Scheduler Agenda
+ assert!(Agenda::::get(5).len() == 1);
+
+ // Run tu block 5 add assert value has been incremented
+ run_to_block(5);
+ let result = get_value(addr.clone());
+ assert_eq!(result.data[1], 10);
+
+ // Run tu block 7 add assert value has been incremented
+ run_to_block(7);
+ let result = get_value(addr.clone());
+ assert_eq!(result.data[1], 20);
+
+ // Run tu block 7 add assert value has been incremented
+ run_to_block(9);
+ let result = get_value(addr.clone());
+ assert_eq!(result.data[1], 30);
+ });
+}
+
+#[test]
+fn cancel_call() {
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ let addr = instantiate();
+
+ // Schedule a call to `increase_value()` that will increment value by 10
+ // params
+ // when: BlockNumber = 5
+ // maybe_periodic: None
+ let result = schedule(addr.clone(), 5, None);
+ assert_ok!(result);
+
+ // Assert that the scheduled call is part of Scheduler Agenda
+ assert!(Agenda::::get(5).len() == 1);
+
+ // Cancel the call
+ // params
+ // when: BlockNumber = 5
+ // index: 0
+ let result = cancel(addr.clone(), 5, 0);
+ assert_ok!(result);
+
+ // Run tu block 5 add assert value has not been incremented
+ run_to_block(5);
+ let result = get_value(addr.clone());
+ assert_eq!(result.data[1], 0);
+ });
+}
+
+fn instantiate() -> AccountId32 {
+ let code =
+ fs::read("./test-contract/scheduler_example.wasm").expect("could not read .wasm file");
+ let min_balance = ::Currency::minimum_balance();
+ let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance);
+ let instance_selector: Vec = [0x9b, 0xae, 0x9d, 0x5e].to_vec();
+ Contracts::bare_instantiate(
+ ALICE,
+ 0,
+ GAS_LIMIT,
+ None,
+ Code::Upload(code),
+ instance_selector,
+ vec![],
+ false,
+ )
+ .result
+ .unwrap()
+ .account_id
+}
+
+fn schedule(
+ addr: AccountId32,
+ when: u32,
+ maybe_periodic: Option<(u32, u32)>,
+) -> Result {
+ let mut data = Vec::new();
+ data.append(&mut [0x9d, 0xb8, 0x31, 0x96].to_vec());
+ data.append(&mut when.encode());
+ data.append(&mut maybe_periodic.encode());
+
+ do_bare_call(addr, data)
+}
+
+fn cancel(addr: AccountId32, when: u32, index: u32) -> Result {
+ let mut data = Vec::new();
+ data.append(&mut [0x97, 0x96, 0xe9, 0xa7].to_vec());
+ data.append(&mut when.encode());
+ data.append(&mut index.encode());
+
+ do_bare_call(addr, data)
+}
+
+fn get_value(addr: AccountId32) -> ExecReturnValue {
+ let selector = [0xca, 0x6f, 0x21, 0x70].to_vec();
+ do_bare_call(addr, selector).unwrap()
+}
+
+fn do_bare_call(addr: AccountId32, input: Vec) -> Result {
+ Contracts::bare_call(
+ ALICE,
+ addr.into(),
+ 0,
+ GAS_LIMIT,
+ None,
+ input,
+ false,
+ Determinism::Deterministic,
+ )
+ .result
+}
diff --git a/chain-extensions/pallet-scheduler/src/weights.rs b/chain-extensions/pallet-scheduler/src/weights.rs
new file mode 100644
index 00000000..cd46fd40
--- /dev/null
+++ b/chain-extensions/pallet-scheduler/src/weights.rs
@@ -0,0 +1,37 @@
+// This file is part of Astar.
+
+// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd.
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+// Astar is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Astar is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Astar. If not, see .
+
+#![cfg_attr(rustfmt, rustfmt_skip)]
+#![allow(unused_parens)]
+#![allow(unused_imports)]
+
+use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
+use sp_std::marker::PhantomData;
+
+/// Weight functions needed for pallet-scheduler chain-extension.
+pub trait WeightInfo {
+ fn read_as_unbounded(n: u32) -> Weight;
+}
+
+/// Weights for pallet-scheduler chain-extension
+pub struct SubstrateWeight(PhantomData);
+impl WeightInfo for SubstrateWeight {
+ fn read_as_unbounded(n: u32) -> Weight {
+ Weight::from_ref_time(1_000).saturating_mul(n.into())
+ }
+}
\ No newline at end of file
diff --git a/chain-extensions/pallet-scheduler/test-contract/scheduler_example.json b/chain-extensions/pallet-scheduler/test-contract/scheduler_example.json
new file mode 100644
index 00000000..399a2f66
--- /dev/null
+++ b/chain-extensions/pallet-scheduler/test-contract/scheduler_example.json
@@ -0,0 +1,620 @@
+{
+ "source": {
+ "hash": "0x6ce7d36cc6cb4d0908b5aa096df9d2f8896ad976df2c1872019859f78a99bc26",
+ "language": "ink! 4.2.0",
+ "compiler": "rustc 1.69.0-nightly",
+ "build_info": {
+ "build_mode": "Debug",
+ "cargo_contract_version": "2.1.0",
+ "rust_toolchain": "nightly-x86_64-unknown-linux-gnu",
+ "wasm_opt_settings": {
+ "keep_debug_symbols": false,
+ "optimization_passes": "Z"
+ }
+ }
+ },
+ "contract": {
+ "name": "scheduler_example",
+ "version": "0.1.0",
+ "authors": [
+ "Stake Technologies "
+ ]
+ },
+ "spec": {
+ "constructors": [
+ {
+ "args": [],
+ "default": false,
+ "docs": [],
+ "label": "new",
+ "payable": false,
+ "returnType": {
+ "displayName": [
+ "ink_primitives",
+ "ConstructorResult"
+ ],
+ "type": 1
+ },
+ "selector": "0x9bae9d5e"
+ }
+ ],
+ "docs": [],
+ "environment": {
+ "accountId": {
+ "displayName": [
+ "AccountId"
+ ],
+ "type": 10
+ },
+ "balance": {
+ "displayName": [
+ "Balance"
+ ],
+ "type": 13
+ },
+ "blockNumber": {
+ "displayName": [
+ "BlockNumber"
+ ],
+ "type": 0
+ },
+ "chainExtension": {
+ "displayName": [
+ "ChainExtension"
+ ],
+ "type": 16
+ },
+ "hash": {
+ "displayName": [
+ "Hash"
+ ],
+ "type": 14
+ },
+ "maxEventTopics": 4,
+ "timestamp": {
+ "displayName": [
+ "Timestamp"
+ ],
+ "type": 15
+ }
+ },
+ "events": [],
+ "lang_error": {
+ "displayName": [
+ "ink",
+ "LangError"
+ ],
+ "type": 3
+ },
+ "messages": [
+ {
+ "args": [
+ {
+ "label": "when",
+ "type": {
+ "displayName": [
+ "BlockNumber"
+ ],
+ "type": 0
+ }
+ },
+ {
+ "label": "maybe_periodic",
+ "type": {
+ "displayName": [
+ "Option"
+ ],
+ "type": 4
+ }
+ }
+ ],
+ "default": false,
+ "docs": [],
+ "label": "schedule",
+ "mutates": true,
+ "payable": false,
+ "returnType": {
+ "displayName": [
+ "ink",
+ "MessageResult"
+ ],
+ "type": 6
+ },
+ "selector": "0x9db83196"
+ },
+ {
+ "args": [
+ {
+ "label": "when",
+ "type": {
+ "displayName": [
+ "BlockNumber"
+ ],
+ "type": 0
+ }
+ },
+ {
+ "label": "index",
+ "type": {
+ "displayName": [
+ "u32"
+ ],
+ "type": 0
+ }
+ }
+ ],
+ "default": false,
+ "docs": [],
+ "label": "cancel",
+ "mutates": true,
+ "payable": false,
+ "returnType": {
+ "displayName": [
+ "ink",
+ "MessageResult"
+ ],
+ "type": 6
+ },
+ "selector": "0x9796e9a7"
+ },
+ {
+ "args": [],
+ "default": false,
+ "docs": [],
+ "label": "increase_value",
+ "mutates": true,
+ "payable": false,
+ "returnType": {
+ "displayName": [
+ "ink",
+ "MessageResult"
+ ],
+ "type": 1
+ },
+ "selector": "0xf8af079d"
+ },
+ {
+ "args": [],
+ "default": false,
+ "docs": [],
+ "label": "get_value",
+ "mutates": false,
+ "payable": false,
+ "returnType": {
+ "displayName": [
+ "ink",
+ "MessageResult"
+ ],
+ "type": 9
+ },
+ "selector": "0xca6f2170"
+ },
+ {
+ "args": [],
+ "default": false,
+ "docs": [],
+ "label": "get_block_number",
+ "mutates": false,
+ "payable": false,
+ "returnType": {
+ "displayName": [
+ "ink",
+ "MessageResult"
+ ],
+ "type": 9
+ },
+ "selector": "0xf37053d6"
+ }
+ ]
+ },
+ "storage": {
+ "root": {
+ "layout": {
+ "struct": {
+ "fields": [
+ {
+ "layout": {
+ "leaf": {
+ "key": "0x00000000",
+ "ty": 0
+ }
+ },
+ "name": "value"
+ }
+ ],
+ "name": "Scheduler"
+ }
+ },
+ "root_key": "0x00000000"
+ }
+ },
+ "types": [
+ {
+ "id": 0,
+ "type": {
+ "def": {
+ "primitive": "u32"
+ }
+ }
+ },
+ {
+ "id": 1,
+ "type": {
+ "def": {
+ "variant": {
+ "variants": [
+ {
+ "fields": [
+ {
+ "type": 2
+ }
+ ],
+ "index": 0,
+ "name": "Ok"
+ },
+ {
+ "fields": [
+ {
+ "type": 3
+ }
+ ],
+ "index": 1,
+ "name": "Err"
+ }
+ ]
+ }
+ },
+ "params": [
+ {
+ "name": "T",
+ "type": 2
+ },
+ {
+ "name": "E",
+ "type": 3
+ }
+ ],
+ "path": [
+ "Result"
+ ]
+ }
+ },
+ {
+ "id": 2,
+ "type": {
+ "def": {
+ "tuple": []
+ }
+ }
+ },
+ {
+ "id": 3,
+ "type": {
+ "def": {
+ "variant": {
+ "variants": [
+ {
+ "index": 1,
+ "name": "CouldNotReadInput"
+ }
+ ]
+ }
+ },
+ "path": [
+ "ink_primitives",
+ "LangError"
+ ]
+ }
+ },
+ {
+ "id": 4,
+ "type": {
+ "def": {
+ "variant": {
+ "variants": [
+ {
+ "index": 0,
+ "name": "None"
+ },
+ {
+ "fields": [
+ {
+ "type": 5
+ }
+ ],
+ "index": 1,
+ "name": "Some"
+ }
+ ]
+ }
+ },
+ "params": [
+ {
+ "name": "T",
+ "type": 5
+ }
+ ],
+ "path": [
+ "Option"
+ ]
+ }
+ },
+ {
+ "id": 5,
+ "type": {
+ "def": {
+ "tuple": [
+ 0,
+ 0
+ ]
+ }
+ }
+ },
+ {
+ "id": 6,
+ "type": {
+ "def": {
+ "variant": {
+ "variants": [
+ {
+ "fields": [
+ {
+ "type": 7
+ }
+ ],
+ "index": 0,
+ "name": "Ok"
+ },
+ {
+ "fields": [
+ {
+ "type": 3
+ }
+ ],
+ "index": 1,
+ "name": "Err"
+ }
+ ]
+ }
+ },
+ "params": [
+ {
+ "name": "T",
+ "type": 7
+ },
+ {
+ "name": "E",
+ "type": 3
+ }
+ ],
+ "path": [
+ "Result"
+ ]
+ }
+ },
+ {
+ "id": 7,
+ "type": {
+ "def": {
+ "variant": {
+ "variants": [
+ {
+ "fields": [
+ {
+ "type": 2
+ }
+ ],
+ "index": 0,
+ "name": "Ok"
+ },
+ {
+ "fields": [
+ {
+ "type": 8
+ }
+ ],
+ "index": 1,
+ "name": "Err"
+ }
+ ]
+ }
+ },
+ "params": [
+ {
+ "name": "T",
+ "type": 2
+ },
+ {
+ "name": "E",
+ "type": 8
+ }
+ ],
+ "path": [
+ "Result"
+ ]
+ }
+ },
+ {
+ "id": 8,
+ "type": {
+ "def": {
+ "variant": {
+ "variants": [
+ {
+ "index": 1,
+ "name": "FailedToSchedule"
+ },
+ {
+ "index": 2,
+ "name": "NotFound"
+ },
+ {
+ "index": 3,
+ "name": "TargetBlockNumberInPast"
+ },
+ {
+ "index": 4,
+ "name": "RescheduleNoChange"
+ },
+ {
+ "index": 5,
+ "name": "Named"
+ },
+ {
+ "index": 98,
+ "name": "OriginCannotBeCaller"
+ },
+ {
+ "index": 99,
+ "name": "RuntimeError"
+ },
+ {
+ "index": 7,
+ "name": "UnknownStatusCode"
+ },
+ {
+ "index": 8,
+ "name": "InvalidScaleEncoding"
+ }
+ ]
+ }
+ },
+ "path": [
+ "scheduler_extension",
+ "SchedulerError"
+ ]
+ }
+ },
+ {
+ "id": 9,
+ "type": {
+ "def": {
+ "variant": {
+ "variants": [
+ {
+ "fields": [
+ {
+ "type": 0
+ }
+ ],
+ "index": 0,
+ "name": "Ok"
+ },
+ {
+ "fields": [
+ {
+ "type": 3
+ }
+ ],
+ "index": 1,
+ "name": "Err"
+ }
+ ]
+ }
+ },
+ "params": [
+ {
+ "name": "T",
+ "type": 0
+ },
+ {
+ "name": "E",
+ "type": 3
+ }
+ ],
+ "path": [
+ "Result"
+ ]
+ }
+ },
+ {
+ "id": 10,
+ "type": {
+ "def": {
+ "composite": {
+ "fields": [
+ {
+ "type": 11,
+ "typeName": "[u8; 32]"
+ }
+ ]
+ }
+ },
+ "path": [
+ "ink_primitives",
+ "types",
+ "AccountId"
+ ]
+ }
+ },
+ {
+ "id": 11,
+ "type": {
+ "def": {
+ "array": {
+ "len": 32,
+ "type": 12
+ }
+ }
+ }
+ },
+ {
+ "id": 12,
+ "type": {
+ "def": {
+ "primitive": "u8"
+ }
+ }
+ },
+ {
+ "id": 13,
+ "type": {
+ "def": {
+ "primitive": "u128"
+ }
+ }
+ },
+ {
+ "id": 14,
+ "type": {
+ "def": {
+ "composite": {
+ "fields": [
+ {
+ "type": 11,
+ "typeName": "[u8; 32]"
+ }
+ ]
+ }
+ },
+ "path": [
+ "ink_primitives",
+ "types",
+ "Hash"
+ ]
+ }
+ },
+ {
+ "id": 15,
+ "type": {
+ "def": {
+ "primitive": "u64"
+ }
+ }
+ },
+ {
+ "id": 16,
+ "type": {
+ "def": {
+ "variant": {}
+ },
+ "path": [
+ "ink_env",
+ "types",
+ "NoChainExtension"
+ ]
+ }
+ }
+ ],
+ "version": "4"
+}
\ No newline at end of file
diff --git a/chain-extensions/pallet-scheduler/test-contract/scheduler_example.wasm b/chain-extensions/pallet-scheduler/test-contract/scheduler_example.wasm
new file mode 100644
index 00000000..7f840101
Binary files /dev/null and b/chain-extensions/pallet-scheduler/test-contract/scheduler_example.wasm differ
diff --git a/chain-extensions/types/scheduler/Cargo.toml b/chain-extensions/types/scheduler/Cargo.toml
new file mode 100644
index 00000000..f0acda94
--- /dev/null
+++ b/chain-extensions/types/scheduler/Cargo.toml
@@ -0,0 +1,27 @@
+[package]
+name = "scheduler-chain-extension-types"
+version = "0.1.0"
+license = "Apache-2.0"
+description = "Types definitions for scheduler chain-extension"
+authors.workspace = true
+edition.workspace = true
+homepage.workspace = true
+repository.workspace = true
+
+[dependencies]
+parity-scale-codec = { workspace = true }
+scale-info = { workspace = true }
+
+frame-system = { workspace = true }
+pallet-contracts = { workspace = true }
+sp-runtime = { workspace = true }
+
+[features]
+default = ["std"]
+std = [
+ "scale-info/std",
+ "parity-scale-codec/std",
+ "pallet-contracts/std",
+ "sp-runtime/std",
+ "frame-system/std",
+]
diff --git a/chain-extensions/types/scheduler/src/lib.rs b/chain-extensions/types/scheduler/src/lib.rs
new file mode 100644
index 00000000..a4e7eea7
--- /dev/null
+++ b/chain-extensions/types/scheduler/src/lib.rs
@@ -0,0 +1,95 @@
+// This file is part of Astar.
+
+// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd.
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+// Astar is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Astar is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Astar. If not, see .
+
+#![cfg_attr(not(feature = "std"), no_std)]
+use parity_scale_codec::MaxEncodedLen;
+use parity_scale_codec::{Decode, Encode};
+use scale_info::prelude::vec::Vec;
+use sp_runtime::{DispatchError, ModuleError};
+
+#[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, Debug)]
+#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
+pub enum Outcome {
+ /// Success
+ Success = 0,
+ /// Failed to schedule a call
+ FailedToSchedule = 1,
+ /// Cannot find the scheduled call.
+ NotFound = 2,
+ /// Given target block number is in the past.
+ TargetBlockNumberInPast = 3,
+ /// Reschedule failed because it does not change scheduled time.
+ RescheduleNoChange = 4,
+ /// Attempt to use a non-named function on a named task.
+ Named = 5,
+ /// Origin Caller is not supported
+ OriginCannotBeCaller = 98,
+ /// Unknown error
+ RuntimeError = 99,
+}
+
+impl From for Outcome {
+ fn from(input: DispatchError) -> Self {
+ let error_text = match input {
+ DispatchError::Module(ModuleError { message, .. }) => message,
+ _ => Some("No module error Info"),
+ };
+ return match error_text {
+ Some("FailedToSchedule") => Outcome::FailedToSchedule,
+ Some("NotFound") => Outcome::NotFound,
+ Some("TargetBlockNumberInPast") => Outcome::TargetBlockNumberInPast,
+ Some("RescheduleNoChange") => Outcome::RescheduleNoChange,
+ Some("Named") => Outcome::Named,
+ _ => Outcome::RuntimeError,
+ };
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)]
+#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
+pub struct ContractCallInput {
+ pub dest: AccountId,
+ pub data: Vec,
+ pub gas_limit: (u64, u64),
+ pub storage_deposit_limit: Option,
+ pub value: Balance,
+ pub max_weight: u64,
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Encode, Decode, MaxEncodedLen)]
+#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
+pub enum Origin {
+ Caller,
+ Address,
+}
+
+impl Default for Origin {
+ fn default() -> Self {
+ Self::Address
+ }
+}
+
+#[macro_export]
+macro_rules! select_origin {
+ ($origin:expr, $account:expr) => {
+ match $origin {
+ Origin::Caller => return Ok(RetVal::Converging(Outcome::OriginCannotBeCaller as u32)),
+ Origin::Address => RawOrigin::Signed($account),
+ }
+ };
+}