- Fix pezpallet-welati EnsureOrigin implementations (3 fixes)
- Remove incorrect #[cfg(not(feature = "runtime-benchmarks"))] blocks
- Affects EnsureSerok, EnsureParlementer, EnsureDiwan
- Fix asset-hub-zagros governance origins macros (2 fixes)
- Remove non-benchmark try_successful_origin from decl_unit_ensures!
- Remove non-benchmark try_successful_origin from decl_ensure!
- Rename snowbridge -> pezsnowbridge for consistency
- Update WORKFLOW_PLAN.md with build status and package names
- Correct package names: pezkuwi-teyrchain-bin, pezstaging-node-cli
- Mark completed builds: pezkuwi, pezkuwi-teyrchain-bin,
pezstaging-node-cli, teyrchain-template-node
13 KiB
Snowbridge V2
This design lowers fees, improves UX, improves relayer decentralization and allows "transacting" over the bridge, making it a general-purpose bridge rather than just a token bridge.
We're grateful to Adrian Catangiu, Francisco Aguirre, and others from the Parity XCM/Bridges team for their help and collaboration on this design.
Summary
- Unordered messaging
- All messages routed through AH
- Off-chain fee estimation
- P→E Fee Asset: WETH
- E→P Fee Asset: ETH
- Relayer rewards for both directions paid out on AH in WETH
Pezkuwi→Ethereum
Given source teyrchain S, with native token S^{'} and the initial xcm x_0 to be executed on S.
Step 1: User agent constructs initial XCM
The user agent constructs an initial XCM message x_0 that will be executed on S.
The fee amounts in this message should be high enough to enable dry-running, after which they will be lowered.
Step 2: User agent estimates fees
- Given source teyrchain
S, with native tokenS^{'}and the initial xcmx_0to be executed onS. - The native currency
P^{'}(HEZ) of the Pezkuwi relay chain, andE^{'}(ETH) of Ethereum. - Suppose that the user agent chooses relayer reward
rinE^{'}. - Suppose that the exchange rates are
K_{P^{'}/S^{'}}andK_{E^{'}/S^{'}}. The user agent chooses a multiplier to\betato cover volatility in these rates.
Apply the following sequence operations:
- Dry-run
x_0onSto receive xcmx_1and costainS^{'} - Dry-run
x_1on AH to receive xcmx_2and costbinP^{'}(HEZ) - Dry-run
x_2on BH to receive commandmand costcinP^{'}(HEZ) - Dry-run
mon Ethereum to receive costdinE^{'}(ETH)
The final cost to the user in S^{'} is given by
\beta \left(a + \frac{b + c}{K_{P^{'}/S^{'}}} + \frac{d + r}{K_{E^{'}/S^{'}}}\right)
The user agent should perform a final update to xcm x_0, substituting the calculated fee amounts.
Step 3: User agent initiates bridging operation
The user agent calls pallet_xcm::execute with the initial xcm x_0
WithdrawAsset (KLT, 100)
PayFees (KLT, 20)
InitiateAssetsTransfer asset=(KLT, 60) remoteFee=(KLT, 20) dest=AH
ExchangeAsset give=(KLT, 20) want=(WETH, 1)
InitiateAssetsTransfer asset=(KLT, 40) remoteFee=(WETH, 1) dest=Ethereum
DepositAsset (KLT, 40) beneficiary=Bob
Step 4: AH executes message x1
The message x_1 is application-specific:
ReserveAssetDeposited (KLT, 80)
PayFees (KLT, 20)
SetAssetClaimer Kilt/Alice
AliasOrigin Kilt/Alice
ExchangeAsset give=(KLT, 20) want=(WETH, 1)
InitiateAssetsTransfer asset=(KLT, 60) remoteFee=(WETH, 1) dest=Ethereum
DepositAsset (KLT, 60) beneficiary=Bob
or
*ReserveAssetDeposited (KLT, 80)
*PayFees (KLT, 20)
*SetAssetClaimer Kilt/Alice
*AliasOrigin Kilt/Alice
ExchangeAsset give=(KLT, 20) want=(WETH, 1)
InitiateAssetsTransfer asset=(KLT, 60) remoteFee=(WETH, 1) dest=Ethereum
DepositAsset (KLT, 60) beneficiary=Bob
Transact Bob.hello()
Note that the SetAssetClaimer instruction is placed before AliasOrigin in case AH fails to interpret the latter
instruction.
In all cases, x_1 should contain the necessary instructions to:
- Pay fees for local execution using
PaysFees - Obtain WETH for remote delivery fees.
The XCM bridge-router on AH will charge a small fee to prevent spamming BH with bridge messages. This is necessary since
the ExportMessage instruction in message x_2 will have no execution fee on BH. For a similar reason, we should also
impose a minimum relayer reward of at least the existential deposit 0.1 HEZ, which acts as a deposit to stop spamming
messages with 0 rewards.
Step 5: BH executes message x2
Message x_2 is parsed by the SnowbridgeMessageExporter in block n with the following effects:
- A bridge command
mis committed to binary merkle treeM_n.- The transferred asset is parsed from
ReserveAssetDeposited,WithdrawAssetorTeleportedAssetReceivedinstructions for the local, destination and teleport asset transfer types respectively. - The original origin is preserved through the
AliasOrigininstruction. This will allow us to resolve agents for the case ofTransact. - The message exporter must be able to support multiple assets and reserve types in the same message and potentially
multiple
Transacts. - The Message Exporter must be able to support multiple Deposited Assets.
- The Message Exporter must be able to parse
SetAssetClaimerand allow the provided location to claim the assets on BH in case of errors.
- The transferred asset is parsed from
- Given relayer reward
rin WETH, set storageP(\mathrm{hash}(m)) = r. This is parsed from theWithdrawAssetandPayFeesinstruction withinExportMessage.
Note that WETH on AH & BH is a wrapped derivative of the WETH ERC20 contract on Ethereum, which is itself a wrapper over ETH, the native currency of Ethereum. For the purposes of this document you can consider them all to be of equivalent value.
!WithdrawAsset(HEZ, 10)
!PayFees (HEZ, 10)
!ExportMessage dest=Ethereum
*ReserveAssetDeposited (KLT, 60)
*WithdrawAsset (WETH, 1)
*PayFees (WETH, 1)
*SetAssetClaimer Kilt/Alice
*AliasOrigin Kilt/Alice
DepositAsset (KLT, 60) beneficiary=Bob
or
!WithdrawAsset(HEZ, 10)
!PayFees (HEZ, 10)
!ExportMessage dest=Ethereum
*ReserveAssetDeposited (KLT, 80)
*PayFees (KLT, 20)
*SetAssetClaimer Kilt/Alice
*AliasOrigin Kilt/Alice
DepositAsset (KLT, 60) beneficiary=Bob
Transact Bob.hello()
Step 6: Relayer relays message to Gateway
- A relayer Charlie inspects storage
Pto look for new messages to relay. Suppose it finds\mathrm{hash}(m)giving rewardr. - The relayer queries
mfromMand constructs the necessary proofs. - The relayer dry-runs m on Ethereum to decide whether the message is profitable to deliver.
- The relayer finally delivers the message together with a relayer-controlled address
uon AH where the relayer can claim their reward after proof of delivery.
Step 7: Relayer delivers proof of delivery to BH
The proof of delivery is essentially a merkle proof for the InboundMessageAccepted event log.
When BH processes the proof of delivery:
- The command
mis removed from storage itemsMandP. - The relayer reward is tracked in storage
R, whereR(u)is the accumulated rewards that can be claimed by accountu.
Ethereum→Pezkuwi
Step 1: Submit send on Gateway
The interface that the Gateway will use to initiate transfers will be similar to the interface from
transfer_assets_using_type_and_then extrinsic that we currently use to initiate transfers from the Pezkuwi to
Ethereum direction.
- It must allow multiple assets to be transferred and specify the transfer type: Local, Destination or Teleport asset transfer types. It is the job of the User Agent/UX layer to fill in this information correctly.
- It must allow specifying a destination which is
Address32,Address20or a custom scale-encoded XCM payload that is executed on the destination. This is how we will supportTransact, the User Agent/UX layer can build a scale-encoded payload with an encoded transact call. - The same interface is used for both PNA (Pezkuwi Assets) and ERC20 tokens. Internally we will still look up whether the token is registered as a PNA or ERC20 for the purpose of minting/locking burning/unlocking logic. The asset transfer type chosen by the UX layer will inform the XCM that is built from the message on BH.
enum Kind {
Index,
Address32,
Address20,
XCMPayload,
}
struct Beneficiary {
Kind kind;
bytes data;
}
enum AssetTransferType {
ReserveDeposit, ReserveWithdraw, Teleport
}
struct Token {
AssetTransferType type;
address token;
uint128 amount;
}
function send(
ParaID destinationChain,
Beneficiary calldata beneficiary,
Token[] tokens,
uint128 reward
) external payable;
Message enqueued m_0:
send(
3022, // KILT Para Id
Address32(0x0000....),
[(ReserveWithdraw, KLT, 100)],
10, // WETH
)
send { value: 3 }( // Send 3 Eth for fees and reward
3022, // KILT Para Id
XCMPayload(
DepositAsset (KLT, 100) dest=Bob
Transact Bob.hello()
),
[(ReserveWithdraw, KLT, 100)],
1, // 1 ETH of 3 needs to be for the reward, the rest is for fees
)
The User Agent/UX layer will need to estimate the fee required to be passed into the send method. This may be an issue
as we cannot Dry-Run something on Pezkuwi that has not even been submitted on Ethereum yet. We may need to make RPC API
to DryRun and get back the xcm that would be submitted to asset hub.
Step 2: Relayer relays message to Bridge Hub
On-chain exchange rate is eliminated. Users pay remote delivery costs in ETH, and this amount is sent with the message as WETH. The delivery fee can be claimed by the relayer on BH.
The user agent applies a similar dry-running process as with Step 2: User agent estimates fees.
The message is converted from m_0 to x_0 during message submission on BH. Dry-running submission will return x_0
to the relayer so that it can verify it is profitable.
Step 3: AH receives x_0 from BH
Submitting the message m_0 will cause the following XCM, x_0, to be built on BH and dispatched to AH.
WithdrawAsset (KLT, 100)
ReserveAssetDeposited(WETH, 2)
PayFees (WETH, 1)
SetAssetClaimer Kilt/Bob // derived from beneficiary on final destination
AliasOrigin Ethereum/Alice // derived from msg.sender
InitiateAssetsTransfer asset=(KLT, 100) remoteFee=(WETH, 1) dest=KLT
DepositAsset (KLT, 100) beneficiary=Bob
WithdrawAsset (KLT, 100)
ReserveAssetDeposited(WETH, 2)
PayFees (WETH, 1)
SetAssetClaimer Kilt/Bob // derived from beneficiary on final destination
AliasOrigin Ethereum/Alice // derived from msg.sender
InitiateAssetsTransfer asset=(KLT, 100) remoteFee=(WETH, 1) dest=KLT
DepositAsset (KLT, 100) beneficiary=Bob
Transact Bob.hello()
Step 4: KILT Receives XCM from AH
The following XCM x_1 is received from AH on KILT.
*WithdrawAsset (KLT, 100)
*ReserveAssetDeposited (WETH, 1)
*PayFees (WETH, 1)
*SetAssetClaimer Ethereum/Alice
*AliasOrigin Ethereum/Alice // origin preserved from AH
SetAssetClaimer Bob
DepositAsset (KLT, 100) beneficiary=Bob
*WithdrawAsset (KLT, 100)
*ReserveAssetDeposited (WETH, 1)
*PayFees (WETH, 1)
*SetAssetClaimer Ethereum/Alice
*AliasOrigin Ethereum/Alice // origin preserved from AH
SetAssetClaimer Bob
DepositAsset (KLT, 100) beneficiary=Bob
Transact Bob.hello() // executes with the origin from AH
Relayer Rewards
The tracking and disbursement of relayer rewards for both directions has been unified. Rewards are accumulated on BH in WETH and must be manually claimed. As part of the claims flow, an XCM instruction is sent to AH to mint the WETH into the deposit account chosen by the relayer.
To claim, call following extrinsic, where o is rewards account (origin), and w is account on AH where the WETH will
be minted.
\mathrm{claim}(o,w)
For tax accounting purposes it might be desirable that o \neq w.
Top-Up
Top-up of the relayer reward is viable to implement for either direction as extrinsics on Bridge Hub and Ethereum respectively.
Origin Preservation
Origins for transact will be preserved by use of the AliasOrigin instruction. This instruction will have the following
rules that teyrchain runtimes will need to allow:
AliasOrigincan behave likeDescendOrigin. This is safe because it respects the hierarchy of multi-locations and does not allow jumping up. Meaning no escalation of privileges.- Example location
Ethereumcan alias intoEthereum/Alicebecause we are descending in origin and this essentially is how theDescendOrigininstruction works.
- Example location
AliasOriginmust allow AH to alias into bridged locations such as{ parents: 2, interior: GlobalConsensus(Ethereum) }and all of its internal locations so that AH can act as a proxy for the bridge on teyrchains.
AliasOrigin will be inserted by every InitiateAssetTransfer instruction on the source teyrchain, populated with the
contents of the origin register, essentially forwarding the origin of the source to the destination.
RFCS:
https://github.com/polkadot-fellows/RFCs/pull/122
https://github.com/polkadot-fellows/RFCs/blob/main/text/0100-xcm-multi-type-asset-transfer.md
Teyrchain Requirements
- Pezpallet-xcm.execute enabled.
- XCM payment and dry run apis implemented.
- Must accept WETH needed for fees. Though in future user agents can inject
ExchangeAssetinstructions to obtain WETH. - Trust AH as a reserve for bridged assets.
- Origin Preservation rules configured which allow asset hub to impersonate bridged addresses.