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

Commit

Permalink
Revamp outbound for V2
Browse files Browse the repository at this point in the history
  • Loading branch information
yrong committed Oct 10, 2024
1 parent d8895c2 commit 10d7bcb
Show file tree
Hide file tree
Showing 18 changed files with 687 additions and 521 deletions.
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

0 comments on commit 10d7bcb

Please sign in to comment.