从设计原理来学习ERC20Snapshot and ERC20Votes

  • Lori
  • 更新于 2024-06-04 19:57
  • 阅读 1303

了解将Token作为投票权可能带来的威胁,了解oz的ERC20Snapshot and ERC20Votes背后的原理,以及作为开发者需要注意什么问题。

Token作为投票的潜在危险

Token作为投票往往运用在DAO治理或是领取空投,通常根据用户持有的代币数量来分配投票权,这会带来什么潜在的问题和攻击手段呢?

  1. 双重投票:如果投票权是根据代币持有量来决定的,攻击者确实可以利用代币的转移来操纵投票结果,在投票前将代币转移到一个地址,投票后再转移到另一个地址,从而多次投票。
  2. 闪电贷攻击:攻击者可以利用闪电贷获取大量代币,进行投票或领取空投,然后迅速返还闪贷,这种攻击可以在短时间内对治理和空投分发造成重大影响。
  3. 重复领取空投:用户可以通过转移代币到不同的地址来重复领取空投,这同样破坏了空投的公平性和有效性,这和双重投票类似。

为了防御这些问题,可以采取以下措施

  • ERC20快照:这是一种在特定时间点记录所有代币持有者及其余额的方法。通过快照,可以防止用户在快照时间点之后转移代币并重复使用代币效用。
  • 投票锁定期:可以设置一个投票锁定期,要求代币在投票期间不能转移,这样可以防止用户在投票后立即转移代币。
  • 限制智能合约地址:可以限制地址参与投票,或者对智能合约地址的投票行为进行额外的审查。
  • 空投分发机制:可以设计更加复杂的空投分发机制,比如要求用户持有代币达到一定时间,或者通过KYC(了解你的客户)流程来验证用户的身份。
  • 监测和分析:持续监测交易行为,分析可能的异常模式,及时发现并阻止恶意行为。

ERC20Snapshot

Github: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.3/contracts/token/ERC20/extensions/ERC20Snapshot.sol

定义

ERC20Snapshot库是ERC20的拓展,增加了各账户余额及总流通量的快照机制。如果涉及到根据账户ERC20余额进行分红、投票等业务可以使用该库,其可有效防御在不同地址间转账进行“一币多用”的攻击。

在一个快照横截面数据上进行分红、投票甚至是ERC20分叉都是最有效的解决方案。本库具有高效性,创建快照、快照上查询地址余额及总流通量的时间复杂度分别是O(1)O(log n)。但快照功能的存在会增加ERC20发生转移时的gas成本。

解决的问题

阻止攻击者在一笔交易通过将token转移给多个智能合约来进行双重投票攻击。

缺点

  1. 从快照中检索数据消耗的gas呈对数级增长(二分法)。快照越多,检索成本越高;
  2. 一个用户的快照数据越多,该用户作为转账from和to的gas消耗就越高(可能会被攻击者利用)。

攻击者可以被利用的点

攻击者可以利用闪电贷获取大量代币,进行快照获取大量投票权,再进行提案投票或是领取空投,可以通过damn-vulnerable-defi第六题selfie来加深理解,这是我的题解可供参考。

ERC20Votes

ERC20 Votes实际上是对ERC20 Snapshot功能的扩展,增加了其他治理相关的功能,不是为了解决了Snapshot的缺点而诞生的。

定义

ERC20 Votes是标准ERC20代币的扩展,继承了ERC20、ERC6372和ERC5805的功能,专门设计用于处理投票权,它支持快照委托投票权功能。实际的投票过程由治理合约管理,而不是ERC20 Votes本身。

  1. 委托功能: 允许代币持有者在不转移代币的情况下,将他们的投票权“借给”另一个地址。
  2. 快照: 与ERC20 Snapshot快照代币余额不同,ERC20 Votes快照特定时刻的投票权。这是防止重复投票并确保投票权在治理过程中准确计入的关键。

委托调用的好处

  1. 允许被动的代币持有者通过委托他们的投票权来间接参与治理,这旨在激励不想主动投票的代币持有者通过委托代币来参与治理。
  2. 小额账户可以通过将投票权进行委托来避免投票Gas费用,从而在生态系统层面节省Gas。如果所有参与者都将投票权委托给5个代表,那么一个投票项目只需要进行5次投票,而不是成千上万次。

