feat: initialize Kurdistan SDK - independent fork of Polkadot SDK
This commit is contained in:
@@ -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
|
||||
|
||||
## 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 token $S^{'}$ and the initial xcm $x_0$ to be executed on $S$.
|
||||
- The native currency $P^{'}$ (HEZ) of the Pezkuwi 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^{'}$ (HEZ)
|
||||
3. Dry-run $x_2$ on BH to receive command $m$ and cost $c$ in $P^{'}$ (HEZ)
|
||||
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 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 $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(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
|
||||
|
||||
```text
|
||||
!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
|
||||
|
||||
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→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.
|
||||
|
||||
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 (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.
|
||||
|
||||
```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 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](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 teyrchain 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 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/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)
|
||||
|
||||
## Teyrchain 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.
|
||||
Reference in New Issue
Block a user