BTC
ETH
HTX
SOL
BNB
View Market
简中
繁中
English
日本語
한국어
ภาษาไทย
Tiếng Việt

Synthetix MEV をキャプチャする詳細なプロセスの簡単な分析

ECN以太坊中国
特邀专栏作者
この記事は約8520文字で、全文を読むには約13分かかります
MEV はとらえどころのないスーパープログラマーの領域だと思われることがありますが、必ずしもそうである必要はありません。楽しくて刺激的なこともあります。
AI要約
MEV はとらえどころのないスーパープログラマーの領域だと思われることがありますが、必ずしもそうである必要はありません。楽しくて刺激的なこともあります。

ソース | bertcmiller.com

著者 | ロバート・ミラー

写真

写真

カレブは以前、シンセティックスの方針変更に関する重大なニュースをリークしており、それには数十万ドルが関与していた。このロボットオペレーターの隠れ家でこの種のニュースを共有することは、ライオンに赤身の肉を投げるようなものであり、契約書をざっと見ると、そこには魅力的な金額が記載されていることがわかります。

最初のレベルのタイトル

ステップ 1: 機会の範囲を決定する

私は Synthetix の専門家ではないので、最初のステップは私が何をしているのかを理解することです。具体的には:

  • 関連する契約書を見つけました

  • Sythetix ブログで機能の概要を読み、すべてのドキュメントを検索しました。

  • 今後のガバナンスの変更については理解していると確信しています

  • 関数を見て、関連性がありそうなものを探します

この段階の作業の概要は、Synthetix が sUSD と sETH を鋳造するための担保として ETH を使用する実験を行ったことです。担保の価値がローンの一定のレベルを下回らないように注意する限り、契約に ETH を預けてこれらの資産を鋳造することができます。

しかし、1年後、議定書は裁判を終了することを決議した。数百万ものローンが残っているのに、どうやってそんなことができるのでしょうか?まあ、どのポジションでも支払い可能にすることができます。実際、長い警告期間の後、ローンは 1 ブロックで安全な状態から、担保の価値に関係なく、誰でも返済できる状態になります。これにより、次からのトランザクションがトリガーされます。"pDAO"パブリック トランザクション プールに送信されるトランザクションに対応します。

最初のレベルのタイトル

ステップ 2: 機会を知る

基本的な仕組みを理解し、関連性があると思われる機能をいくつか理解しました。次に、呼び出す関数、必要なデータ、そのデータの生成方法までドリルダウンします。

2 つの関数が必要です。

  • setLoanLiquidationOpen(): 契約所有者 (pDAO) のみが呼び出すことができます。未払いのローンについては返済が可能です。

  • liquidateUnclosedLoan(): ローン ID とアカウント アドレスが必要で、ローンを返済します。 setLoanLiquidationOpen() が呼び出された後は誰でも使用できます。

他にも関数があることに注意してください。しかし、それらは関連性がないことがすぐにわかりました。次に、返済するローンをどのように選択するか、そしてそのために必要な sETH/sUSD がどのくらいかを計算する必要があります。次の関数は、動作するために必要なもののほとんどをオンにします。

  • openLoanIDsByAccount(): アカウントに関連するすべてのオープンローン ID を返します。

  • getLoanInformation(): 指定された ID と所有者のローン データを返します。

ただし、隠れた問題が 2 つあります。まず第一に、これらの契約書では、どの住所にローンが残っているかが記載されていないため、私にとっては困難です。数分以内に回避策を見つけました。それは、これらの契約を含むすべてのトランザクションを Etherscan にダウンロードし、Excel を使用してこれらの契約とやり取りした固有のアカウントのリストを作成することでした。そこから、次のパイプラインを構築しました。

  • getLoans を使用して、この住所に未払いのローンがあるかどうかを確認し、存在する場合はローン ID を記録します。

  • getLoanInformation を使用して、ローンを裏付ける担保の価値、発行した sUSD/SETH の金額、ローンの利息を確認します。

  • 融資額で並べ替え、最大の融資を最初にリストし、データ構造を JSON ファイルに保存します。

