diff --git a/polkadot/collator/src/lib.rs b/polkadot/collator/src/lib.rs
index c6323776816f9a5149b106c631985b7e322c39d6..20c254b3488993579973f583a65de327f420edc3 100644
--- a/polkadot/collator/src/lib.rs
+++ b/polkadot/collator/src/lib.rs
@@ -56,7 +56,7 @@ use primitives::{ed25519, Pair};
 use polkadot_primitives::{BlockId, SessionKey, Hash, Block};
 use polkadot_primitives::parachain::{
 	self, BlockData, DutyRoster, HeadData, ConsolidatedIngress, Message, Id as ParaId, Extrinsic,
-	PoVBlock,
+	PoVBlock, Status as ParachainStatus,
 };
 use polkadot_cli::{PolkadotService, CustomConfiguration, ParachainHost};
 use polkadot_cli::{Worker, IntoExit, ProvideRuntimeApi, TaskExecutor};
@@ -105,7 +105,7 @@ pub trait ParachainContext: Clone {
 	fn produce_candidate<I: IntoIterator<Item=(ParaId, Message)>>(
 		&self,
 		relay_parent: Hash,
-		last_head: HeadData,
+		status: ParachainStatus,
 		ingress: I,
 	) -> Self::ProduceCandidate;
 }
