JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

治理攻擊:買票、閃電貸奪權與巨鯨壟斷

一個通過的提案,等於拿到金庫的最高權限——於是攻擊者只租一個區塊的選票、趁無人關注時悄悄囤積,或乾脆把票買下來。本篇拆解真實的奪權手法,並教你如何把治理設計得撐得住這些攻擊。

你真正要守護的東西:金庫的鑰匙

想像一座沒有人看守的銀行金庫。打開它的唯一方法,是蒐集到過半數的編號投票籌碼、投進一個投幣孔;只要足夠多的籌碼意見一致,門就會打開,並執行那張勝出的紙條上寫的任何指令——包括「把所有資產轉到 0xATTACKER」。這就是鏈上治理。一個 DAO 不是一種氛圍、也不是一個 Discord 群;它是一份真正握著金庫鑰匙的智慧合約,而一個通過的提案,就是這份合約會以自身權限執行的任意交易。贏得投票不是「有發言權」——而是以協定的身分簽名

這個視角就是整個安全問題的核心。在本階稍早,你看過誠實的流程:提案、投票、排入佇列、執行。一場治理攻擊用的是完全相同的機制,只是操控者的目標是掏空金庫,而非改善協定。而既然規則是「誰握有最多治理代幣誰就說了算」,下面每一種攻擊其實都在問同一個問題:攻擊者能用多便宜的代價、在剛好夠長的時間裡,掌控足夠的投票權重,去通過一個惡意提案?

只租一個區塊的多數:Beanstalk 閃電貸奪權

2022 年 4 月 17 日,穩定幣協定 Beanstalk 在大約 13 秒內——也就是一筆以太坊交易的時間——損失了約 1.82 億美元。攻擊者沒有攻破密碼學,也沒找到重入漏洞。他們只是投了票。武器是閃電貸:一種必須在同一筆交易內借出並還清的無擔保貸款,讓任何人都能在幾百毫秒內動用十億美元等級的資本。Beanstalk 犯下的致命錯誤,是把即時代幣餘額當成投票權重,於是只存在一個區塊的資本,就能在那一個區塊裡投票。

兩天前,攻擊者已悄悄提交了一個惡意提案(BIP-18),其酬載外表用「捐款給烏克蘭」包裝得人畜無害,實際上卻會執行一個任意呼叫,把協定資產一掃而空、轉到自己手上。Beanstalk 有一個 `emergencyCommit` 函式,只要某提案達到 Stalk 治理代幣的三分之二超級多數,就會立即執行任何提案——繞過正常的 7 天等待期。這個即時執行就是第二個致命缺陷:就算你有一道誠實的時間鎖,只要存在一道能跳過它的後門,它就形同虛設。

  1. 透過 Aave、Uniswap 與 SushiSwap 的閃電貸借入約 10 億美元的穩定幣與 ETH——全在同一筆交易內、零擔保。
  2. 把貸款換成 Beanstalk 的 LP 代幣,存入協定的 Silo,瞬間鑄造出全體 Stalk 投票權的超級多數(約 67%)。
  3. 對事先提交好的 BIP-18 呼叫 emergencyCommit。合約看見三分之二多數,當場執行提案的酬載。
  4. 酬載把協定的整個金庫轉給攻擊者。提領、平倉、還清所有閃電貸,剩下的全部收進口袋。
  5. 淨利:約 7,600 萬美元落袋,協定約 1.82 億美元的價值灰飛煙滅——而那借來的十億,從頭到尾都不必在一筆原子交易之外存在過。

用程式碼修補:對過去快照、為未來設延遲

只要投票權重改成從過去某個區塊讀取、而非即時餘額,閃電貸這一類攻擊就會當場失效。如果一個提案的快照區塊在提案建立的當下就固定下來,那麼事後才借來的代幣就沒有任何歷史權重——過去並不存在一個攻擊者持有它們的區塊。這正是 OpenZeppelin 與 Compound 的 Governor 都建立在帶檢查點的代幣(`ERC20Votes`)之上、並讀取 `getPastVotes` 的原因,也是為什麼快照投票是本篇最重要的單一防禦。以下是 Beanstalk 實質上採用的脆弱寫法:

// VULNERABLE: voting weight read from the CURRENT balance.
// A flash loan can borrow millions of tokens, vote, and repay
// in the SAME transaction -- the balance only needs to exist for
// one block, and that is exactly how long it needs to vote.
contract NaiveGovernor {
    IGovToken public immutable gov;

    function castVote(uint256 proposalId, bool support) external {
        uint256 weight = gov.balanceOf(msg.sender); // <-- LIVE balance
        _tally(proposalId, support, weight);
    }

    // Worse: instant execution with no timelock back door.
    function emergencyCommit(uint256 proposalId) external {
        require(_forVotes(proposalId) * 3 >= gov.totalSupply() * 2,
                "no 2/3 supermajority");
        _execute(proposalId); // arbitrary call -> drains the treasury NOW
    }
}
一段程式兩個漏洞:以即時餘額計票,加上一條完全跳過時間鎖的緊急路徑。
// SAFE: weight comes from a PAST block fixed when the proposal
// was created. Tokens acquired after that block have ZERO power,
// so a flash loan within the voting tx cannot help.
function _getVotes(address account, uint256 snapshotBlock)
    internal view returns (uint256)
{
    // ERC20Votes stores per-block checkpoints; you must delegate
    // to yourself (even self-delegation) for a balance to count.
    return token.getPastVotes(account, snapshotBlock); // historical
}

