Skip to content
This repository has been archived by the owner on Oct 22, 2024. It is now read-only.

OutboundQueue V2 #181

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions bridges/snowbridge/pallets/outbound-queue-v2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ bridge-hub-common = { workspace = true }
snowbridge-core = { features = ["serde"], workspace = true }
snowbridge-outbound-queue-merkle-tree-v2 = { workspace = true }
ethabi = { workspace = true }
hex-literal = { workspace = true, default-features = true }

[dev-dependencies]
pallet-message-queue = { workspace = true }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@
#![cfg_attr(not(feature = "std"), no_std)]

use frame_support::traits::tokens::Balance as BalanceT;
use snowbridge_core::{
outbound::{Command, Fee},
PricingParameters,
};
use snowbridge_outbound_queue_merkle_tree_v2::MerkleProof;

sp_api::decl_runtime_apis! {
Expand All @@ -16,8 +12,5 @@ sp_api::decl_runtime_apis! {
/// The merkle root is stored in the block header as a
/// `sp_runtime::generic::DigestItem::Other`
fn prove_message(leaf_index: u64) -> Option<MerkleProof>;

/// Calculate the delivery fee for `command`
fn calculate_fee(command: Command, parameters: Option<PricingParameters<Balance>>) -> Fee<Balance>;
}
}
17 changes: 0 additions & 17 deletions bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,7 @@

use crate::{Config, MessageLeaves};
use frame_support::storage::StorageStreamIter;
use snowbridge_core::{
outbound::{Command, Fee, GasMeter},
PricingParameters,
};
use snowbridge_outbound_queue_merkle_tree_v2::{merkle_proof, MerkleProof};
use sp_core::Get;

pub fn prove_message<T>(leaf_index: u64) -> Option<MerkleProof>
where
Expand All @@ -22,15 +17,3 @@ where
merkle_proof::<<T as Config>::Hashing, _>(MessageLeaves::<T>::stream_iter(), leaf_index);
Some(proof)
}

