SushiToken的投票实现

SushiToken继承标准的ERC20,在此基础上,又实现了代币的投票功能。

1、数据结构

/// @notice A record of each accounts delegatemapping (address => address) internal _delegates;/// @notice A checkpoint for marking number of votes from a given blockstruct Checkpoint {uint32 fromBlock;uint256 votes;}/// @notice A record of votes checkpoints for each account, by indexmapping (address => mapping (uint32 => Checkpoint)) public checkpoints;/// @notice The number of checkpoints for each accountmapping (address => uint32) public numCheckpoints;/// @notice The EIP-712 typehash for the contract's domainbytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");/// @notice The EIP-712 typehash for the delegation struct used by the contractbytes32 public constant DELEGATION_TYPEHASH = keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");/// @notice A record of states for signing / validating signaturesmapping (address => uint) public nonces;

_delegates表示代理,A->B的映射,表示B可以代替A行使投票。

Checkpoint是记录投票的数据结构,类似于一个快照,里面记录了用户在某个区块持有的投票数。

numCheckpoints记录的是快照的ID,每次有新的快照,这个ID都会自增。

checkpoints是用户所有的投票快照记录。

DOMAIN_TYPEHASH、DELEGATION_TYPEHASH和nonces都和EIP-712有关,这个EIP旨在提高链下消息签名对链上的可用性,具体可以看https://eips.ethereum.org/EIPS/eip-712

2、转移投票

