Liquity v2 治理系统基于模块化提案机制,允许 LQTY 持有者通过质押获得随时间线性增长的投票权,并将其分配给提案或用于否决。
核心 Liquity v2 协议在每个抵押品分支上内置了激励机制,旨在同时促进 BOLD 稳定币的价格稳定性和流动性。75% 的借贷活动收入用于激励核心系统的稳定性池,其余 25% 的借贷活动收入(激励部分)则分配给基于模块化倡议的治理。
基于模块化倡议的治理允许 LQTY 持有者通过质押随时间累积的投票权,将激励部分定向到任意地址,这些地址被指定为倡议。倡议可以无需许可地注册。
用户还可以将投票权用作否决权,以尝试阻止他们认为不值得的倡议获得奖励。
系统将时间划分为每周的纪元。投票活动以去中心化的方式完成快照,累积的激励在纪元结束时发放给符合资格标准的倡议——主要是投票阈值。对于给定纪元,符合资格的倡议根据其在纪元投票中的份额,获得该纪元累积的 BOLD 奖励的按比例份额。
治理 - 中央系统合约,管理所有治理操作,包括倡议注册、LQTY 质押/解质押、投票机制和奖励分配。它处理时间加权投票权计算、纪元转换和 BOLD 代币奖励,同时管理用户代理合约的部署和交互。
用户代理工厂 - 一个工厂合约,使用 CREATE2 进行确定性寻址,部署 UserProxy 实现的最小代理克隆。它被 Governance 合约继承,以提供 UserProxy 的部署和管理能力。它还维护用户与其用户代理之间的关系。
用户代理 - 充当单个用户与 Liquity v1 质押系统之间的中介,持有他们质押的 LQTY 头寸。它处理所有直接的 v1 质押操作和奖励收集。只有 Governance 合约可以调用其可变函数。代理架构允许系统代表其用户持有个体质押的 LQTY 头寸。
贿赂倡议 - 一个基础合约,允许外部方通过存入 BOLD 和其他代币作为贿赂来激励对倡议的投票。它记录用户跨纪元的投票分配,确保贿赂按比例分配给投票者。该合约提供了可扩展的 Hook 和函数,允许开发者创建具有自定义逻辑的专门倡议,同时保持核心贿赂分配机制。
治理系统基于每周纪元的方案运行,为投票和领取奖励提供可预测的时间窗口。每个纪元持续 EPOCH_DURATION(7 天)。纪元方案为用户规划投票和否决操作提供了可预测的窗口。
每个纪元有两个不同的阶段:
阶段 1:投票与否决(前 6 天)
阶段 2:仅否决(最后一天)
阶段 2 的目的是防止恶意行为者在最后一刻将投票分配给与 Liquity 生态系统不一致的倡议。
短暂的否决阶段至少给了其他质押者一个机会来否决此类恶意倡议,即使他们不得不从其他倡议中撤回投票权。
纪元以固定的 7 天间隔自动转换,无需手动干预触发新纪元。新纪元中的第一个基于纪元的操作会触发相关快照——参见快照部分。
LQTY 代币持有者可以通过 Governance.depositLQTY 向治理系统存入 LQTY。存入的 LQTY 被质押在 Liquity v1 中,从而从 v1 费用中获得 ETH 和 LUSD 奖励。有关 v1 质押和奖励的更多详情,请参见 Liquity v1(https://docs.liquity.org/faq/staking)。
存入的 LQTY 随时间线性累积投票权。用户从存入的 LQTY 中获得的投票权可以分配给倡议或从倡议中撤销。
用户可以随时增加其存入的 LQTY,并在没有活跃的倡议分配时通过 withdrawLQTY 提取部分或全部存入的 LQTY。
存款和提款都可以通过 ERC2612 许可方式进行,分别使用 depositLQTYViaPermit 和 withdrawLQTYViaPermit。
存款和提款函数允许用户通过传递 _doClaimRewards 布尔值来选择性地领取其 v1 质押奖励(LUSD 和 ETH)。
用户的 LQTY 存款随时间线性累积投票权。也就是说,给定 LQTY 存款的绝对投票权与 1)存入的 LQTY 数量和 2)自存款以来经过的时间成正比。
当存入一笔 LQTY 时,与该笔存款相关的投票权将等于 0。
对用户现有存款的追加以相同方式累积投票权:追加的投票权根据其大小和自追加以来经过的时间线性累积。
用户总存入 LQTY 的投票权等于组成其存款的所有单个 LQTY 存款/追加的投票权之和。
提款从用户的未分配 LQTY 中提取。提款不了解存款历史。提取用户未分配 LQTY 的 x%,会将其未分配 LQTY 的投票权减少 x%——即使用户可能在不同时间进行了存款,较早的存款累积了更多投票权。
因此,提款被认为是“成比例的”,即它们以相同百分比减少用户之前所有存款块的投票权。
因此,一个拥有非零未分配投票权的用户,在存入 m LQTY 后立即提取 m LQTY,其未分配投票权会减少。这种自然惩罚激励用户将其 LQTY 保留在治理系统中。
LQTY 可分配给:
存入的 LQTY 随时间连续累积投票权,无论其分配给哪个实体(即用户或倡议)。所有 LQTY 以相同速率累积投票权。
对于复合 LQTY 金额——即由不同时间点的多个存款“块”组成的存款——每个块从其存入的时间点开始线性赚取投票权。
因此,对于具有随时间进行的 n 次 LQTY 存款的单个用户 A,其投票权由下式给出:
V_A(t) = m_1* (t - t_1) + m_2* (t - t_2) + ... + m_n* (t - t_n)
即
V_A(t) = t*sum(m_i) - sum(m_i*t_i)
所以:
V_A(t) = t*M_A - S_A
其中:
i:表示第 i 次存款事件的索引t_i:第 i 次存款发生的时间V_A:用户 A 从 n 次存款中到时间 t 为止的总投票权M_A:A 的 LQTY 存款总额S_A:“偏移量”,即 A 的存款块按存款时间加权的总和。投票权如上计算——即 V_A(t) = t*M_A - S_A。记账通过存储每个用户的 LQTY 金额和“偏移量”总和来处理。每当用户存款、提款或向倡议分配 LQTY 时,这些跟踪器都会更新。
使用 LQTY 金额和偏移量跟踪器总和的方法同时适用于用户和倡议。
LQTY 金额和偏移量被记录用于:
完整方案在本文档中概述。
用户可以通过 Governance.allocateLQTY 向倡议分配和撤销 LQTY。当 LQTY 分配给倡议时,相应的投票权也被分配。
从用户到倡议的分配在与提款相同的意义上是“成比例的”。
分配后,已分配 LQTY 的投票权继续随时间线性增长。
用户将其每个倡议的选定 LQTY 分配传递给 allocateLQTY。
在内部,分配分两步执行:首先通过单次调用内部函数 _allocateLQTY 将所有当前分配归零,然后通过第二次调用更新为新值。
用户还可以通过 Governance.allocateLQTY 向倡议分配否决。与投票权类似,分配给否决的 LQTY 线性累积“否决权”,并且内部计算和记账相同。
收到足够数量否决的倡议无法领取奖励,并且可以无需许可地注销——参见倡议状态部分以了解确切的阈值公式。
对倡议的 LQTY 分配会跨纪元持续存在,因此分配给该倡议的相应投票权会跨纪元继续线性增长。
分配和撤销 LQTY/投票权是路径无关的——即,当用户将 x 投票权分配给一个倡议然后立即撤销时,其投票权保持不变。
相比之下,存款和提款 LQTY 是路径依赖的——对于一个拥有非零投票权的用户,追加和提取 x LQTY 将减少其投票权。这是因为追加的 LQTY 块具有 0 投票权,但提款的成比例性质会减少组成其存款的所有先前 LQTY 块的投票权。
倡议可以通过 registerInitiative 无需许可地注册。调用者以 BOLD 支付 REGISTRATION_FEE。调用者还必须累积足够的总体投票权(即其已分配和未分配投票权的总和)才能注册倡议。该阈值是动态的——等于前一纪元总投票的快照乘以 REGISTRATION_VOTING_THRESHOLD。因此,前一纪元的总投票越多,注册新倡议所需的投票权就越多。
如果倡议满足这些要求,它将在后续纪元有资格进行投票。
注册会在 registeredInitiatives 映射中记录倡议的地址及其注册的纪元。
倡议可以通过 unregisterInitiative 无需许可地注销。
如果满足以下任一条件,倡议可以被注销:
UNREGISTRATION_AFTER_EPOCHS(4)个纪元,且未被领取奖励。UNREGISTRATION_THRESHOLD_FACTOR。由于 BOLD 奖励基于倡议在每个纪元末的按比例投票份额分配,并且由于投票(和否决)随时间持续累积,因此必须为给定纪元记录倡议累积投票和否决的快照。
此外,必须为每个纪元记录总投票和总否决以及总累积 BOLD 奖励的快照,以进行按比例奖励计算。
倡议快照由 Governance._snapshotVotesForInitiative 处理。
它检查倡议上次快照的时间,如果是在前一纪元结束之前,则记录该倡议当前投票权的新快照。如果已经拍摄了更近期的快照,则该函数为空操作。
倡议快照在用户操作内部进行:向倡议分配 LQTY(allocateLQTY)、注册倡议(registerInitiative)以及领取倡议激励(claimForInitiative)都会在更新其他倡议状态之前执行倡议快照。
倡议快照也可以通过外部函数 Governance.snapshotVotesForInitiative 和 Governance.getInitiativeState 无需许可地记录。
总投票数由 Governance._snapshotVotes 类似地进行快照,该函数在上述所有用户操作中调用,此外还在倡议注册时(registerInitiative)以及通过 calculateVotingThreshold 无需许可地调用。
前一纪元可供领取的总 BOLD——boldAccrued——通过 Governance._snapshotVotes 进行快照。它用作该纪元奖励分配计算中的分母。
由于纪元无缝转换,无需手动触发操作,新纪元中的第一个相关操作会触发快照计算。
由于投票权是 LQTY 和时间的简单线性函数(参见上面的投票权部分),投票快照可以追溯计算,即在上一纪元结束之后。重要的是快照在 LQTY 数量发生变化之前拍摄,这已经满足。为了拍摄快照,在投票权计算中使用前一纪元的结束时间戳。
BOLD 奖励更棘手——它们是不规则的,以不可预测的批次到达(取决于 v2 核心系统的动态)。因此,延迟的 BOLD 快照可能会计入一些在纪元结束后到达的 BOLD。实际上,这略微有利于在前一纪元注册的倡议,并略微减少当前纪元的 BOLD 奖励。
然而,无需许可的快照函数 Governance.calculateVotingThreshold 允许任何人在纪元边界或非常接近边界时拍摄快照,并确保公平的 BOLD 分布。
快照一旦为给定纪元记录,便是不可变的。
治理系统使用状态机来确定每个倡议的状态。相关函数是 Governance.getInitiativeState。状态决定了可以对倡议采取哪些操作。
在给定纪元中,倡议可以处于基于前一纪元快照的几种状态之一。
以下是倡议可能处于的状态、导致这些状态的条件及其后果。 (注意:状态机按以下顺序检查条件——例如,处于 CLAIMABLE 状态的倡议肯定不在 CLAIMABLE 之上的任何状态中):
<img alt="image" src="https://github.com/user-attachments/assets/ab9c5df1-0372-43b4-87d9-73c1daea1a62" />
投票阈值有两种用途:确定倡议是否有足够的净投票数可以被领取,以及部分用于确定倡议是否可以被注销——参见倡议状态部分中的 CLAIMABLE 和 UNREGISTERABLE 状态。
它计算为以下两者的最大值:
VOTING_THRESHOLD_FACTOR * _snapshot.votes,即前一纪元快照总投票数的 2%minVotes,即倡议所需的最低投票数,以满足 MIN_CLAIM 数量的 BOLD 代币,即 500 BOLD。因此,投票阈值是动态的,并随纪元变化。前一纪元累积的总投票数越多,当前纪元倡议可领取所需的投票数就越多。之所以选择这种公式,是因为质押的 LQTY 赚取的投票权随时间线性增长,因此每个纪元的总投票数从长远来看会趋于增加。
每个符合资格标准的倡议都有资格领取,即其在前一纪元累积的 BOLD 奖励份额将转移给它。领取通过 claimForInitiative 进行,无需许可——任何人都可以将奖励从治理系统转移到符合条件的倡议。此函数必须在快照之后的纪元内执行。
当倡议的投票数超过以下两者时,即有资格领取:
符合条件的倡议的奖励金额计算为纪元 BOLD 累积(加上前一纪元未领取的 BOLD,如果有的话)的按比例份额,基于该倡议在所有倡议中总投票数的份额,包括不符合资格的倡议。例如,如果一个倡议在一个纪元中获得了所有投票的 25%,它将获得该纪元累积 BOLD 奖励的 25%。不符合资格倡议的 BOLD 份额将自动滚动到下一纪元的奖励池中。
如果符合条件的倡议未能在快照之后的纪元内领取,其潜在奖励将自动滚动到下一个纪元的奖励池中。这意味着未领取的奖励不会丢失,而是重新分配给下一个纪元符合条件的倡议。
当成功领取时,BOLD 代币会立即转移到倡议地址,并调用倡议合约上的 onClaimForInitiative Hook(如果实现)。该 Hook 允许倡议在收到奖励时执行自定义逻辑,使系统对于不同用例具有高度灵活性。
请注意,倡议必须单独领取——但可以使用 Governance 的 multiDelegateCall() 函数在一次调用中为多个倡议领取。
一个倡议每个纪元最多只能领取一次。在纪元 x 中领取后,只要保持符合条件的投票权,该倡议将在纪元 x+1 中再次可领取。
这些约束由倡议状态机强制执行。
该系统包含一个基础 BribeInitiative 合约,通过代币奖励(“贿赂”)实现针对特定倡议的投票激励。这为外部方提供了一个框架,通过在标准 BOLD 分配之外提供额外奖励来鼓励对特定倡议的投票。
BribeInitiative 合约作为参考实现提供,旨在由实现更具体贿赂逻辑的自定义倡议继承。
外部方可以存入两种代币作为贿赂:
这些贿赂通过 depositBribe 函数分配给特定的未来纪元。在该纪元中投票支持该倡议的用户有资格领取该纪元贿赂的按比例份额。
用户可以通过 claimBribes 函数领取他们的贿赂份额。用户在给定纪元中对倡议的贿赂份额基于其在纪元末分配给该倡议的投票权的按比例份额计算。
可在目标纪元后的任何时间领取贿赂——贿赂不会过期,也不会在纪元之间结转。
该合约维护链表以追踪跨纪元的投票分配:
Governance 调用的 onAfterAllocateLQTY Hook,将每个纪元的用户和总 LQTY 分配记录在上述链表中。链表条目存储 LQTY 数量和时间加权偏移量,允许在每个纪元准确计算投票权。当未分配投票权非零时,存款和提款 LQTY 会减少用户的未分配投票权。参见此部分。
由于可以使用任意贿赂代币,如果代币非标准——例如具有转账收费或代币数量可变(rebase),或者代币是恶意的和/或可升级的,则可能会出现各种问题。上述任何情况都可能导致用户收到的贿赂奖励少于预期。
可升级倡议的所有者可以任意更改其逻辑,从而将资金目的地更改为与用户投票支持的不同地址。
由于系统按 valid_votes / total_votes 的比例分配奖励,因此增加总投票数但不获得任何奖励的倡议本质上是在“窃取”其他倡议的奖励。这些奖励将在下一个纪元重新排队。
要运行 Foundry,只需:
forge test
请注意 TrophiesToFoundry,它们是已破坏的不变性的重现,故意保持失败状态。
由于使用 vm.warp,我们在使用 Medusa 时遇到了一些问题,建议使用 Echidna。
用以下命令运行 Echidna:
echidna . --contract CryticTester --config echidna.yaml
你也可以通过粘贴仓库/分支的 URL 在 Recon 上运行 Echidna。
- 原文链接: github.com/liquity/V2-go...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!
作者暂未设置收款二维码