2 番目の問題は、ローンに対して担保に設定された ETH がいくら返済できるかがすぐには分からないことです。ローン残高の機能があるので大まかな見積りは分かるのですが、もっと正確なデータが欲しいです。これを行うために、ローン返済コードを研究し、その数値がどのように生成されるかを理解しました。

上記のデータはすべてチェーン上で取得または計算できます。しかしそうするとガソリンを大量に消費してしまいます。契約のガス効率について他の企業と競争することになるため、ガス消費を最小限に抑えるためにできるだけ多くのロジックをオフチェーンに移動することが私にとって非常に重要です。

数回繰り返した後、オフチェーンで取得する必要がある最小限のデータは、スマート コントラクトに入力した内容を通知するためにオフチェーンで解決できるいくつかの変数であることがわかりました。ただし、この情報を取得する関数は非常に複雑で、すべてのローンをクエリするのはブロックを処理するよりも時間がかかります。これは実現不可能です。この問題を解決するために、多くのデータ リクエストを集約する短いスマート コントラクトを作成したところ、10 倍以上の高速化が実現しました。以下にその関数の 1 つを示します。

function batchGetLoanInformation(address[ ] calldata _addresses, uint256[ ] calldata _loanIDs, address _contractAddress) external view returns (    uint256[ ] memory,    uint256[ ] memory){    uint256[ ] memory totalRepayment = new uint256[ ](_addresses.length);    uint256[ ] memory totalCollateralLiquidated = new uint256[ ](_addresses.length);    for (uint i = 0; i < _addresses.length; i++){        uint loanAmount;        uint accruedInterest;        (,,        loanAmount,,,,        accruedInterest,        ) = collateralContract(_contractAddress).getLoan(_addresses[i], _loanIDs[i]);        totalRepayment[i] = loanAmount + accruedInterest;        totalCollateralLiquidated[i] = getCollateralAmountSUSD(sUSD, totalRepayment[i], COLLATERAL);    }    return (totalRepayment, totalCollateralLiquidated);}

EmGithub によって提供される rawsAssetsOracle.sol を参照してください。

要約:

要約:このフェーズでは、機会についての洞察を得て、それを効率的に実行するために必要なすべてのデータを収集します。ガス消費量を減らすために、オフチェーンに移動するために最善を尽くす必要があります。最初のレベルのタイトル

副題

フラッシュローン戦略

MEVを引き出すには専用の契約が必要な場合があります。私は早い段階で契約とテスト環境を作成しました。この環境を使用して、契約をより深く理解し、データが正しいことを確認できます。これは、2 番目と 4 番目のステップとほぼ同時に行われます。

sUSD/sETHで数百万ドルを調達する必要があることは分かっているので、フラッシュローンは必須です。また、私はこれらの合成資産を燃やしますが、ステークされたETHを取り戻します。少し考えた結果、いずれにせよ ETH を他の資産と交換することになることに気付きましたが、返済の前でも後ででも交換することを選択できます。考えられるパスは 2 つあります。

  • オプション 1 オプション 1: フラッシュ ローンで ETH を融資 -> USDC を交換 -> sUSD を交換 -> sUSD ローンを返済 -> ETH を受け取る -> ETH フラッシュ ローンを返済

  • オプション 2 オプション 2: フラッシュ ローンで sUSD を融資 -> sUSD ローンを返済 -> ETH を受け取る -> USDC に変換 -> sUSD に変換 -> sUSD フラッシュ ローンを返済

ガスの最適化

ガスの最適化

