<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>BWG Loonshots Blog Blog</title>
        <link>https://bwg-loonshots.pages.dev/articles</link>
        <description>BWG Loonshots Blog Blog</description>
        <lastBuildDate>Mon, 31 Aug 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>ko</language>
        <copyright>Copyright © 2026 Bankware Global.</copyright>
        <item>
            <title><![CDATA[스왑이 남긴 MEV를 사용자에게 돌려줄 수 있을까]]></title>
            <link>https://bwg-loonshots.pages.dev/articles/internalizing-swap-induced-mev</link>
            <guid>https://bwg-loonshots.pages.dev/articles/internalizing-swap-induced-mev</guid>
            <pubDate>Mon, 31 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[플래시론과 교차 DEX 리밸런싱으로 스왑 이후의 차익을 내부화하는 2022년의 설계와 그 한계를 돌아본다.]]></description>
            <content:encoded><![CDATA[<p>2022년, DeFi에서 스왑은 몇 초 안에 끝났지만 그 거래의 경제적 여파는 거기서 끝나지 않았다.</p>
<p>한 사용자가 AMM에서 큰 수량을 스왑한다. 풀의 준비금 비율이 움직이고, 다른 DEX와의 가격 차이가
벌어진다. 그 차이를 발견한 searcher는 곧바로 반대 거래를 넣는다. 조금 전까지 어긋나 있던 두 가격은
다시 가까워지고, searcher는 그 과정에서 차익을 얻는다.</p>
<p>시장은 의도한 대로 작동한 것처럼 보인다. 차익거래가 가격을 다시 맞췄기 때문이다. 하지만 거래의
당사자에게는 묘한 결말이 남는다. 가격 차이를 만든 사람은 사용자였고, 사용자는 슬리피지와 가스비를
부담했다. 정작 그 거래가 만든 차익은 가장 빨리 뒤따라온 다른 누군가에게 돌아갔다.</p>
<p>2022년 말, 우리가 던졌던 질문은 여기서 시작했다.</p>
<blockquote>
<p>그 차익거래가 사용자의 트랜잭션 밖에서 기다리지 않게 만들 수는 없을까? 프로토콜이 먼저 차익을
회수하고, 남은 가치를 사용자에게 돌려줄 수는 없을까?</p>
</blockquote>
<!-- -->
<p>이 글은 그때의 질문과 설계를 다시 꺼내 보는 기록이다. 당시에는 이를 “MEV로 인한 거래 왜곡 방지”라고
불렀다. 몇 해가 지나 관련 연구와 여러 프로토콜을 함께 놓고 보면, 그 표현은 너무 컸다. 더 정확히
말하면 이 설계는 <strong>모든 MEV를 없애려는 해법이 아니라, 사용자 스왑이 만든 일부 backrun 가치를
트랜잭션 안으로 가져오려는 시도</strong>였다.</p>
<h2 class="anchor anchorTargetStickyNavbar_SAay" id="네-개의-풀에서-시작한-질문">네 개의 풀에서 시작한 질문<a href="https://bwg-loonshots.pages.dev/articles/internalizing-swap-induced-mev#%EB%84%A4-%EA%B0%9C%EC%9D%98-%ED%92%80%EC%97%90%EC%84%9C-%EC%8B%9C%EC%9E%91%ED%95%9C-%EC%A7%88%EB%AC%B8" class="hash-link" aria-label="네 개의 풀에서 시작한 질문에 대한 직접 링크" title="네 개의 풀에서 시작한 질문에 대한 직접 링크" translate="no">​</a></h2>
<p>당시 내부 시뮬레이션에는 같은 토큰 쌍을 가진 네 개의 constant-product 풀이 있었다. 각 풀의 준비금을
간단한 숫자로 놓고, 한쪽 풀에서 스왑이 일어난 뒤 서로 달라진 비율을 어떻게 다시 맞출지 계산했다.</p>
<p>스프레드시트에는 복잡한 시장도, 정교한 searcher 경쟁도 없었다. 풀마다 <code>a * b = k</code>를 유지한다는
가장 단순한 AMM만 있었다. 그러나 그 작은 모델은 중요한 질문 하나를 선명하게 보여줬다.</p>
<p>사용자의 스왑 뒤에 가격 차이가 남는다면, 외부 차익거래자가 그 차이를 닫기 전에 스왑을 실행한
프로토콜이 직접 반대 거래를 수행할 수 있다. 그리고 그 실행에서 얻은 순잉여를 사용자에게 돌려주면,
같은 가격 회복 과정이라도 가치의 최종 귀속은 달라진다.</p>
<p>차익거래를 제거하는 것이 아니다. <strong>차익거래를 누가 수행하고, 그 결과를 누가 가져가는지 바꾸는 것</strong>이다.</p>
<h2 class="anchor anchorTargetStickyNavbar_SAay" id="관찰에서-환원까지">관찰에서 환원까지<a href="https://bwg-loonshots.pages.dev/articles/internalizing-swap-induced-mev#%EA%B4%80%EC%B0%B0%EC%97%90%EC%84%9C-%ED%99%98%EC%9B%90%EA%B9%8C%EC%A7%80" class="hash-link" aria-label="관찰에서 환원까지에 대한 직접 링크" title="관찰에서 환원까지에 대한 직접 링크" translate="no">​</a></h2>
<p>설계의 중심에는 둘 이상의 DEX를 바라보는 aggregator가 있다. 실행 흐름을 특허 명세서의 구성요소가
아니라 실제 동작 순서로 옮기면 다음과 같다.</p>
<ol>
<li class="">동일한 토큰 쌍을 가진 여러 DEX의 준비금과 스왑 가격을 확인한다.</li>
<li class="">사용자 스왑 전후의 비율 변화와 DEX 사이의 가격 차이를 감지한다.</li>
<li class="">교차 스왑으로 얻을 수 있는 총차익과 모든 실행 비용을 계산한다.</li>
<li class="">순이익이 양수일 때만 상대적으로 고평가된 토큰을 플래시론으로 빌린다.</li>
<li class="">고평가된 DEX에서 팔고 저평가된 DEX에서 되사는 교차 스왑을 실행한다.</li>
<li class="">플래시론을 상환하고 남은 순잉여를 사용자의 가격 개선 또는 환급으로 처리한다.</li>
</ol>
<p>플래시론은 없는 수익을 만들어내는 장치가 아니다. 이미 존재하는 가격 차이를 자기자본 없이 한 번의
실행 흐름에서 닫을 수 있게 해주는 유동성 수단이다. 대출금과 프리미엄을 같은 트랜잭션 안에서 갚지
못하면 전체 실행이 되돌아간다. <a href="https://developers.uniswap.org/docs/protocols/v2/concepts/flash-swap" target="_blank" rel="noopener noreferrer" class="">Uniswap v2의 flash swap 문서</a>가
차익거래를 대표적인 사용 사례로 드는 이유도 여기에 있다.</p>
<p>따라서 “차익이 있다”는 것만으로는 실행 조건이 되지 않는다. 다음 값이 양수여야 한다.</p>
<div class="language-text codeBlockContainer_ZGJx theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-text codeBlock_TAPP thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_AdAo"><div class="token-line" style="color:#393A34"><span class="token plain">사용자 가격 개선분 = 교차 DEX 총차익</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    - DEX 수수료</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    - 플래시론 프리미엄</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    - 가스비와 실행 오버헤드</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    - 프로토콜 수수료(있는 경우)</span><br></div></code></pre></div></div>
<p>실제 구현이라면 이 값이 최소 이익 기준보다 작거나 사용자의 <code>minOut</code>을 만족하지 못할 때 실행 전체를
중단해야 한다.</p>
<h3 class="anchor anchorTargetStickyNavbar_SAay" id="숫자로-끝까지-계산해-보기">숫자로 끝까지 계산해 보기<a href="https://bwg-loonshots.pages.dev/articles/internalizing-swap-induced-mev#%EC%88%AB%EC%9E%90%EB%A1%9C-%EB%81%9D%EA%B9%8C%EC%A7%80-%EA%B3%84%EC%82%B0%ED%95%B4-%EB%B3%B4%EA%B8%B0" class="hash-link" aria-label="숫자로 끝까지 계산해 보기에 대한 직접 링크" title="숫자로 끝까지 계산해 보기에 대한 직접 링크" translate="no">​</a></h3>
<p>사용자 스왑이 끝난 직후, 같은 A/B 토큰 쌍을 가진 두 풀이 다음 상태라고 가정해 보자.</p>
<table><thead><tr><th>풀</th><th style="text-align:right">A 준비금</th><th style="text-align:right">B 준비금</th><th style="text-align:right">A의 한계가격 (<code>B/A</code>)</th></tr></thead><tbody><tr><td>DEX A</td><td style="text-align:right">100</td><td style="text-align:right">100</td><td style="text-align:right">1.0 B</td></tr><tr><td>DEX B</td><td style="text-align:right">100</td><td style="text-align:right">120</td><td style="text-align:right">1.2 B</td></tr></tbody></table>
<p>DEX B에서 A가 상대적으로 비싸므로 A를 빌려 DEX B에 팔고, 받은 B로 DEX A에서 A를 되사는 방향이
후보다. 두 풀의 스왑 수수료를 각각 0.3%라고 하면 입력량 <code>q</code>에 대한 출력량은 다음과 같다.</p>
<div class="language-text codeBlockContainer_ZGJx theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-text codeBlock_TAPP thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_AdAo"><div class="token-line" style="color:#393A34"><span class="token plain">gamma = 1 - 0.003 = 0.997</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">amountOut = reserveOut * gamma * q</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            / (reserveIn + gamma * q)</span><br></div></code></pre></div></div>
<p>플래시론 프리미엄을 0.05%, 가스비를 A로 환산해 0.01 A라고 가정하자. 이 조건에서 순이익을 가장 크게
만드는 대출량은 약 4.196 A이고, 설명을 위해 4.2 A로 반올림하면 계산은 다음과 같다.</p>
<div class="language-text codeBlockContainer_ZGJx theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-text codeBlock_TAPP thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_AdAo"><div class="token-line" style="color:#393A34"><span class="token plain">1. 4.2 A를 플래시론으로 대출</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">2. DEX B에서 4.2 A -&gt; 4.822925 B</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">3. DEX A에서 4.822925 B -&gt; 4.587851 A</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">교차 스왑 총차익       4.587851 - 4.2     = 0.387851 A</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">플래시론 프리미엄      4.2 * 0.0005       = 0.002100 A</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">가스비 환산                                  0.010000 A</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">사용자에게 돌릴 순잉여                       0.375751 A</span><br></div></code></pre></div></div>
<p>두 번의 0.3% DEX 수수료는 각 <code>amountOut</code> 계산에 이미 들어 있다. 프로토콜 수수료가 없고 잉여를 모두
A로 지급한다면 사용자가 받는 개선분은 약 0.375751 A다. 잉여를 사용자의 출력 토큰인 B로 바꾸어
지급한다면 그 변환의 수수료와 가격 영향까지 다시 계산해야 한다.</p>
<p>거래 뒤 A의 한계가격은 DEX A에서 약 1.0986 B, DEX B에서 약 1.1053 B가 된다. 가격이 완전히 같아지지
않는 것은 오류가 아니다. 수수료가 있는 시장에서는 두 번의 스왑 비용보다 작은 가격 차이는 더 이상
수익성 있는 차익거래가 아니므로 no-arbitrage band가 남는다. 이 예제는 “모든 풀을 같은 비율로
맞춘다”보다 “비용을 포함한 순이익을 최대화한다”가 실제 실행 조건에 더 가깝다는 점을 보여준다.</p>
<h3 class="anchor anchorTargetStickyNavbar_SAay" id="실행-경로를-코드로-옮기면">실행 경로를 코드로 옮기면<a href="https://bwg-loonshots.pages.dev/articles/internalizing-swap-induced-mev#%EC%8B%A4%ED%96%89-%EA%B2%BD%EB%A1%9C%EB%A5%BC-%EC%BD%94%EB%93%9C%EB%A1%9C-%EC%98%AE%EA%B8%B0%EB%A9%B4" class="hash-link" aria-label="실행 경로를 코드로 옮기면에 대한 직접 링크" title="실행 경로를 코드로 옮기면에 대한 직접 링크" translate="no">​</a></h3>
<p>실행 전 시뮬레이션과 실제 onchain 실행의 경계를 포함한 의사코드는 다음과 같이 잡을 수 있다.</p>
<div class="language-text codeBlockContainer_ZGJx theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-text codeBlock_TAPP thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_AdAo"><div class="token-line" style="color:#393A34"><span class="token plain">function swapWithInternalizedArbitrage(order):</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    snapshot  = readEligiblePools(order.pair)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    baseline  = simulateBestUserRoute(order, snapshot)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    candidate = optimizeCrossDexTrade(baseline.postSwapState)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    if candidate.netSurplus &lt;= MIN_PROFIT:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        return executeBaselineSwap(order, baseline)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    result = atomicExecute([</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        validateQuotes(snapshot, order.deadline),</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        executeUserSwap(order, baseline.route),</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        flashBorrow(candidate.borrowToken, candidate.amount),</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        executeCrossDexTrade(candidate.route),</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        repayFlashLoan(),</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        convertSurplusTo(order.tokenOut),</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        refundSurplus(order.owner),</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    ])</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    require(result.totalUserOut &gt;= order.minOut)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    require(result.totalUserOut &gt;= baseline.userOut)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    require(result.flashLoanRepaid)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    require(result.netSurplus &gt; MIN_PROFIT)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    require(result.unaccountedBalance &lt;= DUST_LIMIT)</span><br></div></code></pre></div></div>
<p><code>baseline.userOut</code>은 가능하면 실행 시점의 상태 또는 유효기간이 있는 서명된 quote에서 계산해야 한다.
그렇지 않으면 시뮬레이션과 블록 포함 사이에 가격이 바뀌었을 때 지킬 수 없는 기준이 된다. 또한 환급
토큰을 사용자의 출력 토큰으로 통일하지 않는다면 <code>totalUserOut</code>을 비교할 신뢰할 수 있는 가격 기준이
필요하다.</p>
<p>이 의사코드에서 핵심은 차익거래 성공 자체가 아니라 다음 불변식을 함께 지키는 것이다.</p>
<ul>
<li class="">내부화 경로가 없을 때보다 사용자의 결과가 나빠지지 않는다.</li>
<li class="">플래시론 원금과 프리미엄은 같은 실행에서 전액 상환된다.</li>
<li class="">모든 비용을 제한 뒤에도 최소 순잉여가 남는다.</li>
<li class="">잔여 토큰은 사용자 환급, 명시된 수수료 또는 허용된 dust 중 하나로 전부 회계 처리된다.</li>
<li class="">어느 조건이든 깨지면 사용자 스왑을 포함한 전체 경로를 되돌리거나, 안전하게 baseline 경로만 실행한다.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_SAay" id="수식은-정답이-아니라-출발점이었다">수식은 정답이 아니라 출발점이었다<a href="https://bwg-loonshots.pages.dev/articles/internalizing-swap-induced-mev#%EC%88%98%EC%8B%9D%EC%9D%80-%EC%A0%95%EB%8B%B5%EC%9D%B4-%EC%95%84%EB%8B%88%EB%9D%BC-%EC%B6%9C%EB%B0%9C%EC%A0%90%EC%9D%B4%EC%97%88%EB%8B%A4" class="hash-link" aria-label="수식은 정답이 아니라 출발점이었다에 대한 직접 링크" title="수식은 정답이 아니라 출발점이었다에 대한 직접 링크" translate="no">​</a></h2>
<p>2022년 모델은 각 풀의 두 토큰 준비금을 <code>a_i</code>, <code>b_i</code>라고 두고 다음의 관계에서 시작했다.</p>
<div class="language-text codeBlockContainer_ZGJx theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-text codeBlock_TAPP thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_AdAo"><div class="token-line" style="color:#393A34"><span class="token plain">k_i = a_i * b_i</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">A = sum(a_i)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">B = sum(b_i)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">S = A / B</span><br></div></code></pre></div></div>
<p>여기서 <code>S</code>는 참조하는 모든 풀의 준비금을 합산해 만든 목표 비율이다. 각 풀의 invariant를 유지하면서
준비금 비율을 <code>S</code>에 맞추기 위한 목표값은 다음과 같이 계산했다.</p>
<div class="language-text codeBlockContainer_ZGJx theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-text codeBlock_TAPP thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_AdAo"><div class="token-line" style="color:#393A34"><span class="token plain">a_i' = sqrt(S * a_i * b_i)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">b_i' = (a_i * b_i) / a_i'</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">필요한 거래 방향과 수량 = a_i' - a_i</span><br></div></code></pre></div></div>
<p><code>a_i' - a_i</code>가 양수라면 해당 풀에는 토큰 A를 넣는 방향의 거래가 필요하고, 음수라면 반대 방향의
거래가 필요하다. 여러 풀에서 필요한 양을 합산해 어느 토큰을 얼마나 빌릴지 결정하고, 교차 스왑 뒤
대출을 갚는다는 구상이었다.</p>
<p>수식은 간결하다. 바로 그 때문에 가정도 많다.</p>
<p>우선 이 모델은 수수료가 없는 constant-product AMM을 가정한다. 오늘날 흔한 concentrated liquidity,
서로 다른 fee tier, stable-swap curve는 반영하지 않는다. <code>S=A/B</code> 또한 외부 시장의 공정가격이 아니다.
참조 풀의 준비금으로 만든 내부 기준일 뿐이며, 어떤 풀을 선택하느냐와 준비금 조작에 민감하다. 토큰 A의
가격을 토큰 B로 표시할 때는 준비금 비율과 가격의 방향이 역수가 될 수 있으므로 표기 규칙도 명확히 해야
한다.</p>
<p>각 풀의 <code>k</code>가 보존된다는 사실만 확인해서도 부족하다. 전체 경로에서 토큰이 보존되는지, 대출 원금과
프리미엄을 갚은 뒤 무엇이 남는지, 그 잔여분을 어느 토큰으로 사용자에게 지급할지까지 하나의 원장처럼
맞춰봐야 한다.</p>
<p>그래서 이 수식은 완성된 최적화 알고리즘이라기보다 <strong>어디서 차익이 생기고 어느 방향으로 거래해야
하는지를 드러내는 첫 모델</strong>로 보는 편이 정확하다.</p>
<h2 class="anchor anchorTargetStickyNavbar_SAay" id="트랜잭션-안으로-가져온다는-것">트랜잭션 안으로 가져온다는 것<a href="https://bwg-loonshots.pages.dev/articles/internalizing-swap-induced-mev#%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EC%95%88%EC%9C%BC%EB%A1%9C-%EA%B0%80%EC%A0%B8%EC%98%A8%EB%8B%A4%EB%8A%94-%EA%B2%83" class="hash-link" aria-label="트랜잭션 안으로 가져온다는 것에 대한 직접 링크" title="트랜잭션 안으로 가져온다는 것에 대한 직접 링크" translate="no">​</a></h2>
<p>이 접근의 가장 중요한 경계는 원자성이다.</p>
<p>사용자 스왑과 뒤따르는 대출·교차 스왑·상환을 하나의 트랜잭션으로 묶을 수 있다면, 외부 참여자가 그
내부 호출 사이에 자기 거래를 끼워 넣을 수는 없다. 어느 단계에서든 수익 조건이나 상환 조건을 만족하지
못하면 전체 트랜잭션을 되돌릴 수도 있다.</p>
<div class="language-text codeBlockContainer_ZGJx theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_kX1v"><pre tabindex="0" class="prism-code language-text codeBlock_TAPP thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_AdAo"><div class="token-line" style="color:#393A34"><span class="token plain">builder가 결정하는 블록 내 순서</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">┌──────────────────────────────────────────────────────────┐</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│ 공격자 트랜잭션: 전체 경로 앞에 배치 가능                │</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├──────────────────────────────────────────────────────────┤</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│ 우리의 단일 트랜잭션                                     │</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│ quote 검증 -&gt; 사용자 스왑 -&gt; flash borrow -&gt; 교차 스왑   │</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│             -&gt; 상환 -&gt; 사용자 환급                       │</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│             위 호출 사이에는 외부 거래가 들어올 수 없음 │</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├──────────────────────────────────────────────────────────┤</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│ 공격자 트랜잭션: 전체 경로 뒤에 배치 가능                │</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">└──────────────────────────────────────────────────────────┘</span><br></div></code></pre></div></div>
<p>하지만 이것이 거래 순서 문제 전체를 해결한다는 뜻은 아니다. builder나 proposer는 여전히 이
트랜잭션의 앞이나 뒤에 다른 트랜잭션을 놓을 수 있다. 사용자의 주문이 공개 멤풀에 먼저 노출된다면
트랜잭션 전체를 대상으로 한 frontrun이나 sandwich도 남는다. 내부 단계의 원자성과 블록 안에서의
transaction-level ordering 보호는 서로 다른 문제다.</p>
<p>private order flow, batch auction, intent와 builder 수준의 보호가 필요한 이유가 여기에 있다.
<a href="https://docs.cow.fi/" target="_blank" rel="noopener noreferrer" class="">CoW Protocol</a>은 batch auction과 Coincidence of Wants를 사용하고,
<a href="https://www.flashbots.net/" target="_blank" rel="noopener noreferrer" class="">Flashbots</a>는 private transaction과 MEV 수익 공유를 별도의 축에서 다룬다.
이들은 이 글의 리밸런싱 설계와 경쟁하는 하나의 정답이라기보다, 서로 다른 공격면을 다루는 설계들이다.</p>
<p>이 설계가 최소한 가정해야 할 threat model을 실행 경계에 맞춰 정리하면 다음과 같다.</p>
<table><thead><tr><th>공격면</th><th>가능한 실패</th><th>필요한 방어</th></tr></thead><tbody><tr><td>Builder / proposer</td><td>전체 트랜잭션 앞뒤에 거래를 배치하거나 포함을 지연</td><td>private submission, deadline, <code>minOut</code>, quote 유효성 검사</td></tr><tr><td>얕거나 조작된 참조 풀</td><td>준비금을 순간적으로 바꿔 잘못된 목표 가격과 거래량 유도</td><td>최소 유동성, 가격 편차 제한, 외부 기준가격, 풀별 가중치</td></tr><tr><td>악성 DEX adapter</td><td>거짓 반환값, 임의 callback, 재진입으로 잔액 탈취</td><td>adapter allowlist, balance-delta 회계, reentrancy guard</td></tr><tr><td>Stale state</td><td>시뮬레이션 이후 reserve가 변해 수익 또는 상환 조건 붕괴</td><td>실행 시 재검증, 최소 순이익 조건, 전체 revert</td></tr><tr><td>비표준 토큰</td><td>fee-on-transfer, rebasing, callback으로 예상 수량 불일치</td><td>지원 토큰 제한, 실제 잔액 변화 기준 정산, dust 한도</td></tr></tbody></table>
<p>여기서 contract가 직접 보장할 수 있는 것은 가운데 단일 트랜잭션의 상태 전이와 잔액 불변식이다. 그
밖의 주문 노출과 블록 순서는 별도의 전달 경로와 시장 구조가 담당해야 한다.</p>
<h2 class="anchor anchorTargetStickyNavbar_SAay" id="그래서-어떤-mev가-줄어드는가">그래서 어떤 MEV가 줄어드는가<a href="https://bwg-loonshots.pages.dev/articles/internalizing-swap-induced-mev#%EA%B7%B8%EB%9E%98%EC%84%9C-%EC%96%B4%EB%96%A4-mev%EA%B0%80-%EC%A4%84%EC%96%B4%EB%93%9C%EB%8A%94%EA%B0%80" class="hash-link" aria-label="그래서 어떤 MEV가 줄어드는가에 대한 직접 링크" title="그래서 어떤 MEV가 줄어드는가에 대한 직접 링크" translate="no">​</a></h2>
<p>이 설계가 직접 겨냥하는 것은 좁다.</p>
<ul>
<li class="">사용자 스왑 직후 여러 DEX 사이에 남는 residual arbitrage</li>
<li class="">그 차익을 가져가는 외부 backrun 경쟁</li>
<li class="">비용을 제외한 잉여가 사용자에게 돌아가지 않는 가치 배분</li>
</ul>
<p>반대로 다음 문제는 그대로 남는다.</p>
<ul>
<li class="">전체 트랜잭션을 감싸는 frontrun과 sandwich</li>
<li class="">builder의 거래 포함·제외·재배치와 censorship</li>
<li class="">liquidation, oracle manipulation 등 다른 종류의 MEV</li>
<li class="">조작된 참조 풀이나 잘못된 목표 비율</li>
<li class="">stale quote, 악성 adapter, callback과 reentrancy 위험</li>
<li class="">수수료와 가스비가 차익보다 커지는 실행 실패</li>
</ul>
<p>당시 문서에 썼던 “MEV 피해 방지”나 “슬리피지 제거”를 지금 그대로 반복하지 않는 이유다. 더 적절한
표현은 <strong>swap-induced arbitrage의 내부화</strong>다. 이 접근은 차익거래의 경제적 필요 자체를 없애지 않는다.
다만 그 가치가 외부 searcher에게만 흘러가지 않도록 실행 경로와 귀속 규칙을 바꾼다.</p>
<p>이 차이는 LP 관점에서도 중요하다. <a href="https://arxiv.org/abs/2208.06046" target="_blank" rel="noopener noreferrer" class="">Loss-Versus-Rebalancing 연구</a>가
보여주듯, AMM과 외부 시장 사이의 가격 차이에서 발생하는 arbitrage는 LP의 성과와도 연결된다. 차익을
사용자에게 환원한다고 해서 underlying adverse selection이 사라지는 것은 아니다. 누가 이익을 얻는지는
바뀔 수 있지만, 누구의 가치에서 그 이익이 나왔는지는 별도로 측정해야 한다.</p>
<h2 class="anchor anchorTargetStickyNavbar_SAay" id="2022년의-질문은-우리만의-것이-아니었다">2022년의 질문은 우리만의 것이 아니었다<a href="https://bwg-loonshots.pages.dev/articles/internalizing-swap-induced-mev#2022%EB%85%84%EC%9D%98-%EC%A7%88%EB%AC%B8%EC%9D%80-%EC%9A%B0%EB%A6%AC%EB%A7%8C%EC%9D%98-%EA%B2%83%EC%9D%B4-%EC%95%84%EB%8B%88%EC%97%88%EB%8B%A4" class="hash-link" aria-label="2022년의 질문은 우리만의 것이 아니었다에 대한 직접 링크" title="2022년의 질문은 우리만의 것이 아니었다에 대한 직접 링크" translate="no">​</a></h2>
<p>이 아이디어를 “최초”라고 부를 수는 없다. 비슷한 시기 같은 방향의 질문이 이미 여러 곳에서 등장했다.</p>
<p>2022년 8월 공개된 <a href="https://medium.com/wowmax-exchange/wowmax-fc317c63eae6" target="_blank" rel="noopener noreferrer" class="">WOWMAX의 설명</a>은 swap과
arbitrage를 하나의 atomic transaction으로 실행하고, DEX 사이의 가격을 리밸런싱한 뒤 이익을 거래자에게
돌려주는 구상을 소개했다. 이후 <a href="https://app.uniswap.org/whitepaper-uniswapx.pdf" target="_blank" rel="noopener noreferrer" class="">UniswapX whitepaper</a>도
filler 경쟁과 routing, batching을 통해 MEV를 내부화하고 surplus를 가격 개선으로 돌리는 방향을 제시했다.</p>
<p>구현은 서로 다르지만 공통된 질문이 있다.</p>
<blockquote>
<p>사용자 주문이 만든 잉여는 반드시 가장 빠른 searcher의 몫이어야 하는가?</p>
</blockquote>
<p>2022년의 작은 스프레드시트가 흥미로운 이유도 독점적인 정답을 갖고 있어서가 아니다. 여러 설계가
같은 시기에 같은 질문으로 수렴했다는 사실, 그리고 프로토콜이 주문의 실행 방식뿐 아니라 <strong>실행 후 남는
가치의 귀속 방식</strong>까지 설계할 수 있다는 사실을 보여주기 때문이다.</p>
<h2 class="anchor anchorTargetStickyNavbar_SAay" id="특허-그리고-공개">특허, 그리고 공개<a href="https://bwg-loonshots.pages.dev/articles/internalizing-swap-induced-mev#%ED%8A%B9%ED%97%88-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EA%B3%B5%EA%B0%9C" class="hash-link" aria-label="특허, 그리고 공개에 대한 직접 링크" title="특허, 그리고 공개에 대한 직접 링크" translate="no">​</a></h2>
<p>이 글의 설계는 뱅크웨어글로벌 주식회사가 보유한 대한민국 등록특허 제10-2564770호
「거래 왜곡을 방지하기 위한 탈중앙화 거래소를 통한 토큰 스왑 시스템 및 방법」에 기술되어 있다.
출원일은 2023년 1월 12일, 등록일은 2023년 8월 3일이다.</p>
<p>여기서 특허 등록 사실은 기술적 완전성의 증명이나 글의 권위를 위한 장식이 아니다. 2022년에 시작한
설계가 어떤 형태로 정리되고 공개되었는지 출처와 이력을 투명하게 밝히기 위한 정보다. 이 글 또한 특허의
실시허락이나 권리 불행사 선언을 대신하지 않는다. 기술을 공공재에 가깝게 공개하려면 오픈소스 코드와
함께 방어적 권리 행사 원칙, patent pledge 또는 명시적인 라이선스 범위를 별도로 정해야 한다.</p>
<p>블록체인 생태계에서 중요한 것은 특허의 존재 자체보다 그 권리를 어떻게 사용할 것인지에 가깝다. 이
기록의 목적은 권리를 앞세우는 것이 아니라, 아이디어의 가정과 한계까지 공개해 다음 사람이 더 나은
모델을 만들 수 있게 하는 데 있다.</p>
<h2 class="anchor anchorTargetStickyNavbar_SAay" id="다시-스왑이-끝난-뒤">다시, 스왑이 끝난 뒤<a href="https://bwg-loonshots.pages.dev/articles/internalizing-swap-induced-mev#%EB%8B%A4%EC%8B%9C-%EC%8A%A4%EC%99%91%EC%9D%B4-%EB%81%9D%EB%82%9C-%EB%92%A4" class="hash-link" aria-label="다시, 스왑이 끝난 뒤에 대한 직접 링크" title="다시, 스왑이 끝난 뒤에 대한 직접 링크" translate="no">​</a></h2>
<p>2022년의 질문으로 돌아가 보자.</p>
<p>사용자의 스왑이 끝난 뒤 가격 차이가 남고, 누군가 그 차이를 닫아야 한다면, 그 과정에서 생기는 가치는
누구에게 돌아가야 할까?</p>
<p>이 설계는 MEV를 없애지 못한다. 외부의 모든 ordering 권한도 통제하지 못한다. 단순한 준비금 비율을
공정가격으로 믿어서도 안 된다. 하지만 한 가지 선택지는 분명하게 제시한다. 사용자 행동이 만든 차익을
외부 경쟁의 전리품으로만 남겨두지 않고, 프로토콜 실행 안으로 가져와 사용자에게 다시 돌려줄 수 있다는
선택지다.</p>
<p>스왑은 몇 초 안에 끝난다. 그러나 그 스왑이 만든 가치가 어디로 가는지는 코드가 정한다.</p>
<p>2022년에 시작한 우리의 질문은 바로 그 코드에 관한 것이었다.</p>]]></content:encoded>
            <category>금융 인프라</category>
            <category>프로토콜 엔지니어링</category>
        </item>
    </channel>
</rss>