// AND every approved proposal flows through a Timelock: even a
// passed proposal must wait (e.g. 2 days) before it can touch
// the treasury, giving honest users time to see it and exit/fork.
function execute(uint256 proposalId) external {
    require(state(proposalId) == ProposalState.Queued, "not queued");
    require(block.timestamp >= eta[proposalId], "timelock not elapsed");
    _execute(proposalId);
}
建立時即快照能扼殺閃電貸投票;強制的時間鎖則拔除即時執行的後門。

當沒人在投票時:巨鯨壟斷與惡意收購

並非每場攻擊都需要閃電貸。更安靜的危險,是一幣一票加上選民冷感。在多數 DAO 裡,真正會出來投票的,往往只佔代幣供給的個位數百分比。這讓有效法定門檻變成一個很小、容易被攻下的數字:如果 5% 的代幣就能決定結果,那麼一個耐心在公開市場上、看起來像普通買家、慢慢囤到略多於 5% 的攻擊者,就能違背沉默的 95% 之意願通過提案。這是預設的金權政治:財富即權力,而低參與讓財富變得廉價。

Build Finance DAO(2022 年 2 月)是教科書級的案例。攻擊者買進便宜、籌碼分散的 BUILD 治理代幣,直到能通過一個把合約鑄造權與控制權授予自己的提案。第一個惡意提案失敗了;第二個則因為幾乎沒人在看而通過。接著他們大量增發新代幣,捲走約 47 萬美元——一場徹底、外表完全合法的治理奪權,完全沒有用到任何漏洞。是協定自己的規則把鑰匙交了出去。

乾脆把票買下來:賄選市場與自利提案

如果選票是可轉移的代幣,那麼投票權就有價格——而只要有價格,市場就會出現。最有名的例子是「Curve 戰爭」。Curve 依照鎖倉 CRV(vote-escrowed CRV)持有者的投票,把 CRV 增發導向各個流動性池,於是想讓增發流向自家池子的協定,乾脆直接賄賂那些投票者。整套市集(Votium、Hidden Hand、bribe.crv)應運而生,讓任何人都能付錢請代幣持有者按特定方向投票。這種「買票」大多合法、且公開進行,但它揭露了一個令人不安的事實:代幣加權治理可能把協定的決策導向出價最高者,而非它的長期健康。

更陰暗的近親,是用偷來或借來的代幣投票讓自己脫罪。在 Mango Markets 攻擊事件(2022 年 10 月)中,攻擊者先用預言機操縱把 MNGO 價格拉高,以灌水後的擔保品借出約 1.14 億美元、掏空協定。接著治理的轉折來了:攻擊者握著用贓款買來的 MNGO 代幣,提交並為自己的提案投票——這個提案要讓他保留約 4,700 萬美元當「賞金」、並免除刑事責任。提案最終沒有執行成功,但它示範了治理被當成逃逸用的車,而非破門而入的工具。

給 DAO 設計者的縱深防禦清單

沒有任何單一控制能擋下每一種治理攻擊;你要把它們層層疊起,逼攻擊者必須同時擊穿全部。請把這份清單讀成從「攻擊者握有代幣」到「攻擊者拿到你金庫」之間,一道道彼此獨立的關卡:

  1. 在提案建立時就固定一個過去區塊來快照投票權重——這是擊敗閃電貸與同區塊買票攻擊、不容妥協的一道防線。
  2. 讓每個核准的提案都必經一道強制時間鎖(例如 2 至 7 天),且不留任何緊急繞道——反應時間是你最後一道防線。
  3. 設定真正的法定門檻,讓被動的多數不會被一小撮積極的少數推翻;並把提案門檻設得夠高,以嚇阻洗版式的垃圾提案。
  4. 鼓勵委託,讓積極且需負責的受託人集中起誠實的投票權,去對抗那些從不現身的被動巨鯨。
  5. 加入一個守護者或安全委員會(通常是一個多簽),它能否決或取消已排入佇列的提案,但刻意被設計成自己無法動用金庫。
  6. 把承載價值的代幣與投票代幣分開,或把投票代幣長期鎖倉,讓租來或借來的影響力背負真實的成本與時間風險。

最後,把治理當成任何其他高價值的合約攻擊面來對待:讀一讀 Beanstalk、Build Finance 與 Mango 的事後檢討,然後反問自己的系統:「掌控一個區塊的多數,最便宜的方法是什麼?惡意提案通過後的那幾秒會發生什麼事?」如果誠實的答案是「瞬間、全額損失」,那你建的不是治理——你建的是一座密碼可供出售的金庫。真正的功夫,在於讓一次攻擊的成本更高、速度更慢,高過、慢過你金庫本身的價值。