Skip to content
Open
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
/MD/md-4/ @l-monninger
/MD/md-5/ @l-monninger @apenzk
/MD/md-15/ @l-monninger @apenzk
/MD/md-69/ @franck44
/MD/md-93/ @andygolay

## MIPs
Expand All @@ -22,6 +23,7 @@
/MIP/mip-58/ @primata @apenzk
/MIP/mip-61/ @apenzk @musitdev
/MIP/mip-60/ @franck44 @apenzk
/MIP/mip-69/ @franck44
/MIP/mip-84/ @apenzk
/MIP/mip-88/ @apenzk @Primata
/MIP/mip-91/ @apenzk
Expand Down
79 changes: 79 additions & 0 deletions MD/md-69/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# MD-69: Bridge fees

- **Description**: This MD provides some background on the bridge fees mechanism requirements.
- **Authors**: [Franck Cassez]([email protected])
- **Approval**: :white_check_mark:

## Overview

To use the Movement Mainnet, users need to pay for _transaction fees_ in \$L2MOVE tokens. As the \$MOVE token is minted on Ethereum (\$L1MOVE), users need to transfer some \$L1MOVE tokens from the Ethereum network to the Movement Mainnet (to get \$L2MOVE), and back.
This asset transfer capability is provided by the Movement Mainnet **native bridge**, which is a [lock/mint bridge](https://chain.link/education-hub/cross-chain-bridge#types-of-cross-chain-bridges) described in [MIP-58](tdc).

This bridge (transfer) operation requires several steps, two of which are transactions:

1. initiate a transfer on one chain;
2. finalize the transfer on the other chain.

Step (1) is performed by the user on the network (Ethereum or Movement), and step (2) is performed by the bridge operator via a _relayer_ (**ADD link to relayer MIP here**).
As a result, the operator has to cover the transaction fees for the _finalize_ step.
When transferring from Ethereum to Movement, the fees are expected to be very low so we can consider that the operator can cover them using a pool of transaction fees collected from the users on the Movement Mainnet.
In the other direction, bridging from Movement to Ethereum, the Ethereum fees are expected to be higher, and the operator may not be able to cover them all.

> [!IMPORTANT]
> We need to define a mechanism to collect the Ethereum fees from the users.

> [!TIP]
> This MD describes the requirements for this fee collection mechanism when bridging from Movement Mainnet to Ethereum.

## Desiderata

### D1: User bridges from Movement Mainnet to Ethereum

**User Journey**: A user can initiate a _bridge_ transaction on the Movement Mainnet.

**Description**: When a user initiates a bridge transaction from the Movement Mainnet to Ethereum, the user pays the Movement Mainnet transaction fees in \$L2MOVE tokens. They request a transfer of \$L2MOVE to $L1MOVE.

**Justification**: The user should be able to transfer their tokens from the Movement Mainnet to Ethereum.

### D2: Operator covers the Ethereum transaction fees

**User Journey**: Once initiated, the bridge transaction is completed by the operator of the Movement Mainnet via a _relayer_.

**Description**: The operator must cover the Ethereum transaction fees for the completion step.

**Justification**: Only the operator can finalize the transfer on the Ethereum network.

### D3: Operator collects the Ethereum transaction fees from the user

**User Journey**: The operator collects the Ethereum transaction fees from the user in \$MOVE.

**Description**: The operator collects the Ethereum transaction fees from the user in \$MOVE tokens.

**Justification**: We must collect the fees in \$MOVE tokens. The fees cannot be collected in \$ETH as

1) we cannot ask the user to approve the relayer to transfer \$ETH to them; and
2) even if we could, the user may not have enough \$ETH and they initiated the transfer on the Movement Mainnet.

### D4: Robustness of the fee collection mechanism

**User Journey**: The operator pays the Ethereum transaction fees in \$ETH and charges the user for bridge fees in \$MOVE. The operator must not run a deficit

**Description**: The operator must ensure that the fees collected from the users are sufficient to cover the Ethereum transaction fees to complete transfers. This implies that

1) we accurately estimate the Ethereum fees in advance, and
2) we also estimate the ratio \$MOVE/\$ETH.

**Justification**: The operator must not run a deficit. We must decide how much the user is charged when they initiate a transfer at time $t$ from the Movement Mainnet to Ethereum.
The counterpart complete transactions will be executed on the Ethereum at a later time $t' > t$. To cover the transaction fees, we have to

1) estimate the Ethereum fees at $t'$, and
2) the ratio \$MOVE/\$ETH at $t'$.

### D5: Minimize the user fees

