diff --git a/substrate/frame/staking-rewards/src/lib.rs b/substrate/frame/staking-rewards/src/lib.rs
index 091de5a3f6351049b2ae13e12a38fd0773efdbf8..7e1fbfa4644173f0114b9f05b9eb7afc8c181f68 100644
--- a/substrate/frame/staking-rewards/src/lib.rs
+++ b/substrate/frame/staking-rewards/src/lib.rs
@@ -68,6 +68,11 @@ use sp_core::Get;
 use sp_runtime::DispatchError;
 use sp_std::boxed::Box;
 
+#[cfg(test)]
+mod mock;
+#[cfg(test)]
+mod tests;
+
 /// The type of the unique id for each pool.
 pub type PoolId = u32;
 
@@ -80,7 +85,7 @@ pub struct PoolStakerInfo<Balance> {
 }
 
 /// A staking pool.
-#[derive(Decode, Encode, Default, PartialEq, Eq, MaxEncodedLen, TypeInfo)]
+#[derive(Debug, Decode, Encode, Default, PartialEq, Eq, MaxEncodedLen, TypeInfo)]
 pub struct PoolInfo<AccountId, AssetId, Balance, BlockNumber> {
 	/// The asset that is staked in this pool.
 	staking_asset_id: AssetId,
@@ -103,7 +108,7 @@ pub mod pallet {
 	use super::*;
 	use frame_support::{pallet_prelude::*, traits::tokens::AssetId};
 	use frame_system::pallet_prelude::*;
-	use sp_runtime::traits::AccountIdConversion;
+	use sp_runtime::traits::{AccountIdConversion, Saturating};
 
 	#[pallet::pallet]
 	pub struct Pallet<T>(_);
@@ -158,7 +163,7 @@ pub mod pallet {
 	///
 	/// Incremented when a new pool is created.
 	#[pallet::storage]
-	pub type NextPoolId<T: Config> = StorageValue<_, PoolId>;
+	pub type NextPoolId<T: Config> = StorageValue<_, PoolId, ValueQuery>;
 
 	#[pallet::event]
 	#[pallet::generate_deposit(pub(super) fn deposit_event)]
@@ -166,7 +171,7 @@ pub mod pallet {
 		/// An account staked some tokens in a pool.
 		Staked {
 			/// The account that staked assets.
-			staker: T::AccountId,
+			who: T::AccountId,
 			/// The pool.
 			pool_id: PoolId,
 			/// The staked asset amount.
@@ -175,7 +180,7 @@ pub mod pallet {
 		/// An account unstaked some tokens from a pool.
 		Unstaked {
 			/// The account that unstaked assets.
-			staker: T::AccountId,
+			who: T::AccountId,
 			/// The pool.
 			pool_id: PoolId,
 			/// The unstaked asset amount.
@@ -183,6 +188,8 @@ pub mod pallet {
 		},
 		/// An account harvested some rewards.
 		RewardsHarvested {
+			/// The extrinsic caller.
+			who: T::AccountId,
 			/// The staker whos rewards were harvested.
 			staker: T::AccountId,
 			/// The pool.
@@ -192,6 +199,8 @@ pub mod pallet {
 		},
 		/// A new reward pool was created.
 		PoolCreated {
+			/// The account that created the pool.
+			creator: T::AccountId,
 			/// Unique ID for the new pool.
 			pool_id: PoolId,
 			/// The staking asset.
@@ -200,26 +209,21 @@ pub mod pallet {
 			reward_asset_id: T::AssetId,
 			/// The initial reward rate per block.
 			reward_rate_per_block: T::Balance,
+			/// The account allowed to modify the pool.
+			admin: T::AccountId,
 		},
-		/// A reward pool was deleted.
+		/// A reward pool was deleted by the admin.
 		PoolDeleted {
 			/// The deleted pool id.
 			pool_id: PoolId,
 		},
-		/// A pool was modified.
+		/// A pool was modified by the admin.
 		PoolModifed {
 			/// The modified pool.
 			pool_id: PoolId,
 			/// The new reward rate.
 			new_reward_rate_per_block: T::Balance,
 		},
-		/// Reward assets were withdrawn from a pool.
-		RewardPoolWithdrawal {
-			/// The affected pool.
-			pool_id: PoolId,
-			/// The acount of reward asset withdrawn.
-			amount: T::Balance,
-		},
 	}
 
 	#[pallet::error]
@@ -231,7 +235,7 @@ pub mod pallet {
 	#[pallet::hooks]
 	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
 		fn integrity_test() {
-			todo!()
+			// TODO: Proper implementation
 		}
 	}
 
@@ -243,12 +247,49 @@ pub mod pallet {
 	impl<T: Config> Pallet<T> {
 		/// Create a new reward pool.
 		pub fn create_pool(
-			_origin: OriginFor<T>,
-			_staked_asset_id: Box<T::AssetId>,
-			_reward_asset_id: Box<T::AssetId>,
-			_admin: Option<T::AccountId>,
+			origin: OriginFor<T>,
+			staked_asset_id: Box<T::AssetId>,
+			reward_asset_id: Box<T::AssetId>,
+			reward_rate_per_block: T::Balance,
+			admin: Option<T::AccountId>,
 		) -> DispatchResult {
-			todo!()
+			// Ensure Origin is allowed to create pools.
+			T::PermissionedPoolCreator::ensure_origin(origin.clone())?;
+
+			// Get the admin, or try to use the origin as admin.
+			let origin_acc_id = ensure_signed(origin)?;
+			let admin = match admin {
+				Some(admin) => admin,
+				None => origin_acc_id,
+			};
+
+			// Create the pool.
+			let pool = PoolInfo::<T::AccountId, T::AssetId, T::Balance, BlockNumberFor<T>> {
+				staking_asset_id: *staked_asset_id.clone(),
+				reward_asset_id: *reward_asset_id.clone(),
+				reward_rate_per_block,
+				total_tokens_staked: 0u32.into(),
+				accumulated_rewards_per_share: 0u32.into(),
+				last_rewarded_block: 0u32.into(),
+				admin: admin.clone(),
+			};
+
+			// Insert the pool into storage.
+			let pool_id = NextPoolId::<T>::get();
+			Pools::<T>::insert(pool_id, pool);
+			NextPoolId::<T>::put(pool_id.saturating_add(1));
+
+			// Emit the event.
+			Self::deposit_event(Event::PoolCreated {
+				creator: origin_acc_id,
+				pool_id,
+				staking_asset_id: *staked_asset_id,
+				reward_asset_id: *reward_asset_id,
+				reward_rate_per_block,
+				admin,
+			});
+
+			Ok(())
 		}
 
 		/// Removes an existing reward pool.
diff --git a/substrate/frame/staking-rewards/src/mock.rs b/substrate/frame/staking-rewards/src/mock.rs
new file mode 100644
index 0000000000000000000000000000000000000000..658d1bb68426de33129ace5e13f86beb2b97af81
--- /dev/null
+++ b/substrate/frame/staking-rewards/src/mock.rs
@@ -0,0 +1,168 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Test environment for Staking Rewards pallet.
+
+use super::*;
+use crate as pallet_staking_rewards;
+use core::default::Default;
+use frame_support::{
+	construct_runtime, derive_impl,
+	instances::Instance1,
+	ord_parameter_types, parameter_types,
+	traits::{
+		tokens::fungible::{NativeFromLeft, NativeOrWithId, UnionOf},
+		AsEnsureOriginWithArg, ConstU128, ConstU32, EnsureOrigin,
+	},
+	PalletId,
+};
+use frame_system::{ensure_signed, EnsureSigned};
+use sp_runtime::{
+	traits::{AccountIdConversion, IdentityLookup},
+	BuildStorage,
+};
+
+type Block = frame_system::mocking::MockBlock<MockRuntime>;
+
+construct_runtime!(
+	pub enum MockRuntime
+	{
+		System: frame_system,
+		Balances: pallet_balances,
+		Assets: pallet_assets::<Instance1>,
+		StakingRewards: pallet_staking_rewards,
+	}
+);
+
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
+impl frame_system::Config for MockRuntime {
+	type AccountId = u128;
+	type Lookup = IdentityLookup<Self::AccountId>;
+	type Block = Block;
+	type AccountData = pallet_balances::AccountData<u128>;
+}
+
+impl pallet_balances::Config for MockRuntime {
+	type Balance = u128;
+	type DustRemoval = ();
+	type RuntimeEvent = RuntimeEvent;
+	type ExistentialDeposit = ConstU128<100>;
+	type AccountStore = System;
+	type WeightInfo = ();
+	type MaxLocks = ();
+	type MaxReserves = ConstU32<50>;
+	type ReserveIdentifier = [u8; 8];
+	type FreezeIdentifier = ();
+	type MaxFreezes = ();
+	type RuntimeHoldReason = ();
+	type RuntimeFreezeReason = ();
+}
+
+impl pallet_assets::Config<Instance1> for MockRuntime {
+	type RuntimeEvent = RuntimeEvent;
+	type Balance = u128;
+	type RemoveItemsLimit = ConstU32<1000>;
+	type AssetId = u32;
+	type AssetIdParameter = u32;
+	type Currency = Balances;
+	type CreateOrigin = AsEnsureOriginWithArg<EnsureSigned<Self::AccountId>>;
+	type ForceOrigin = frame_system::EnsureRoot<Self::AccountId>;
+	type AssetDeposit = ConstU128<1>;
+	type AssetAccountDeposit = ConstU128<10>;
+	type MetadataDepositBase = ConstU128<1>;
+	type MetadataDepositPerByte = ConstU128<1>;
+	type ApprovalDeposit = ConstU128<1>;
+	type StringLimit = ConstU32<50>;
+	type Freezer = ();
+	type Extra = ();
+	type WeightInfo = ();
+	type CallbackHandle = ();
+	pallet_assets::runtime_benchmarks_enabled! {
+		type BenchmarkHelper = ();
+	}
+}
+
+parameter_types! {
+	pub const StakingRewardsPalletId: PalletId = PalletId(*b"py/stkrd");
+	pub const Native: NativeOrWithId<u32> = NativeOrWithId::Native;
+	pub const PermissionedAccountId: u128 = 1;
+}
+ord_parameter_types! {
+	pub const AssetConversionOrigin: u128 = AccountIdConversion::<u128>::into_account_truncating(&StakingRewardsPalletId::get());
+}
+
+pub struct MockPermissionedPoolCreator;
+impl EnsureOrigin<RuntimeOrigin> for MockPermissionedPoolCreator {
+	type Success = ();
+
+	fn try_origin(
+		origin: RuntimeOrigin,
+		// key: &RuntimeParametersKey,
+	) -> Result<Self::Success, RuntimeOrigin> {
+		// Set account 1 to admin in tests
+		if ensure_signed(origin.clone()).map_or(false, |acc| acc == 1) {
+			return Ok(());
+		}
+
+		return Err(origin);
+	}
+
+	#[cfg(feature = "runtime-benchmarks")]
+	fn try_successful_origin() -> Result<O, ()> {
+		todo!()
+	}
+}
+
+pub type NativeAndAssets = UnionOf<Balances, Assets, NativeFromLeft, NativeOrWithId<u32>, u128>;
+
+impl Config for MockRuntime {
+	type RuntimeEvent = RuntimeEvent;
+	type AssetId = NativeOrWithId<u32>;
+	type Balance = <Self as pallet_balances::Config>::Balance;
+	type Assets = NativeAndAssets;
+	type PalletId = StakingRewardsPalletId;
+	// allow account id 1 to be permissioned creator
+	type PermissionedPoolCreator = MockPermissionedPoolCreator;
+}
+
+pub(crate) fn new_test_ext() -> sp_io::TestExternalities {
+	let mut t = frame_system::GenesisConfig::<MockRuntime>::default().build_storage().unwrap();
+
+	// pallet_assets::GenesisConfig::<MockRuntime, Instance1> {
+	// 	// Genesis assets: id, owner, is_sufficient, min_balance
+	// 	// pub assets: Vec<(T::AssetId, T::AccountId, bool, T::Balance)>,
+	// 	assets: vec![(1, 1, true, 10000)],
+	// 	// Genesis metadata: id, name, symbol, decimals
+	// 	// pub metadata: Vec<(T::AssetId, Vec<u8>, Vec<u8>, u8)>,
+	// 	metadata: vec![(1, b"test".to_vec(), b"TST".to_vec(), 18)],
+	// 	// Genesis accounts: id, account_id, balance
+	// 	// pub accounts: Vec<(T::AssetId, T::AccountId, T::Balance)>,
+	// 	accounts: vec![(1, 1, 10000)],
+	// }
+	// .assimilate_storage(&mut t)
+	// .unwrap();
+
+	pallet_balances::GenesisConfig::<MockRuntime> {
+		balances: vec![(1, 10000), (2, 20000), (3, 30000), (4, 40000)],
+	}
+	.assimilate_storage(&mut t)
+	.unwrap();
+
+	let mut ext = sp_io::TestExternalities::new(t);
+	ext.execute_with(|| System::set_block_number(1));
+	ext
+}
diff --git a/substrate/frame/staking-rewards/src/tests.rs b/substrate/frame/staking-rewards/src/tests.rs
new file mode 100644
index 0000000000000000000000000000000000000000..18e1f86b516b07d203b47271d30e4e83f4f458e7
--- /dev/null
+++ b/substrate/frame/staking-rewards/src/tests.rs
@@ -0,0 +1,163 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::{mock::*, *};
+use frame_support::{assert_ok, traits::fungible::NativeOrWithId};
+
+fn create_tokens(owner: u128, tokens: Vec<NativeOrWithId<u32>>) {
+	create_tokens_with_ed(owner, tokens, 1)
+}
+
+fn create_tokens_with_ed(owner: u128, tokens: Vec<NativeOrWithId<u32>>, ed: u128) {
+	for token_id in tokens {
+		let asset_id = match token_id {
+			NativeOrWithId::WithId(id) => id,
+			_ => unreachable!("invalid token"),
+		};
+		assert_ok!(Assets::force_create(RuntimeOrigin::root(), asset_id, owner, false, ed));
+	}
+}
+
+fn events() -> Vec<Event<MockRuntime>> {
+	let result = System::events()
+		.into_iter()
+		.map(|r| r.event)
+		.filter_map(|e| {
+			if let mock::RuntimeEvent::StakingRewards(inner) = e {
+				Some(inner)
+			} else {
+				None
+			}
+		})
+		.collect();
+
+	System::reset_events();
+
+	result
+}
+
+fn pools() -> Vec<(u32, PoolInfo<u128, NativeOrWithId<u32>, u128, u64>)> {
+	Pools::<MockRuntime>::iter().collect()
+}
+
+#[test]
+fn create_pool_works() {
+	new_test_ext().execute_with(|| {
+		// Setup
+		let user = 1;
+		let staking_asset_id = NativeOrWithId::<u32>::Native;
+		let reward_asset_id = NativeOrWithId::<u32>::WithId(1);
+		let reward_rate_per_block = 100;
+
+		create_tokens(user, vec![reward_asset_id.clone()]);
+		assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 1000));
+
+		// Create a pool with default admin.
+		assert_eq!(NextPoolId::<MockRuntime>::get(), 0);
+		assert_ok!(StakingRewards::create_pool(
+			RuntimeOrigin::signed(user),
+			Box::new(staking_asset_id.clone()),
+			Box::new(reward_asset_id.clone()),
+			reward_rate_per_block,
+			None
+		));
+
+		// Event is emitted.
+		assert_eq!(
+			events(),
+			[Event::<MockRuntime>::PoolCreated {
+				pool_id: 0,
+				staking_asset_id: staking_asset_id.clone(),
+				reward_asset_id: reward_asset_id.clone(),
+				reward_rate_per_block,
+				admin: user,
+			}]
+		);
+
+		// State is updated correctly.
+		assert_eq!(NextPoolId::<MockRuntime>::get(), 1);
+		assert_eq!(
+			pools(),
+			vec![(
+				0,
+				PoolInfo {
+					staking_asset_id: staking_asset_id.clone(),
+					reward_asset_id: reward_asset_id.clone(),
+					reward_rate_per_block,
+					admin: user,
+					total_tokens_staked: 0,
+					accumulated_rewards_per_share: 0,
+					last_rewarded_block: 0
+				}
+			)]
+		);
+
+		// Create another pool with explicit admin.
+		let admin = 2;
+		assert_ok!(StakingRewards::create_pool(
+			RuntimeOrigin::signed(user),
+			Box::new(staking_asset_id.clone()),
+			Box::new(reward_asset_id.clone()),
+			reward_rate_per_block,
+			Some(admin)
+		));
+
+		// Event is emitted.
+		assert_eq!(
+			events(),
+			[Event::<MockRuntime>::PoolCreated {
+				pool_id: 1,
+				staking_asset_id: staking_asset_id.clone(),
+				reward_asset_id: reward_asset_id.clone(),
+				reward_rate_per_block,
+				admin,
+			}]
+		);
+
+		// State is updated correctly.
+		assert_eq!(NextPoolId::<MockRuntime>::get(), 2);
+		assert_eq!(
+			pools(),
+			vec![
+				(
+					0,
+					PoolInfo {
+						staking_asset_id: staking_asset_id.clone(),
+						reward_asset_id: reward_asset_id.clone(),
+						reward_rate_per_block,
+						admin: user,
+						total_tokens_staked: 0,
+						accumulated_rewards_per_share: 0,
+						last_rewarded_block: 0
+					}
+				),
+				(
+					1,
+					PoolInfo {
+						staking_asset_id,
+						reward_asset_id,
+						reward_rate_per_block,
+						admin,
+						total_tokens_staked: 0,
+						accumulated_rewards_per_share: 0,
+						last_rewarded_block: 0
+					}
+				)
+			]
+		);
+	});
+}