diff --git a/substrate/frame/staking-rewards/src/lib.rs b/substrate/frame/staking-rewards/src/lib.rs
index b74d4b50eb4635f3126f42c7935252eb3794f174..1883b1dffba04535c8ad93cf55e6c8c47f906d80 100644
--- a/substrate/frame/staking-rewards/src/lib.rs
+++ b/substrate/frame/staking-rewards/src/lib.rs
@@ -238,6 +238,8 @@ pub mod pallet {
 
 	#[pallet::error]
 	pub enum Error<T> {
+		/// The staker does not have enough tokens to perform the operation.
+		NotEnoughTokens,
 		/// An operation was attempted on a non-existent pool.
 		NonExistentPool,
 		/// An operation was attempted using a non-existent asset.
@@ -357,16 +359,21 @@ pub mod pallet {
 			// Always start by updating the pool rewards.
 			Self::update_pool_rewards(&pool_id, &caller)?;
 
+			// Check the staker has enough staked tokens.
+			let mut staker = PoolStakers::<T>::get(pool_id, &caller).unwrap_or_default();
+			ensure!(staker.amount >= amount, Error::<T>::NotEnoughTokens);
+
 			// Unfreeze staker assets.
 			// TODO: (blocked https://github.com/paritytech/polkadot-sdk/issues/3342)
 
 			// Update Pools.
 			let mut pool = Pools::<T>::get(pool_id).ok_or(Error::<T>::NonExistentPool)?;
 			pool.total_tokens_staked.saturating_reduce(amount);
+			Pools::<T>::insert(pool_id, pool);
 
 			// Update PoolStakers.
-			let mut staker = PoolStakers::<T>::get(pool_id, &caller).unwrap_or_default();
 			staker.amount.saturating_reduce(amount);
+			PoolStakers::<T>::insert(pool_id, &caller, staker);
 
 			Ok(())
 		}
diff --git a/substrate/frame/staking-rewards/src/tests.rs b/substrate/frame/staking-rewards/src/tests.rs
index 1494f1c776b450c6b459e1c348713c1e54314945..ddcc8b5d29672be5120d5c96ded8d2d0a71d2123 100644
--- a/substrate/frame/staking-rewards/src/tests.rs
+++ b/substrate/frame/staking-rewards/src/tests.rs
@@ -55,6 +55,8 @@ fn pools() -> Vec<(u32, PoolInfo<u128, NativeOrWithId<u32>, u128, u64>)> {
 }
 
 mod create_pool {
+	use sp_runtime::traits::BadOrigin;
+
 	use super::*;
 
 	#[test]
@@ -168,7 +170,7 @@ mod create_pool {
 	}
 
 	#[test]
-	fn non_existent_asset_fails() {
+	fn fails_for_non_existent_asset() {
 		new_test_ext().execute_with(|| {
 			let valid_asset = NativeOrWithId::<u32>::WithId(1);
 			let invalid_asset = NativeOrWithId::<u32>::WithId(200);
@@ -207,6 +209,27 @@ mod create_pool {
 			);
 		})
 	}
+
+	#[test]
+	fn fails_for_not_admin() {
+		new_test_ext().execute_with(|| {
+			let user = 100;
+			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_err!(
+				StakingRewards::create_pool(
+					RuntimeOrigin::signed(user),
+					Box::new(staking_asset_id.clone()),
+					Box::new(reward_asset_id.clone()),
+					reward_rate_per_block,
+					Some(999)
+				),
+				BadOrigin
+			);
+		});
+	}
 }
 
 mod stake {
@@ -220,7 +243,6 @@ mod stake {
 			let staking_asset_id = NativeOrWithId::<u32>::WithId(1);
 			let reward_asset_id = NativeOrWithId::<u32>::Native;
 			let reward_rate_per_block = 100;
-
 			create_tokens(user, vec![staking_asset_id.clone()]);
 
 			assert_ok!(StakingRewards::create_pool(
@@ -258,17 +280,14 @@ mod stake {
 	}
 
 	#[test]
-	fn non_existent_pool() {
+	fn fails_for_non_existent_pool() {
 		new_test_ext().execute_with(|| {
-			// Setup
 			let user = 1;
 			let staking_asset_id = NativeOrWithId::<u32>::WithId(1);
-
 			create_tokens(user, vec![staking_asset_id.clone()]);
 
 			let non_existent_pool_id = 999;
 
-			// User tries to stake tokens in a non-existent pool
 			assert_err!(
 				StakingRewards::stake(RuntimeOrigin::signed(user), non_existent_pool_id, 1000),
 				Error::<MockRuntime>::NonExistentPool
@@ -277,7 +296,101 @@ mod stake {
 	}
 
 	#[test]
-	fn insufficient_balance() {
+	fn fails_for_insufficient_balance() {
 		// TODO: When we're able to freeze assets.
 	}
 }
+
+mod unstake {
+	use super::*;
+
+	#[test]
+	fn success() {
+		new_test_ext().execute_with(|| {
+			// Setup
+			let user = 1;
+			let staking_asset_id = NativeOrWithId::<u32>::WithId(1);
+			let reward_asset_id = NativeOrWithId::<u32>::WithId(2);
+			let reward_rate_per_block = 100;
+			create_tokens(user, vec![staking_asset_id.clone(), reward_asset_id.clone()]);
+
+			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
+			));
+
+			let pool_id = 0;
+
+			// User stakes tokens
+			assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(user), pool_id, 1000));
+
+			// User unstakes tokens
+			assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(user), pool_id, 500));
+
+			// Check that the user's staked amount is updated
+			assert_eq!(PoolStakers::<MockRuntime>::get(pool_id, user).unwrap().amount, 500);
+
+			// Check that the pool's total tokens staked is updated
+			assert_eq!(Pools::<MockRuntime>::get(pool_id).unwrap().total_tokens_staked, 500);
+
+			// User unstakes remaining tokens
+			assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(user), pool_id, 500));
+
+			// Check that the user's staked amount is zero
+			assert_eq!(PoolStakers::<MockRuntime>::get(pool_id, user).unwrap().amount, 0);
+
+			// Check that the pool's total tokens staked is zero
+			assert_eq!(Pools::<MockRuntime>::get(pool_id).unwrap().total_tokens_staked, 0);
+		});
+	}
+
+	#[test]
+	fn fails_for_non_existent_pool() {
+		new_test_ext().execute_with(|| {
+			// Setup
+			let user = 1;
+			let non_existent_pool_id = 999;
+
+			// User tries to unstake tokens from a non-existent pool
+			assert_err!(
+				StakingRewards::unstake(RuntimeOrigin::signed(user), non_existent_pool_id, 500),
+				Error::<MockRuntime>::NonExistentPool
+			);
+		});
+	}
+
+	#[test]
+	fn fails_for_insufficient_staked_amount() {
+		new_test_ext().execute_with(|| {
+			// Setup
+			let user = 1;
+			let staking_asset_id = NativeOrWithId::<u32>::WithId(1);
+			let reward_asset_id = NativeOrWithId::<u32>::WithId(2);
+			let reward_rate_per_block = 100;
+
+			create_tokens(user, vec![staking_asset_id.clone(), reward_asset_id.clone()]);
+
+			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
+			));
+
+			let pool_id = 0;
+
+			// User stakes tokens
+			assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(user), pool_id, 1000));
+
+			// User tries to unstake more tokens than they have staked
+			assert_err!(
+				StakingRewards::unstake(RuntimeOrigin::signed(user), pool_id, 1500),
+				Error::<MockRuntime>::NotEnoughTokens
+			);
+		});
+	}
+}