-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Outbound queue v2 #4
base: snowbridge-v2
Are you sure you want to change the base?
Conversation
Workflow
which means we can execute multiple commands on Ethereum in one message.
|
@@ -172,3 +174,13 @@ impl Default for AssetMetadata { | |||
|
|||
/// Maximum length of a string field in ERC20 token metada | |||
const METADATA_FIELD_MAX_LEN: u32 = 32; | |||
|
|||
// Origin for high-priority governance commands | |||
pub fn primary_governance_origin() -> H256 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is now a V1-only type, so should go in core/src/v1/mod.rs
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
850fe96
Actually it's V2 specific.
The usage in
polkadot-sdk/bridges/snowbridge/pallets/outbound-queue-v2/src/send_message_impl.rs
Lines 45 to 47 in ba03ae6
if ticket.origin != primary_governance_origin() { | |
ensure!(!Self::operating_mode().is_halted(), SendError::Halted); | |
} |
} | ||
|
||
// Origin for lower-priority governance commands | ||
pub fn second_governance_origin() -> H256 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...rachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs
Outdated
Show resolved
Hide resolved
Check details in emulated tests how to construct transfer message for both ENA&PNA with XCMV5 instructions.
|
fffd895
to
df137c0
Compare
9492f1e
to
d359108
Compare
bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs
Outdated
Show resolved
Hide resolved
bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs
Outdated
Show resolved
Hide resolved
/// - `metadata`: Metadata to include in the instantiated ERC20 contract on Ethereum | ||
#[pallet::call_index(11)] | ||
#[pallet::weight(T::WeightInfo::register_token())] | ||
pub fn register_token_v2( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively can we reimplement the original register_token
extrinsic to optionally use V2 protocol, depending on a config field in storage.
When the ethereum contracts have been upgraded to V2, we can configure register_token
to use V2.
The benefit is that user-level apps won't need to change any code to register tokens using V2.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, ignore my comment, I realize we need separate register extrinsics for V1 and V2 as they have different fee-taking behavior.
I see now there is also the problem of how to provide WETH as the fees for these governance calls. On BH users can only provide DOT
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we need to make the rewards pallet support both WETH and DOT.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how to provide WETH as the fees for these governance calls.
Actually there is no fee charged for governance calls. So it's possible pendingOrder
bound with zero fee and no need to reward in this case.
polkadot-sdk/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs
Lines 278 to 281 in fb3b30c
// No fee for governance order | |
if !order.fee.is_zero() { | |
T::RewardLedger::deposit(envelope.reward_address, order.fee.into())?; | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is that then no off-chain relayer will be incentivized to deliver the message. Letting users optionally pay using DOT is a solution we should consider.
Another issue is that when we make register_token_v2
permissionless, there is still no way for many parachains to call the extrinsic since they won't have HRMP channels registered.
So I think we have to force users to route their governance calls through AH and provide DOT. In which case, BH will see message like:
AliasOrigin /Parachain/Hydra
ReceiveTeleportedAsset (DOT, 10)
DepositAsset (DOT, /Parachain/Hydra)
Transact(EthereumSystem.register_token_v2)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I think we have to force users to route their governance calls through AH and provide DOT.
Agree. All calls should be through AH.
But I'd assume in this case the fee could also be swapped to WETH
beforehand in AH so that in BH we only use WETH
as fee for simplicity. Some todo logic here:
polkadot-sdk/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs
Lines 289 to 294 in cff1cba
// Todo: Validate fee asset is WETH | |
let fee_amount = match fee_asset { | |
Asset { id: _, fun: Fungible(amount) } => Some(*amount), | |
_ => None, | |
} | |
.ok_or(AssetResolutionFailed)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would also prefer a solution where we only have 1 asset type in the rewards pallet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that would be best.
If we move governance extrinsics such as register-token, create-agent to AH, then that would neatly solve the problem
As we could burn the WETH on AH and then send message to BH with the weth reward for the outbound message
Example flow:
- User calls
EthereumSystem.register_token
extrinsic on AH, supplying WETH for reward - WETH is burnt, and AH sends
Transact(EthereumSystem.register_token)
XCM to BH - On BH, the low-level
EthereumSystem.register_token
adds burnt WETH to rewards pallet and queues outbound command
use alloy_sol_types::SolValue; | ||
|
||
sol! { | ||
struct InboundMessageWrapper { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to use modules as namespaces instead of appending Wrapper
to the struct names? Thinking of this pattern:
mod abi {
sol! {
struct InboundMessageWrapper { ... }
}
}
Then we can reference the type using abi::InboundMessage
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The point of adding the abi
namespace is that we rename InboundMessageWrapper
and CommandWrapper
to InboundMessage
and Command
respectively
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it seems a bit cubersome with InboundMessage
and InboundMessageWrapper
.
The reason is that it's not allowed to add macro #[derive(Encode, Decode, RuntimeDebug, PartialEq,TypeInfo)]
into InboundMessageWrapper which is required for storing the data on chain, or error as follows:
--> /Users/yangrong/Projects/polkadot-sdk/bridges/snowbridge/primitives/core/src/outbound/v2.rs:31:12
|
31 | #[derive(Encode, Decode, RuntimeDebug, PartialEq,TypeInfo)]
| ^^^^^^ the trait `WrapperTypeEncode` is not implemented for `alloy_primitives::FixedBytes<32>`, which is required by `alloy_primitives::FixedBytes<32>: Encode`
|
= help: the following other types implement trait `WrapperTypeEncode`:
&T
&mut T
Arc<T>
Box<T>
Cow<'a, T>
Rc<T>
alloy_primitives::bytes::Bytes
parity_scale_codec::Ref<'a, T, U>
and 3 others
= note: required for `alloy_primitives::FixedBytes<32>` to implement `Encode`
= note: this error originates in the derive macro `Encode` (in Nightly builds, run with -Z macro-backtrace for more info)
...
So InboundMessageWrapper
is the intermediate data structure only for abi encode, will be converted to InboundMessage
here when storing on chain.
})?; | ||
|
||
// Inspect AliasOrigin as V2 message | ||
let mut instructions = message.clone().0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like this logic should be inside XcmConverter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's for compatible with V1, for xcm which does not contain AliasOrigin
will fallback to EthereumBlobExporterV1
.
polkadot-sdk/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs
Lines 220 to 224 in cff1cba
type MessageExporter = ( | |
XcmOverBridgeHubRococo, | |
crate::bridge_to_ethereum_config::SnowbridgeExporterV2, | |
crate::bridge_to_ethereum_config::SnowbridgeExporter, | |
); |
}; | ||
} | ||
|
||
pub struct XcmConverter<'a, ConvertAssetId, Call> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is going to be a lot more conversion code for v2, so I suggest moving XcmConverter
to a new file like outbound/v2/converter.rs
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | ||
|
||
pub fn convert(&mut self) -> Result<Message, XcmConverterError> { | ||
let result = match self.jump_to() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think its safe to just "jump" over instructions, we should validate each instruction in turn to see whether it matches expectations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | ||
} | ||
|
||
pub fn convert(&mut self) -> Result<Message, XcmConverterError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For V2, an InboundMessage
can contain many commands. This conversion code looks like it only matches enough instructions to produce a single command?
Also we need support for XCM::Transact, ie convert it to gateway CallContract
command.
We should support any combination of UnlockNativeToken
, MintForeignToken
, CallContract
commands.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, for simplicity we start from a single command, will improve it later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Btw: Transact support is not included yet. I'd prefer to limit the scope of this PR and implement transact in another one.
Co-authored-by: Vincent Geddes <117534+vgeddes@users.noreply.github.com>
ensure!(<PendingOrders<T>>::contains_key(nonce), Error::<T>::PendingNonceNotExist); | ||
|
||
let order = <PendingOrders<T>>::get(nonce).ok_or(Error::<T>::PendingNonceNotExist)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ensure!(<PendingOrders<T>>::contains_key(nonce), Error::<T>::PendingNonceNotExist); | |
let order = <PendingOrders<T>>::get(nonce).ok_or(Error::<T>::PendingNonceNotExist)?; | |
let order = <PendingOrders<T>>::get(nonce).ok_or(Error::<T>::PendingNonceNotExist)?; |
Is the first check necessary? Would it not return an error if the nonce does not exist?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
/// A message which can be accepted by implementations of `/[`SendMessage`\]` | ||
#[derive(Encode, Decode, TypeInfo, PartialEq, Clone, RuntimeDebug)] | ||
pub struct Message { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For both the inbound and outbound pallets, I was wondering if we want to do message versioning at all like we did for v1? Or did we see that if we want to make large changes we'll create a new pallet like we are doing for v2? @vgeddes
pub fn primary_governance_origin() -> H256 { | ||
hex!("0000000000000000000000000000000000000000000000000000000000000001").into() | ||
} | ||
|
||
// Origin for lower-priority governance commands | ||
pub fn second_governance_origin() -> H256 { | ||
hex!("0000000000000000000000000000000000000000000000000000000000000002").into() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is this used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | ||
}, | ||
); | ||
ensure!(result.is_err(), SendError::NotApplicable); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't quite understand this. Why are we asserting that we get an error here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, seems a bit weird.
Because we're using match_next_inst_while
macro to match against AliasOrigin
with error return, if not matched, then fallback to V1.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But doesn't this expect the result to always be an error? For example, if result is Ok()
, this ensure will still error?
@@ -55,7 +57,7 @@ impl From<AggregateMessageOrigin> for Location { | |||
Sibling(id) => Location::new(1, Junction::Parachain(id.into())), | |||
// NOTE: We don't need this conversion for Snowbridge. However, we have to | |||
// implement it anyway as xcm_builder::ProcessXcmMessage requires it. | |||
Snowbridge(_) => Location::default(), | |||
_ => Location::default(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not add SnowbridgeV2
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need this conversion for Snowbridge. However, we have to implement it anyway as xcm_builder::ProcessXcmMessage requires it.
Just as the comment above.
Resolves: https://linear.app/snowfork/issue/SNO-1205