/solana-mev

Decentralized MEV extraction from inside the validator

Primary LanguageRustOtherNOASSERTION

Solana-MEV

Solana-MEV is a modification of the upstream Solana validator that handles certain MEV opportunities right in the banking stage of the validator. Running this validator instead of the upstream one can generate a small amount of additional income.

Warning: This is a proof of concept. Chorus One has used it in production for several months, but it is not at a level of polish where it is a drop-in replacement for the upstream validator.

Further reading and other media

Status

Chorus One has developed this prototype as a proof of concept. Currently it is able to take arbitrage opportunities on Orca non-concentrated-liquidity pools. However, with the current trading volume on Solana, developing Solana-MEV is not sustaintable. Even if we were to cover the majority of Solana AMMs, as of December 2022, a validator with ~1% of leader slots would extract only a few dollars per day.

At this point, Chorus One does not plan to actively invest resources in developing Solana-MEV further. We do hope that our prototype can serve as evidence that a centralized marketplace is not a technical prerequisite for MEV extraction, and we hope to get a discussion started around possible MEV architectures on Solana to ensure that MEV benefits the entire community.

Details

We are interested making arbitrages on Automated Market Maker (AMM) pools. When we are a validator during block production, we look at every transaction to see if the program id is one of the configured known program ids. If a transaction interacts with a known program id, we get all pools balances before and after the transaction executed and log this information. Futhermore, we scan the pool for arbitrage opportunities only after the transaction is executed, in that way we forgo making sandwich arbitrage transactions, i.e. acting in between the user's transactions.

Our strategy for arbitraging is checking for every configured path that start and finish at the same token if there could be a transaction that generates profit.

  • We introduce a config file that statically configures all cycles to watch for arbitrage opportunities. The config file is parsed and injected into the banking stage when MEV is enabled.
  • In BankingStage::process_and_record_transactions, we introduce an additional output: a single optional transaction, that should extract the MEV created by the transaction batch.
  • At the call site, BankingStage::process_transactions, if an MEV transaction was produced, we execute it.
  • runtime/src/mev.rs and arbitrage.rs contain methods that given a set of AMM pools, compute optimal input amount that maximizes profit. When the profit is smaller than the transaction fee, or even negative, we bail out.

Solana transactions are organized in batches called Entries that can execute in parallel. Accounts in these entries can be referenced only once for write-access and multiple times for read-access. Due to this fact, when we encounter an arbitrage opportunity, we create a new Entry just for that opportunity, this Entry ought to be executed immediately after the transactions that resulted in the arbitrage, but it might happen that the arbitrage transactions are not executed atomically after we spotted the arbitrage, see more details in the following limitations section.

Limitations

We have some limitations when executing arbitrage transactions. The main one is that we don't lock accounts in-between entries and it might happen that a worker thread executes other entries in-between the entry produced for arbitrage, this can lead to an incorrect behavior of the program. This issue is remedied by defining all minimum_output of the arbitrage instructions (except the first) as the input of the previous instruction. As an extra guarantee that the transaction produces a profitable transaction, we check that the transaction is profitable. Transactions that execute but fail are not included in the block. Note, it is possible to lift this limitation, but the changes with respect to the upstream validator would become more invasive than our current patches.

We are limited to a maximum of three instructions per transaction, this is due to Solana's limitations on the transaction's length, one could extend an arbitrage to spawn over multiple sequential transactions to circumvent the limitation.

Comparison to alternatives

Compared to jito-solana, Solana-MEV differs in a few key aspects:

  • No central server. Solana-MEV does not introduce new connections to third party servers, everything happens inside the validator.
  • No mempool. Solana-MEV does not buffer transactions. It inserts its own transactions in between user transactions, but it does not change the way in which user transactions are processed. This also means that Solana-MEV has virtually zero latency impact compared to Jito, which introduces several additional network hops in the transaction processing path.
  • No transaction reordering. Solana-MEV processes transactions in the same order as upstream Solana. It does not buffer transactions, so it has no way to reorder; it only inserts its own transactions in between user transactions.
  • Built-in searcher. Solana-MEV does not rely on external searchers for identifying MEV opportunities, it has a few basic strategies built-in. A limitation of this is that the strategies are not as advanced as those of a dedicated searcher, and Solana-MEV cannot respond as quickly to changes in the ecosystem (e.g. the launch of a new AMM).

Despite the differences, Solana-MEV and Jito are not incompatible, they are complementary. Jito’s patches stream groups of entries (parts of a block) to the validator, while Solana-MEV generates those internally based on the ones it saw before. There is no fundamental technical barrier to combining the two sources, however it is unclear what the marginal benefit is.

