Open rpanic opened 1 month ago
In this design, there exist multiple communication channel from a parent contract to some child contract. In some instances, integrity of those calls has to be ensured since some action executed on the child contract might rely on some validation to happen on the parent contract.
This is the case in the following instances:
Unfortunately, the account update protocol doesn't give us tools to make statements about the parent account update in a given tree, therefore we have to use a workaround for that
To still accomplish this, the following protocol is implemented:
Through that protocol, the child can be sure that a certain call has indeed originated at the parent with the exact data that the child receives.
The authorization data should be a hash of
This allows us to not reset the "authorization" state field in update (3), since the authorization can only be used once, saving us one proof. But: This assumes that the child zkapp implements a state transition $S1 \rightarrow{ST} S_2$ that has no valid transition $S2 \rightarrow{ST} S_3$, i.e. applying the same transition twice is impossible.
This spec outlines the interactions and protocol that we use for bridging custom tokens (tokens that are not $Mina) to and from protokit appchains.
This work is based on the Mina-settlement. The spec for this, although very slightly outdated can be found here https://github.com/proto-kit/framework/issues/83 Also related work: https://github.com/MinaProtocol/MIPs/blob/main/MIPS/mip-0004-zkapps.md#token-mechanics
For custom token bridging we have the following components:
Token Manager
). It manages the bridged tokenbridged token
with tokenIdtokenId
Bridging contract
)Bridge Token Holder
). It manages the withdrawal token with idwithdrawTokenId
.General approach
The high level design of custom token bridging keeps the same rough flow as the native token bridging. But since token managers because part of the equation and we want to make the least amount of assumptions about the tokens themselves, the protocol still requires some adaptations.
Custom tokens are controlled entirely by the token owner, which will be a zkapp in most cases. However, even though a token standard exists, we don't want to lock in the exact circuit that these token owners operate on, instead we push the complexity of that to the users. For that, this protocol essentially decouples the token operations from the main appchains operation so that we can permit all forms of token owners to interact with protokit appchains without risking potential deadlocks or liveness attacks.
The already established protocol fortunately has properties that nicely go hand in hand with these requirements. Mainly, deposits are push-based and have to be processed eagerly, while withdrawals, since they can be processed permissionlessly, only have to be processed in-order in the first stage, and are pull-based in the second stage (more about that below at Withdrawals).
Bridge token holder
For every bridged token, the settlement contract has to deploy another contract to the respective custom token. This deployment happens to the same address and happens on every deposit. Authorization of bridge token holders is covered in the "Authorization" section
The responsibility of the bridge token holder is two fold:
Deposits
For deposits, the user creates a AU on the bridging contract calling
deposit(tokenid, amount)
. This AU will then be given to the token manager to approve it, therefore authorizing it's token operations.Withdrawals
For withdrawals, let's look back at the original custom-token polling-based withdrawal mechanism we already implemented for $Mina. In a nutshell, the withdrawal has two phases, after settlement that includes a new batch of outgoing messages, we iterate through these messages and for all messages of type withdrawal, we mint the amount to the user in a custom token where the bridging contract is the token owner. In the second step, the user goes back to the bridging contract, burns the custom token and receives the burned amount in mina from the bridging contract.
This mechanism stays for custom tokens. Now however, we spin up a separate instance of that mechanism per token, where every instance only takes care of their own mints and redeems.
Account / contract structure
For a given instance with token id
tokenId
, the desired account structure looks like thisCustom token withdrawals flow
Modified withdrawal map on the L2
Withdrawals are initiated on the L2 by putting the withdrawal's data into a StateMap, which means it will be part of the L2's state and therefore state root. However, with the introduction of custom tokens, we now track the counters and withdrawals separately for every token.
A withdrawal is a struct of type
Step 1: Rollup of withdrawal actions + mint of withdrawal tokens
Push new state root to the bridge token holder After any settlement, anyone can invoke a function on the token bridge contract to pull the newest settled state root from the settlement contract on the default token.
Authorizing the new state root
Since the settlement happens on the settlement contract and not on the custom token, the contract on the token ledger has to somehow know that the state root is valid and indeed came from the settlement contract and not some other caller. Unfortunately we cannot make statements about the parent account update in the zkapps protocol, therefore we have to establish this backwards communication channel by using a child account update that we carefully craft to guarantee us the value of that state root.
The account update layout of this transaction looks like this
Iteratively mint withdrawal tokens
This happens pretty much the same as the native version of minting, with the difference that the two account updates (call of Bridge Token Manager + minting transaction) need to be approved by the Token Manager
The number of mints per transaction is determined by the limit that the zkapps protocol enforces, currently 9.
Step 2: Redeem of custom tokens
For the redeeming, two things have to happen atomically
The AU layout we are aiming for looks like this:
Thanks to @mrmr1993 for his help and input