ERC721实现治理和Vote的分析

  • SmileBits
  • 更新于 2024-04-17 21:02
  • 阅读 218

分析基于NounsDAO的ERC721Checkpointable.sol,他是基于CompoundGovernance代码修改的。数据结构1.投票权结构///@noticeAcheckpointformarkingnumberofvotesfromagiven

分析基于NounsDAO 的ERC721Checkpointable.sol, 他是基于Compound Governance代码修改的。

数据结构

1. 投票权结构


/// @notice A checkpoint for marking number of votes from a given block
//当前blockNumber具有多少投票权

struct Checkpoint {

uint32 fromBlock;

uint96 votes;

}

/// @notice 用户投票权记录, by index,相同的blockNumber肯定是相同的index

mapping(address => mapping(uint32 => Checkpoint)) public checkpoints;

/// @notice 用户投票权变化的索引 index 
mapping(address => uint32) public numCheckpoints;

2. 委托投票结构

/// @notice A record of each accounts delegate
//只有一个映射记录,那么只能记录当前的委托状态
mapping(address => address) private _delegates;

实现逻辑

1. 给某个地址记录新的投票权

function _writeCheckpoint(address delegatee,uint32 nCheckpoints,uint96 oldVotes,uint96 newVotes) internal {

uint32 blockNumber = safe32(

block.number,

'ERC721Checkpointable::_writeCheckpoint: block number exceeds 32 bits'

);

//相同区块是同一条记录
if (nCheckpoints > 0 && checkpoints[delegatee][nCheckpoints - 1].fromBlock == blockNumber) {

checkpoints[delegatee][nCheckpoints - 1].votes = newVotes;

} else {
//否则新增一条记录,numCheckpoints保存了该地址有多少条记录
checkpoints[delegatee][nCheckpoints] = Checkpoint(blockNumber, newVotes);

numCheckpoints[delegatee] = nCheckpoints + 1;

}

emit DelegateVotesChanged(delegatee, oldVotes, newVotes);

}

2. 投票权转移——源地址减少,目的地址增加

function _moveDelegates(address srcRep,address dstRep,uint96 amount
) internal {

if (srcRep != dstRep && amount > 0) {

if (srcRep != address(0)) {

uint32 srcRepNum = numCheckpoints[srcRep];

uint96 srcRepOld = srcRepNum > 0 ? checkpoints[srcRep][srcRepNum - 1].votes : 0;

uint96 srcRepNew = sub96(srcRepOld, amount, 'ERC721Checkpointable::_moveDelegates: amount underflows');

_writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew);

}

if (dstRep != address(0)) {

uint32 dstRepNum = numCheckpoints[dstRep];

uint96 dstRepOld = dstRepNum > 0 ? checkpoints[dstRep][dstRepNum - 1].votes : 0;

uint96 dstRepNew = add96(dstRepOld, amount, 'ERC721Checkpointable::_moveDelegates: amount overflows');

_writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew);

}

}

}

3. nft转移——即投票权转移

function _beforeTokenTransfer(address from,address to,uint256 tokenId) internal override {

super._beforeTokenTransfer(from, to, tokenId);

/// @notice Differs from `_transferTokens()` to use `delegates` override method to simulate auto-delegation

_moveDelegates(delegates(from), delegates(to), 1);

}

4. 委托投票

//委托给自己,并不会改变存储状态
function delegate(address delegatee) public {

if (delegatee == address(0)) delegatee = msg.sender;

return _delegate(msg.sender, delegatee);

}

//一次委托,终身有效,除非重新委托回来自己——通过一个空地址中转一下(委托一个一个空地址,空地址再委托给他)
function _delegate(address delegator, address delegatee) internal {

/// @notice differs from `_delegate()` in `Comp.sol` to use `delegates` override method to simulate auto-delegation

address currentDelegate = delegates(delegator);

_delegates[delegator] = delegatee;

emit DelegateChanged(delegator, currentDelegate, delegatee);

uint96 amount = votesToDelegate(delegator);

_moveDelegates(currentDelegate, delegatee, amount);

}

function _moveDelegates(address srcRep,address dstRep,uint96 amount
) internal {
//关键在这里,委托给自己,并不会改变存储状态
if (srcRep != dstRep && amount > 0) {

if (srcRep != address(0)) {

uint32 srcRepNum = numCheckpoints[srcRep];

uint96 srcRepOld = srcRepNum > 0 ? checkpoints[srcRep][srcRepNum - 1].votes : 0;

uint96 srcRepNew = sub96(srcRepOld, amount, 'ERC721Checkpointable::_moveDelegates: amount underflows');

_writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew);

}

if (dstRep != address(0)) {

uint32 dstRepNum = numCheckpoints[dstRep];

uint96 dstRepOld = dstRepNum > 0 ? checkpoints[dstRep][dstRepNum - 1].votes : 0;

uint96 dstRepNew = add96(dstRepOld, amount, 'ERC721Checkpointable::_moveDelegates: amount overflows');

_writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew);

}

}

}

5. 查找某历史区块的投票数——需要通过二分法来查找历史时间

function getPriorVotes(address account, uint256 blockNumber) public view returns (uint96) {

require(blockNumber < block.number, 'ERC721Checkpointable::getPriorVotes: not yet determined');

uint32 nCheckpoints = numCheckpoints[account];

if (nCheckpoints == 0) {

return 0;

}

// First check most recent balance

if (checkpoints[account][nCheckpoints - 1].fromBlock <= blockNumber) {

return checkpoints[account][nCheckpoints - 1].votes;

}

// Next check implicit zero balance

if (checkpoints[account][0].fromBlock > blockNumber) {

return 0;

}

uint32 lower = 0;

uint32 upper = nCheckpoints - 1;

while (upper > lower) {

uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow

Checkpoint memory cp = checkpoints[account][center];

if (cp.fromBlock == blockNumber) {

return cp.votes;

} else if (cp.fromBlock < blockNumber) {

lower = center;

} else {

upper = center - 1;

}

}

return checkpoints[account][lower].votes;

}

攻击途径

1、闪电贷借NFT ——通过指定区块为上一个区块的投票数,来规避。 但是仍然可以通过闪电借出NFT,委托为攻击者来投票,实现投票操控 2、NFT在多个地址中转投票 ——通过指定区块为上一个区块的投票数,来规避。

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
SmileBits
SmileBits
智能合约安全审计