私の契約書の全文はここで見つけることができますが、これは私が dYdX から ETH を受け取り、sUSD ローンを返済した後の部分です。

 //This is the function called by dydx after giving us the loanfunction callFunction(address sender, Account.Info memory accountInfo, bytes memory data) external {    // Use chi tokens    uint256 gasStart = gasleft();    // Let the executor or the dYdX contract call this function    // probably fine to restrict to dYdX    require(msg.sender == executor || msg.sender == address(soloMargin));    // Decode the passed variables from the data object    (        address[] memory sUSDAddresses,        uint256[] memory sUSDLoanIDs,        uint256 wethEstimate,        uint256 usdcEstimate,        uint256 ethToCoinbase    )        = abi.decode(data,    (        address[],        uint256[],        uint256,        uint256,        uint256    ));    // Swap WETH for USDC on uniswap v3    uniswapRouter.exactOutputSingle(        ISwapRouter.ExactOutputSingleParams(            address(WETH),        // address tokenIn;            usdcTokenAddress,     // address tokenOut;            3000,                 // uint24 fee;            address(this),        // address recipient;            10**18,               // uint256 deadline;            usdcEstimate,         // uint256 amountOut;            wethEstimate,         // uint256 amountInMaximum;            0                     // uint160 sqrtPriceLimitX96;        )    );    // Swap USDC for sUSD on Curve    curvePoolSUSD.exchange_underlying(        1, // usdc        3, // sUSD        usdcEstimate, // usdc input        1); // min sUSD, generally not advisible to make a trade with a min amount out of 1, but its fine here I think because the overall risk of getting rekt is low    // Liquidate the loans    for (uint256 i = 0; i < sUSDAddresses.length; i++) {        sUSDLoansAddress.liquidateUnclosedLoan(sUSDAddresses[i], sUSDLoanIDs[i]);    }    // We got back ETH but must pay dYdX in WETH, so deposit our whole balance sans what is paid to miners    WETH.deposit{value: address(this).balance - ethToCoinbase}();    // Pay the miner    block.coinbase.transfer(ethToCoinbase);    // Use for chi tokens    uint256 gasSpent = 21000 + gasStart - gasleft() + (16 * msg.data.length);    CHI.freeFromUpTo(owner, (gasSpent + 14154) / 41947);}

EmGithub によって提供される rawdYdXLiquidator.sol を参照してください。

私はガス消費量を最小限に抑えるために多くの時間を費やしています。私のデザイン選択の多くはこれに基づいています。この契約の戦略に関して、いくつかの注意点があります

  • 私は、償還と交換のトランザクションを個別に大量に送信する代わりに、複数の返済を 1 つのトランザクションにバンドルすることを選択しました。これにより、固定ガスのオーバーヘッドを複数の返済に分散できるため、トランザクション バンドルの競争力が向上します。

  • ETHをUSDCからsUSDに最適な方法で変換する必要があり、exactInput関数とexactOutput関数を持つUniswap v3のどちらで取引するかを決定する必要があります。何をしてもどこかでスリッページが発生することになるので、balanceOf の呼び出しを避けるために、exactOutput 関数を使用することにしました。

  • これらのトランザクションの精度とガス効率の間にはトレードオフの関係があります。フラッシュローンを返済できる限り、精度が低くても大丈夫です。ガス効率で勝負しているので、それを最適化することにしました。

注意すべき「戦術的」事項もいくつかあります。

  • すべての承認は、契約のビルド関数に前方ロードされます。こうすることで、デプロイメント時のオーバーヘッドを支払い、実行に使用されるガスを削減できます。

  • 私はガス効率を高めるために、自分のアカウントからではなく、契約からガストークンを焼きます。

  • 関数名はすべて指定されており、関数セレクターの先頭文字は 0x であり、関数セレクターを使用するとガスの使用量もわずかに削減できます。

  • 関数修飾子は、require ステートメントを直接追加するよりもわずかに多くのガスを消費します。

  • コインベース転送の代わりにガス料金を使用するなど、この契約を最適化できる方法もいくつかあります。

  • 要約:

