Skip to content
lib.rs 72.8 KiB
Newer Older
	_phantom: PhantomData<I>,
impl<T: Config<I>, I: 'static> InboundLaneStorage for RuntimeInboundLaneStorage<T, I> {
	type MessageFee = T::InboundMessageFee;
	type Relayer = T::InboundRelayer;
	fn max_unrewarded_relayer_entries(&self) -> MessageNonce {
		T::MaxUnrewardedRelayerEntriesAtInboundLane::get()
	}

	fn max_unconfirmed_messages(&self) -> MessageNonce {
		T::MaxUnconfirmedMessagesAtInboundLane::get()
	}

	fn data(&self) -> InboundLaneData<T::InboundRelayer> {
		match self.cached_data.clone().into_inner() {
			Some(data) => data,
			None => {
				let data: InboundLaneData<T::InboundRelayer> =
					InboundLanes::<T, I>::get(&self.lane_id).into();
				*self.cached_data.try_borrow_mut().expect(
					"we're in the single-threaded environment;\
						we have no recursive borrows; qed",
				) = Some(data.clone());
				data
hacpy's avatar
hacpy committed
			},
	fn set_data(&mut self, data: InboundLaneData<T::InboundRelayer>) {
		*self.cached_data.try_borrow_mut().expect(
			"we're in the single-threaded environment;\
				we have no recursive borrows; qed",
		) = Some(data.clone());
		InboundLanes::<T, I>::insert(&self.lane_id, StoredInboundLaneData::<T, I>(data))
	}
}

/// Runtime outbound lane storage.
struct RuntimeOutboundLaneStorage<T, I = ()> {
	lane_id: LaneId,
	_phantom: PhantomData<(T, I)>,
}

impl<T: Config<I>, I: 'static> OutboundLaneStorage for RuntimeOutboundLaneStorage<T, I> {
	type MessageFee = T::OutboundMessageFee;

	fn id(&self) -> LaneId {
		self.lane_id
	}

	fn data(&self) -> OutboundLaneData {
		OutboundLanes::<T, I>::get(&self.lane_id)
	}

	fn set_data(&mut self, data: OutboundLaneData) {
		OutboundLanes::<T, I>::insert(&self.lane_id, data)
	fn message(&self, nonce: &MessageNonce) -> Option<MessageData<T::OutboundMessageFee>> {
hacpy's avatar
hacpy committed
		OutboundMessages::<T, I>::get(MessageKey { lane_id: self.lane_id, nonce: *nonce })
hacpy's avatar
hacpy committed
	fn save_message(
		&mut self,
		nonce: MessageNonce,
		mesage_data: MessageData<T::OutboundMessageFee>,
	) {
		OutboundMessages::<T, I>::insert(MessageKey { lane_id: self.lane_id, nonce }, mesage_data);
	}

	fn remove_message(&mut self, nonce: &MessageNonce) {
hacpy's avatar
hacpy committed
		OutboundMessages::<T, I>::remove(MessageKey { lane_id: self.lane_id, nonce: *nonce });
/// Verify messages proof and return proved messages with decoded payload.
fn verify_and_decode_messages_proof<Chain: SourceHeaderChain<Fee>, Fee, DispatchPayload: Decode>(
	proof: Chain::MessagesProof,
	messages_count: u32,
) -> Result<ProvedMessages<DispatchMessage<DispatchPayload, Fee>>, Chain::Error> {
	// `receive_messages_proof` weight formula and `MaxUnconfirmedMessagesAtInboundLane` check
	// guarantees that the `message_count` is sane and Vec<Message> may be allocated.
	// (tx with too many messages will either be rejected from the pool, or will fail earlier)
	Chain::verify_messages_proof(proof, messages_count).map(|messages_by_lane| {
		messages_by_lane
			.into_iter()
			.map(|(lane, lane_data)| {
				(
					lane,
					ProvedLaneMessages {
						lane_state: lane_data.lane_state,
						messages: lane_data.messages.into_iter().map(Into::into).collect(),
					},
				)
			})
			.collect()
	})
}

#[cfg(test)]
mod tests {
	use super::*;
	use crate::mock::{
		message, message_payload, run_test, unrewarded_relayer, Event as TestEvent, Origin,
hacpy's avatar
hacpy committed
		TestMessageDeliveryAndDispatchPayment, TestMessagesDeliveryProof, TestMessagesParameter,
		TestMessagesProof, TestOnDeliveryConfirmed1, TestOnDeliveryConfirmed2,
		TestOnMessageAccepted, TestRuntime, TokenConversionRate, MAX_OUTBOUND_PAYLOAD_SIZE,
		PAYLOAD_REJECTED_BY_TARGET_CHAIN, REGULAR_PAYLOAD, TEST_LANE_ID, TEST_RELAYER_A,
		TEST_RELAYER_B,
	use bp_messages::{UnrewardedRelayer, UnrewardedRelayersState};
	use bp_test_utils::generate_owned_bridge_module_tests;
	use frame_support::{
		assert_noop, assert_ok,
		storage::generator::{StorageMap, StorageValue},
		weights::Weight,
	};
Hernando Castano's avatar
Hernando Castano committed
	use frame_system::{EventRecord, Pallet as System, Phase};
	use sp_runtime::DispatchError;
		System::<TestRuntime>::set_block_number(1);
		System::<TestRuntime>::reset_events();
	fn inbound_unrewarded_relayers_state(
		lane: bp_messages::LaneId,
	) -> bp_messages::UnrewardedRelayersState {
		let inbound_lane_data = InboundLanes::<TestRuntime, ()>::get(&lane).0;
		let last_delivered_nonce = inbound_lane_data.last_delivered_nonce();
		let relayers = inbound_lane_data.relayers;
		bp_messages::UnrewardedRelayersState {
			unrewarded_relayer_entries: relayers.len() as _,
			messages_in_oldest_entry: relayers
				.front()
				.map(|entry| 1 + entry.messages.end - entry.messages.begin)
				.unwrap_or(0),
			total_messages: total_unrewarded_messages(&relayers).unwrap_or(MessageNonce::MAX),
	fn send_regular_message() -> Weight {
hacpy's avatar
hacpy committed
		let message_nonce =
			outbound_lane::<TestRuntime, ()>(TEST_LANE_ID).data().latest_generated_nonce + 1;
		let weight = Pallet::<TestRuntime>::send_message(
			Origin::signed(1),
			TEST_LANE_ID,
			REGULAR_PAYLOAD,
			REGULAR_PAYLOAD.declared_weight,
		)
		.expect("send_message has failed")
		.actual_weight
		.expect("send_message always returns Some");

		// check event with assigned nonce
		assert_eq!(
			System::<TestRuntime>::events(),
			vec![EventRecord {
				phase: Phase::Initialization,
				event: TestEvent::Messages(Event::MessageAccepted {
					lane_id: TEST_LANE_ID,
					nonce: message_nonce
				}),
				topics: vec![],
			}],
		);

		// check that fee has been withdrawn from submitter
		assert!(TestMessageDeliveryAndDispatchPayment::is_fee_paid(
			1,
			REGULAR_PAYLOAD.declared_weight
		));
	}

	fn receive_messages_delivery_proof() {
		System::<TestRuntime>::set_block_number(1);
		System::<TestRuntime>::reset_events();

Hernando Castano's avatar
Hernando Castano committed
		assert_ok!(Pallet::<TestRuntime>::receive_messages_delivery_proof(
			TestMessagesDeliveryProof(Ok((
				TEST_LANE_ID,
				InboundLaneData {
					last_confirmed_nonce: 1,
					relayers: vec![UnrewardedRelayer {
						relayer: 0,
						messages: DeliveredMessages::new(1, true),
					}]
					.into_iter()
					.collect(),
			UnrewardedRelayersState {
				unrewarded_relayer_entries: 1,
				total_messages: 1,
		));

		assert_eq!(
			System::<TestRuntime>::events(),
			vec![EventRecord {
				phase: Phase::Initialization,
				event: TestEvent::Messages(Event::MessagesDelivered {
					lane_id: TEST_LANE_ID,
					messages: DeliveredMessages::new(1, true),
				}),
	#[test]
	fn pallet_parameter_may_be_updated_by_root() {
		run_test(|| {
			get_ready_for_events();

			let parameter = TestMessagesParameter::TokenConversionRate(10.into());
Hernando Castano's avatar
Hernando Castano committed
			assert_ok!(Pallet::<TestRuntime>::update_pallet_parameter(
				Origin::root(),
				parameter.clone(),
			));

			assert_eq!(TokenConversionRate::get(), 10.into());
			assert_eq!(
				System::<TestRuntime>::events(),
				vec![EventRecord {
					phase: Phase::Initialization,
					event: TestEvent::Messages(Event::ParameterUpdated { parameter }),
					topics: vec![],
				}],
			);
		});
	}

	#[test]
	fn pallet_parameter_may_be_updated_by_owner() {
		run_test(|| {
Hernando Castano's avatar
Hernando Castano committed
			PalletOwner::<TestRuntime>::put(2);
			let parameter = TestMessagesParameter::TokenConversionRate(10.into());
Hernando Castano's avatar
Hernando Castano committed
			assert_ok!(Pallet::<TestRuntime>::update_pallet_parameter(
				Origin::signed(2),
				parameter.clone(),
			));

			assert_eq!(TokenConversionRate::get(), 10.into());
			assert_eq!(
				System::<TestRuntime>::events(),
				vec![EventRecord {
					phase: Phase::Initialization,
					event: TestEvent::Messages(Event::ParameterUpdated { parameter }),
					topics: vec![],
				}],
			);
		});
	}

	#[test]
	fn pallet_parameter_cant_be_updated_by_arbitrary_submitter() {
		run_test(|| {
			assert_noop!(
Hernando Castano's avatar
Hernando Castano committed
				Pallet::<TestRuntime>::update_pallet_parameter(
					TestMessagesParameter::TokenConversionRate(10.into()),
Hernando Castano's avatar
Hernando Castano committed
			PalletOwner::<TestRuntime>::put(2);
Hernando Castano's avatar
Hernando Castano committed
				Pallet::<TestRuntime>::update_pallet_parameter(
					TestMessagesParameter::TokenConversionRate(10.into()),
				),
				DispatchError::BadOrigin,
			);
		});
	}

	#[test]
	fn fixed_u128_works_as_i_think() {
		// this test is here just to be sure that conversion rate may be represented with FixedU128
		run_test(|| {
			use sp_runtime::{FixedPointNumber, FixedU128};

			// 1:1 conversion that we use by default for testnets
			let rialto_token = 1u64;
hacpy's avatar
hacpy committed
			let rialto_token_in_millau_tokens =
				TokenConversionRate::get().saturating_mul_int(rialto_token);
			assert_eq!(rialto_token_in_millau_tokens, 1);

			// let's say conversion rate is 1:1.7
			let conversion_rate = FixedU128::saturating_from_rational(170, 100);
			let rialto_tokens = 100u64;
			let rialto_tokens_in_millau_tokens = conversion_rate.saturating_mul_int(rialto_tokens);
			assert_eq!(rialto_tokens_in_millau_tokens, 170);

			// let's say conversion rate is 1:0.25
			let conversion_rate = FixedU128::saturating_from_rational(25, 100);
			let rialto_tokens = 100u64;
			let rialto_tokens_in_millau_tokens = conversion_rate.saturating_mul_int(rialto_tokens);
			assert_eq!(rialto_tokens_in_millau_tokens, 25);
		});
	}

	#[test]
	fn pallet_rejects_transactions_if_halted() {
		run_test(|| {
			// send message first to be able to check that delivery_proof fails later
			send_regular_message();

			PalletOperatingMode::<TestRuntime, ()>::put(MessagesOperatingMode::Basic(
				BasicOperatingMode::Halted,
			));
Hernando Castano's avatar
Hernando Castano committed
				Pallet::<TestRuntime>::send_message(
					Origin::signed(1),
					TEST_LANE_ID,
					REGULAR_PAYLOAD,
					REGULAR_PAYLOAD.declared_weight,
				Error::<TestRuntime, ()>::NotOperatingNormally,
			assert_noop!(
				Pallet::<TestRuntime>::increase_message_fee(Origin::signed(1), TEST_LANE_ID, 1, 1,),
				Error::<TestRuntime, ()>::BridgeModule(bp_runtime::OwnedBridgeModuleError::Halted),
Hernando Castano's avatar
Hernando Castano committed
				Pallet::<TestRuntime>::receive_messages_proof(
					Origin::signed(1),
					TEST_RELAYER_A,
					Ok(vec![message(2, REGULAR_PAYLOAD)]).into(),
					REGULAR_PAYLOAD.declared_weight,
				Error::<TestRuntime, ()>::BridgeModule(bp_runtime::OwnedBridgeModuleError::Halted),
Hernando Castano's avatar
Hernando Castano committed
				Pallet::<TestRuntime>::receive_messages_delivery_proof(
					TestMessagesDeliveryProof(Ok((
						TEST_LANE_ID,
						InboundLaneData {
							last_confirmed_nonce: 1,
hacpy's avatar
hacpy committed
							relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)]
								.into_iter()
								.collect(),
					UnrewardedRelayersState {
						unrewarded_relayer_entries: 1,
						messages_in_oldest_entry: 1,
						total_messages: 1,
				Error::<TestRuntime, ()>::BridgeModule(bp_runtime::OwnedBridgeModuleError::Halted),
	#[test]
	fn pallet_rejects_new_messages_in_rejecting_outbound_messages_operating_mode() {
		run_test(|| {
			// send message first to be able to check that delivery_proof fails later
			send_regular_message();

			PalletOperatingMode::<TestRuntime, ()>::put(
				MessagesOperatingMode::RejectingOutboundMessages,
			);

			assert_noop!(
				Pallet::<TestRuntime>::send_message(
					Origin::signed(1),
					TEST_LANE_ID,
					REGULAR_PAYLOAD,
					REGULAR_PAYLOAD.declared_weight,
				Error::<TestRuntime, ()>::NotOperatingNormally,
			);

			assert_ok!(Pallet::<TestRuntime>::increase_message_fee(
				Origin::signed(1),
				TEST_LANE_ID,
				1,
				1,
			));

			assert_ok!(Pallet::<TestRuntime>::receive_messages_proof(
				Origin::signed(1),
				TEST_RELAYER_A,
				Ok(vec![message(1, REGULAR_PAYLOAD)]).into(),
				1,
				REGULAR_PAYLOAD.declared_weight,
			),);

			assert_ok!(Pallet::<TestRuntime>::receive_messages_delivery_proof(
				Origin::signed(1),
				TestMessagesDeliveryProof(Ok((
					TEST_LANE_ID,
					InboundLaneData {
						last_confirmed_nonce: 1,
hacpy's avatar
hacpy committed
						relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)]
							.into_iter()
							.collect(),
				UnrewardedRelayersState {
					unrewarded_relayer_entries: 1,
					messages_in_oldest_entry: 1,
					total_messages: 1,
	#[test]
	fn send_message_works() {
		run_test(|| {
			send_regular_message();
		});
	}

	#[test]
	fn send_message_rejects_too_large_message() {
		run_test(|| {
			let mut message_payload = message_payload(1, 0);
			// the payload isn't simply extra, so it'll definitely overflow
			// `MAX_OUTBOUND_PAYLOAD_SIZE` if we add `MAX_OUTBOUND_PAYLOAD_SIZE` bytes to extra
			message_payload
				.extra
				.extend_from_slice(&[0u8; MAX_OUTBOUND_PAYLOAD_SIZE as usize]);
			assert_noop!(
				Pallet::<TestRuntime>::send_message(
					Origin::signed(1),
					TEST_LANE_ID,
					message_payload,
					0,
				),
				Error::<TestRuntime, ()>::MessageIsTooLarge,
			);
		})
	}

	#[test]
	fn chain_verifier_rejects_invalid_message_in_send_message() {
		run_test(|| {
			// messages with this payload are rejected by target chain verifier
			assert_noop!(
Hernando Castano's avatar
Hernando Castano committed
				Pallet::<TestRuntime>::send_message(
					Origin::signed(1),
					TEST_LANE_ID,
					PAYLOAD_REJECTED_BY_TARGET_CHAIN,
					PAYLOAD_REJECTED_BY_TARGET_CHAIN.declared_weight
				Error::<TestRuntime, ()>::MessageRejectedByChainVerifier,
			);
		});
	}

	#[test]
	fn lane_verifier_rejects_invalid_message_in_send_message() {
		run_test(|| {
			// messages with zero fee are rejected by lane verifier
			assert_noop!(
hacpy's avatar
hacpy committed
				Pallet::<TestRuntime>::send_message(
					Origin::signed(1),
					TEST_LANE_ID,
					REGULAR_PAYLOAD,
					0
				),
				Error::<TestRuntime, ()>::MessageRejectedByLaneVerifier,
			);
		});
	}

	#[test]
	fn message_send_fails_if_submitter_cant_pay_message_fee() {
		run_test(|| {
			TestMessageDeliveryAndDispatchPayment::reject_payments();
			assert_noop!(
Hernando Castano's avatar
Hernando Castano committed
				Pallet::<TestRuntime>::send_message(
					Origin::signed(1),
					TEST_LANE_ID,
					REGULAR_PAYLOAD,
					REGULAR_PAYLOAD.declared_weight
				Error::<TestRuntime, ()>::FailedToWithdrawMessageFee,
			);
		});
	}

	#[test]
	fn receive_messages_proof_works() {
		run_test(|| {
Hernando Castano's avatar
Hernando Castano committed
			assert_ok!(Pallet::<TestRuntime>::receive_messages_proof(
				TEST_RELAYER_A,
				Ok(vec![message(1, REGULAR_PAYLOAD)]).into(),
				REGULAR_PAYLOAD.declared_weight,
			assert_eq!(InboundLanes::<TestRuntime>::get(TEST_LANE_ID).0.last_delivered_nonce(), 1);
		});
	}

	#[test]
	fn receive_messages_proof_updates_confirmed_message_nonce() {
		run_test(|| {
			// say we have received 10 messages && last confirmed message is 8
			InboundLanes::<TestRuntime, ()>::insert(
				TEST_LANE_ID,
				InboundLaneData {
					last_confirmed_nonce: 8,
					relayers: vec![
						unrewarded_relayer(9, 9, TEST_RELAYER_A),
						unrewarded_relayer(10, 10, TEST_RELAYER_B),
					]
					.into_iter()
					.collect(),
				inbound_unrewarded_relayers_state(TEST_LANE_ID),
				UnrewardedRelayersState {
					unrewarded_relayer_entries: 2,
					messages_in_oldest_entry: 1,

			// message proof includes outbound lane state with latest confirmed message updated to 9
hacpy's avatar
hacpy committed
			let mut message_proof: TestMessagesProof =
				Ok(vec![message(11, REGULAR_PAYLOAD)]).into();
			message_proof.result.as_mut().unwrap()[0].1.lane_state =
				Some(OutboundLaneData { latest_received_nonce: 9, ..Default::default() });
Hernando Castano's avatar
Hernando Castano committed
			assert_ok!(Pallet::<TestRuntime>::receive_messages_proof(
				Origin::signed(1),
				TEST_RELAYER_A,
				message_proof,
				REGULAR_PAYLOAD.declared_weight,
				InboundLanes::<TestRuntime>::get(TEST_LANE_ID).0,
					last_confirmed_nonce: 9,
					relayers: vec![
						unrewarded_relayer(10, 10, TEST_RELAYER_B),
						unrewarded_relayer(11, 11, TEST_RELAYER_A)
					]
					.into_iter()
					.collect(),
				inbound_unrewarded_relayers_state(TEST_LANE_ID),
				UnrewardedRelayersState {
					unrewarded_relayer_entries: 2,
					messages_in_oldest_entry: 1,
	fn receive_messages_proof_does_not_accept_message_if_dispatch_weight_is_not_enough() {
			assert_ok!(Pallet::<TestRuntime>::receive_messages_proof(
				Origin::signed(1),
				TEST_RELAYER_A,
				Ok(vec![message(1, REGULAR_PAYLOAD)]).into(),
				1,
				REGULAR_PAYLOAD.declared_weight - 1,
			));
			assert_eq!(InboundLanes::<TestRuntime>::get(TEST_LANE_ID).last_delivered_nonce(), 0);
		});
	}

	#[test]
	fn receive_messages_proof_rejects_invalid_proof() {
		run_test(|| {
			assert_noop!(
				Pallet::<TestRuntime, ()>::receive_messages_proof(
					Origin::signed(1),
					TEST_RELAYER_A,
					Err(()).into(),
				Error::<TestRuntime, ()>::InvalidMessagesProof,
	#[test]
	fn receive_messages_proof_rejects_proof_with_too_many_messages() {
		run_test(|| {
			assert_noop!(
				Pallet::<TestRuntime, ()>::receive_messages_proof(
					Origin::signed(1),
					TEST_RELAYER_A,
					Ok(vec![message(1, REGULAR_PAYLOAD)]).into(),
					u32::MAX,
					0,
				),
				Error::<TestRuntime, ()>::TooManyMessagesInTheProof,
	#[test]
	fn receive_messages_delivery_proof_works() {
		run_test(|| {
			send_regular_message();
			receive_messages_delivery_proof();

			assert_eq!(
				OutboundLanes::<TestRuntime, ()>::get(&TEST_LANE_ID).latest_received_nonce,
	#[test]
	fn receive_messages_delivery_proof_rewards_relayers() {
		run_test(|| {
Hernando Castano's avatar
Hernando Castano committed
			assert_ok!(Pallet::<TestRuntime>::send_message(
				Origin::signed(1),
				TEST_LANE_ID,
				REGULAR_PAYLOAD,
				1000,
			));
Hernando Castano's avatar
Hernando Castano committed
			assert_ok!(Pallet::<TestRuntime>::send_message(
				Origin::signed(1),
				TEST_LANE_ID,
				REGULAR_PAYLOAD,
				2000,
			));

			// this reports delivery of message 1 => reward is paid to TEST_RELAYER_A
Hernando Castano's avatar
Hernando Castano committed
			assert_ok!(Pallet::<TestRuntime>::receive_messages_delivery_proof(
				TestMessagesDeliveryProof(Ok((
					TEST_LANE_ID,
					InboundLaneData {
hacpy's avatar
hacpy committed
						relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)]
							.into_iter()
							.collect(),
				UnrewardedRelayersState {
					unrewarded_relayer_entries: 1,
					total_messages: 1,
hacpy's avatar
hacpy committed
			assert!(TestMessageDeliveryAndDispatchPayment::is_reward_paid(TEST_RELAYER_A, 1000));
			assert!(!TestMessageDeliveryAndDispatchPayment::is_reward_paid(TEST_RELAYER_B, 2000));
hacpy's avatar
hacpy committed
			// this reports delivery of both message 1 and message 2 => reward is paid only to
			// TEST_RELAYER_B
Hernando Castano's avatar
Hernando Castano committed
			assert_ok!(Pallet::<TestRuntime>::receive_messages_delivery_proof(
				TestMessagesDeliveryProof(Ok((
					TEST_LANE_ID,
					InboundLaneData {
						relayers: vec![
							unrewarded_relayer(1, 1, TEST_RELAYER_A),
							unrewarded_relayer(2, 2, TEST_RELAYER_B)
						]
						.into_iter()
						.collect(),
				UnrewardedRelayersState {
					unrewarded_relayer_entries: 2,
					total_messages: 2,
hacpy's avatar
hacpy committed
			assert!(!TestMessageDeliveryAndDispatchPayment::is_reward_paid(TEST_RELAYER_A, 1000));
			assert!(TestMessageDeliveryAndDispatchPayment::is_reward_paid(TEST_RELAYER_B, 2000));
	#[test]
	fn receive_messages_delivery_proof_rejects_invalid_proof() {
		run_test(|| {
			assert_noop!(
Hernando Castano's avatar
Hernando Castano committed
				Pallet::<TestRuntime>::receive_messages_delivery_proof(
					Origin::signed(1),
					TestMessagesDeliveryProof(Err(())),
					Default::default(),
				),
				Error::<TestRuntime, ()>::InvalidMessagesDeliveryProof,
	#[test]
	fn receive_messages_delivery_proof_rejects_proof_if_declared_relayers_state_is_invalid() {
		run_test(|| {
			// when number of relayers entries is invalid
Hernando Castano's avatar
Hernando Castano committed
				Pallet::<TestRuntime>::receive_messages_delivery_proof(
					TestMessagesDeliveryProof(Ok((
							relayers: vec![
								unrewarded_relayer(1, 1, TEST_RELAYER_A),
								unrewarded_relayer(2, 2, TEST_RELAYER_B)
							]
							.into_iter()
							.collect(),
					UnrewardedRelayersState {
						unrewarded_relayer_entries: 1,
						total_messages: 2,
				Error::<TestRuntime, ()>::InvalidUnrewardedRelayersState,
			);

			// when number of messages is invalid
			assert_noop!(
Hernando Castano's avatar
Hernando Castano committed
				Pallet::<TestRuntime>::receive_messages_delivery_proof(
					TestMessagesDeliveryProof(Ok((
							relayers: vec![
								unrewarded_relayer(1, 1, TEST_RELAYER_A),
								unrewarded_relayer(2, 2, TEST_RELAYER_B)
							]
							.into_iter()
							.collect(),
					UnrewardedRelayersState {
						unrewarded_relayer_entries: 2,
						total_messages: 1,
						last_delivered_nonce: 2,
						..Default::default()
					},
				),
				Error::<TestRuntime, ()>::InvalidUnrewardedRelayersState,
			);

			// when last delivered nonce is invalid
			assert_noop!(
				Pallet::<TestRuntime>::receive_messages_delivery_proof(
					Origin::signed(1),
					TestMessagesDeliveryProof(Ok((
						TEST_LANE_ID,
						InboundLaneData {
							relayers: vec![
								unrewarded_relayer(1, 1, TEST_RELAYER_A),
								unrewarded_relayer(2, 2, TEST_RELAYER_B)
							]
							.into_iter()
							.collect(),
							..Default::default()
						}
					))),
					UnrewardedRelayersState {
						unrewarded_relayer_entries: 2,
						total_messages: 2,
						last_delivered_nonce: 8,
				Error::<TestRuntime, ()>::InvalidUnrewardedRelayersState,
	#[test]
	fn receive_messages_accepts_single_message_with_invalid_payload() {
		run_test(|| {
			let mut invalid_message = message(1, REGULAR_PAYLOAD);
			invalid_message.data.payload = Vec::new();

			assert_ok!(Pallet::<TestRuntime, ()>::receive_messages_proof(
				Origin::signed(1),
				TEST_RELAYER_A,
				Ok(vec![invalid_message]).into(),
				0, // weight may be zero in this case (all messages are improperly encoded)
			),);

hacpy's avatar
hacpy committed
			assert_eq!(InboundLanes::<TestRuntime>::get(&TEST_LANE_ID).last_delivered_nonce(), 1,);
		});
	}

	#[test]
	fn receive_messages_accepts_batch_with_message_with_invalid_payload() {
		run_test(|| {
			let mut invalid_message = message(2, REGULAR_PAYLOAD);
			invalid_message.data.payload = Vec::new();

			assert_ok!(Pallet::<TestRuntime, ()>::receive_messages_proof(
				Origin::signed(1),
hacpy's avatar
hacpy committed
				Ok(
					vec![message(1, REGULAR_PAYLOAD), invalid_message, message(3, REGULAR_PAYLOAD),]
				)
				REGULAR_PAYLOAD.declared_weight + REGULAR_PAYLOAD.declared_weight,
hacpy's avatar
hacpy committed
			assert_eq!(InboundLanes::<TestRuntime>::get(&TEST_LANE_ID).last_delivered_nonce(), 3,);
	#[test]
	fn actual_dispatch_weight_does_not_overlow() {
		run_test(|| {
			let message1 = message(1, message_payload(0, Weight::MAX / 2));
			let message2 = message(2, message_payload(0, Weight::MAX / 2));
			let message3 = message(3, message_payload(0, Weight::MAX / 2));
			assert_ok!(Pallet::<TestRuntime, ()>::receive_messages_proof(
				Origin::signed(1),
				TEST_RELAYER_A,
				// this may cause overflow if source chain storage is invalid
				Ok(vec![message1, message2, message3]).into(),
				3,
				Weight::MAX,
			));
			assert_eq!(InboundLanes::<TestRuntime>::get(TEST_LANE_ID).last_delivered_nonce(), 2);

	#[test]
	fn increase_message_fee_fails_if_message_is_already_delivered() {
		run_test(|| {
			send_regular_message();
			receive_messages_delivery_proof();

			assert_noop!(
hacpy's avatar
hacpy committed
				Pallet::<TestRuntime, ()>::increase_message_fee(
					Origin::signed(1),
					TEST_LANE_ID,
					1,
					100,
				),
				Error::<TestRuntime, ()>::MessageIsAlreadyDelivered,
			);
		});
	}

	#[test]
	fn increase_message_fee_fails_if_message_is_not_yet_sent() {
		run_test(|| {
			assert_noop!(
hacpy's avatar
hacpy committed
				Pallet::<TestRuntime, ()>::increase_message_fee(
					Origin::signed(1),
					TEST_LANE_ID,
					1,
					100,
				),
				Error::<TestRuntime, ()>::MessageIsNotYetSent,
			);
		});
	}

	#[test]
	fn increase_message_fee_fails_if_submitter_cant_pay_additional_fee() {
		run_test(|| {
			send_regular_message();

			TestMessageDeliveryAndDispatchPayment::reject_payments();

			assert_noop!(
hacpy's avatar
hacpy committed
				Pallet::<TestRuntime, ()>::increase_message_fee(
					Origin::signed(1),
					TEST_LANE_ID,
					1,
					100,
				),
				Error::<TestRuntime, ()>::FailedToWithdrawMessageFee,
			);
		});
	}

	#[test]
	fn increase_message_fee_succeeds() {
		run_test(|| {
			send_regular_message();

			assert_ok!(Pallet::<TestRuntime, ()>::increase_message_fee(
				Origin::signed(1),
				TEST_LANE_ID,
				1,
				100,
			),);
			assert!(TestMessageDeliveryAndDispatchPayment::is_fee_paid(1, 100));
		});
	}

	#[test]
	fn weight_refund_from_receive_messages_proof_works() {
		run_test(|| {
			fn submit_with_unspent_weight(
				nonce: MessageNonce,
				unspent_weight: Weight,
				is_prepaid: bool,
			) -> (Weight, Weight) {
				let mut payload = REGULAR_PAYLOAD;
				payload.dispatch_result.unspent_weight = unspent_weight;
				payload.dispatch_result.dispatch_fee_paid_during_dispatch = !is_prepaid;
				let proof = Ok(vec![message(nonce, payload)]).into();
				let messages_count = 1;
hacpy's avatar
hacpy committed
				let pre_dispatch_weight =
					<TestRuntime as Config>::WeightInfo::receive_messages_proof_weight(
						&proof,
						messages_count,
						REGULAR_PAYLOAD.declared_weight,
					);
				let post_dispatch_weight = Pallet::<TestRuntime>::receive_messages_proof(
					Origin::signed(1),
					TEST_RELAYER_A,
					proof,
					messages_count,
					REGULAR_PAYLOAD.declared_weight,
				)
				.expect("delivery has failed")
				.actual_weight
				.expect("receive_messages_proof always returns Some");

				(pre_dispatch_weight, post_dispatch_weight)
			}

			// when dispatch is returning `unspent_weight < declared_weight`
			let (pre, post) = submit_with_unspent_weight(1, 1, false);
			assert_eq!(post, pre - 1);

			// when dispatch is returning `unspent_weight = declared_weight`
			let (pre, post) = submit_with_unspent_weight(2, REGULAR_PAYLOAD.declared_weight, false);
			assert_eq!(post, pre - REGULAR_PAYLOAD.declared_weight);

			// when dispatch is returning `unspent_weight > declared_weight`
hacpy's avatar
hacpy committed
			let (pre, post) =
				submit_with_unspent_weight(3, REGULAR_PAYLOAD.declared_weight + 1, false);
			assert_eq!(post, pre - REGULAR_PAYLOAD.declared_weight);

			// when there's no unspent weight
			let (pre, post) = submit_with_unspent_weight(4, 0, false);
			assert_eq!(post, pre);

			// when dispatch is returning `unspent_weight < declared_weight` AND message is prepaid
			let (pre, post) = submit_with_unspent_weight(5, 1, true);
			assert_eq!(
				post,
				pre - 1 - <TestRuntime as Config>::WeightInfo::pay_inbound_dispatch_fee_overhead()
			);
		});
	}