2021年开发Xai-Maker借贷协议时,我们遇到了一个让整个团队头疼的问题:谁来决定借贷利率?
传统金融里,这种事银行说了算。但在DeFi的世界,如果让项目方单方面控制利率,用户凭什么信任你?如果完全放任市场,万一出现极端情况怎么办?经过调研,我把目光投向了Uniswap——这个管理着数百亿美元资金的去中心化治理典范。
深入研究Uniswap的治理合约后,我有种"原来如此"的恍然大悟感。这套看似复杂的系统,实际上是一部精妙的"民主机器",每个齿轮都有其深层的设计哲学。今天我想从一个开发者的角度,与你分享一下这套治理机制到底有什么过人之处。
想象一下,如果要设计一套让千万人参与决策的系统,你会怎么做?
Uniswap给出的答案是三个核心合约的完美配合:
UNI Token合约 — 相当于"选民证",不仅记录你的投票权,还能让你把票委托给信任的人。这解决了"我有UNI但没时间研究每个提案"的现实问题。
GovernorAlpha合约 — 就像"议会大厅",负责收集提案、统计投票、判断结果。虽然名字里有"Alpha"(说明这是早期版本),但功能已经相当完备。
Timelock合约 — 这是最关键的"安全阀"。任何通过的提案都不能立即执行,必须等待2-30天的公示期。这给了社区最后的反悔机会,也给了恶意提案被发现的时间。
有意思的是,Uniswap用的GovernorAlpha确实有些"年代感"了。现在更新的治理合约有更多功能,但Uniswap依然在用这套相对简单的系统。为什么?或许正是"简单即美"的体现——功能够用就行,稳定性更重要。
说起Uniswap的治理流程,我最佩服的是它的"渐进式筛选"设计。
首先是3天的温度检查。就像你想在朋友圈发个想法试试水一样,提案者会在论坛、Discord等地方抛出想法,看看有没有人响应。门槛很低:只要25,000个UNI支持就算通过。
接着是5天的共识检查。这时候讨论会更正式,类似于"听证会"。支持门槛提高到50,000个UNI,让提案者必须回应质疑,完善方案。
这两个环节都在Snapshot平台进行,最大的好处是不需要Gas费。
想象一下,如果每次投票都要花几十美元手续费,还有多少人愿意参与?
经过链下"预热",提案正式进入链上投票。这时门槛骤然提高,看看GovernorAlpha合约里的关键参数设置:
/// @notice 发起提案需要的投票权重门槛
function proposalThreshold() public pure returns (uint) {
return 10_000_000e18; // 1000万UNI,占总供应量1%
}
/// @notice 提案通过需要的最低支持票数
function quorumVotes() public pure returns (uint) {
return 40_000_000e18; // 4000万UNI,占总供应量4%
}
/// @notice 投票期长度(以区块计算)
function votingPeriod() public pure returns (uint) {
return 40_320; // 约7天(按15秒/区块计算)
}
/// @notice 单个提案最多可包含的操作数
function proposalMaxOperations() public pure returns (uint) {
return 10; // 防止复杂攻击
}
为什么门槛设置得这么高?我理解这是一种"负责任的民主"。1%的发起门槛确保提案者是真心的,不是来捣乱的;4%的通过门槛确保重大决策有足够的民意支持。
最后是执行阶段。通过的提案不会立即生效,而是进入Timelock"冷静期",Timelock合约中的时间参数设置如下:
uint public constant GRACE_PERIOD = 14 days; // 宽限期
uint public constant MINIMUM_DELAY = 2 days; // 最小延迟
uint public constant MAXIMUM_DELAY = 30 days; // 最大延迟
算下来,一个提案从想法到落地,最快也要17天,正常情况下需要22天,复杂提案可能长达45天。
这效率在传统公司看来简直是"龟速",但在去中心化治理中,这恰恰是安全的保障。毕竟,一旦执行错误,可能造成数亿美元的损失。
说到委托机制,这是我觉得最巧妙的设计之一。
现实中,大部分UNI持有者并没有时间和精力研究每个技术提案。怎么办?委托给你信任的专家呗!这就像选举制度中的"代议制"。
但这里面有个技术难题:如何防止有人在投票期间突然买入大量代币来操纵结果?
Uniswap的解决方案是Checkpoint机制——每当代币发生转移或委托变化,系统就会在那个区块号记录一个"快照"。投票时,使用的是提案开始时的历史权重,而不是当前权重。
/// @notice 检查点结构,记录特定区块的投票权重
struct Checkpoint {
uint32 fromBlock; // 生效区块号
uint96 votes; // 投票权重
}
/// @notice 获取账户在指定区块的历史投票权重
function getPriorVotes(address account, uint blockNumber)
public view returns (uint96) {
require(blockNumber < block.number, "not yet determined");
uint32 nCheckpoints = numCheckpoints[account];
if (nCheckpoints == 0) {
return 0;
}
// 检查最新检查点
if (checkpoints[account][nCheckpoints - 1].fromBlock <= blockNumber) {
return checkpoints[account][nCheckpoints - 1].votes;
}
// 二分查找历史权重
uint32 lower = 0;
uint32 upper = nCheckpoints - 1;
while (upper > lower) {
uint32 center = upper - (upper - lower) / 2;
// ... 二分查找逻辑
}
return checkpoints[account][lower].votes;
}
这套机制的精妙之处在于:既允许了灵活的委托,又防止了恶意操纵。每个人的投票权重在提案开始的那一刻就"锁定"了,买再多币也没用。
Uniswap的治理参数设置,我觉得是博弈论在实践中的经典案例:
| 环节 | 门槛 | 设计意图 |
|---|---|---|
| 温度检查 | 25,000 UNI | 过滤明显不靠谱的想法 |
| 共识检查 | 50,000 UNI | 确保有一定民意基础 |
| 发起提案 | 1000万 UNI(1%) |
防止垃圾提案泛滥 |
| 通过提案 | 4000万 UNI(4%) |
确保重大决策有足够支持 |
这种"递进式门槛"设计很聪明:既给了小想法萌芽的机会,又确保重大决策不被少数人绑架。
1%的发起门槛意味着,按当前价格,你大概需要几千万美元的UNI才能发起提案。这听起来很高,但考虑到Uniswap管理的资金规模,这个门槛其实很合理。
4%的通过门槛更是关键。想想看,如果门槛太低,比如1%,那可能几个大户就能通过影响整个协议的提案;如果太高,比如10%,那可能永远都通过不了任何提案。4%正好在"民主"和"效率"之间找到了平衡点。
深入看GovernorAlpha的代码实现,我发现它的设计哲学是"简单粗暴,但管用"。
比如投票选项,只有"支持"和"反对"两种,没有现代治理合约常见的"弃权"选项。这看似简陋,但在实际使用中反而避免了很多复杂情况。
/// @notice 投票记录结构
struct Receipt {
bool hasVoted; // 是否已投票
bool support; // 是否支持(true-支持,false-反对)
uint96 votes; // 投票权重
}
/// @notice 内部投票逻辑
function _castVote(address voter, uint proposalId, bool support) internal {
require(state(proposalId) == ProposalState.Active, "voting is closed");
Proposal storage proposal = proposals[proposalId];
Receipt storage receipt = proposal.receipts[voter];
require(receipt.hasVoted == false, "voter already voted");
// 获取提案开始时的历史投票权重
uint96 votes = uni.getPriorVotes(voter, proposal.startBlock);
if (support) {
proposal.forVotes = add256(proposal.forVotes, votes);
} else {
proposal.againstVotes = add256(proposal.againstVotes, votes);
}
receipt.hasVoted = true;
receipt.support = support;
receipt.votes = votes;
emit VoteCast(voter, proposalId, support, votes);
}
提案的生命周期也很清晰,总共8个状态:从Pending(等待开始)到Executed(执行完成),每个状态转换都有明确的条件。
enum ProposalState {
Pending, // 等待投票开始
Active, // 投票进行中
Canceled, // 已取消
Defeated, // 投票失败
Succeeded, // 投票成功,等待排队
Queued, // 已排队到Timelock
Expired, // 执行期过期
Executed // 已执行
}
function state(uint proposalId) public view returns (ProposalState) {
Proposal storage proposal = proposals[proposalId];
if (proposal.canceled) {
return ProposalState.Canceled;
} else if (block.number <= proposal.startBlock) {
return ProposalState.Pending;
} else if (block.number <= proposal.endBlock) {
return ProposalState.Active;
} else if (proposal.forVotes <= proposal.againstVotes ||
proposal.forVotes < quorumVotes()) {
return ProposalState.Defeated;
} else if (proposal.eta == 0) {
return ProposalState.Succeeded;
} else if (proposal.executed) {
return ProposalState.Executed;
} else if (block.timestamp >= add256(proposal.eta, timelock.GRACE_PERIOD())) {
return ProposalState.Expired;
} else {
return ProposalState.Queued;
}
}
最有意思的是,GovernorAlpha限制每个地址同时只能有一个活跃提案。这防止了"提案轰炸"攻击,也确保了每个提案都能得到充分的讨论。
function propose(...) public returns (uint) {
// 检查提案者是否有足够投票权
require(
uni.getPriorVotes(msg.sender, sub256(block.number, 1)) > proposalThreshold(),
"proposer votes below proposal threshold"
);
// 检查是否有其他活跃提案
uint latestProposalId = latestProposalIds[msg.sender];
if (latestProposalId != 0) {
ProposalState proposersLatestProposalState = state(latestProposalId);
require(
proposersLatestProposalState != ProposalState.Active &&
proposersLatestProposalState != ProposalState.Pending,
"one live proposal per proposer"
);
}
// ... 创建提案逻辑
}
如果说GovernorAlpha是民主决策的机器,那Timelock就是防止决策错误的保险丝。
任何通过的提案都必须先进入"排队"状态,等待指定的时间后才能执行。这个等待期最短2天,最长30天,具体时长取决于提案的重要性。
function queueTransaction(
address target,
uint value,
string memory signature,
bytes memory data,
uint eta
) public returns (bytes32) {
require(msg.sender == admin, "Call must come from admin");
require(
eta >= getBlockTimestamp().add(delay),
"Estimated execution block must satisfy delay"
);
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
queuedTransactions[txHash] = true;
emit QueueTransaction(txHash, target, value, signature, data, eta);
return txHash;
}
function executeTransaction(
address target,
uint value,
string memory signature,
bytes memory data,
uint eta
) public payable returns (bytes memory) {
require(msg.sender == admin, "Call must come from admin");
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
require(queuedTransactions[txHash], "Transaction hasn't been queued");
require(getBlockTimestamp() >= eta, "Transaction hasn't surpassed time lock");
require(
getBlockTimestamp() <= eta.add(GRACE_PERIOD),
"Transaction is stale"
);
queuedTransactions[txHash] = false;
// 执行交易
bytes memory callData = bytes(signature).length == 0 ?
data :
abi.encodePacked(bytes4(keccak256(bytes(signature))), data);
(bool success, bytes memory returnData) = target.call{value: value}(callData);
require(success, "Transaction execution reverted");
emit ExecuteTransaction(txHash, target, value, signature, data, eta);
return returnData;
}
更巧妙的是,即使等待期结束,提案也只有14天的"宽限期"来执行。超过这个时间窗口,提案就自动过期,需要重新走流程。
这种设计的深层逻辑是:给社区"后悔"的机会,也给恶意提案被发现的时间。想象一下,如果有人通过某种方式推动了一个有害的提案,至少还有几天到几十天的时间来阻止它。
从效率角度看,Uniswap的治理确实"慢"。一个简单的参数修改可能需要几个星期才能生效。但从安全角度看,这种"慢"恰恰是必要的。
在传统公司,CEO可能一个电话就能改变重要决策。但在管理数百亿美元的去中心化协议中,任何匆忙的决定都可能带来灾难性后果。
我们在设计Xai-Maker的治理机制时,也面临着同样的权衡。最终我们选择了类似的渐进式流程,虽然慢一点,但至少能睡得安稳。
经过深度研究Uniswap的治理机制,我开始思考一个更现实的问题:什么情况下适合用DAO?
在Web3圈子里,我们经常听到"未来公司的组织形式就是DAO"这样的论调。但经过几年的实践,我发现理想很丰满,现实很骨感。
从Uniswap的成功经验来看,DAO适用需要满足几个关键条件:
1. 利益相关者众多,决策影响面大 Uniswap管理着数百亿美元资金,每个参数调整都影响成千上万用户。这种情况下去中心化决策是必要的。但如果你的项目只有几个核心用户,为了调整一个参数要走22天流程,这不是民主,这是自虐。
2. 安全性优于效率,决策可以"慢一点" 协议级别的重大升级、资金分配可以慢几个星期,但一旦出错可能万劫不复。但如果需要快速响应市场变化,DAO治理可能让你错失良机。
3. 治理对象相对客观,不需要太多主观判断 代码升级、参数调整有相对客观的评判标准。但产品设计、品牌策略这类需要主观判断的决策,DAO往往力不从心。想象一下让几万人投票决定UI颜色——这画面太美我不敢看。
早期项目快速迭代期:需要"快速试错、快速调整",民主流程天然与"快速"相冲突。
复杂专业决策:智能合约安全审计、算法优化等,普通代币持有者投票其实是"伪民主"。
信息不对称场景:商业谈判、战略合作等无法完全公开的决策。
最成功的项目往往采用"混合治理":
Uniswap实际上也是这样做的——重大决策通过DAO,日常开发还是由专业团队负责。
先问目的:治理的目的是获得信任、分散风险,还是因为"DAO很酷"?
从边缘开始:从社区活动、小额资金分配开始实验,积累经验后再扩大范围。
保留紧急机制:即使Uniswap也保留了Guardian角色,完全去中心化在现阶段还不现实。
DAO不是万能药,也不是必需品。它更像是一种工具,适合特定的场景和阶段。Uniswap的成功证明了DAO在协议层面的可行性,但不意味着所有项目都应该复制这套模式。
回过头看,Uniswap的治理系统并不完美,但它找到了一个相对平衡的解决方案。在安全性、效率性和民主性之间,它选择了安全和民主,适度牺牲了效率。
对于我们这些后来者,Uniswap最大的价值不是提供了一个完美的模板,而是证明了去中心化治理是可行的。它用实际行动告诉我们:代码可以是法律,社区可以是正攵府,技术可以服务于更崇高的理想。
也许未来真会出现完全去中心化的组织,但在那之前,我们需要的不是盲目理想主义,而是务实的渐进式探索。就像Uniswap的治理——不完美,但足够好;不激进,但很稳定。
这也是我为什么选择在这个行业深耕的原因——我们不仅仅是在写代码,更是在重新定义人类协作的方式。
基于全文检索与主题相似度
自17年学习区块链以来,笔者先后参与开发诸多web3项目,如DeFi借贷、NFT交易平台、链游、IDO、O2C、DAO、版权、Fabric等DAPPs,近期想做些知识沉淀与分享,坚持原创,内容持续更新,欢迎关注我的公众号。 本文主要分享Uniswap是怎样做治理的,读完全文大约需要12分钟。 主要内
说在前面 作为一个在Web3领域深耕多年的开发者,我经常被面试官问到Uniswap各个版本之间的区别。 今天,我想用最直白的方式,带你理解Uniswap从v1到v4的核心变化,让你在面对相关问题时能够条理清晰地回答。 核心区别一览 先给你一个全局视角,Uniswap四个版本的关键变化: - v1:开
在区块链的世界里,DEX的效率,说白了,直接决定了你交易时能不能“多省点、多赚点”。你有没有遇到过这种情况:在Uniswap V3上把 ETH换成USDC,有时候直接兑换就挺划算的,有时候却发现绕个弯,比如先换成WBTC,再换成USDC,反而能多拿一点? 这可不是巧合,而是一套精心设计的最优路径查找