要約:最初のレベルのタイトル

副題

MEV の返済とガス価格の最適化の経済学

よく設計された契約と機会をしっかりと理解した上で、それを実現するための戦略を洗練する必要があります。要約すると、Flashbot の MEV-Geth クライアントは、最も高いガス価格のバンドルが落札され、オンチェーンにバンドルされるオークションを効果的に実行できます。この重要な事実は、支払う ETH の総額ではなく、トランザクション バンドルのガス価格を最大化する必要があることを意味します。

写真

写真

この結果は少し驚くべきものであることに注意してください。sUSD ローン (30 件中) の最初の 4 件のみを返済することが最もガス効率が高かったのです。その後のローンごとに全体的な利益は増加しますが、トランザクション バンドルのガソリン価格が下がり、競争力が低下します。他の人が最初の 10 秒ドルのローンを一度に返済しようとすると、ガソリンの消費効率が 30% 近く低下することになります。

未払いの sETH ローンが少ないことを考えると、sUSD ローンのみを実行し、sUSD と sETH を 1 つの取引に組み合わせないことが最も合理的です。その結果、潜在的な報酬とマイナーに支払われる金額が減り、相対的にガス効率が悪くなります。これらの発見を見たとき、私は思わず笑みを浮かべました。他の人が貪欲でローン全額を一度に返済するか、怠け者で分割して返済するなら、私の勝ちです。

副題

Flashbot オークションと私のバンドル注文戦略

状況を繰り返すと、私はガソリン効率で競争し、各ローン返済の見返りを最大化したいと考えています。最良の戦略は、1 つのバンドルにつき複数の返済を複数のバンドルに分けて送信することです。これらのバンドルは、Flashbot オークションで個別に評価されます。ただし、すべての取引は pDAO からの取引に依存しているため、ローンは誰でも返済可能になります。

  • pDAO トランザクションがバンドルに含まれていない場合、バンドルは失敗します。ただし、各バンドルに pDAO トランザクションがある場合、成功するバンドルは 1 つだけです。つまり、トランザクション バンドルが正常にラップされた後は、pDAO トランザクションをダブルパックしようとするため、他のすべてのバンドルは無効になります。したがって、最初のトランザクション バンドルでのみ pDAO トランザクションを送信する方法を見つける必要がありますが、同時に、他のトランザクション バンドルが pDAO トランザクションがないために失敗して破棄されないようにする必要があります。

解決策は、Flashbot オークションの巧妙さにあります。 Flashbot は、トランザクションバンドル後のマイナー手数料を削減するために、検索エンジンがオークションで「トリック」を使用し始める前に、2 ラウンドのシミュレーションを実行しました。まず、すべてのトランザクション バンドルが個別にシミュレートされ、そのガス価格が導出され、失敗がないかチェックされます。 2 番目のラウンドでは、成功したバンドルがガス価格で並べ替えられ、シミュレーションが再度実行されて一貫性のないバンドルが検出され、ガス価格が予想より低いバンドルがないことが確認されます。あなたが望まない限り、合併後にガス価格が値下げされるトランザクション バンドルを作成することはおそらくありません。

上記のサーチャーとは逆のこともできることに気付きました。つまり、トランザクション バンドルが予想よりも少ないガスを支払う代わりに、シミュレーションの 2 回目のラウンドではより多くのガスを支払うことになります。これを行うには、予想どおりに最初のトランザクション バンドルに pDAO トランザクションをラップしますが、残りのトランザクション バンドルに対して追加のチェックを行います。これらのトランザクション バンドルは、どの「ラウンド」でシミュレートされるかを推測し、それに応じて実行を変更します。彼らが「ラウンド 1」にいる場合、ローンを返済することはありません。返済しようとしても失敗するからです。そして、最初のラウンドのシミュレーションに合格するために、とにかく鉱山労働者に高いガソリン価格を支払います。