pub fn calculate_fee<T>(
command: Command,
parameters: Option<PricingParameters<T::Balance>>,
) -> Fee<T::Balance>
where
T: Config,
{
let gas_used_at_most = T::GasMeter::maximum_gas_used_at_most(&command);
let parameters = parameters.unwrap_or(T::PricingParameters::get());
crate::Pallet::<T>::calculate_fee(gas_used_at_most, parameters)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use bridge_hub_common::AggregateMessageOrigin;
use codec::Encode;
use frame_benchmarking::v2::*;
use snowbridge_core::{
outbound::{Command, Initializer},
outbound::{Command, Initializer, QueuedMessage},
ChannelId,
};
use sp_core::{H160, H256};
Expand Down
144 changes: 36 additions & 108 deletions bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,20 +107,17 @@ use bridge_hub_common::{AggregateMessageOrigin, CustomDigestItem};
use codec::Decode;
use frame_support::{
storage::StorageStreamIter,
traits::{tokens::Balance, Contains, Defensive, EnqueueMessage, Get, ProcessMessageError},
traits::{tokens::Balance, EnqueueMessage, Get, ProcessMessageError},
weights::{Weight, WeightToFee},
};
use snowbridge_core::{
outbound::{Fee, GasMeter, QueuedMessage, VersionedQueuedMessage, ETHER_DECIMALS},
BasicOperatingMode, ChannelId,
outbound_v2::{CommandWrapper, Fee, GasMeter, Message},
BasicOperatingMode,
};
use snowbridge_outbound_queue_merkle_tree_v2::merkle_root;
pub use snowbridge_outbound_queue_merkle_tree_v2::MerkleProof;
use sp_core::{H256, U256};
use sp_runtime::{
traits::{CheckedDiv, Hash},
DigestItem, Saturating,
};
use sp_core::H256;
use sp_runtime::{traits::Hash, DigestItem};
use sp_std::prelude::*;
pub use types::{CommittedMessage, ProcessMessageOriginOf};
pub use weights::WeightInfo;
Expand All @@ -132,8 +129,6 @@ pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use snowbridge_core::PricingParameters;
use sp_arithmetic::FixedU128;

#[pallet::pallet]
pub struct Pallet<T>(_);
Expand All @@ -151,10 +146,6 @@ pub mod pallet {

type Balance: Balance + From<u128>;

/// Number of decimal places in native currency
#[pallet::constant]
type Decimals: Get<u8>;

/// Max bytes in a message payload
#[pallet::constant]
type MaxMessagePayloadSize: Get<u32>;
Expand All @@ -163,11 +154,6 @@ pub mod pallet {
#[pallet::constant]
type MaxMessagesPerBlock: Get<u32>;

/// Check whether a channel exists
type Channels: Contains<ChannelId>;

type PricingParameters: Get<PricingParameters<Self::Balance>>;

/// Convert a weight value into a deductible fee based.
type WeightToFee: WeightToFee<Balance = Self::Balance>;

Expand Down Expand Up @@ -232,13 +218,17 @@ pub mod pallet {

/// The current nonce for each message origin
#[pallet::storage]
pub type Nonce<T: Config> = StorageMap<_, Twox64Concat, ChannelId, u64, ValueQuery>;
pub type Nonce<T: Config> = StorageValue<_, u64, ValueQuery>;

/// The current operating mode of the pallet.
#[pallet::storage]
#[pallet::getter(fn operating_mode)]
pub type OperatingMode<T: Config> = StorageValue<_, BasicOperatingMode, ValueQuery>;

/// Fee locked by nonce
#[pallet::storage]
pub type LockedFee<T: Config> = StorageMap<_, Twox64Concat, u64, u128, ValueQuery>;

#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T>
where
Expand All @@ -255,11 +245,6 @@ pub mod pallet {
fn on_finalize(_: BlockNumberFor<T>) {
Self::commit();
}

fn integrity_test() {
let decimals = T::Decimals::get();
assert!(decimals == 10 || decimals == 12, "Decimals should be 10 or 12");
}
}

#[pallet::call]
Expand Down Expand Up @@ -313,108 +298,51 @@ pub mod pallet {
);

// Decode bytes into versioned message
let versioned_queued_message: VersionedQueuedMessage =
VersionedQueuedMessage::decode(&mut message).map_err(|_| Corrupt)?;

// Convert versioned message into latest supported message version
let queued_message: QueuedMessage =
versioned_queued_message.try_into().map_err(|_| Unsupported)?;

// Obtain next nonce
let nonce = <Nonce<T>>::try_mutate(
queued_message.channel_id,
|nonce| -> Result<u64, ProcessMessageError> {
*nonce = nonce.checked_add(1).ok_or(Unsupported)?;
Ok(*nonce)
},
)?;

let pricing_params = T::PricingParameters::get();
let command = queued_message.command.index();
let params = queued_message.command.abi_encode();
let max_dispatch_gas =
T::GasMeter::maximum_dispatch_gas_used_at_most(&queued_message.command);
let reward = pricing_params.rewards.remote;
let message: Message = Message::decode(&mut message).map_err(|_| Corrupt)?;

let nonce = Nonce::<T>::get();

let commands: Vec<CommandWrapper> = message
.commands
.into_iter()
.map(|command| CommandWrapper {
kind: command.index(),
max_dispatch_gas: T::GasMeter::maximum_dispatch_gas_used_at_most(&command),
command: command.clone(),
})
.collect();

// Construct the final committed message
let message = CommittedMessage {
channel_id: queued_message.channel_id,
let committed_message = CommittedMessage {
origin: message.origin,
nonce,
command,
params,
max_dispatch_gas,
max_fee_per_gas: pricing_params
.fee_per_gas
.try_into()
.defensive_unwrap_or(u128::MAX),
reward: reward.try_into().defensive_unwrap_or(u128::MAX),
id: queued_message.id,
id: message.id,
commands: commands.try_into().expect("should work"),
};

// ABI-encode and hash the prepared message
let message_abi_encoded = ethabi::encode(&[message.clone().into()]);
let message_abi_encoded = ethabi::encode(&[committed_message.clone().into()]);
let message_abi_encoded_hash = <T as Config>::Hashing::hash(&message_abi_encoded);

Messages::<T>::append(Box::new(message));
Messages::<T>::append(Box::new(committed_message.clone()));
MessageLeaves::<T>::append(message_abi_encoded_hash);
Nonce::<T>::set(nonce.saturating_add(1));
<LockedFee<T>>::try_mutate(nonce, |amount| -> DispatchResult {
*amount = amount.saturating_add(message.fee);
Ok(())
})
.map_err(|_| Corrupt)?;

Self::deposit_event(Event::MessageAccepted { id: queued_message.id, nonce });
Self::deposit_event(Event::MessageAccepted { id: message.id, nonce });

Ok(true)
}

/// Calculate total fee in native currency to cover all costs of delivering a message to the
/// remote destination. See module-level documentation for more details.
pub(crate) fn calculate_fee(
gas_used_at_most: u64,
params: PricingParameters<T::Balance>,
) -> Fee<T::Balance> {
// Remote fee in ether
let fee = Self::calculate_remote_fee(
gas_used_at_most,
params.fee_per_gas,
params.rewards.remote,
);

// downcast to u128
let fee: u128 = fee.try_into().defensive_unwrap_or(u128::MAX);

// multiply by multiplier and convert to local currency
let fee = FixedU128::from_inner(fee)
.saturating_mul(params.multiplier)
.checked_div(&params.exchange_rate)
.expect("exchange rate is not zero; qed")
.into_inner();

// adjust fixed point to match local currency
let fee = Self::convert_from_ether_decimals(fee);

Fee::from((Self::calculate_local_fee(), fee))
}

/// Calculate fee in remote currency for dispatching a message on Ethereum
pub(crate) fn calculate_remote_fee(
gas_used_at_most: u64,
fee_per_gas: U256,
reward: U256,
) -> U256 {
fee_per_gas.saturating_mul(gas_used_at_most.into()).saturating_add(reward)
}

/// The local component of the message processing fees in native currency
pub(crate) fn calculate_local_fee() -> T::Balance {
T::WeightToFee::weight_to_fee(
&T::WeightInfo::do_process_message().saturating_add(T::WeightInfo::commit_single()),
)
}

// 1 DOT has 10 digits of precision
// 1 KSM has 12 digits of precision
// 1 ETH has 18 digits of precision
pub(crate) fn convert_from_ether_decimals(value: u128) -> T::Balance {
let decimals = ETHER_DECIMALS.saturating_sub(T::Decimals::get()) as u32;
let denom = 10u128.saturating_pow(decimals);
value.checked_div(denom).expect("divisor is non-zero; qed").into()
}
}
}
46 changes: 25 additions & 21 deletions bridges/snowbridge/pallets/outbound-queue-v2/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@ use frame_support::{
derive_impl, parameter_types,
traits::{Everything, Hooks},
weights::IdentityFee,
BoundedVec,
};

use snowbridge_core::{
gwei, meth,
outbound::*,
outbound_v2::*,
pricing::{PricingParameters, Rewards},
ParaId, PRIMARY_GOVERNANCE_CHANNEL,
ParaId,
};
use sp_core::{ConstU32, ConstU8, H160, H256};
use sp_core::{ConstU32, H160, H256};
use sp_runtime::{
traits::{BlakeTwo256, IdentityLookup, Keccak256},
AccountId32, BuildStorage, FixedU128,
Expand Down Expand Up @@ -84,13 +85,10 @@ impl crate::Config for Test {
type RuntimeEvent = RuntimeEvent;
type Hashing = Keccak256;
type MessageQueue = MessageQueue;
type Decimals = ConstU8<12>;
type MaxMessagePayloadSize = ConstU32<1024>;
type MaxMessagesPerBlock = ConstU32<20>;
type GasMeter = ConstantGasMeter;
type Balance = u128;
type PricingParameters = Parameters;
type Channels = Everything;
type WeightToFee = IdentityFee<u128>;
type WeightInfo = ();
}
Expand Down Expand Up @@ -129,13 +127,15 @@ where
let _marker = PhantomData::<T>; // for clippy

Message {
id: None,
channel_id: PRIMARY_GOVERNANCE_CHANNEL,
command: Command::Upgrade {
impl_address: H160::zero(),
impl_code_hash: H256::zero(),
origin: Default::default(),
id: Default::default(),
fee: 0,
commands: BoundedVec::try_from(vec![Command::Upgrade {
impl_address: Default::default(),
impl_code_hash: Default::default(),
initializer: None,
},
}])
.unwrap(),
}
}

Expand All @@ -147,28 +147,32 @@ where
let _marker = PhantomData::<T>; // for clippy

Message {
id: None,
channel_id: PRIMARY_GOVERNANCE_CHANNEL,
command: Command::Upgrade {
origin: Default::default(),
id: Default::default(),
fee: 0,
commands: BoundedVec::try_from(vec![Command::Upgrade {
impl_address: H160::zero(),
impl_code_hash: H256::zero(),
initializer: Some(Initializer {
params: (0..1000).map(|_| 1u8).collect::<Vec<u8>>(),
maximum_required_gas: 0,
}),
},
}])
.unwrap(),
}
}

pub fn mock_message(sibling_para_id: u32) -> Message {
pub fn mock_message(_sibling_para_id: u32) -> Message {
Message {
id: None,
channel_id: ParaId::from(sibling_para_id).into(),
command: Command::TransferNativeToken {
origin: Default::default(),
id: Default::default(),
fee: 0,
commands: BoundedVec::try_from(vec![Command::UnlockNativeToken {
agent_id: Default::default(),
token: Default::default(),
recipient: Default::default(),
amount: 0,
},
}])
.unwrap(),
}
}
Loading
Loading