function _moveDelegates(address srcRep, address dstRep, uint256 amount) internal {if (srcRep != dstRep && amount > 0) {if (srcRep != address(0)) {// decrease old representativeuint32 srcRepNum = numCheckpoints[srcRep];uint256 srcRepOld = srcRepNum > 0 ? checkpoints[srcRep][srcRepNum - 1].votes : 0;uint256 srcRepNew = srcRepOld.sub(amount);_writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew);}if (dstRep != address(0)) {// increase new representativeuint32 dstRepNum = numCheckpoints[dstRep];uint256 dstRepOld = dstRepNum > 0 ? checkpoints[dstRep][dstRepNum - 1].votes : 0;uint256 dstRepNew = dstRepOld.add(amount);_writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew);}}}function _writeCheckpoint(address delegatee,uint32 nCheckpoints,uint256 oldVotes,uint256 newVotes)internal{uint32 blockNumber = safe32(block.number, "SUSHI::_writeCheckpoint: block number exceeds 32 bits");if (nCheckpoints > 0 && checkpoints[delegatee][nCheckpoints - 1].fromBlock == blockNumber) {checkpoints[delegatee][nCheckpoints - 1].votes = newVotes;} else {checkpoints[delegatee][nCheckpoints] = Checkpoint(blockNumber, newVotes);numCheckpoints[delegatee] = nCheckpoints + 1;}emit DelegateVotesChanged(delegatee, oldVotes, newVotes);}function safe32(uint n, string memory errorMessage) internal pure returns (uint32) {require(n < 2**32, errorMessage);return uint32(n);}

_moveDelegates是在用户间转移投票,srcRep是转出地址会减少当前投票数量,dstRep是转入地址会增加当前投票数量,最终都会调用_writeCheckpoint。

_writeCheckpoint是记录投票快照,每次进来先会获取当前区块高度,然后查看最近一次记录是否和当前区块高度一致,如果一致就直接覆盖投票数量,否则将快照ID加一,重新生成一个快照对象,然后记下来。

3、投票的代理人

/*** @notice Delegate votes from `msg.sender` to `delegatee`* @param delegator The address to get delegatee for*/function delegates(address delegator)externalviewreturns (address){return _delegates[delegator];}/*** @notice Delegate votes from `msg.sender` to `delegatee`* @param delegatee The address to delegate votes to*/function delegate(address delegatee) external {return _delegate(msg.sender, delegatee);}/*** @notice Delegates votes from signatory to `delegatee`* @param delegatee The address to delegate votes to* @param nonce The contract state required to match the signature* @param expiry The time at which to expire the signature* @param v The recovery byte of the signature* @param r Half of the ECDSA signature pair* @param s Half of the ECDSA signature pair*/function delegateBySig(address delegatee,uint nonce,uint expiry,uint8 v,bytes32 r,bytes32 s)external{bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_TYPEHASH,keccak256(bytes(name())),getChainId(),address(this)));bytes32 structHash = keccak256(abi.encode(DELEGATION_TYPEHASH,delegatee,nonce,expiry));bytes32 digest = keccak256(abi.encodePacked("\x19\x01",domainSeparator,structHash));address signatory = ecrecover(digest, v, r, s);require(signatory != address(0), "SUSHI::delegateBySig: invalid signature");require(nonce == nonces[signatory]++, "SUSHI::delegateBySig: invalid nonce");require(now <= expiry, "SUSHI::delegateBySig: signature expired");return _delegate(signatory, delegatee);}function _delegate(address delegator, address delegatee)internal{address currentDelegate = _delegates[delegator];uint256 delegatorBalance = balanceOf(delegator); // balance of underlying SUSHIs (not scaled);_delegates[delegator] = delegatee;emit DelegateChanged(delegator, currentDelegate, delegatee);_moveDelegates(currentDelegate, delegatee, delegatorBalance);}

用户可以直接调用delegate或者通过签名调用delegateBySig,来指定自己的代理人,最终这些方法都会调用_delegate。

_delegate拿到当前持有的sushi的数量,然后通过_moveDelegates给代理人delegatee增加投票权,同时减少当前代理人currentDelegate的投票权。

delegates方法则是查看用户的代理人。

4、查看投票

/*** @notice Gets the current votes balance for `account`* @param account The address to get votes balance* @return The number of current votes for `account`*/function getCurrentVotes(address account)externalviewreturns (uint256){uint32 nCheckpoints = numCheckpoints[account];return nCheckpoints > 0 ? checkpoints[account][nCheckpoints - 1].votes : 0;}/*** @notice Determine the prior number of votes for an account as of a block number* @dev Block number must be a finalized block or else this function will revert to prevent misinformation.* @param account The address of the account to check* @param blockNumber The block number to get the vote balance at* @return The number of votes the account had as of the given block*/function getPriorVotes(address account, uint blockNumber)externalviewreturns (uint256){require(blockNumber < block.number, "SUSHI::getPriorVotes: not yet determined");uint32 nCheckpoints = numCheckpoints[account];if (nCheckpoints == 0) {return 0;}// First check most recent balanceif (checkpoints[account][nCheckpoints - 1].fromBlock <= blockNumber) {return checkpoints[account][nCheckpoints - 1].votes;}// Next check implicit zero balanceif (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 overflowCheckpoint 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;}

getCurrentVotes是获取最新的投票,先通过numCheckpoints[account]拿到最近保存的快照ID,然后获取这个快照的投票数。

getPriorVotes是获取用户在某个区块的历史投票数,内部使用二分查找来找到最接近的快照。

5、mint函数

function mint(address _to, uint256 _amount) public onlyOwner {_mint(_to, _amount);_moveDelegates(address(0), _delegates[_to], _amount);}

SushiToken重写了mint函数,通过一比一分配了投票权。

6、存在的问题

a) SushiToken只是在mint函数中分配了投票权,但是没有重写transfer/transferFrom,这样就无法转移投票权,而且也没有重写burn,无法销毁投票权,一个用户第一次分配了SushiToken后,无论他卖掉了这些币还是销毁了这些币,仍然有相应的投票权,这其实是不合理的。

b) 用户通过delegate方法给代理人A分配投票权,然后经过一段时间后用户持有的sushi代币增加了,这时候再调用delegate转移给B,这时候A的投票权少于要转移的投票权,会导致转移失败。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部