Reward distribution

The MEV module generates transactions that increase the balance of the SPL token accounts owned by the MEV Authority (see also the configuration section below). Currently no mechanism is implemented to share those proceeds further. In the case of a staking pool such as Lido, one simple way to share the rewards would be to transfer any excess balance to the pool’s reserve.

Configuration

MEV extraction is enabled by providing the --mev-config-path command-line option to solana-validator. Without this option, the validator will run as usual. --mev-config-path should point to a TOML file with the following schema:

# File to log details about MEV opportunities and AMM pools to.
log_path = '/path/to/mev.log'

# Programs to watch for interactions. After a user transaction interacts with
# one of these programs, we check for MEV opportunities afterwards.
watched_programs = [
  # Orca Swap v1
  'DjVE6JNiYqPL2QXyCUUh8rNjHrbz9hXHNYt99MQ59qw1',
  # Orca Swap v2
  '9W959DqEETiGZocYWCQPaJ6sBmUzgfxXfqGeTEdp3aQP',
]

# Path to the keypair of the "MEV Authority". This address is the owner of all
# SPL token accounts that are involved in MEV extraction, and it signs all
# transactions generated by the MEV module. For example, if there exists a
# triangular opportunity between the pools USDC/stSOL, stSOL/stETH, stETH/USDC,
# then this address should have an associated token account for USDC, stSOL,
# and stETH. This key is optional, if not provided, we only monitor for
# opportunities but don't extract.
user_authority_path = '/path/to/keypair.json'

[minimum_profit]
# Per token mint address, the minimum profit before we generate a transaction.
# This is to ensure that we don’t execute transactions whose profit is lower
# than the cost of the transaction fees. Note that because we only execute
# transactions when the validator itself is leading, we pay the fee to
# ourselves. However, because half of the fee is burned, we still need a mimum
# of half the transaction fee (5,000 lamports currently). The number is in the
# smallest unit of the token (e.g. lamports for SOL, 1e-6 USDC for USDC).
"So11111111111111111111111111111111111111112" = 2501  # 0.000_002_501 SOL
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" = 101  # 0.000_101 USDC

# Next are the paths that we want to consider. A path is a sequence of Orca
# pools that should form a cycle. Note, due to the transaction size limit on
# Solana, it is generally not possible to use cycles of more than three hops,
# because they would need to reference too many accounts.
[[mev_path]]
name = "USDC->wstETH->stSOL->USDC"
path = [
    { pool = "v51xWrRwmFVH6EKe8eZTjgK5E4uC2tzY5sVt5cHbrkG", direction = "BtoA" },
    { pool = "B32UuhPSp6srSBbRTh4qZNjkegsehY9qXTwQgnPWYMZy", direction = "BtoA" },
    { pool = "EfK84vYEKT1PoTJr6fBVKFbyA7ZoftfPo2LQPAJG1exL", direction = "AtoB" },
]

# For every Orca pool involved, we also need to specify its details.
[[orca_account]]
_id = "stSOL/SOL"
address = "71zvJycCiY2JRRwKr27oiu48mFzrstCoP6riGEyCyEB2"
pool_a_account = "HQ2XUmQefvBdpN8nseBSWNP2D1crncodLL73AWnYBiSy"
pool_b_account = "8y8X4JuZn1MckRo5J6rirpr2Dxj1RKQshj7VzuX6dMUw"
pool_mint = "4jjQSgFx33DUb1a7pgPsi3FbtZXDQ94b6QywjNK3NtZw"
pool_fee = "7nxYhYUaD7og4rYce263CCPh9pPTnGixfBtQrXE7UUvZ"

# If we want to also extract MEV and not only monitor for opportunities, we also
# need to provide the addresses of SPL associated token accounts, owned by the
# MEV authority defined earlier, for token A and token B. These are called
# "source" and "destination" respectively, though the roles can be reversed if
# the pool is used with the BtoA swap direction.
source = "..."
destination = "..."

Future work

  • For technical reasons, inserting the MEV-extracting Entry currently does not happen atomically — it is not guaranteed that the entry lands directly after the one that created the opportunity. Doing so is possible, but requires more invasive changes to the codebase that would make the diff more difficult to maintain. Solana-MEV does ensure that it does not include transactions that would make a loss, nor transactions that fail.
  • Currently Solana-MEV only observes Orca non-concentrated-liquidity pools, a logical next step would be to watch concentrated liquidity pools as well.

License

Our modifications are licensed under the Apache 2.0 license like the original Solana validator. As stated in the license, Chorus One is not liable for damages that result from using this software.