**User Journey**: Minimize the bridge transfer fees for the user.

**Description**: We have to minimize the fees for the users.

**Justification**:
The user pays the minimum fees to transfer their tokens from the Movement Mainnet to Ethereum. If a user transfers 10 \$L2MOVE, they expect to receive $10 - \epsilon$ \$L1MOVE on Ethereum, with $\epsilon$ small.
1 change: 1 addition & 0 deletions MD/md-bridge-fees/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
delete this file
262 changes: 262 additions & 0 deletions MIP/mip-69/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
# MIP-69: Bridge Fees

- **Description**: How to charge bridges fees when bridging \$L2MOVE to \$L1MOVE.
- **Authors**: [Franck Cassez](mailto:[email protected])
- **Desiderata**: [MD-69](../../MD/md-69/README.md)
- **Approval**: Stagnant

## Abstract

As pointed out in [MD-69](../../MD/md-69/README.md), bridging \$MOVE tokens from Movement Chain to Ethereum (L1) requires the _relayer_ to pay for Ethereum (L1) part of bridge transaction, the _L1 fees_.
Ethereum's transaction fees are substantially higher than Move Chain fees, and we may not sponsor the L1 fees but instead charge the user
for the L1 fees.
As a result the operator has to charge some transfer fees on top pf the Move Chain _initiate_ transaction fees.

The requirements for the bridge fees from Move Chain to L1 are gathered in [MD-69](../../MD/md-69/README.md) and this
MIP proposes some strategies to satisfy these requirements.

More specifically, this MIP covers the following topics:

- adjusting bridge fees dynamically,
- sources of uncertainty in fees,
- using oracles to collect trustworthy and recent estimates of prices,
- possible strategies to avoid/reduce deficit.

## Motivation

### Bridging mechanism

Bridging \$L2MOVE to \$L1MOVE involves the 4 following steps (Figure 1):

1. User request a transfer of `n` \$L2MOVE, which initiates a _burn_ transaction on Move Chain. D1 in [MD-69](../../MD/md-69/README.md).
1. the _burn_ transaction executes and completes and an event `FundsBurnt!` is appended to the Move Chain logs. `n` tokens are burnt on Move Chain.
1. The relayer _relays_ the `FundsBurnt!` event to the L1 by submitting a _unlock_ transaction.
1. The _unlock_ transaction executes and completes and an event `FundsUnlocked!`. `n` tokens are transferred from the L1 Bridge to the user (on L1).

---

![alt text](steps.png)
---

**Figure 1.** The 4 steps to bridge funds from Move Chain to Ethereum (L1)

---

### Fees

The relayer pays the L1 fees (D2, [MD-69](../../MD/md-69/README.md)) and it is hard to predict them _at the time_ the user requests the funds transfer. Indeed the fees depend on the L1 _gas price_ that evolves dynamically according to rules that take into account the network congestion (how full blocks are, gas-wise), which is described in [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md). On top of that, the L1 fees are paid in \$ETH by the relayer and the price of \$ETH is also time-dependent.

Figure 2 below examplifies the changes in gas price, \$ETH price and their effect on the L1 fees paid by the relayer.

---

![alt text](timediag.png)

---

**Figure 2**: L1 fees fluctuate per block according to [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md)

---

> [!WARNING]
> To cover the L1 fees, we need to collect some fees from the user (D3, [MD-69](../../MD/md-69/README.md)), and adjust the fees to 1) cover the costs incurred by the relayer to submit the _unlock_ transaction (D4, [MD-69](../../MD/md-69/README.md)), and 2) minimise the difference between what we charge the user and what we actually pay (D5, [MD-69](../../MD/md-69/README.md)).

This MIP proposes strategies to satisfy the requirements D1-D5, [MD-69](../../MD/md-69/README.md).

## Specification

> [!NOTE]
> We charge the user some bridging fees to cover the cost of the (relayer) L1 fees. This section reviews some possible solutions.

As the fees are charged in \$MOVE but the transaction on the L1 are paid in \$ETH, we need to track the ratio $r = \text{\$ETH price}/\text{\$MOVE price}$.
Assuming we have two pools of tokens for the Move to L1 bridge, $A$ (\$MOVE) and $B$ \$ETH, we pay with the tokens in $B$ and get revenues in $A$.
We should try to ensure that

$$
Balance(A) \times \text{\$MOVE price} \times r \approx Balance(B) \times \text{\$ETH price}
$$
to balance the funds in the two pools.

More precisely we may try to bound the difference in value in the two pools by a given constant:

