diff --git a/contracts/token/T.sol b/contracts/token/T.sol index c74d6b65..e4e49a4c 100644 --- a/contracts/token/T.sol +++ b/contracts/token/T.sol @@ -2,27 +2,14 @@ pragma solidity 0.8.4; +import "../utils/Checkpoints.sol"; +import "@openzeppelin/contracts/utils/math/SafeCast.sol"; import "@thesis/solidity-contracts/contracts/token/ERC20WithPermit.sol"; import "@thesis/solidity-contracts/contracts/token/MisfundRecovery.sol"; /// @title T token /// @notice Threshold Network T token. -contract T is ERC20WithPermit, MisfundRecovery { - /// @notice A checkpoint for marking number of votes from a given block. - struct Checkpoint { - uint32 fromBlock; - uint96 votes; - } - - /// @notice A record of each account's delegate. - mapping(address => address) public delegates; - - /// @notice A record of votes checkpoints for each account, by index. - mapping(address => mapping(uint32 => Checkpoint)) public checkpoints; - - /// @notice The number of checkpoints for each account. - mapping(address => uint32) public numCheckpoints; - +contract T is ERC20WithPermit, MisfundRecovery, Checkpoints { /// @notice The EIP-712 typehash for the delegation struct used by /// `delegateBySig`. bytes32 public constant DELEGATION_TYPEHASH = @@ -30,27 +17,12 @@ contract T is ERC20WithPermit, MisfundRecovery { "Delegation(address delegatee,uint256 nonce,uint256 deadline)" ); - /// @notice An event emitted when an account changes its delegate. - event DelegateChanged( - address indexed delegator, - address indexed fromDelegate, - address indexed toDelegate - ); - - /// @notice An event emitted when a delegatee account's vote balance - /// changes. - event DelegateVotesChanged( - address indexed delegatee, - uint256 previousBalance, - uint256 newBalance - ); - constructor() ERC20WithPermit("Threshold Network Token", "T") {} /// @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); + function delegate(address delegatee) public virtual { + return delegate(msg.sender, delegatee); // _msgSender()? } /// @notice Delegates votes from signatory to `delegatee` @@ -108,9 +80,7 @@ contract T is ERC20WithPermit, MisfundRecovery { /// @param account The address to get votes balance /// @return The number of current votes for `account` function getCurrentVotes(address account) external view returns (uint96) { - uint32 nCheckpoints = numCheckpoints[account]; - return - nCheckpoints > 0 ? checkpoints[account][nCheckpoints - 1].votes : 0; + return uint96(getVotes(account)); } /// @notice Determine the prior number of votes for an account as of @@ -125,37 +95,7 @@ contract T is ERC20WithPermit, MisfundRecovery { view returns (uint96) { - require(blockNumber < block.number, "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; + return uint96(getPastVotes(account, blockNumber)); } // slither-disable-next-line dead-code @@ -164,104 +104,34 @@ contract T is ERC20WithPermit, MisfundRecovery { address to, uint256 amount ) internal override { - // Does not allow to mint more than uin96 can fit. Otherwise, the - // Checkpoint might not fit the balance. + uint96 safeAmount = SafeCast.toUint96(amount); + + // When minting: if (from == address(0)) { + // Does not allow to mint more than uint96 can fit. Otherwise, the + // Checkpoint might not fit the balance. require( - totalSupply + amount <= type(uint96).max, + totalSupply + amount <= _maxSupply(), "Maximum total supply exceeded" ); + _writeCheckpoint(_totalSupplyCheckpoints, _add, safeAmount); } - moveDelegates( - delegates[from], - delegates[to], - safe96(amount, "Transfer amount overflows") - ); - } - - function delegate(address delegator, address delegatee) internal { - address currentDelegate = delegates[delegator]; - uint96 delegatorBalance = safe96( - balanceOf[delegator], - "Delegator balance overflows" - ); - delegates[delegator] = delegatee; - - emit DelegateChanged(delegator, currentDelegate, delegatee); - - moveDelegates(currentDelegate, delegatee, delegatorBalance); - } - - 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 = srcRepOld - amount; - 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 = dstRepOld + amount; - writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew); - } + // When burning: + if (to == address(0)) { + _writeCheckpoint(_totalSupplyCheckpoints, _subtract, safeAmount); } - } - - function writeCheckpoint( - address delegatee, - uint32 nCheckpoints, - uint96 oldVotes, - uint96 newVotes - ) internal { - uint32 blockNumber = safe32( - block.number, - "Block number exceeds 32 bits" - ); - if ( - // slither-disable-next-line incorrect-equality - 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); + _moveVotingPower(delegates(from), delegates(to), safeAmount); } - function safe32(uint256 n, string memory errorMessage) - internal - pure - returns (uint32) - { - require(n < type(uint32).max, errorMessage); - return uint32(n); - } + function delegate(address delegator, address delegatee) internal virtual { + address currentDelegate = delegates(delegator); + uint96 delegatorBalance = SafeCast.toUint96(balanceOf[delegator]); + _delegates[delegator] = delegatee; - function safe96(uint256 n, string memory errorMessage) - internal - pure - returns (uint96) - { - require(n < type(uint96).max, errorMessage); - return uint96(n); + emit DelegateChanged(delegator, currentDelegate, delegatee); + + _moveVotingPower(currentDelegate, delegatee, delegatorBalance); } } diff --git a/test/token/T.test.js b/test/token/T.test.js index 51cc0c33..617c19dc 100644 --- a/test/token/T.test.js +++ b/test/token/T.test.js @@ -51,7 +51,7 @@ describe("T token", () => { it("should revert", async () => { await expect( t.getPriorVotes(tokenHolder.address, await lastBlockNumber()) - ).to.be.revertedWith("Not yet determined") + ).to.be.revertedWith("Checkpoints: block not yet mined") }) }) })