diff --git a/bridges/bin/millau/runtime/src/lib.rs b/bridges/bin/millau/runtime/src/lib.rs index 7d493d8493..2f06419071 100644 --- a/bridges/bin/millau/runtime/src/lib.rs +++ b/bridges/bin/millau/runtime/src/lib.rs @@ -336,6 +336,7 @@ impl pallet_message_lane::Config for Runtime { type Event = Event; // TODO: https://github.com/paritytech/parity-bridges-common/issues/390 type WeightInfo = pallet_message_lane::weights::RialtoWeight; + type Parameter = rialto_messages::MillauToRialtoMessageLaneParameter; type MaxMessagesToPruneAtOnce = MaxMessagesToPruneAtOnce; type MaxUnrewardedRelayerEntriesAtInboundLane = MaxUnrewardedRelayerEntriesAtInboundLane; type MaxUnconfirmedMessagesAtInboundLane = MaxUnconfirmedMessagesAtInboundLane; diff --git a/bridges/bin/millau/runtime/src/rialto_messages.rs b/bridges/bin/millau/runtime/src/rialto_messages.rs index 1ba7f8361f..59f7542d90 100644 --- a/bridges/bin/millau/runtime/src/rialto_messages.rs +++ b/bridges/bin/millau/runtime/src/rialto_messages.rs @@ -21,17 +21,25 @@ use crate::Runtime; use bp_message_lane::{ source_chain::TargetHeaderChain, target_chain::{ProvedMessages, SourceHeaderChain}, - InboundLaneData, LaneId, Message, MessageNonce, + InboundLaneData, LaneId, Message, MessageNonce, Parameter as MessageLaneParameter, }; use bp_runtime::{InstanceId, RIALTO_BRIDGE_INSTANCE}; use bridge_runtime_common::messages::{self, ChainWithMessageLanes, MessageBridge}; +use codec::{Decode, Encode}; use frame_support::{ + parameter_types, weights::{DispatchClass, Weight, WeightToFeePolynomial}, RuntimeDebug, }; use sp_core::storage::StorageKey; +use sp_runtime::{FixedPointNumber, FixedU128}; use sp_std::{convert::TryFrom, ops::RangeInclusive}; +parameter_types! { + /// Rialto to Millau conversion rate. Initially we treat both tokens as equal. + storage RialtoToMillauConversionRate: FixedU128 = 1.into(); +} + /// Storage key of the Millau -> Rialto message in the runtime storage. pub fn message_key(lane: &LaneId, nonce: MessageNonce) -> StorageKey { pallet_message_lane::storage_keys::message_key::::MessageLaneInstance>( @@ -145,8 +153,8 @@ impl MessageBridge for WithRialtoMessageBridge { } fn bridged_balance_to_this_balance(bridged_balance: bp_rialto::Balance) -> bp_millau::Balance { - // 1:1 conversion that will probably change in the future - bridged_balance as _ + bp_millau::Balance::try_from(RialtoToMillauConversionRate::get().saturating_mul_int(bridged_balance)) + .unwrap_or(bp_millau::Balance::MAX) } } @@ -227,3 +235,20 @@ impl SourceHeaderChain for Rialto { messages::target::verify_messages_proof::(proof, messages_count) } } + +/// Millau -> Rialto message lane pallet parameters. +#[derive(RuntimeDebug, Clone, Encode, Decode, PartialEq, Eq)] +pub enum MillauToRialtoMessageLaneParameter { + /// The conversion formula we use is: `MillauTokens = RialtoTokens * conversion_rate`. + RialtoToMillauConversionRate(FixedU128), +} + +impl MessageLaneParameter for MillauToRialtoMessageLaneParameter { + fn save(&self) { + match *self { + MillauToRialtoMessageLaneParameter::RialtoToMillauConversionRate(ref conversion_rate) => { + RialtoToMillauConversionRate::set(conversion_rate) + } + } + } +} diff --git a/bridges/bin/rialto/runtime/src/lib.rs b/bridges/bin/rialto/runtime/src/lib.rs index 52344cd27e..6e56676a53 100644 --- a/bridges/bin/rialto/runtime/src/lib.rs +++ b/bridges/bin/rialto/runtime/src/lib.rs @@ -443,6 +443,7 @@ pub(crate) type WithMillauMessageLaneInstance = pallet_message_lane::DefaultInst impl pallet_message_lane::Config for Runtime { type Event = Event; type WeightInfo = pallet_message_lane::weights::RialtoWeight; + type Parameter = millau_messages::RialtoToMillauMessageLaneParameter; type MaxMessagesToPruneAtOnce = MaxMessagesToPruneAtOnce; type MaxUnrewardedRelayerEntriesAtInboundLane = MaxUnrewardedRelayerEntriesAtInboundLane; type MaxUnconfirmedMessagesAtInboundLane = MaxUnconfirmedMessagesAtInboundLane; diff --git a/bridges/bin/rialto/runtime/src/millau_messages.rs b/bridges/bin/rialto/runtime/src/millau_messages.rs index b43226d60d..3fc11f59c7 100644 --- a/bridges/bin/rialto/runtime/src/millau_messages.rs +++ b/bridges/bin/rialto/runtime/src/millau_messages.rs @@ -21,17 +21,25 @@ use crate::Runtime; use bp_message_lane::{ source_chain::TargetHeaderChain, target_chain::{ProvedMessages, SourceHeaderChain}, - InboundLaneData, LaneId, Message, MessageNonce, + InboundLaneData, LaneId, Message, MessageNonce, Parameter as MessageLaneParameter, }; use bp_runtime::{InstanceId, MILLAU_BRIDGE_INSTANCE}; use bridge_runtime_common::messages::{self, ChainWithMessageLanes, MessageBridge}; +use codec::{Decode, Encode}; use frame_support::{ + parameter_types, weights::{DispatchClass, Weight, WeightToFeePolynomial}, RuntimeDebug, }; use sp_core::storage::StorageKey; +use sp_runtime::{FixedPointNumber, FixedU128}; use sp_std::{convert::TryFrom, ops::RangeInclusive}; +parameter_types! { + /// Millau to Rialto conversion rate. Initially we treat both tokens as equal. + storage MillauToRialtoConversionRate: FixedU128 = 1.into(); +} + /// Storage key of the Rialto -> Millau message in the runtime storage. pub fn message_key(lane: &LaneId, nonce: MessageNonce) -> StorageKey { pallet_message_lane::storage_keys::message_key::::MessageLaneInstance>( @@ -145,8 +153,8 @@ impl MessageBridge for WithMillauMessageBridge { } fn bridged_balance_to_this_balance(bridged_balance: bp_millau::Balance) -> bp_rialto::Balance { - // 1:1 conversion that will probably change in the future - bridged_balance as _ + bp_rialto::Balance::try_from(MillauToRialtoConversionRate::get().saturating_mul_int(bridged_balance)) + .unwrap_or(bp_rialto::Balance::MAX) } } @@ -227,3 +235,20 @@ impl SourceHeaderChain for Millau { messages::target::verify_messages_proof::(proof, messages_count) } } + +/// Rialto -> Millau message lane pallet parameters. +#[derive(RuntimeDebug, Clone, Encode, Decode, PartialEq, Eq)] +pub enum RialtoToMillauMessageLaneParameter { + /// The conversion formula we use is: `RialtoTokens = MillauTokens * conversion_rate`. + MillauToRialtoConversionRate(FixedU128), +} + +impl MessageLaneParameter for RialtoToMillauMessageLaneParameter { + fn save(&self) { + match *self { + RialtoToMillauMessageLaneParameter::MillauToRialtoConversionRate(ref conversion_rate) => { + MillauToRialtoConversionRate::set(conversion_rate) + } + } + } +} diff --git a/bridges/modules/message-lane/src/lib.rs b/bridges/modules/message-lane/src/lib.rs index 117bd2b465..45da09eba0 100644 --- a/bridges/modules/message-lane/src/lib.rs +++ b/bridges/modules/message-lane/src/lib.rs @@ -48,7 +48,7 @@ use bp_message_lane::{ source_chain::{LaneMessageVerifier, MessageDeliveryAndDispatchPayment, RelayersRewards, TargetHeaderChain}, target_chain::{DispatchMessage, MessageDispatch, ProvedLaneMessages, ProvedMessages, SourceHeaderChain}, total_unrewarded_messages, InboundLaneData, LaneId, MessageData, MessageKey, MessageNonce, MessagePayload, - OutboundLaneData, UnrewardedRelayersState, + OutboundLaneData, Parameter as MessageLaneParameter, UnrewardedRelayersState, }; use bp_runtime::Size; use codec::{Decode, Encode}; @@ -84,6 +84,12 @@ pub trait Config: frame_system::Config { type Event: From> + Into<::Event>; /// Benchmarks results from runtime we're plugged into. type WeightInfo: WeightInfoExt; + /// Pallet parameter that is opaque to the pallet itself, but may be used by the runtime + /// for integrating the pallet. + /// + /// All pallet parameters may only be updated either by the root, or by the pallet owner. + type Parameter: MessageLaneParameter; + /// Maximal number of messages that may be pruned during maintenance. Maintenance occurs /// whenever new message is sent. The reason is that if you want to use lane, you should /// be ready to pay for its maintenance. @@ -211,9 +217,13 @@ decl_storage! { } decl_event!( - pub enum Event where - ::AccountId, + pub enum Event + where + AccountId = ::AccountId, + Parameter = >::Parameter, { + /// Pallet parameter has been updated. + ParameterUpdated(Parameter), /// Message has been accepted and is waiting to be delivered. MessageAccepted(LaneId, MessageNonce), /// Messages in the inclusive range have been delivered and processed by the bridged chain. @@ -274,6 +284,18 @@ decl_module! { frame_support::debug::info!("Resuming pallet operations."); } + /// Update pallet parameter. + /// + /// May only be called either by root, or by `ModuleOwner`. + /// + /// The weight is: single read for permissions check + 2 writes for parameter value and event. + #[weight = (T::DbWeight::get().reads_writes(1, 2), DispatchClass::Operational)] + pub fn update_pallet_parameter(origin, parameter: T::Parameter) { + ensure_owner_or_root::(origin)?; + parameter.save(); + Self::deposit_event(RawEvent::ParameterUpdated(parameter)); + } + /// Send message over lane. #[weight = T::WeightInfo::send_message_weight(payload)] pub fn send_message( @@ -821,9 +843,9 @@ fn verify_and_decode_messages_proof, Fee, Dispatch mod tests { use super::*; use crate::mock::{ - message, run_test, Event as TestEvent, Origin, TestMessageDeliveryAndDispatchPayment, - TestMessagesDeliveryProof, TestMessagesProof, TestPayload, TestRuntime, PAYLOAD_REJECTED_BY_TARGET_CHAIN, - REGULAR_PAYLOAD, TEST_LANE_ID, TEST_RELAYER_A, TEST_RELAYER_B, + message, run_test, Event as TestEvent, Origin, TestMessageDeliveryAndDispatchPayment, TestMessageLaneParameter, + TestMessagesDeliveryProof, TestMessagesProof, TestPayload, TestRuntime, TokenConversionRate, + PAYLOAD_REJECTED_BY_TARGET_CHAIN, REGULAR_PAYLOAD, TEST_LANE_ID, TEST_RELAYER_A, TEST_RELAYER_B, }; use bp_message_lane::UnrewardedRelayersState; use frame_support::{assert_noop, assert_ok}; @@ -831,9 +853,13 @@ mod tests { use hex_literal::hex; use sp_runtime::DispatchError; - fn send_regular_message() { + fn get_ready_for_events() { System::::set_block_number(1); System::::reset_events(); + } + + fn send_regular_message() { + get_ready_for_events(); assert_ok!(Module::::send_message( Origin::signed(1), @@ -940,6 +966,101 @@ mod tests { }); } + #[test] + fn pallet_parameter_may_be_updated_by_root() { + run_test(|| { + get_ready_for_events(); + + let parameter = TestMessageLaneParameter::TokenConversionRate(10.into()); + assert_ok!(Module::::update_pallet_parameter( + Origin::root(), + parameter.clone(), + )); + + assert_eq!(TokenConversionRate::get(), 10.into()); + assert_eq!( + System::::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: TestEvent::pallet_message_lane(RawEvent::ParameterUpdated(parameter)), + topics: vec![], + }], + ); + }); + } + + #[test] + fn pallet_parameter_may_be_updated_by_owner() { + run_test(|| { + ModuleOwner::::put(2); + get_ready_for_events(); + + let parameter = TestMessageLaneParameter::TokenConversionRate(10.into()); + assert_ok!(Module::::update_pallet_parameter( + Origin::signed(2), + parameter.clone(), + )); + + assert_eq!(TokenConversionRate::get(), 10.into()); + assert_eq!( + System::::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: TestEvent::pallet_message_lane(RawEvent::ParameterUpdated(parameter)), + topics: vec![], + }], + ); + }); + } + + #[test] + fn pallet_parameter_cant_be_updated_by_arbitrary_submitter() { + run_test(|| { + assert_noop!( + Module::::update_pallet_parameter( + Origin::signed(2), + TestMessageLaneParameter::TokenConversionRate(10.into()), + ), + DispatchError::BadOrigin, + ); + + ModuleOwner::::put(2); + + assert_noop!( + Module::::update_pallet_parameter( + Origin::signed(1), + TestMessageLaneParameter::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; + 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(|| { diff --git a/bridges/modules/message-lane/src/mock.rs b/bridges/modules/message-lane/src/mock.rs index 948c107d05..cf14ca105c 100644 --- a/bridges/modules/message-lane/src/mock.rs +++ b/bridges/modules/message-lane/src/mock.rs @@ -25,6 +25,7 @@ use bp_message_lane::{ }, target_chain::{DispatchMessage, MessageDispatch, ProvedLaneMessages, ProvedMessages, SourceHeaderChain}, InboundLaneData, LaneId, Message, MessageData, MessageKey, MessageNonce, OutboundLaneData, + Parameter as MessageLaneParameter, }; use bp_runtime::Size; use codec::{Decode, Encode}; @@ -33,7 +34,7 @@ use sp_core::H256; use sp_runtime::{ testing::Header as SubstrateHeader, traits::{BlakeTwo256, IdentityLookup}, - Perbill, + FixedU128, Perbill, }; use std::collections::BTreeMap; @@ -119,11 +120,28 @@ parameter_types! { pub const MaxMessagesToPruneAtOnce: u64 = 10; pub const MaxUnrewardedRelayerEntriesAtInboundLane: u64 = 16; pub const MaxUnconfirmedMessagesAtInboundLane: u64 = 32; + pub storage TokenConversionRate: FixedU128 = 1.into(); +} + +#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] +pub enum TestMessageLaneParameter { + TokenConversionRate(FixedU128), +} + +impl MessageLaneParameter for TestMessageLaneParameter { + fn save(&self) { + match *self { + TestMessageLaneParameter::TokenConversionRate(conversion_rate) => { + TokenConversionRate::set(&conversion_rate) + } + } + } } impl Config for TestRuntime { type Event = Event; type WeightInfo = (); + type Parameter = TestMessageLaneParameter; type MaxMessagesToPruneAtOnce = MaxMessagesToPruneAtOnce; type MaxUnrewardedRelayerEntriesAtInboundLane = MaxUnrewardedRelayerEntriesAtInboundLane; type MaxUnconfirmedMessagesAtInboundLane = MaxUnconfirmedMessagesAtInboundLane; diff --git a/bridges/primitives/message-lane/src/lib.rs b/bridges/primitives/message-lane/src/lib.rs index 0c1dee7f2a..de2dbd9ae6 100644 --- a/bridges/primitives/message-lane/src/lib.rs +++ b/bridges/primitives/message-lane/src/lib.rs @@ -32,6 +32,12 @@ pub mod target_chain; // Weight is reexported to avoid additional frame-support dependencies in message-lane related crates. pub use frame_support::weights::Weight; +/// Message lane pallet parameter. +pub trait Parameter: frame_support::Parameter { + /// Save parameter value in the runtime storage. + fn save(&self); +} + /// Lane identifier. pub type LaneId = [u8; 4];