治理代币 Comp
Comp是一个ERC20代币是Compound的基础代币。
关于ERC20相关的不再详述。
本文源码解析,文章的所有内容均不构成任何投资比特币或其他数字货币的意见和建议,也不赞成个人炒作任何数字货币!
Comp 代币除了普通的erc20代币的转账,授权功能以外还有计票的功能。计票的目的是为了获取某个地址在某个区块的票数。
计票的逻辑功能如下:
- 计票,当区块变动时记录某人的在某个区块票数,并将区块变动的次数记入合约中。使用的变量为checkpoints,属于结构为 用户地址=》票数改变的次数=》{改变时的区块号,票数}
- 查票,根据传入的区块地址二分查找区块,并返回票数
票数改变只有当转账和委托时票数才会发生改变。
转账:当票数不为零时,票数减少,减少的票数为转账的金额。
委托:获取账户之前委托的地址a1,账户的代币数量A,和将要委托的地址a2。如果a1不是0地址,将a1的票数减去A。如果a2不是0地址,a2的地址加上A。
代码解析
-
erc20 相关
string public constant name = "Compound"; string public constant symbol = "COMP"; uint8 public constant decimals = 18; uint public totalSupply = 10000000e18; address public minter; mapping (address => mapping(address => uint96)) internal allowances; mapping (address => uint96) internal balances;`这些是ERC20的标准属性,分别说明了代币名称COMP,精度18,发行量为10,000,000,管理员以及授权和余额。
-
mapping (address => address) public delegates
委托,存储着投票委托数据 -
struct Checkpoint { uint32 fromBlock; uint96 votes; }
Checkpoint 存储着某个区块的票数 -
mapping (address => mapping (uint32 => Checkpoint)) public checkpoints;
存储某个地址在区块的票数 -
mapping (address => uint32) public numCheckpoints;
存储某个地址在某个区块发生了改动 -
线下签名授权相关
bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); bytes32 public constant DELEGATION_TYPEHASH = keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)"); -
mapping (address => uint) public nonces;
记录线下签名投票的数量。 -
constructor(address account, address minter_) public
构造方法 将所有的代币给传入的account,将管理员设置为minter_constructor(address account) public { balances[account] = uint96(totalSupply); emit Transfer(address(0), account, totalSupply); } -
function allowance(address account, address spender)
erc20的授权查询,查询 account给spender授权了多少转账额度function allowance(address account, address spender) external view returns (uint) { return allowances[account][spender]; }
* function approve(address spender, uint rawAmount)
授权,给spender授权rawAmount的转账额度
```js
function approve(address spender, uint rawAmount) external returns (bool) {
uint96 amount;
if (rawAmount == uint(-1)) {
amount = uint96(-1);
} else {
amount = safe96(rawAmount, "Comp::approve: amount exceeds 96 bits");
}
allowances[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function balanceOf(address account) external view returns (uint)
查询account的代币余额function balanceOf(address account) external view returns (uint) { return balances[account]; }function transfer(address dst, uint rawAmount) external returns (bool)
转账function transfer(address dst, uint rawAmount) external returns (bool) { uint96 amount = safe96(rawAmount, "Comp::transfer: amount exceeds 96 bits"); _transferTokens(msg.sender, dst, amount); return true; }-
function transferFrom(address src, address dst, uint rawAmount) external returns (bool)
转账方法,将amount个代币从src转入dst,并且减少src授权给发送者的rawAmount额度function transferFrom(address src, address dst, uint rawAmount) external returns (bool) { address spender = msg.sender; uint96 spenderAllowance = allowances[src][spender]; uint96 amount = safe96(rawAmount, "Comp::approve: amount exceeds 96 bits"); if (spender != src && spenderAllowance != uint96(-1)) { uint96 newAllowance = sub96(spenderAllowance, amount, "Comp::transferFrom: transfer amount exceeds spender allowance"); allowances[src][spender] = newAllowance; emit Approval(src, spender, newAllowance); } _transferTokens(src, dst, amount); return true; } function delegate(address delegatee) public
委托代表,允许发送者委托delegatee进行投票function delegate(address delegatee) public { return _delegate(msg.sender, delegatee); }function delegateBySig(address delegatee, uint 下·nonce, uint expiry, uint8 v, bytes32 r, bytes32 s)
线下签名委托function delegateBySig(address delegatee, uint nonce, uint expiry, uint8 v, bytes32 r, bytes32 s) public { 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), "Comp::delegateBySig: invalid signature"); require(nonce == nonces[signatory]++, "Comp::delegateBySig: invalid nonce"); require(now <= expiry, "Comp::delegateBySig: signature expired"); return _delegate(signatory, delegatee); }function getCurrentVotes(address account) external view returns (uint96)
获取现在的票数function getCurrentVotes(address function getCurrentVotes(address account) external view returns (uint96) { uint32 nCheckpoints = numCheckpoints[account]; return nCheckpoints > 0 ? checkpoints[account][nCheckpoints - 1].votes : 0; }
* ` function getPriorVotes(address account, uint blockNumber)`
获取票数
获取某个用户在某个区块的票数
1. 检查传入区块是否大于当前区块。
2. 获取account票数改变的次数,因为初始票数是0,如果改变次数是0则一共0票。
3. 检查该账户最后记录的区块票数区块,如果小于等传入区块,则返回票数。
4. 检查该账户的第一个记录的区块票数区块,如果大于传入区块,则返回0.
5. 使用二分法遍历全部记录票数的区块,返回相应的区块的票数。
```js
function getPriorVotes(address account, uint blockNumber) public view returns (uint96) {
require(blockNumber < block.number, "Comp::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;
}
-
function _delegate(address delegator, address delegatee)
内部方法委托,允许delegator委托delegatee进行投票。这里会产生一个更改委托人的日志。如果是第一次委托的话日志将纪录从0地址变成delegatee。委托delegator拥有代币的数量票数给delegateefunction _delegate(address delegator, address delegatee) internal { address currentDelegate = delegates[delegator]; uint96 delegatorBalance = balances[delegator]; delegates[delegator] = delegatee; emit DelegateChanged(delegator, currentDelegate, delegatee); _moveDelegates(currentDelegate, delegatee, delegatorBalance); } -
function _transferTokens(address src, address dst, uint96 amount)
内部方法转账,将amount个代币从src转入dst,转账的地址不允许是0地址。并且转移 所有授权的地址的票数。function _transferTokens(address src, address dst, uint96 amount) internal { require(src != address(0), "Comp::_transferTokens: cannot transfer from the zero address"); require(dst != address(0), "Comp::_transferTokens: cannot transfer to the zero address"); balances[src] = sub96(balances[src], amount, "Comp::_transferTokens: transfer amount exceeds balance"); balances[dst] = add96(balances[dst], amount, "Comp::_transferTokens: transfer amount overflows"); emit Transfer(src, dst, amount); _moveDelegates(delegates[src], delegates[dst], amount); } -
function _moveDelegates(address srcRep, address dstRep, uint96 amount)
更改票数 参数为转出地址,转入地址,金额。
如果转出地址不是0地址则取之前的票数进行减运算,记录下更改后的票数。
如果转入地址不是0地址则取之前的票数进行减运算,记录下更改后的票数。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, "Comp::_moveVotes: vote 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, "Comp::_moveVotes: vote amount overflows"); _writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew); } } } -
function _writeCheckpoint(address delegatee, uint32 nCheckpoints, uint96 oldVotes, uint96 newVotes)
记录票数改变。delegatee 要记录的地址,nCheckpoints票数改变的长度,oldVotes旧的票数,newVotes新的票数
检查 票数长度是否大于0且最后一次记录票数改变的区块是否是当前区块,如果是更新当前区块的票数
如果不是则增加一个新票数改变的记录,且长度+1。
这里的检查是为了防止多次转账导致一个区块内存储多个票数,检查后,这里只会存在最后一个票数,即当前区块的最新票数。function _writeCheckpoint(address delegatee, uint32 nCheckpoints, uint96 oldVotes, uint96 newVotes) internal { uint32 blockNumber = safe32(block.number, "Comp::_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)安全32function safe96(uint n, string memory errorMessage)安全96function add96(uint96 a, uint96 b, string memory errorMessage)安全加法function sub96(uint96 a, uint96 b, string memory errorMessage)安全减法function getChainId()获取链id
存在必有BUG