$$
| Balance(A) \times \text{\$MOVE price} \times r - Balance(B) \times \text{\$ETH price} | < \text{MaxDeficit} \mathpunct.
$$

### Request to transfer \$L2MOVE (Move Chain)

1. The user pays for the Move Chain request transaction fees, `ReqTxFees` in \$L2MOVE,
1. The user MUST be able to request the transfer of `n` \$L2MOVE provided their account balance is larger or equal to `n` (after taking into the `ReqTxFees`).
1. The user SHOULD be charged some _bridging fees_ and MUST be able to provide a maximum fee bound, `MaxBridgeFee`, they agree to be charged.
1. `n` MUST be larger than `MaxBridgeFee`
1. The bridging fees MUST be charged in \$MOVE and deducted from the user initial amount `n` during the transfer of assets.

### Completion of transfer (L1)

6. The relayer MUST pay the L1 fees, `L1Fees`, in \$ETH.
1. The user MUST get at least `n - MaxBridgeFee` \$L1MOVE.
1. The relayer (operator) gets `L1BridgeFees` \$MOVE, and `L1BridgeFees` MUST be at most `MaxBridgeFee`.
1. The L1 fees `L1Fees` (in \$ETH) paid by the relayer SHOULD be _covered_ by the equivalent bridge fees `L1BridgeFees` in \$MOVE.

The previous requirements imply:

10. We MUST track or estimate the \$ETH price and the L1 gas price.
10. We MUST track or estimate the \$MOVE/\$ETH ratio.

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

## Reference Implementation

### Request to transfer \$L2MOVE (Move Chain)

The user (or a dedicated front-end) submits a transaction to request the transfer of `n` \$L2MOVE with a maximum bridge fees expressed in \$MOVE.
As a result we expect the `initialize` (bridge transfer) on the Move Chain to have the following signature:

```Rust
/// Provides MOVE native bridge
module native_bridge {

/// User submits a transfer request
///
/// @param user The initiator on Move Chain of the transfer
/// @param recipient The recipient on L1 of the transfer
/// @param amount The amount (\$L2MOVE) to be transferred
/// @param max_fess The maximum amount the `user` agrees to be charged for bridging fees.
///
/// Aborts if (not exhaustive): max_fees > amount, user balance is less than `amount`.
public entry initialize(user: &signer, recipient: ethereum_address, amount: u64, max_fees: u64)
{
assert!(max_fees <= amount>);
...
// 3 in Figure 1, FundsBurnt
event::emit_event(user, recipient, actual_amount, max_fees,...);

}
...
}
```

To compute the bridge (and charge) fees we have three options:

- **[Option 1]**: compute the bridge fees at the time of the request. In that case the `actual_amount` takes into account the bridge fees. **The contract on the Move Chain must keep track of the L1 gas price and the \$ETH/\$MOVE prices.**
- **[Option 2]**: the bridging fees are computed at the time of the `unlock` transaction, and the fees are a parameter of the `unlock` (Figure 1). The Relayer computes the fees and relays the `actual_amount`. **The relayer must track the L1 gas price and the \$ETH/\$MOVE prices.**
- **[Option 3]**: the bridging fees are computed and **charged in the L1** contract that executes the `unlock` (Figure 1). **The L1 contract must track the L1 gas price and the \$ETH/\$MOVE prices.**

> [!IMPORTANT]
> Overall, the bridging fees can be computed at different times, but we charge them in the L1 `unlock` (complete transfer) transaction.
> We also need an oracle to provide a trustworthy estimate of the \$ETH price and L1 gas price.