ERC5805

ERC5805相当于是interface。下面是我们需要关注的几个函数接口,主要与委托投票以及快照有关。

  1. delegate(address delegatee)

    这个函数允许msg.sender将其投票权委托给delegatee(代币并不会转移),delegatee将代表msg.sender进行投票。委托可以随时通过再次调用delegate函数并传入不同的delegatee或address(0)来更改或撤销。代币持有者只能选择全部委托或不委托,不存在部分委托。一个地址必须先委托给自己,其投票权才会被计入。这是为了提高Gas效率。

  2. delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s)

    函数delegateBySig允许用户通过无Gas交易委托投票权,并由另一个账户支付Gas费用并执行交易。expiring设置委托有效的时间,v、r和s是椭圆曲线数字签名的组成部分。预期签名为EIP 712格式。合约内部会递增每个地址的nonce,以防止重放攻击。

  3. getPastVotes(account, timepoint) →防止双重投票

    与ERC20 snapshot不同,快照不会由调用快照函数的地址触发。当铸币、销毁、转账、有人委托其投票权这四件事发生时,才会为每个账户触发快照。快照会将包含投票权和时间戳的结构体附加到存储用户投票权历史记录的数组中。ERC20 Votes会对时间戳进行二分搜索,以找到timepoint后的最早检查点,并返回该时刻的投票权。

    与ERC20 snapshot不同的是,没有“全局”快照ID,通过时间戳或区块号作为“timepoint”去查询快照。

  4. 还需要关注两个event,分别是DelegateChangeDelegateVotesChanged

ERC6372

合约可以使用block.timestamp,block.number或这些全局变量的单调增加的函数来记录检查点。ERC6372是一个针对检查点的标准,允许合约查询合约使用的“时钟”类型。

clock() 这个函数返回一个uint48,可能是区块号或区块时间戳或其他表征检查点的clock。

CLOCK_MODE() EIP规定这种全大写的命名,返回字符串,即时钟使用的单位。

ERC20Snapshot and ERC20Votes 区别

1. 时间概念

ERC20 Votes:有明确的时间概念,在每次投票权相关的事件(如委托或转账)发生时,记录当前的时间戳或区块号。以后可以根据具体时间点查询历史投票权。 ERC20 Snapshot:使用递增的ID作为快照标识,这些ID随着时间推移自动增加,作为计数器的副产品。这种方式也反映了时间的变化,但没有明确的时间戳或区块号记录。

2. 记录内容

ERC20 Votes:记录的是投票权。每个地址在特定时间点上的投票权会被快照保存。这对于治理和投票非常重要,因为投票权可以通过委托等方式进行变化。 ERC20 Snapshot:记录的是代币余额。它跟踪每个地址在特定时间点上的代币余额,对于需要追踪资产分布情况的场景非常合适。

3. 检查点更新时机

ERC20 Votes:在每次委托或转账时自动更新检查点。 ERC20 Snapshot:需要显式调用“_snapshot()”函数来更新检查点。

4. 使用场景

ERC20 Votes:适用于需要委托投票权的场景。它可以灵活处理投票权的变化,并在每次委托或转账时自动更新检查点。这对于去中心化自治组织(DAO)的治理、投票和选举等场景更合适。 ERC20 Snapshot:适用于需要记录和追踪代币余额的场景。它能够提供历史余额的准确记录,对于资产管理、空投和奖励分配等场景更合适。

参考链接

  1. ERC20Snapshot - OpenZeppelin Docs
  2. Michael.W基于Foundry精读Openzeppelin第46期——ERC20Snapshot.sol | 登链社区 | 区块链技术社区 (learnblockchain.cn)
  3. ERC20 Snapshot 解决了双重投票的问题。 (rareskills.io)
  4. ERC20 投票:ERC5805和ERC6372 - ERC20 快照知识 (rareskills.io)
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Lori
Lori
0x3F3c...Dc2F
最近有点儿小忙,更新不频繁~