@@ -128,7 +128,7 @@ pub trait RelayChainContext {
 pub fn collate<'a, R, P>(
 	relay_parent: Hash,
 	local_id: ParaId,
-	last_head: HeadData,
+	parachain_status: ParachainStatus,
 	relay_context: R,
 	para_context: P,
 	key: Arc<ed25519::Pair>,
@@ -146,7 +146,7 @@ pub fn collate<'a, R, P>(
 		.and_then(move |ingress| {
 			para_context.produce_candidate(
 				relay_parent,
-				last_head,
+				parachain_status,
 				ingress.0.iter().flat_map(|&(id, ref msgs)| msgs.iter().cloned().map(move |msg| (id, msg)))
 			)
 				.into_future()
@@ -311,8 +311,8 @@ impl<P, E> Worker for CollationNode<P, E> where
 
 				let work = future::lazy(move || {
 					let api = client.runtime_api();
-					let last_head = match try_fr!(api.parachain_head(&id, para_id)) {
-						Some(last_head) => last_head,
+					let status = match try_fr!(api.parachain_status(&id, para_id)) {
+						Some(status) => status,
 						None => return future::Either::A(future::ok(())),
 					};
 
@@ -333,7 +333,7 @@ impl<P, E> Worker for CollationNode<P, E> where
 					let collation_work = collate(
 						relay_parent,
 						para_id,
-						HeadData(last_head),
+						status,
 						context,
 						parachain_context,
 						key,
@@ -403,7 +403,7 @@ pub fn run_collator<P, E, I, ArgT>(
 #[cfg(test)]
 mod tests {
 	use std::collections::HashMap;
-	use polkadot_primitives::parachain::OutgoingMessage;
+	use polkadot_primitives::parachain::{OutgoingMessage, FeeSchedule};
 	use keyring::AuthorityKeyring;
 	use super::*;
 
@@ -433,7 +433,7 @@ mod tests {
 		fn produce_candidate<I: IntoIterator<Item=(ParaId, Message)>>(
 			&self,
 			_relay_parent: Hash,
-			_last_head: HeadData,
+			_status: ParachainStatus,
 			ingress: I,
 		) -> Result<(BlockData, HeadData, Extrinsic), InvalidHead> {
 			// send messages right back.
@@ -484,7 +484,14 @@ mod tests {
 		let collation = collate(
 			Default::default(),
 			id,
-			HeadData(vec![5]),
+			ParachainStatus {
+				head_data: HeadData(vec![5]),
+				balance: 10,
+				fee_schedule: FeeSchedule {
+					base: 0,
+					per_byte: 1,
+				},
+			},
 			context.clone(),
 			DummyParachainContext,
 			AuthorityKeyring::Alice.pair().into(),
diff --git a/polkadot/network/src/tests/validation.rs b/polkadot/network/src/tests/validation.rs
index 3ec328f805b64eb31ee11f1cee044eacd3d96b88..cc5e60ea0cb05c0ef1887d48a4a71c65a1e9dcb0 100644
--- a/polkadot/network/src/tests/validation.rs
+++ b/polkadot/network/src/tests/validation.rs
@@ -30,7 +30,8 @@ use polkadot_validation::{SharedTable, MessagesFrom, Network};
 use polkadot_primitives::{SessionKey, Block, Hash, Header, BlockId};
 use polkadot_primitives::parachain::{
 	Id as ParaId, Chain, DutyRoster, ParachainHost, OutgoingMessage,
-	ValidatorId, StructuredUnroutedIngress, BlockIngressRoots,
+	ValidatorId, StructuredUnroutedIngress, BlockIngressRoots, Status,
+	FeeSchedule, HeadData,
 };
 use parking_lot::Mutex;
 use substrate_client::error::Result as ClientResult;
@@ -282,14 +283,21 @@ impl ParachainHost<Block> for RuntimeApi {
 		Ok(NativeOrEncoded::Native(self.data.lock().active_parachains.clone()))
 	}
 
-	fn ParachainHost_parachain_head_runtime_api_impl(
+	fn ParachainHost_parachain_status_runtime_api_impl(
 		&self,
 		_at: &BlockId,
 		_: ExecutionContext,
 		_: Option<ParaId>,
 		_: Vec<u8>,
-	) -> ClientResult<NativeOrEncoded<Option<Vec<u8>>>> {
-		Ok(NativeOrEncoded::Native(Some(Vec::new())))
+	) -> ClientResult<NativeOrEncoded<Option<Status>>> {
+		Ok(NativeOrEncoded::Native(Some(Status {
+			head_data: HeadData(Vec::new()),
+			balance: 0,
+			fee_schedule: FeeSchedule {
+				base: 0,
+				per_byte: 0,
+			}
+		})))
 	}
 
 	fn ParachainHost_parachain_code_runtime_api_impl(
diff --git a/polkadot/primitives/src/parachain.rs b/polkadot/primitives/src/parachain.rs
index eb0bba3092327edcae303baf68cf184bdec18865..c8ff2c3d0d7587e1f17451a138166f0b82446855 100644
--- a/polkadot/primitives/src/parachain.rs
+++ b/polkadot/primitives/src/parachain.rs
@@ -28,7 +28,9 @@ use serde::{Serialize, Deserialize};
 use primitives::bytes;
 use primitives::ed25519;
 
-pub use polkadot_parachain::{Id, AccountIdConversion, ParachainDispatchOrigin};
+pub use polkadot_parachain::{
+	Id, AccountIdConversion, ParachainDispatchOrigin,
+};
 
 /// Identity that collators use.
 pub type CollatorId = ed25519::Public;
@@ -328,6 +330,39 @@ impl AttestedCandidate {
 	}
 }
 
+/// A fee schedule for messages. This is a linear function in the number of bytes of a message.
+#[derive(PartialEq, Eq, PartialOrd, Hash, Default, Clone, Copy, Encode, Decode)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
+pub struct FeeSchedule {
+	/// The base fee charged for all messages.
+	pub base: Balance,
+	/// The per-byte fee charged on top of that.
+	pub per_byte: Balance,
+}
+
+impl FeeSchedule {
+	/// Compute the fee for a message of given size.
+	pub fn compute_fee(&self, n_bytes: usize) -> Balance {
+		use rstd::mem;
+		debug_assert!(mem::size_of::<Balance>() >= mem::size_of::<usize>());
+
+		let n_bytes = n_bytes as Balance;
+		self.base.saturating_add(n_bytes.saturating_mul(self.per_byte))
+	}
+}
+
+/// Current Status of a parachain.
+#[derive(PartialEq, Eq, Clone, Encode, Decode)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
+pub struct Status {
+	/// The head of the parachain.
+	pub head_data: HeadData,
+	/// The current balance of the parachain.
+	pub balance: Balance,
+	/// The fee schedule for messages coming from this parachain.
+	pub fee_schedule: FeeSchedule,
+}
+
 substrate_client::decl_runtime_apis! {
 	/// The API for querying the state of parachains on-chain.
 	pub trait ParachainHost {
@@ -337,8 +372,8 @@ substrate_client::decl_runtime_apis! {
 		fn duty_roster() -> DutyRoster;
 		/// Get the currently active parachains.
 		fn active_parachains() -> Vec<Id>;
-		/// Get the given parachain's head data blob.
-		fn parachain_head(id: Id) -> Option<Vec<u8>>;
+		/// Get the given parachain's status.
+		fn parachain_status(id: Id) -> Option<Status>;
 		/// Get the given parachain's head code blob.
 		fn parachain_code(id: Id) -> Option<Vec<u8>>;
 		/// Get all the unrouted ingress roots at the given block that
@@ -354,3 +389,16 @@ pub mod id {
 	/// Parachain host runtime API id.
 	pub const PARACHAIN_HOST: ApiId = *b"parahost";
 }
+
+#[cfg(test)]
+mod tests {
+	use super::*;
+
+	#[test]
+	fn balance_bigger_than_usize() {
+		let zero_b: Balance = 0;
+		let zero_u: usize = 0;
+
+		assert!(zero_b.leading_zeros() >= zero_u.leading_zeros());
+	}
+}
diff --git a/polkadot/runtime/src/lib.rs b/polkadot/runtime/src/lib.rs
index d817a39151b894673584d42fe110bb8aaa66b339..1eda4a6773203f5bccff930b558951f1e212e09f 100644
--- a/polkadot/runtime/src/lib.rs
+++ b/polkadot/runtime/src/lib.rs
@@ -234,6 +234,7 @@ impl grandpa::Trait for Runtime {
 impl parachains::Trait for Runtime {
 	type Origin = Origin;
 	type Call = Call;
+	type ParachainCurrency = Balances;
 }
 
 parameter_types!{
@@ -364,8 +365,8 @@ impl_runtime_apis! {
 		fn active_parachains() -> Vec<parachain::Id> {
 			Parachains::active_parachains()
 		}
-		fn parachain_head(id: parachain::Id) -> Option<Vec<u8>> {
-			Parachains::parachain_head(&id)
+		fn parachain_status(id: parachain::Id) -> Option<parachain::Status> {
+			Parachains::parachain_status(&id)
 		}
 		fn parachain_code(id: parachain::Id) -> Option<Vec<u8>> {
 			Parachains::parachain_code(&id)
diff --git a/polkadot/runtime/src/parachains.rs b/polkadot/runtime/src/parachains.rs
index 04370f87a357e55fac7fd5a533fed9ee55a0cd5b..4b077c0475fa89575b73e37dd44c64d18331feaf 100644
--- a/polkadot/runtime/src/parachains.rs
+++ b/polkadot/runtime/src/parachains.rs
@@ -25,13 +25,14 @@ use bitvec::{bitvec, BigEndian};
 use sr_primitives::traits::{
 	Hash as HashT, BlakeTwo256, Member, CheckedConversion, Saturating, One, Zero,
 };
-use primitives::{Hash, parachain::{
-	Id as ParaId, Chain, DutyRoster, AttestedCandidate, Statement, AccountIdConversion,
+use primitives::{Hash, Balance, parachain::{
+	self, Id as ParaId, Chain, DutyRoster, AttestedCandidate, Statement, AccountIdConversion,
 	ParachainDispatchOrigin, UpwardMessage, BlockIngressRoots,
 }};
 use {system, session};
 use srml_support::{
-	StorageValue, StorageMap, storage::AppendableStorageMap, Parameter, Dispatchable, dispatch::Result
+	StorageValue, StorageMap, storage::AppendableStorageMap, Parameter, Dispatchable, dispatch::Result,
+	traits::{Currency, WithdrawReason, ExistenceRequirement}
 };
 
 #[cfg(feature = "std")]
@@ -142,12 +143,46 @@ impl<T: Trait> ParachainRegistrar<T::AccountId> for Module<T> {
 	}
 }
 
+// wrapper trait because an associated type of `Currency<Self::AccountId,Balance=Balance>`
+// doesn't work.`
+pub trait ParachainCurrency<AccountId> {
+	fn free_balance(para_id: ParaId) -> Balance;
+	fn deduct(para_id: ParaId, amount: Balance) -> Result;
+}
+
+impl<AccountId, T: Currency<AccountId>> ParachainCurrency<AccountId> for T where
+	T::Balance: From<Balance> + Into<Balance>,
+	ParaId: AccountIdConversion<AccountId>,
+{
+	fn free_balance(para_id: ParaId) -> Balance {
+		let para_account = para_id.into_account();
+		T::free_balance(&para_account).into()
+	}
+
+	fn deduct(para_id: ParaId, amount: Balance) -> Result {
+		let para_account = para_id.into_account();
+
+		// burn the fee.
+		let _ = T::withdraw(
+			&para_account,
+			amount.into(),
+			WithdrawReason::Fee,
+			ExistenceRequirement::KeepAlive,
+		)?;
+
+		Ok(())
+	}
+}
+
 pub trait Trait: session::Trait {
 	/// The outer origin type.
 	type Origin: From<Origin> + From<system::RawOrigin<Self::AccountId>>;
 
 	/// The outer call dispatch type.
 	type Call: Parameter + Dispatchable<Origin=<Self as Trait>::Origin>;
+
+	/// Some way of interacting with balances for fees.
+	type ParachainCurrency: ParachainCurrency<Self::AccountId>;
 }
 
 /// Origin for the parachains module.
@@ -269,7 +304,7 @@ decl_module! {
 					}
 				}
 
-				Self::check_attestations(&heads)?;
+				Self::check_candidates(&heads)?;
 
 				let current_number = <system::Module<T>>::block_number();
 
@@ -549,6 +584,21 @@ impl<T: Trait> Module<T> {
 			.collect())
 	}
 
+	/// Get the parachain status necessary for validation.
+	pub fn parachain_status(id: &parachain::Id) -> Option<parachain::Status> {
+		let balance = T::ParachainCurrency::free_balance(*id);
+		Self::parachain_head(id).map(|head_data| parachain::Status {
+			head_data: parachain::HeadData(head_data),
+			balance,
+			// TODO: https://github.com/paritytech/polkadot/issues/92
+			// plug in some real values here. most likely governable.
+			fee_schedule: parachain::FeeSchedule {
+				base: 0,
+				per_byte: 0,
+			}
+		})
+	}
+
 	fn check_egress_queue_roots(head: &AttestedCandidate, active_parachains: &[ParaId]) -> Result {
 		let mut last_egress_id = None;
 		let mut iter = active_parachains.iter();
@@ -584,7 +634,7 @@ impl<T: Trait> Module<T> {
 
 	// check the attestations on these candidates. The candidates should have been checked
 	// that each candidates' chain ID is valid.
-	fn check_attestations(attested_candidates: &[AttestedCandidate]) -> Result {
+	fn check_candidates(attested_candidates: &[AttestedCandidate]) -> Result{
 		use primitives::parachain::ValidityAttestation;
 		use sr_primitives::traits::Verify;
 
@@ -661,7 +711,8 @@ impl<T: Trait> Module<T> {
 		let mut validator_groups = GroupedDutyIter::new(&sorted_validators[..]);
 
 		for candidate in attested_candidates {
-			let validator_group = validator_groups.group_for(candidate.parachain_index())
+			let para_id = candidate.parachain_index();
+			let validator_group = validator_groups.group_for(para_id)
 				.ok_or("no validator group for parachain")?;
 
 			ensure!(
@@ -669,6 +720,9 @@ impl<T: Trait> Module<T> {
 				"Not enough validity attestations"
 			);
 
+			let fees = candidate.candidate().fees;
+			T::ParachainCurrency::deduct(para_id, fees)?;
+
 			let mut candidate_hash = None;
 			let mut encoded_implicit = None;
 			let mut encoded_explicit = None;
@@ -824,7 +878,7 @@ mod tests {
 	}
 
 	impl balances::Trait for Test {
-		type Balance = u64;
+		type Balance = Balance;
 		type OnFreeBalanceZero = ();
 		type OnNewAccount = ();
 		type Event = ();
@@ -852,6 +906,7 @@ mod tests {
 	impl Trait for Test {
 		type Origin = Origin;
 		type Call = Call;
+		type ParachainCurrency = balances::Module<Test>;
 	}
 
 	type Parachains = Module<Test>;
diff --git a/polkadot/test-parachains/adder/collator/src/main.rs b/polkadot/test-parachains/adder/collator/src/main.rs
index ca0ab87064a393f40b09cb75f7caef7342a6ae7d..2692ee260136f12b8e679622911046d26e0fef78 100644
--- a/polkadot/test-parachains/adder/collator/src/main.rs
+++ b/polkadot/test-parachains/adder/collator/src/main.rs
@@ -23,7 +23,10 @@ use std::sync::Arc;
 use adder::{HeadData as AdderHead, BlockData as AdderBody};
 use substrate_primitives::Pair;
 use parachain::codec::{Encode, Decode};
-use primitives::{Hash, parachain::{HeadData, BlockData, Id as ParaId, Message, Extrinsic}};
+use primitives::Hash;
+use primitives::parachain::{
+	HeadData, BlockData, Id as ParaId, Message, Extrinsic, Status as ParachainStatus,
+};
 use collator::{InvalidHead, ParachainContext, VersionInfo};
 use parking_lot::Mutex;
 
@@ -50,11 +53,11 @@ impl ParachainContext for AdderContext {
 	fn produce_candidate<I: IntoIterator<Item=(ParaId, Message)>>(
 		&self,
 		_relay_parent: Hash,
-		last_head: HeadData,
+		status: ParachainStatus,
 		ingress: I,
 	) -> Result<(BlockData, HeadData, Extrinsic), InvalidHead>
 	{
-		let adder_head = AdderHead::decode(&mut &last_head.0[..])
+		let adder_head = AdderHead::decode(&mut &status.head_data.0[..])
 			.ok_or(InvalidHead)?;
 
 		let mut db = self.db.lock();
diff --git a/polkadot/validation/src/collation.rs b/polkadot/validation/src/collation.rs
index 77c6a905293be72905974c29e9968b900fd07592..8d1785ba82c8fa4dd2e165c901fa1183bcafca86 100644
--- a/polkadot/validation/src/collation.rs
+++ b/polkadot/validation/src/collation.rs
@@ -21,9 +21,9 @@
 
 use std::sync::Arc;
 
-use polkadot_primitives::{Block, Hash, BlockId, parachain::CollatorId, parachain::{
-	ConsolidatedIngress, StructuredUnroutedIngress, CandidateReceipt, ParachainHost,
-	Id as ParaId, Collation, Extrinsic, OutgoingMessage, UpwardMessage
+use polkadot_primitives::{Block, Hash, BlockId, Balance, parachain::{
+	CollatorId, ConsolidatedIngress, StructuredUnroutedIngress, CandidateReceipt, ParachainHost,
+	Id as ParaId, Collation, Extrinsic, OutgoingMessage, UpwardMessage, FeeSchedule,
 }};
 use runtime_primitives::traits::ProvideRuntimeApi;
 use parachain::{wasm_executor::{self, ExternalitiesError}, MessageRef, UpwardMessageRef};
@@ -167,6 +167,9 @@ pub enum Error {
 	/// Parachain validation produced wrong relay-chain messages
 	#[display(fmt = "Parachain validation produced wrong relay-chain messages (expected: {:?}, got {:?})", expected, got)]
 	UpwardMessagesInvalid { expected: Vec<UpwardMessage>, got: Vec<UpwardMessage> },
+	/// Parachain validation produced wrong fees to charge to parachain.
+	#[display(fmt = "Parachain validation produced wrong relay-chain fees (expected: {:?}, got {:?})", expected, got)]
+	FeesChargedInvalid { expected: Balance, got: Balance },
 }
 
 impl std::error::Error for Error {
@@ -268,17 +271,19 @@ struct Externalities {
 	parachain_index: ParaId,
 	outgoing: Vec<OutgoingMessage>,
 	upward: Vec<UpwardMessage>,
+	fees_charged: Balance,
+	free_balance: Balance,
+	fee_schedule: FeeSchedule,
 }
 
 impl wasm_executor::Externalities for Externalities {
 	fn post_message(&mut self, message: MessageRef) -> Result<(), ExternalitiesError> {
-		// TODO: https://github.com/paritytech/polkadot/issues/92
-		// check per-message and per-byte fees for the parachain.
 		let target: ParaId = message.target.into();
 		if target == self.parachain_index {
 			return Err(ExternalitiesError::CannotPostMessage("posted message to self"));
 		}
 
+		self.apply_message_fee(message.data.len())?;
 		self.outgoing.push(OutgoingMessage {
 			target,
 			data: message.data.to_vec(),
@@ -290,8 +295,8 @@ impl wasm_executor::Externalities for Externalities {
 	fn post_upward_message(&mut self, message: UpwardMessageRef)
 		-> Result<(), ExternalitiesError>
 	{
-		// TODO: https://github.com/paritytech/polkadot/issues/92
-		// check per-message and per-byte fees for the parachain.
+		self.apply_message_fee(message.data.len())?;
+
 		self.upward.push(UpwardMessage {
 			origin: message.origin,
 			data: message.data.to_vec(),
@@ -300,9 +305,18 @@ impl wasm_executor::Externalities for Externalities {
 	}
 }
 
-
-
 impl Externalities {
+	fn apply_message_fee(&mut self, message_len: usize) -> Result<(), ExternalitiesError> {
+		let fee = self.fee_schedule.compute_fee(message_len);
+		let new_fees_charged = self.fees_charged.saturating_add(fee);
+		if new_fees_charged > self.free_balance {
+			Err(ExternalitiesError::CannotPostMessage("could not cover fee."))
+		} else {
+			self.fees_charged = new_fees_charged;
+			Ok(())
+		}
+	}
+
 	// Performs final checks of validity, producing the extrinsic data.
 	fn final_checks(
 		self,
@@ -315,6 +329,13 @@ impl Externalities {
 			});
 		}
 
+		if self.fees_charged != candidate.fees {
+			return Err(Error::FeesChargedInvalid {
+				expected: candidate.fees.clone(),
+				got: self.fees_charged.clone(),
+			});
+		}
+
 		check_extrinsic(
 			self.outgoing,
 			&candidate.egress_queue_roots[..],
@@ -383,15 +404,16 @@ pub fn validate_collation<P>(
 	let validation_code = api.parachain_code(relay_parent, para_id)?
 		.ok_or_else(|| Error::InactiveParachain(para_id))?;
 
-	let chain_head = api.parachain_head(relay_parent, para_id)?
+	let chain_status = api.parachain_status(relay_parent, para_id)?
 		.ok_or_else(|| Error::InactiveParachain(para_id))?;
 
 	let roots = api.ingress(relay_parent, para_id)?
 		.ok_or_else(|| Error::InactiveParachain(para_id))?;
+
 	validate_incoming(&roots, &collation.pov.ingress)?;
 
 	let params = ValidationParams {
-		parent_head: chain_head,
+		parent_head: chain_status.head_data.0,
 		block_data: collation.pov.block_data.0.clone(),
 		ingress: collation.pov.ingress.0.iter()
 			.flat_map(|&(source, ref messages)| {
@@ -407,6 +429,9 @@ pub fn validate_collation<P>(
 		parachain_index: collation.receipt.parachain_index.clone(),
 		outgoing: Vec::new(),
 		upward: Vec::new(),
+		free_balance: chain_status.balance,
+		fee_schedule: chain_status.fee_schedule,
+		fees_charged: 0,
 	};
 
 	match wasm_executor::validate_candidate(&validation_code, params, &mut ext) {
@@ -481,6 +506,12 @@ mod tests {
 			parachain_index: 5.into(),
 			outgoing: Vec::new(),
 			upward: Vec::new(),
+			fees_charged: 0,
+			free_balance: 1_000_000,
+			fee_schedule: FeeSchedule {
+				base: 1000,
+				per_byte: 10,
+			},
 		};
 
 		assert!(ext.post_message(MessageRef { target: 1.into(), data: &[] }).is_ok());
@@ -495,6 +526,12 @@ mod tests {
 			upward: vec![
 				UpwardMessage{ data: vec![42], origin: ParachainDispatchOrigin::Parachain },
 			],
+			fees_charged: 0,
+			free_balance: 1_000_000,
+			fee_schedule: FeeSchedule {
+				base: 1000,
+				per_byte: 10,
+			},
 		};
 		let receipt = CandidateReceipt {
 			parachain_index: 5.into(),
@@ -550,4 +587,43 @@ mod tests {
 		};
 		assert!(ext().final_checks(&receipt).is_ok());
 	}
+
+	#[test]
+	fn ext_checks_fees_and_updates_correctly() {
+		let mut ext = Externalities {
+			parachain_index: 5.into(),
+			outgoing: Vec::new(),
+			upward: vec![
+				UpwardMessage{ data: vec![42], origin: ParachainDispatchOrigin::Parachain },
+			],
+			fees_charged: 0,
+			free_balance: 1_000_000,
+			fee_schedule: FeeSchedule {
+				base: 1000,
+				per_byte: 10,
+			},
+		};
+
+		ext.apply_message_fee(100).unwrap();
+		assert_eq!(ext.fees_charged, 2000);
+
+		ext.post_message(MessageRef {
+			target: 1.into(),
+			data: &[0u8; 100],
+		}).unwrap();
+		assert_eq!(ext.fees_charged, 4000);
+
+		ext.post_upward_message(UpwardMessageRef {
+			origin: ParachainDispatchOrigin::Signed,
+			data: &[0u8; 100],
+		}).unwrap();
+		assert_eq!(ext.fees_charged, 6000);
+
+
+		ext.apply_message_fee((1_000_000 - 6000 - 1000) / 10).unwrap();
+		assert_eq!(ext.fees_charged, 1_000_000);
+
+		// cannot pay fee.
+		assert!(ext.apply_message_fee(1).is_err());
+	}
 }