diff --git a/bridges/snowbridge/docs/v2.md b/bridges/snowbridge/docs/v2.md new file mode 100644 index 000000000000..8ec440c47cec --- /dev/null +++ b/bridges/snowbridge/docs/v2.md @@ -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.