Skip to content

Commit

Permalink
Update README.md
Browse files Browse the repository at this point in the history
  • Loading branch information
0xfuturistic committed Aug 3, 2024
1 parent 2593ccb commit 2684123
Showing 1 changed file with 25 additions and 24 deletions.
49 changes: 25 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,37 @@
# 🥪🦄 Sandwich-Resistant Uniswap V2

<img src="unicorn.png" width="60%">
<img src="unicorn.png" width="55%">

> **Background:** Matheus V. X. Ferreira and David C. Parkes. _Credible Decentralized Exchange Design via Verifiable Sequencing Rules._ URL: https://arxiv.org/pdf/2209.15569.
We modify Uniswap V2 to implement the Greedy Sequencing Rule (GSR), a verifiable sequencing rule that prevents sandwich attacks and provides the following guarantees (Theorem 5.2, p. 22):
1. The user's execution price is at least as good as if their swap was alone in the block.
2. If their price is worse, the proposer doesn't profit from including the transaction.
3. The protocol can detect rule violations (i.e., the proposer didn't follow the GSR).
Uniswap V2 is minimally modified to implement the Greedy Sequencing Rule (GSR), a verifiable sequencing rule that mitigates sandwich attacks.

## How It Works
The swap function in `UniswapV2Pair` is modified to enforce the GSR, which works as follows (Algorithm 3, p. 20):
1. Initialize an empty execution ordering T.
2. Partition outstanding transactions into buy orders (B_buy) and sell orders (B_sell).
3. While both B_buy and B_sell are non-empty:
## The Greedy Sequencing Rule

The GSR is a specific implementation of a verifiable sequencing rule that provides strong execution guarantees for users. For a user transaction $A$ that the proposer includes in the block (Theorem 5.2, p. 22), it guarantees that for a valid ordering it must be that either:

1. the user efficiently detects the proposer did not respect the sequencing rule, or
2. the execution price of $A$ for the user is at least as good as if $A$ was the only transaction in the block, or
3. the execution price of $A$ is worse than this standalone price but the proposer does not gain when including A in the block.

It works as follows:

1. Initialize an empty execution ordering $T$.
2. Partition outstanding transactions into buy orders ($B_{buy}$) and sell orders ($B_{sell}$).
3. While both $B_{buy}$ and $B_{sell}$ are non-empty:
- If current token 1 reserves ≥ initial token 1 reserves:
- Append any order from B_buy to T
- Append any order from $B_{buy}$ to $T$
- Else:
- Append any order from B_sell to T
4. If any orders remain, append them to T in any order.
- Append any order from $B_{sell}$ to $T$
4. If any orders remain, append them to $T$ in any order.

This rule exploits a key property of two-token liquidity pools: at any state, either all buy orders or all sell orders will receive a better execution price than at the initial state (Theorem 5.1 "Duality Theorem", p. 20).
The rule exploits a key property of two-token liquidity pools: at any state, either all buy orders or all sell orders will receive a better execution price than at the initial state (Theorem 5.1 "Duality Theorem", p. 20).

### Algorithm
## Implementation

The implementation involves adding 24 lines of code to the swap function. After each swap, the protocol runs the following algorithm:
1. If this is a new block, set the sequencing rule info, setting the initial state to the pair's current reserves of token 0.
2. Else, check whether we had already run out of buy or sell orders before the swap.
- If we had, validate that the type (i.e., buy or sell) of the swap matches the type of the swaps in the tail of the permutation under the GSR.
3. Else, if we hadn't run out of buy or sell orders before the swap, compare the current reserves to the initial state to determine the required order type (i.e., a buy or sell) according to the sequencing rule.
- If the swap types don't match, register that we must have run out of buy or sell orders. The type of the swap now makes up the type of the swaps in the tail of the permutation under the GSR.
The goal is for the algorithm to check new transactions in real-time, determining whether a new transaction violates the GSR. This differs from the original algorithm in the paper, which checks the entire order of transactions in a block. Instead of checking all the past transactions every time we receive a new one to determine whether the order is valid, we assume the state up to that point is valid and only check the new transaction. If the new transaction is valid, the order after the inclusion of the transaction is valid. If the new transaction is invalid, the order is invalid. This approach is more efficient than checking the entire block, and leads to a constant-time verification algorithm for new swaps, instead of a linear-time algorithm, as in the original paper.

The implementation of the entire algorithm involves adding 24 lines of code to the swap function.

```solidity
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
Expand Down Expand Up @@ -63,19 +64,19 @@ function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data)
}
```

This implementation is computationally efficient (Definition 3.2, p. 13) and verifiable (Definition 3.4, p. 15), allowing anyone to check if the execution ordering follows the GSR. It does not have any external depedencies, and it does not dependent on any off-chain computation, trust in external parties, or additional infrastructure.
This implementation is computationally efficient and verifiable, allowing anyone to check if the execution ordering follows the GSR. It does not have any external depedencies, and it does not dependent on any off-chain computation, trust in external parties, or additional infrastructure.

## Benefits

This solution is effective because it:

- Protects against sandwich attacks (Theorem 5.2, p. 22).
- Protects against sandwich attacks.
- Has a constant-time overhead on the swap function.
- Preserves atomic composability.
- Requires minimal changes to the existing Uniswap v2 codebase.

## Limitations

While the GSR prevents classic sandwich attacks, it doesn't eliminate all forms of miner extractable value (MEV). The paper proves that for any sequencing rule, there exist scenarios where miners can obtain risk-free profits (Theorem 4.2, p. 17-18).
While the GSR prevents classic sandwich attacks, it doesn't eliminate all forms of MEV. The paper proves that for any sequencing rule, there exist scenarios where proposers can obtain risk-free profits (Theorem 4.2, p. 17-18).

Multi-block MEV remains a consideration, where a proposer that proposes consecutive blocks could influence the initial price used by a given swap. Nevertheless, this can potentially be addressed by updating this initial price less frequently or using a moving average over several past blocks instead. Either approach would raise the costs associated with this vector.

0 comments on commit 2684123

Please sign in to comment.