-
Notifications
You must be signed in to change notification settings - Fork 741
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Here are MD docs for V2 @acatangiu @franciscoaguirre . Let me know what you think. --------- Co-authored-by: Adrian Catangiu <[email protected]> Co-authored-by: Francisco Aguirre <[email protected]> Co-authored-by: Alistair Singh <[email protected]>
- Loading branch information
1 parent
292bfac
commit 90ff47d
Showing
1 changed file
with
356 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,356 @@ | ||
# 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 | ||
|
||
## Polkadot→Ethereum | ||
|
||
Given source parachain $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 parachain $S$, with native token $S^{'}$ and the initial xcm $x_0$ to be executed on $S$. | ||
- The native currency $P^{'}$ (DOT) of the Polkadot relay chain, and $E^{'}$ (ETH) of Ethereum. | ||
- Suppose that the user agent chooses relayer reward $r$ in $E^{'}$. | ||
- Suppose that the exchange rates are $K_{P^{'}/S^{'}}$ and $K_{E^{'}/S^{'}}$. The user agent chooses a multiplier to | ||
$\beta$ to cover volatility in these rates. | ||
|
||
Apply the following sequence operations: | ||
|
||
1. Dry-run $x_0$ on $S$ to receive xcm $x_1$ and cost $a$ in $S^{'}$ | ||
2. Dry-run $x_1$ on AH to receive xcm $x_2$ and cost $b$ in $P^{'}$ (DOT) | ||
3. Dry-run $x_2$ on BH to receive command $m$ and cost $c$ in $P^{'}$ (DOT) | ||
4. Dry-run $m$ on Ethereum to receive cost $d$ in $E^{'}$ (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$ | ||
|
||
```text | ||
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: | ||
|
||
```text | ||
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 | ||
|
||
```text | ||
*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: | ||
|
||
1. Pay fees for local execution using `PaysFees` | ||
2. 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 DOT, 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 $m$ is committed to binary merkle tree $M_n$. | ||
- The transferred asset is parsed from `ReserveAssetDeposited` , `WithdrawAsset` or `TeleportedAssetReceived` | ||
instructions for the local, destination and teleport asset transfer types respectively. | ||
- The original origin is preserved through the `AliasOrigin` instruction. This will allow us to resolve agents for the | ||
case of `Transact`. | ||
- 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 `SetAssetClaimer` and allow the provided location to claim the assets on | ||
BH in case of errors. | ||
- Given relayer reward $r$ in WETH, set storage $P(\mathrm{hash}(m)) = r$. This is parsed from the `WithdrawAsset` and | ||
`PayFees` instruction within `ExportMessage`. | ||
|
||
Note that WETH on AH & BH is a wrapped derivative of the | ||
[WETH](https://etherscan.io/token/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2) 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. | ||
|
||
```text | ||
!WithdrawAsset(DOT, 10) | ||
!PayFees (DOT, 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 | ||
|
||
```text | ||
!WithdrawAsset(DOT, 10) | ||
!PayFees (DOT, 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 | ||
|
||
1. A relayer _Charlie_ inspects storage $P$ to look for new messages to relay. Suppose it finds $\mathrm{hash}(m)$ | ||
giving reward $r$. | ||
2. The relayer queries $m$ from $M$ and constructs the necessary proofs. | ||
3. The relayer dry-runs m on Ethereum to decide whether the message is profitable to deliver. | ||
4. The relayer finally delivers the message together with a relayer-controlled address $u$ on 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: | ||
|
||
1. The command $m$ is removed from storage items $M$ and $P$. | ||
2. The relayer reward is tracked in storage $R$, where $R(u)$ is the accumulated rewards that can be claimed by account | ||
$u$. | ||
|
||
## Ethereum→Polkadot | ||
|
||
### 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 Polkadot to | ||
Ethereum direction. | ||
|
||
1. 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. | ||
2. It must allow specifying a destination which is `Address32`, `Address20` or a custom scale-encoded XCM payload that | ||
is executed on the destination. This is how we will support `Transact` , the User Agent/UX layer can build a | ||
scale-encoded payload with an encoded transact call. | ||
3. The same interface is used for both PNA (Polkadot 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. | ||
|
||
```solidity | ||
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$: | ||
|
||
```solidity | ||
send( | ||
3022, // KILT Para Id | ||
Address32(0x0000....), | ||
[(ReserveWithdraw, KLT, 100)], | ||
10, // WETH | ||
) | ||
``` | ||
|
||
```solidity | ||
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 Polkadot 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](https://www.notion.so/Step-2-User-agent-estimates-fees-113296aaabef8159bcd0e6dd2e64c3d0?pvs=21). | ||
|
||
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. | ||
|
||
```text | ||
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 | ||
``` | ||
|
||
```text | ||
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. | ||
|
||
```text | ||
*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 | ||
``` | ||
|
||
```text | ||
*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 parachain runtimes will need to allow: | ||
|
||
1. `AliasOrigin` can behave like `DescendOrigin`. This is safe because it respects the hierarchy of multi-locations and | ||
does not allow jumping up. Meaning no escalation of privileges. | ||
1. Example location `Ethereum` can alias into `Ethereum/Alice` because we are descending in origin and this | ||
essentially is how the `DescendOrigin` instruction works. | ||
2. `AliasOrigin` must 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 parachains. | ||
|
||
`AliasOrigin` will be inserted by every `InitiateAssetTransfer` instruction on the source parachain, 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/pull/122) | ||
|
||
[https://github.com/polkadot-fellows/RFCs/blob/main/text/0100-xcm-multi-type-asset-transfer.md](https://github.com/polkadot-fellows/RFCs/blob/main/text/0100-xcm-multi-type-asset-transfer.md) | ||
|
||
## Parachain Requirements | ||
|
||
1. Pallet-xcm.execute enabled. | ||
2. XCM payment and dry run apis implemented. | ||
3. Must accept WETH needed for fees. Though in future user agents can inject `ExchangeAsset` instructions to obtain | ||
WETH. | ||
4. Trust AH as a reserve for bridged assets. | ||
5. Origin Preservation rules configured which allow asset hub to impersonate bridged addresses. |