シミュレーションの最初のラウンドに合格した後、これらのトランザクション バンドルは、シミュレーションの 2 番目のラウンドで pDAO トランザクションを含むトランザクション バンドルに続きます。この時点で、彼らはローンを無事に返済することができます。また、これらのバンドルのガス価格はオークションの予想よりも低くなく高くなるため、ここでは実装の変更は問題になりません。

要約:

要約:最初のレベルのタイトル

ステップ 5: 実行

これで、データ、契約書、実行を開始できる計画が手に入りました。基本的に、このようなトランザクション バンドルを複数構築する必要があります。一方では、上記の計画を実行でき、他方では、テール トラッキングのためにトランザクション プール内の Synthetix に関連するトランザクションをリッスンできます。現時点では、それは主に実装の問題です。

まず、Blocknative を使用して、関連するトランザクションの pDAO アカウントをリッスンしました。 pDAO アカウントから送信されたトランザクションがすべて私のボットに送信されるようにします。

次に、2 つのリスニング スクリプト (sETH と sUSD 用) を同時に実行してチェーンからデータを取得し、最適なトランザクション バンドル戦略を考え出します (たとえば、最初の 3 つの sETH ローンを最初に返済し、フラッシュ ローン X ETH、最後の 2 つなどにも同じことを行い、契約に必要なデータを生成します。価格が変化したり誰かがローンをクローズしたりして最適な戦略が変わった場合に備えて、これをブロックごとに行う必要があります。これらの結果はローカルに保存されます。

最終的に、ボットに送信された保留中の取引を取得し、リスニング スクリプトから最適なバンドル戦略の結果を読み込み、自動的にバンドルを構築して Flashbot に送信する実行スクリプトが完成しました。

最初のレベルのタイトル

ステップ 6: その瞬間が到来しました

興味深いことに、誰かが関連するコントラクトにトランザクションを送信することで、ボットをだまして早い段階で失敗させようとしました。他の人のボットでも同じことが起こるかどうかはわかりませんが、私のボットはうまく説得できませんでした。

数時間後、pDAO は実際のトランザクションを送信しました。何週間もリサーチと準備を行った後、その瞬間が来たと悟りました。私の側ではすべてが順調に進んでいます。リスナー スクリプトは正常に実行され、トランザクションが受信され、トランザクション バンドルが構築されてコミットされます。

……そして事故は起きた。複数の連続ブロックに対して Flashbot ブロックがマイニングされていません。私が機会を失っただけでなく、Flashbot クローラーが機会を獲得したことはありませんでした。ブロックの先頭でブロックする Flashbot がいないため、野心的なトランザクション プール ボットが介入し、収益性の高いローンをすべて奪い取ります。

負けはしたものの、それでも自分のアプローチは正しかったと思います。私の強みは、PGA (ベスト ガス オークション) ではなく、戦略と新しい機会を見つけることにあります。そのため、Flashbot を使用することで最も勝てる可能性が高まりました。 Flashbot が広く普及していることを考えると、Flashbot 以外のブロックが複数連続するのはかなり不運です。

MEV はとらえどころのないスーパープログラマーの領域だと思われることがありますが、必ずしもそうである必要はありません。楽しくて刺激的なこともあります。そして、ゲームのルールは、探しているのであれば、いわばオープンです。この記事は、私がプレイするゲームのルールを学び、そこから戦略を考え出し、最終的にその戦略を実行するプロセスについて説明しています。負けはしましたが、途中でたくさんのことを学び、楽しかったです。あなたもそうなれることを願っています。そして、次のラウンドの試合に参加してくれることを願っています。

元のリンク: https://bertcmiller.com/2021/09/05/mev-synthetix.html

ETH
Synthetix
Odaily公式コミュニティへの参加を歓迎します
購読グループ:
https://t.me/Odaily_News
チャットグループ:
https://t.me/Odaily_GoldenApe
公式アカウント:
https://twitter.com/OdailyChina