> [!NOTE]
> The resource consumption of the `unlock` transaction is **fixed** and depends on the _size_ of the `calldata` (fixed for unlock`). The computation of the L1 transaction fees is explained in [MIP-16](https://github.com/movementlabsxyz/MIP/tree/gas-fee-calculation/MIP/mip-16).

### Comparisons

#### Quality of the estimates

The quality of the estimates for \$ETH price ad L1 gas price depends on two main factors:

- how and where we get the estimates. We can assume that we use trusted _oracles_ or an average on many oracles to solve this issue.
- the _age_ of the estimates. The older the estimates the less accurate they may be.

As a result, if we use the same oracle device for the three options, the quality of the estimates solely depends on the _age_ and:

Option 3 > Option 2 > Option 1.

#### Flexibility in implementation

Options 1 and 3 require the contracts to query the oracle. This is doable to query _on-chain_ oracles as explained in this [Ethereum developers section](https://ethereum.org/en/developers/docs/oracles/).
These options are _trusted_ provided the oracle is trusted too.

Option 2 is an _off-chain_ solution and may provide more flexibility but probably requires that **the relayer** can query the estimates and **is trusted**.

### Completion and adjustment of bridge fees

If we follow Options 1 (resp. 3), the bridging fees are adjusted either in the Move (resp. Solidity) contract that provides the `initialize` (resp. `unlock`) function.
For option 2, the Relayer keeps track of the estimates and implements the adjustment.

### When do we make an adjustment?

There are several options:

- at each request (every transaction), or every $k$ transactions,
- at time intervals (to be defined).

> [!IMPORTANT]
> In between two adjustments, the fees remain constant. We refer to the **adjustment window** as the intervals in which the fees are constant.

The choice may depend on the frequency of bridging requests: if bridging are rare, we may update at every request at most.
If they are frequent and we cannot afford to query oracles too frequently, we may decide on a time interval.

### Strategies to adjust the bridge fees

To adjust the bridge fees dynamically, we may want to ensure:

$$
| Balance(A) \times \text{\$MOVE price} \times r - Balance(B) \times \text{\$ETH price} | < \text{MaxDeficit} \mathpunct.
$$

We need to design a _strategy_ (in the sense of a two-player game) that guarantees the above safety property.

> [!WARNING]
> Ideally, our solution should be better with recent estimates rather than older ones, but it may not be easy to guarantee.

The most powerful state-based strategy can be obtained by recording the values of the L1 gas price, \$ETH and \$MOVE prices since the genesis.
It also depends on the funds in both pools $A$ and $B$.
However, we cannot reasonably store all these data, and we have to base our strategy on a smaller quantity of information.

To start with we may use:

- the estimated max gas price, \$ETH/\$MOVE prices when the next `unlock` transaction will be processed,
- the current difference between the revenues (in \$MOVE) and expenses (in \$ETH), i.e. the gap surplus or deficit in our pools,
- the time (seconds or blocks or transactions) we would like to cover a deficit.

As discussed during the co-location we may start with a simple strategy limited to increasing the fees by a constant factor. The decision to increase or decrease can be taken using oracles or simply based on the funds in pools $A$ and $B$.

### Simple strategies

Denote $Balance(A, B)$ the difference of the balances, in USD, of pools $A$ and $B$.

$$ Balance(A, B) = Balance(A).USD - Balance(B).USD \mathpunct. $$

If $Balance(A, B) \geq 0$ we are running a _surplus_, otherwise a _deficit_.

#### Balance based strategy

A simple strategy is to increase/decrease the bridge fees by a $K$ (\$MOVE) tokens every time we update the fees.
If $Balance(A, B) == 0$, we keep the fees as they are, if $Balance(A, B) > 0$, we increase and otherwise decrease.

Assume we want to adjust the fees according to:

- the most recent value of $Balance(A, B)$,
- estimates of L1 gas price, \$ETH/\$MOVE prices in the next window.

If we run a deficit we want to cover it by the next time window, and if we run a surplus, we may keep the fees unchanged or decrease them.

#### Estimate and balance based strategies

## Verification

Needs discussion.

---

## Change Log

---

## Appendix

### A1: Notes from [MIP-58](https://github.com/movementlabsxyz/MIP/pull/58)

!!! warning These notes were present in MIP-58. However they do belong here instead. Please consider how to incorporate these.

- When bridging from L1 to L2, the protocol, through the Relayer, sponsors the gas cost on Movement. We do not need to make any modification on contracts or Relayer to support it.
- When bridging from L2 to L1, we have a few viable solutions but it's preferable to highlight two.
1. Relayer sets a fee on L2, a global variable that can be set at any given time. Considering that block validation time on L1 is bigger than on L2, it becomes a viable approach since L2 can rapidly adjust the fee according to the current block and always charge an above L1 gas cost fee to attempt that the bridge is net positive. \$L2MOVE is deducted from the amount of tokens that are currently being bridged and transferred to a funds manager. This gives the protocol a very reliable way to estimate how much MOVE will be charged and feed to the user a precise amount of tokens. However, bridge transfers cannot always immediately be initiated on the L1, e.g. if there is a surge in transactions.
2. Enable the Relayer to specify on the L1 `completeBridgeTransfer` transaction, the bridge fee per transaction. The amount is deducted from the total amount of tokens that were bridged and transferred to a funds manager. The dangerous situation is that we expect is this takes much more than 10 minutes before the transfer can occur, and this could lead to a big disparity between the expected amount of funds and the actual amount of tokens received.

---

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).
Binary file added MIP/mip-69/steps.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added MIP/mip-69/timediag.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.