diff --git a/contracts/ISuper1155.sol b/contracts/ISuper1155.sol index 33c7266..c452bc6 100644 --- a/contracts/ISuper1155.sol +++ b/contracts/ISuper1155.sol @@ -169,7 +169,6 @@ interface ISuper1155 { /// A mapping of data for each item group. function itemGroups (uint256) external view returns (ItemGroup memory); - /* function itemGroups (uint256) external view returns (bool initialized, string memory _name, uint8 supplyType, uint256 supplyData, uint8 itemType, uint256 itemData, uint8 burnType, uint256 burnData, uint256 _circulatingSupply, uint256 _mintCount, uint256 _burnCount); */ /// A mapping of circulating supplies for each individual token. function circulatingSupply (uint256) external view returns (uint256); diff --git a/contracts/IToken.sol b/contracts/IToken.sol new file mode 100644 index 0000000..a46b142 --- /dev/null +++ b/contracts/IToken.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.7.6; +pragma abicoder v2; + +/** + @title An interface for the `Token` ERC-20 contract. + @author Liam Clancy + @author Tim Clancy + + September 2nd, 2021. +*/ +interface IToken { + + /// The public identifier for the right to mint tokens. + function MINT () external view returns (bytes32); + + /// The public identifier for the right to unlock token transfers. + function UNLOCK_TRANSFERS () external view returns (bytes32); + + /// The EIP-712 typehash for the contract's domain. + function DOMAIN_TYPEHASH () external view returns (bytes32); + + /// The EIP-712 typehash for the delegation struct used by the contract. + function DELEGATION_TYPEHASH () external view returns (bytes32); + + /// A flag for whether or not token transfers are enabled. + function transfersUnlocked () external view returns (bool); + + /// A mapping to record delegates for each address. + function delegates (address) external view returns (address); + + /** + A checkpoint structure to count some number of votes from a given block. + + @param fromBlock The block to begin counting votes from. + @param votes The number of votes counted from block `fromBlock`. + */ + struct Checkpoint { + uint32 fromBlock; + uint256 votes; + } + + /// A mapping to record indexed `Checkpoint` votes for each address. + function checkpoints (address, uint32) external view returns (Checkpoint memory); + + /// A mapping to record the number of `Checkpoint`s for each address. + function numCheckpoints (address) external view returns (uint32); + + /// A mapping to record per-caller states for signing / validating signatures. + function nonce (address) external view returns (uint256); + + /** + Return a version number for this contract's interface. + */ + function version () external pure returns (uint256); + + /** + Get the current votes balance for the address `_account`. + + @param _account The address to get the votes balance of. + @return The number of current votes for `_account`. + */ + function getCurrentVotes( + address _account + ) external view returns (uint256); + + /** + Determine the prior number of votes for an address as of a block number. The + block number must be a finalized block or this function will revert to + prevent misinformation. + + @param _account The address to check. + @param _blockNumber The block number to get the vote balance at. + @return The number of votes `_account` had as of the given block. + */ + function getPriorVotes( + address _account, + uint _blockNumber + ) external view returns (uint256); + + /** + Allows users to transfer tokens to a recipient, moving delegated votes with + the transfer. + + @param _recipient The address to transfer tokens to. + @param _amount The amount of tokens to send to `_recipient`. + */ + function transfer( + address _recipient, + uint256 _amount + ) external returns (bool); + + /** + Allow an approved user to unlock transfers of this token. + */ + function unlockTransfers() external; + + /** + Allows Token creator to mint `_amount` of this Token to the address `_to`. + New tokens of this Token cannot be minted if it would exceed the supply cap. + Users are delegated votes when they are minted Token. + + @param _to the address to mint Tokens to. + @param _amount the amount of new Token to mint. + */ + function mint( + address _to, + uint256 _amount + ) external; + + /** + Allow the caller to burn some `_amount` of their Tokens. + + @param _amount The amount of tokens that the caller will try to burn. + */ + function burn( + uint256 _amount + ) external; + + /** + Allow the caller to burn some `_amount` of Tokens from `_account`. The + caller can only burn these tokens if they have been granted an allowance by + `_account`. + + @param _account The account to burn tokens from. + @param _amount The amount of tokens to burn. + */ + function burnFrom( + address _account, + uint256 _amount + ) external; + + /** + Delegate votes from the caller to `_delegatee`. + + @param _delegatee The address to delegate votes to. + */ + function delegate( + address _delegatee + ) external; + + /** + Delegate votes by signature from the caller to `_delegatee`. + + @param _delegatee The address to delegate votes to. + @param _nonce The contract state required for signature matching. + @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; +} diff --git a/contracts/MintShop1155.sol b/contracts/MintShop1155.sol index a5326ef..62040a3 100644 --- a/contracts/MintShop1155.sol +++ b/contracts/MintShop1155.sol @@ -8,16 +8,20 @@ import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; import "@openzeppelin/contracts/math/SafeMath.sol"; import "./base/Sweepable.sol"; -import "./Super1155.sol"; +import "./ISuper1155.sol"; +import "./IToken.sol"; import "./Staker.sol"; /** - @title A Shop contract for selling NFTs via direct minting through particular - pools with specific participation requirements. + @title A Shop contract for selling items and tokens via direct minting through + particular pools with specific participation requirements. @author Tim Clancy + @author Liam Clancy - This launchpad contract sells new items by minting them into existence. It - cannot be used to sell items that already exist. + This launchpad contract sells new items and tokens by minting them into + existence. It cannot be used to sell items that already exist. + + September 2nd, 2021. */ contract MintShop1155 is Sweepable, ReentrancyGuard { using SafeERC20 for IERC20; @@ -48,7 +52,10 @@ contract MintShop1155 is Sweepable, ReentrancyGuard { uint256 constant GROUP_MASK = uint256(uint128(~0)) << 128; /// The item collection contract that minted items are sold from. - Super1155 public item; + ISuper1155 public item; + + /// The token that may be minted as accompaniment to sold items. + IToken public token; /** The address where the payment from each item buyer is sent. Care must be @@ -142,8 +149,8 @@ contract MintShop1155 is Sweepable, ReentrancyGuard { @param itemGroups An array of all item groups currently present in this pool. @param currentPoolVersion A version number hashed with item group IDs before - being used as keys to other mappings. This supports efficient - invalidation of stale mappings. + being used as keys to other mappings. This supports efficient invalidation + of stale mappings. @param itemCaps A mapping of item group IDs to the maximum number this pool is allowed to mint. @param itemMinted A mapping of item group IDs to the number this pool has @@ -164,6 +171,7 @@ contract MintShop1155 is Sweepable, ReentrancyGuard { uint256[] itemGroups; uint256 currentPoolVersion; mapping (bytes32 => uint256) itemCaps; + mapping (bytes32 => uint256) tokenMints; mapping (bytes32 => uint256) itemMinted; mapping (bytes32 => uint256) itemPricesLength; mapping (bytes32 => mapping (uint256 => Price)) itemPrices; @@ -467,15 +475,14 @@ contract MintShop1155 is Sweepable, ReentrancyGuard { @param updater The calling address which updated this pool. @param poolId The ID of the pool being updated. @param pool The input data used to update the pool. - @param groupIds The groupIds that are now on sale in the item pool. - @param caps The caps, keyed to `groupIds`, of the maximum that each groupId - may mint up to. - @param prices The prices, keyed to `groupIds`, of the arrays for `Price` - objects that each item group may be able be bought with. + @param poolItems The updated state of the items being sold in this pool. */ - event PoolUpdated(address indexed updater, uint256 poolId, - PoolInput indexed pool, uint256[] groupIds, uint256[] caps, - Price[][] indexed prices); + event PoolUpdated( + address indexed updater, + uint256 poolId, + PoolInput indexed pool, + PoolItemInput[] poolItems + ); /** An event to track the purchase of items from an item pool. @@ -494,12 +501,19 @@ contract MintShop1155 is Sweepable, ReentrancyGuard { @param _owner The address of the administrator governing this collection. @param _item The address of the Super1155 item collection contract that will be minting new items in sales. + @param _token The address of a Token ERC-20 token that can optionally be + given with each purchase. @param _paymentReceiver The address where shop earnings are sent. @param _globalPurchaseLimit A global limit on the number of items that a single address may purchase across all item pools in the shop. */ - constructor(address _owner, Super1155 _item, address _paymentReceiver, - uint256 _globalPurchaseLimit) public { + constructor( + address _owner, + ISuper1155 _item, + IToken _token, + address _paymentReceiver, + uint256 _globalPurchaseLimit + ) { // Do not perform a redundant ownership transfer if the deployer should // remain as the owner of the collection. @@ -509,6 +523,7 @@ contract MintShop1155 is Sweepable, ReentrancyGuard { // Continue initialization. item = _item; + token = _token; paymentReceiver = _paymentReceiver; globalPurchaseLimit = _globalPurchaseLimit; } @@ -826,26 +841,40 @@ contract MintShop1155 is Sweepable, ReentrancyGuard { return poolOutputs; } + /** + A struct to track the addition of a single item sale in a pool. + + @param groupId The specific item group ID that makes up this item sale. + @param tokenMint The amount of accompanying token to mint for this item when + it is sold by its pool. + @param issueNumberOffset The offset for the next issue number minted for + this item group. This is *important* to handle a pre-minted or + partially-minted item group. + @param cap The maximum amount of this particular groupId that can be sold + by its pool. + @param prices The price pairings to use for selling this item. + */ + struct PoolItemInput { + uint256 groupId; + uint256 tokenMint; + uint256 issueNumberOffset; + uint256 cap; + Price[] prices; + } + /** Allow the owner of the shop or an approved manager to add a new pool of items that users may purchase. @param _pool The PoolInput full of data defining the pool's operation. - @param _groupIds The specific item group IDs to sell in this pool, - keyed to the `_amounts` array. - @param _issueNumberOffsets The offset for the next issue number minted for a - particular item group in `_groupIds`. This is *important* to handle - pre-minted or partially-minted item groups. - @param _caps The maximum amount of each particular groupId that can be sold - by this pool. - @param _prices The asset address to price pairings to use for selling each - item. + @param _poolItems The items to add to this pool with their associated + details. */ - function addPool(PoolInput calldata _pool, uint256[] calldata _groupIds, - uint256[] calldata _issueNumberOffsets, uint256[] calldata _caps, - Price[][] memory _prices) external hasValidPermit(UNIVERSAL, POOL) { - updatePool(nextPoolId, _pool, _groupIds, _issueNumberOffsets, _caps, - _prices); + function addPool( + PoolInput calldata _pool, + PoolItemInput[] calldata _poolItems + ) external hasValidPermit(UNIVERSAL, POOL) { + updatePool(nextPoolId, _pool, _poolItems); // Increment the ID which will be used by the next pool added. nextPoolId = nextPoolId.add(1); @@ -853,39 +882,45 @@ contract MintShop1155 is Sweepable, ReentrancyGuard { /** A private helper function for `updatePool` to prevent it from running too - deep into the stack. This function will store the amount of each item group - that this pool may mint. + deeply into the stack. This function will store the amount of each item + group that this pool may mint. @param _id The ID of the pool to update. - @param _groupIds The specific item group IDs to sell in this pool, - keyed to the `_amounts` array. - @param _issueNumberOffsets The offset for the next issue number minted for a - particular item group in `_groupIds`. This is *important* to handle - pre-minted or partially-minted item groups. - @param _caps The maximum amount of each particular groupId that can be sold - by this pool. - @param _prices The asset address to price pairings to use for selling each - item. + @param _poolItems The items to add to this pool with their associated + details. */ - function _updatePoolHelper(uint256 _id, - uint256[] calldata _groupIds, uint256[] calldata _issueNumberOffsets, - uint256[] calldata _caps, Price[][] memory _prices) private { - for (uint256 i = 0; i < _groupIds.length; i++) { - require(_caps[i] > 0, + function _updatePoolHelper( + uint256 _id, + PoolItemInput[] calldata _poolItems + ) private { + uint256[] memory groupIds = new uint256[](_poolItems.length); + for (uint256 i = 0; i < _poolItems.length; i++) { + PoolItemInput memory poolItem = _poolItems[i]; + require(poolItem.cap > 0, "MintShop1155: cannot add an item group with no mintable amount"); bytes32 itemKey = keccak256(abi.encode( - pools[_id].currentPoolVersion, _groupIds[i])); - pools[_id].itemCaps[itemKey] = _caps[i]; + pools[_id].currentPoolVersion, poolItem.groupId)); + pools[_id].itemCaps[itemKey] = poolItem.cap; // Pre-seed the next item issue IDs given the pool offsets. - nextItemIssues[_groupIds[i]] = _issueNumberOffsets[i]; + nextItemIssues[poolItem.groupId] = poolItem.issueNumberOffset; // Store future purchase information for the item group. - for (uint256 j = 0; j < _prices[i].length; j++) { - pools[_id].itemPrices[itemKey][j] = _prices[i][j]; + for (uint256 j = 0; j < poolItem.prices.length; j++) { + pools[_id].itemPrices[itemKey][j] = poolItem.prices[j]; } - pools[_id].itemPricesLength[itemKey] = _prices[i].length; + pools[_id].itemPricesLength[itemKey] = poolItem.prices.length; + + // Store token mint amounts for the item group. + pools[_id].tokenMints[itemKey] = poolItem.tokenMint; + + // Track the group IDs. + groupIds[i] = poolItem.groupId; } + + // Store the item groups that appear in this pool. + Pool storage pool = pools[_id]; + pool.itemGroups = groupIds; } /** @@ -894,32 +929,20 @@ contract MintShop1155 is Sweepable, ReentrancyGuard { @param _id The ID of the pool to update. @param _pool The PoolInput full of data defining the pool's operation. - @param _groupIds The specific item group IDs to sell in this pool, - keyed to the `_amounts` array. - @param _issueNumberOffsets The offset for the next issue number minted for a - particular item group in `_groupIds`. This is *important* to handle - pre-minted or partially-minted item groups. - @param _caps The maximum amount of each particular groupId that can be sold - by this pool. - @param _prices The asset address to price pairings to use for selling each - item. + @param _poolItems The items to add to this pool with their associated + details. */ - function updatePool(uint256 _id, PoolInput calldata _pool, - uint256[] calldata _groupIds, uint256[] calldata _issueNumberOffsets, - uint256[] calldata _caps, Price[][] memory _prices) public - hasValidPermit(UNIVERSAL, POOL) { + function updatePool( + uint256 _id, + PoolInput calldata _pool, + PoolItemInput[] calldata _poolItems + ) public hasValidPermit(UNIVERSAL, POOL) { require(_id <= nextPoolId, "MintShop1155: cannot update a non-existent pool"); require(_pool.endTime >= _pool.startTime, "MintShop1155: cannot create a pool which ends before it starts"); - require(_groupIds.length > 0, + require(_poolItems.length > 0, "MintShop1155: must list at least one item group"); - require(_groupIds.length == _issueNumberOffsets.length, - "MintShop1155: item groups length must equal issue offsets length"); - require(_groupIds.length == _caps.length, - "MintShop1155: item groups length must equal caps length"); - require(_groupIds.length == _prices.length, - "MintShop1155: item groups length must equal prices input length"); // Immediately store some given information about this pool. Pool storage pool = pools[_id]; @@ -928,15 +951,35 @@ contract MintShop1155 is Sweepable, ReentrancyGuard { pool.endTime = _pool.endTime; pool.purchaseLimit = _pool.purchaseLimit; pool.singlePurchaseLimit = _pool.singlePurchaseLimit; - pool.itemGroups = _groupIds; pool.currentPoolVersion = pools[_id].currentPoolVersion.add(1); pool.requirement = _pool.requirement; // Delegate work to a helper function to avoid stack-too-deep errors. - _updatePoolHelper(_id, _groupIds, _issueNumberOffsets, _caps, _prices); + _updatePoolHelper(_id, _poolItems); // Emit an event indicating that a pool has been updated. - emit PoolUpdated(_msgSender(), _id, _pool, _groupIds, _caps, _prices); + emit PoolUpdated(_msgSender(), _id, _pool, _poolItems); + } + + /** + This private helper function for `mintFromPool` will help it from running + too deeply into the stack. This function will mint any tokens that might be + associated with an item purchase. + + @param _id The ID of the particular item pool that the user would like to + purchase from. + @param _itemKey The bytes32 key for accessing a particular item in a pool + mapping. + @param _amount The amount of items being purchased. + */ + function _mintHelper( + uint256 _id, + bytes32 _itemKey, + uint256 _amount + ) private { + if (pools[_id].tokenMints[_itemKey] > 0) { + token.mint(_msgSender(), pools[_id].tokenMints[_itemKey].mul(_amount)); + } } /** @@ -1013,7 +1056,7 @@ contract MintShop1155 is Sweepable, ReentrancyGuard { // Verify that any possible ERC-1155 ownership requirements are met. } else if (poolRequirement.requiredType == AccessType.ItemRequired) { - Super1155 requiredItem = Super1155(poolRequirement.requiredAsset); + ISuper1155 requiredItem = ISuper1155(poolRequirement.requiredAsset); require(requiredItem.totalBalances(_msgSender()) >= poolRequirement.requiredAmount, "MintShop1155: you do not have enough required item for this pool"); @@ -1082,6 +1125,9 @@ contract MintShop1155 is Sweepable, ReentrancyGuard { // Update the global count of items that a user has purchased. globalPurchaseCounts[_msgSender()] = userGlobalPurchaseAmount; + // Use the mint helper to avoid a stack-too-deep issue. + _mintHelper(_id, itemKey, _amount); + // Emit an event indicating a successful purchase. emit ItemPurchased(_msgSender(), _id, itemIds, amounts); } diff --git a/contracts/Token.sol b/contracts/Token.sol index 2e19e2d..63552d9 100644 --- a/contracts/Token.sol +++ b/contracts/Token.sol @@ -3,315 +3,436 @@ pragma solidity 0.7.6; import "@openzeppelin/contracts/math/SafeMath.sol"; import "@openzeppelin/contracts/token/ERC20/ERC20Capped.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; + +import "./base/Sweepable.sol"; /** - @title A basic ERC-20 token with voting functionality. + @title A mintable, unpausable ERC-20 token with voting functionality and + administative permissions based on `PermitControl`. @author Tim Clancy + @author Liam Clancy + + This contract is used when deploying SuperFarm ERC-20 tokens. The token has a + fixed supply cap and governance functions ultimately copied and modified from + Compound. It can optionally be created with public transfers paused. - This contract is used when deploying SuperFarm ERC-20 tokens. - This token is created with a fixed, immutable cap and includes voting rights. - Voting functionality is copied and modified from Sushi, and in turn from YAM: - https://github.com/yam-finance/yam-protocol/blob/master/contracts/token/YAMGovernanceStorage.sol - https://github.com/yam-finance/yam-protocol/blob/master/contracts/token/YAMGovernance.sol - Which is in turn copied and modified from COMPOUND: - https://github.com/compound-finance/compound-protocol/blob/master/contracts/Governance/Comp.sol + September 2nd, 2021. */ -contract Token is ERC20Capped, Ownable { +contract Token is Sweepable, ERC20Capped { using SafeMath for uint256; - /// A version number for this Token contract's interface. - uint256 public version = 1; + /// The public identifier for the right to mint tokens. + bytes32 public constant MINT = keccak256("MINT"); - /** - Construct a new Token by providing it a name, ticker, and supply cap. + /// The public identifier for the right to unlock token transfers. + bytes32 public constant UNLOCK_TRANSFERS = keccak256("UNLOCK_TRANSFERS"); - @param _name The name of the new Token. - @param _ticker The ticker symbol of the new Token. - @param _cap The supply cap of the new Token. - */ - constructor (string memory _name, string memory _ticker, uint256 _cap) public ERC20(_name, _ticker) ERC20Capped(_cap) { } + /// The EIP-712 typehash for the contract's domain. + bytes32 public constant DOMAIN_TYPEHASH = keccak256( + "EIP712Domain(string name,uint256 chainId,address verifyingContract)"); - /** - * @dev Destroys `amount` tokens from the caller. - * - * See {ERC20-_burn}. - */ - function burn(uint256 amount) public virtual { - _burn(_msgSender(), amount); - } + /// The EIP-712 typehash for the delegation struct used by the contract. + bytes32 public constant DELEGATION_TYPEHASH = keccak256( + "Delegation(address delegatee,uint256 nonce,uint256 expiry)"); - /** - * @dev Destroys `amount` tokens from `account`, deducting from the caller's - * allowance. - * - * See {ERC20-_burn} and {ERC20-allowance}. - * - * Requirements: - * - * - the caller must have allowance for ``accounts``'s tokens of at least - * `amount`. - */ - function burnFrom(address account, uint256 amount) public virtual { - require(amount >= allowance(account, _msgSender()), - "ERC20: burn amount exceeds allowance"); - uint256 decreasedAllowance = allowance(account, _msgSender()).sub(amount); - - _approve(account, _msgSender(), decreasedAllowance); - _burn(account, amount); - } + /// A flag for whether or not token transfers are enabled. + bool public transfersUnlocked; - /** - Allows Token creator to mint `_amount` of this Token to the address `_to`. - New tokens of this Token cannot be minted if it would exceed the supply cap. - Users are delegated votes when they are minted Token. - - @param _to the address to mint Tokens to. - @param _amount the amount of new Token to mint. - */ - function mint(address _to, uint256 _amount) external onlyOwner { - _mint(_to, _amount); - _moveDelegates(address(0), _delegates[_to], _amount); - } + /// A mapping to record delegates for each address. + mapping( address => address ) public delegates; /** - Allows users to transfer tokens to a recipient, moving delegated votes with - the transfer. + A checkpoint structure to count some number of votes from a given block. - @param recipient The address to transfer tokens to. - @param amount The amount of tokens to send to `recipient`. + @param fromBlock The block to begin counting votes from. + @param votes The number of votes counted from block `fromBlock`. */ - function transfer(address recipient, uint256 amount) public override returns (bool) { - _transfer(_msgSender(), recipient, amount); - _moveDelegates(_delegates[msg.sender], _delegates[recipient], amount); - return true; - } - - /// @dev A mapping to record delegates for each address. - mapping (address => address) internal _delegates; - - /// A checkpoint structure to mark some number of votes from a given block. struct Checkpoint { uint32 fromBlock; uint256 votes; } - /// A mapping to record indexed Checkpoint votes for each address. - mapping (address => mapping (uint32 => Checkpoint)) public checkpoints; + /// A mapping to record indexed `Checkpoint` votes for each address. + mapping( address => mapping( uint32 => Checkpoint )) public checkpoints; - /// A mapping to record the number of Checkpoints for each address. - mapping (address => uint32) public numCheckpoints; + /// A mapping to record the number of `Checkpoint`s for each address. + mapping( address => uint32 ) public numCheckpoints; - /// The EIP-712 typehash for the contract's domain. - bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); + /// A mapping to record per-caller states for signing / validating signatures. + mapping( address => uint256 ) public nonces; - /// The EIP-712 typehash for the delegation struct used by the contract. - bytes32 public constant DELEGATION_TYPEHASH = keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)"); + /** + An event emitted when an address changes its delegate. - /// A mapping to record per-address states for signing / validating signatures. - mapping (address => uint) public nonces; + @param delegator The delegating caller updating its targeted delegate. + @param fromDelegate The old delegate for the caller `delegator`. + @param toDelegate The new delegate for the caller `delegator`. + */ + event DelegateChanged( + address indexed delegator, + address indexed fromDelegate, + address indexed toDelegate + ); - /// An event emitted when an address changes its delegate. - event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate); + /** + An event emitted when the vote balance of a delegated address changes. - /// An event emitted when the vote balance of a delegated address changes. - event DelegateVotesChanged(address indexed delegate, uint previousBalance, uint newBalance); + @param delegate The address being delegated votes to. + @param previousBalance The previous number of votes delegated to `delegate`. + @param newBalance The new number of votes delegated to `delegate`. + */ + event DelegateVotesChanged( + address indexed delegate, + uint256 previousBalance, + uint256 newBalance + ); /** - Return the address delegated to by `delegator`. + An event emitted when transfers are enabled for this token. - @return The address delegated to by `delegator`. + @param unlocker The caller that unlocked transfers. */ - function delegates(address delegator) external view returns (address) { - return _delegates[delegator]; - } + event TransfersUnlocked( + address indexed unlocker + ); /** - Delegate votes from `msg.sender` to `delegatee`. + Construct a new Token by providing it a name, ticker, and supply cap. - @param delegatee The address to delegate votes to. + @param _owner The address of the administrator governing this portal. + @param _name The name of the new Token. + @param _ticker The ticker symbol of the new Token. + @param _cap The supply cap of the new Token. + @param _transfersUnlocked Whether or not transfers of this Token are + immediately unlocked or not. */ - function delegate(address delegatee) external { - return _delegate(msg.sender, delegatee); + constructor( + address _owner, + string memory _name, + string memory _ticker, + uint256 _cap, + bool _transfersUnlocked + ) ERC20(_name, _ticker) ERC20Capped(_cap) { + + // Do not perform a redundant ownership transfer if the deployer should + // remain as the owner of this contract. + if (_owner != owner()) { + transferOwnership(_owner); + } + + // If transfers begin unlocked, emit an event as such. + transfersUnlocked = _transfersUnlocked; + if (_transfersUnlocked) { + emit TransfersUnlocked(_msgSender()); + } } /** - Delegate votes from signatory to `delegatee`. - - @param delegatee The address to delegate votes to. - @param nonce The contract state required for signature matching. - @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. + Return a version number for this contract's interface. */ - 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), "Invalid signature."); - require(nonce == nonces[signatory]++, "Invalid nonce."); - require(block.timestamp <= expiry, "Signature expired."); - return _delegate(signatory, delegatee); + function version() external virtual override(Sweepable) pure returns + (uint256) { + return 1; } /** - Get the current votes balance for the address `account`. + Get the current votes balance for the address `_account`. - @param account The address to get the votes balance of. - @return The number of current votes for `account`. + @param _account The address to get the votes balance of. + @return The number of current votes for `_account`. */ - function getCurrentVotes(address account) external view returns (uint256) { - uint32 nCheckpoints = numCheckpoints[account]; - return nCheckpoints > 0 ? checkpoints[account][nCheckpoints - 1].votes : 0; + function getCurrentVotes( + address _account + ) external view returns (uint256) { + uint32 nCheckpoints = numCheckpoints[_account]; + return nCheckpoints > 0 ? checkpoints[_account][nCheckpoints - 1].votes : 0; } /** - Determine the prior number of votes for an address as of a block number. + Determine the prior number of votes for an address as of a block number. The + block number must be a finalized block or this function will revert to + prevent misinformation. - @dev The block number must be a finalized block or else this function will revert to prevent misinformation. - @param account The address 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. + @param _account The address to check. + @param _blockNumber The block number to get the vote balance at. + @return The number of votes `_account` had as of the given block. */ - function getPriorVotes(address account, uint blockNumber) external view returns (uint256) { - require(blockNumber < block.number, "The specified block is not yet finalized."); - - uint32 nCheckpoints = numCheckpoints[account]; + function getPriorVotes( + address _account, + uint _blockNumber + ) external view returns (uint256) { + require(_blockNumber < block.number, + "The specified block is not yet finalized."); + + uint32 nCheckpoints = numCheckpoints[_account]; if (nCheckpoints == 0) { return 0; } // First check the most recent balance. - if (checkpoints[account][nCheckpoints - 1].fromBlock <= blockNumber) { - return checkpoints[account][nCheckpoints - 1].votes; + if (checkpoints[_account][nCheckpoints - 1].fromBlock <= _blockNumber) { + return checkpoints[_account][nCheckpoints - 1].votes; } // Then check the implicit zero balance. - if (checkpoints[account][0].fromBlock > blockNumber) { + if (checkpoints[_account][0].fromBlock > _blockNumber) { return 0; } + // Perform checks on each block. 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) { + uint32 center = upper - (upper - lower) / 2; + Checkpoint memory cp = checkpoints[_account][center]; + if (cp.fromBlock == _blockNumber) { return cp.votes; - } else if (cp.fromBlock < blockNumber) { + } else if (cp.fromBlock < _blockNumber) { lower = center; } else { upper = center - 1; } } - return checkpoints[account][lower].votes; + return checkpoints[_account][lower].votes; } /** - An internal function to actually perform the delegation of votes. + A function to safely limit a number to less than 2^32. - @param delegator The address delegating to `delegatee`. - @param delegatee The address receiving delegated votes. + @param _n the number to limit. + @param _errorMessage the error message to revert with should `_n` be too + large. + @return The number `_n` limited to 32 bits. */ - function _delegate(address delegator, address delegatee) internal { - address currentDelegate = _delegates[delegator]; - uint256 delegatorBalance = balanceOf(delegator); - _delegates[delegator] = delegatee; - /* console.log('a-', currentDelegate, delegator, delegatee); */ - emit DelegateChanged(delegator, currentDelegate, delegatee); - - _moveDelegates(currentDelegate, delegatee, delegatorBalance); + function safe32( + uint _n, + string memory _errorMessage + ) internal pure returns (uint32) { + require(_n < 2**32, _errorMessage); + return uint32(_n); } /** - An internal function to move delegated vote amounts between addresses. + A function to return the ID of the contract's particular network or chain. - @param srcRep the previous representative who received delegated votes. - @param dstRep the new representative to receive these delegated votes. - @param amount the amount of delegated votes to move between representatives. + @return The ID of the contract's network or chain. */ - function _moveDelegates(address srcRep, address dstRep, uint256 amount) internal { - if (srcRep != dstRep && amount > 0) { + function getChainId() internal pure returns (uint) { + uint256 chainId; + assembly { chainId := chainid() } + return chainId; + } - // Decrease the number of votes delegated to the previous representative. - if (srcRep != address(0)) { - uint32 srcRepNum = numCheckpoints[srcRep]; - uint256 srcRepOld = srcRepNum > 0 ? checkpoints[srcRep][srcRepNum - 1].votes : 0; - uint256 srcRepNew = srcRepOld.sub(amount); - _writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew); - } + /** + Allows users to transfer tokens to a recipient, moving delegated votes with + the transfer. - // Increase the number of votes delegated to the new representative. - if (dstRep != address(0)) { - uint32 dstRepNum = numCheckpoints[dstRep]; - uint256 dstRepOld = dstRepNum > 0 ? checkpoints[dstRep][dstRepNum - 1].votes : 0; - uint256 dstRepNew = dstRepOld.add(amount); - _writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew); - } - } + @param _recipient The address to transfer tokens to. + @param _amount The amount of tokens to send to `_recipient`. + */ + function transfer( + address _recipient, + uint256 _amount + ) public override returns (bool) { + require(transfersUnlocked, + "Token::transfer::token transfers are locked"); + _transfer(_msgSender(), _recipient, _amount); + _moveDelegates(delegates[_msgSender()], delegates[_recipient], _amount); + return true; + } + + /** + Allow an approved user to unlock transfers of this token. + */ + function unlockTransfers() external + hasValidPermit(UNIVERSAL, UNLOCK_TRANSFERS) { + transfersUnlocked = true; + emit TransfersUnlocked(_msgSender()); + } + + /** + Allows Token creator to mint `_amount` of this Token to the address `_to`. + New tokens of this Token cannot be minted if it would exceed the supply cap. + Users are delegated votes when they are minted Token. + + @param _to the address to mint Tokens to. + @param _amount the amount of new Token to mint. + */ + function mint( + address _to, + uint256 _amount + ) external hasValidPermit(UNIVERSAL, MINT) { + _mint(_to, _amount); + _moveDelegates(address(0), delegates[_to], _amount); + } + + /** + Allow the caller to burn some `_amount` of their Tokens. + + @param _amount The amount of tokens that the caller will try to burn. + */ + function burn( + uint256 _amount + ) public virtual { + _burn(_msgSender(), _amount); + } + + /** + Allow the caller to burn some `_amount` of Tokens from `_account`. The + caller can only burn these tokens if they have been granted an allowance by + `_account`. + + @param _account The account to burn tokens from. + @param _amount The amount of tokens to burn. + */ + function burnFrom( + address _account, + uint256 _amount + ) public virtual { + require(_amount >= allowance(_account, _msgSender()), + "ERC20: burn amount exceeds allowance"); + uint256 decreasedAllowance = allowance(_account, _msgSender()).sub(_amount); + _approve(_account, _msgSender(), decreasedAllowance); + _burn(_account, _amount); } /** An internal function to write a checkpoint of modified vote amounts. This function is guaranteed to add at most one checkpoint per block. - @param delegatee The address whose vote count is changed. - @param nCheckpoints The number of checkpoints by address `delegatee`. - @param oldVotes The prior vote count of address `delegatee`. - @param newVotes The new vote count of address `delegatee`. + @param _delegatee The address whose vote count is changed. + @param _nCheckpoints The number of checkpoints by address `_delegatee`. + @param _oldVotes The prior vote count of address `_delegatee`. + @param _newVotes The new vote count of address `_delegatee`. */ - function _writeCheckpoint(address delegatee, uint32 nCheckpoints, uint256 oldVotes, uint256 newVotes) internal { + function _writeCheckpoint( + address _delegatee, + uint32 _nCheckpoints, + uint256 _oldVotes, + uint256 _newVotes + ) internal { uint32 blockNumber = safe32(block.number, "Block number exceeds 32 bits."); - if (nCheckpoints > 0 && checkpoints[delegatee][nCheckpoints - 1].fromBlock == blockNumber) { - checkpoints[delegatee][nCheckpoints - 1].votes = newVotes; + 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; + checkpoints[_delegatee][_nCheckpoints] = Checkpoint(blockNumber, + _newVotes); + numCheckpoints[_delegatee] = _nCheckpoints + 1; } - emit DelegateVotesChanged(delegatee, oldVotes, newVotes); + // Emit the delegate vote change event. + emit DelegateVotesChanged(_delegatee, _oldVotes, _newVotes); } /** - A function to safely limit a number to less than 2^32. + An internal function to move delegated vote amounts between addresses. - @param n the number to limit. - @param errorMessage the error message to revert with should `n` be too large. - @return The number `n` limited to 32 bits. + @param _srcRep the previous representative who received delegated votes. + @param _dstRep the new representative to receive these delegated votes. + @param _amount the amount of delegated votes to move between + representatives. */ - function safe32(uint n, string memory errorMessage) internal pure returns (uint32) { - require(n < 2**32, errorMessage); - return uint32(n); + function _moveDelegates( + address _srcRep, + address _dstRep, + uint256 _amount + ) internal { + if (_srcRep != _dstRep && _amount > 0) { + + // Decrease the number of votes delegated to the previous representative. + if (_srcRep != address(0)) { + uint32 srcRepNum = numCheckpoints[_srcRep]; + uint256 srcRepOld = srcRepNum > 0 ? checkpoints[_srcRep][srcRepNum - 1].votes : 0; + uint256 srcRepNew = srcRepOld.sub(_amount); + _writeCheckpoint(_srcRep, srcRepNum, srcRepOld, srcRepNew); + } + + // Increase the number of votes delegated to the new representative. + if (_dstRep != address(0)) { + uint32 dstRepNum = numCheckpoints[_dstRep]; + uint256 dstRepOld = dstRepNum > 0 ? checkpoints[_dstRep][dstRepNum - 1].votes : 0; + uint256 dstRepNew = dstRepOld.add(_amount); + _writeCheckpoint(_dstRep, dstRepNum, dstRepOld, dstRepNew); + } + } } /** - A function to return the ID of the contract's particular network or chain. + An internal function to actually perform the delegation of votes. - @return The ID of the contract's network or chain. + @param _delegator The address delegating to `_delegatee`. + @param _delegatee The address receiving delegated votes. */ - function getChainId() internal pure returns (uint) { - uint256 chainId; - assembly { chainId := chainid() } - return chainId; + function _delegate( + address _delegator, + address _delegatee + ) internal { + address currentDelegate = delegates[_delegator]; + uint256 delegatorBalance = balanceOf(_delegator); + delegates[_delegator] = _delegatee; + _moveDelegates(currentDelegate, _delegatee, delegatorBalance); + emit DelegateChanged(_delegator, currentDelegate, _delegatee); + } + + /** + Delegate votes from the caller to `_delegatee`. + + @param _delegatee The address to delegate votes to. + */ + function delegate( + address _delegatee + ) external { + return _delegate(_msgSender(), _delegatee); + } + + /** + Delegate votes by signature from the caller to `_delegatee`. + + @param _delegatee The address to delegate votes to. + @param _nonce The contract state required for signature matching. + @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)); + + // Recover and verify the signatory. + address signatory = ecrecover(digest, _v, _r, _s); + require(signatory != address(0), + "Token::delegateBySig::invalid signature"); + require(_nonce == nonces[signatory]++, + "Token::delegateBySig::invalid nonce"); + require(block.timestamp <= _expiry, + "Token::delegateBySig::signature expired"); + return _delegate(signatory, _delegatee); } } diff --git a/legacy-contracts/Token.sol b/legacy-contracts/Token.sol new file mode 100644 index 0000000..2e19e2d --- /dev/null +++ b/legacy-contracts/Token.sol @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.7.6; + +import "@openzeppelin/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20Capped.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +/** + @title A basic ERC-20 token with voting functionality. + @author Tim Clancy + + This contract is used when deploying SuperFarm ERC-20 tokens. + This token is created with a fixed, immutable cap and includes voting rights. + Voting functionality is copied and modified from Sushi, and in turn from YAM: + https://github.com/yam-finance/yam-protocol/blob/master/contracts/token/YAMGovernanceStorage.sol + https://github.com/yam-finance/yam-protocol/blob/master/contracts/token/YAMGovernance.sol + Which is in turn copied and modified from COMPOUND: + https://github.com/compound-finance/compound-protocol/blob/master/contracts/Governance/Comp.sol +*/ +contract Token is ERC20Capped, Ownable { + using SafeMath for uint256; + + /// A version number for this Token contract's interface. + uint256 public version = 1; + + /** + Construct a new Token by providing it a name, ticker, and supply cap. + + @param _name The name of the new Token. + @param _ticker The ticker symbol of the new Token. + @param _cap The supply cap of the new Token. + */ + constructor (string memory _name, string memory _ticker, uint256 _cap) public ERC20(_name, _ticker) ERC20Capped(_cap) { } + + /** + * @dev Destroys `amount` tokens from the caller. + * + * See {ERC20-_burn}. + */ + function burn(uint256 amount) public virtual { + _burn(_msgSender(), amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, deducting from the caller's + * allowance. + * + * See {ERC20-_burn} and {ERC20-allowance}. + * + * Requirements: + * + * - the caller must have allowance for ``accounts``'s tokens of at least + * `amount`. + */ + function burnFrom(address account, uint256 amount) public virtual { + require(amount >= allowance(account, _msgSender()), + "ERC20: burn amount exceeds allowance"); + uint256 decreasedAllowance = allowance(account, _msgSender()).sub(amount); + + _approve(account, _msgSender(), decreasedAllowance); + _burn(account, amount); + } + + /** + Allows Token creator to mint `_amount` of this Token to the address `_to`. + New tokens of this Token cannot be minted if it would exceed the supply cap. + Users are delegated votes when they are minted Token. + + @param _to the address to mint Tokens to. + @param _amount the amount of new Token to mint. + */ + function mint(address _to, uint256 _amount) external onlyOwner { + _mint(_to, _amount); + _moveDelegates(address(0), _delegates[_to], _amount); + } + + /** + Allows users to transfer tokens to a recipient, moving delegated votes with + the transfer. + + @param recipient The address to transfer tokens to. + @param amount The amount of tokens to send to `recipient`. + */ + function transfer(address recipient, uint256 amount) public override returns (bool) { + _transfer(_msgSender(), recipient, amount); + _moveDelegates(_delegates[msg.sender], _delegates[recipient], amount); + return true; + } + + /// @dev A mapping to record delegates for each address. + mapping (address => address) internal _delegates; + + /// A checkpoint structure to mark some number of votes from a given block. + struct Checkpoint { + uint32 fromBlock; + uint256 votes; + } + + /// A mapping to record indexed Checkpoint votes for each address. + mapping (address => mapping (uint32 => Checkpoint)) public checkpoints; + + /// A mapping to record the number of Checkpoints for each address. + mapping (address => uint32) public numCheckpoints; + + /// The EIP-712 typehash for the contract's domain. + bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); + + /// The EIP-712 typehash for the delegation struct used by the contract. + bytes32 public constant DELEGATION_TYPEHASH = keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)"); + + /// A mapping to record per-address states for signing / validating signatures. + mapping (address => uint) public nonces; + + /// An event emitted when an address changes its delegate. + event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate); + + /// An event emitted when the vote balance of a delegated address changes. + event DelegateVotesChanged(address indexed delegate, uint previousBalance, uint newBalance); + + /** + Return the address delegated to by `delegator`. + + @return The address delegated to by `delegator`. + */ + function delegates(address delegator) external view returns (address) { + return _delegates[delegator]; + } + + /** + 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); + } + + /** + Delegate votes from signatory to `delegatee`. + + @param delegatee The address to delegate votes to. + @param nonce The contract state required for signature matching. + @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), "Invalid signature."); + require(nonce == nonces[signatory]++, "Invalid nonce."); + require(block.timestamp <= expiry, "Signature expired."); + return _delegate(signatory, delegatee); + } + + /** + Get the current votes balance for the address `account`. + + @param account The address to get the votes balance of. + @return The number of current votes for `account`. + */ + function getCurrentVotes(address account) external view returns (uint256) { + uint32 nCheckpoints = numCheckpoints[account]; + return nCheckpoints > 0 ? checkpoints[account][nCheckpoints - 1].votes : 0; + } + + /** + Determine the prior number of votes for an address as of a block number. + + @dev The block number must be a finalized block or else this function will revert to prevent misinformation. + @param account The address 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) external view returns (uint256) { + require(blockNumber < block.number, "The specified block is not yet finalized."); + + uint32 nCheckpoints = numCheckpoints[account]; + if (nCheckpoints == 0) { + return 0; + } + + // First check the most recent balance. + if (checkpoints[account][nCheckpoints - 1].fromBlock <= blockNumber) { + return checkpoints[account][nCheckpoints - 1].votes; + } + + // Then check the 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; + } + + /** + An internal function to actually perform the delegation of votes. + + @param delegator The address delegating to `delegatee`. + @param delegatee The address receiving delegated votes. + */ + function _delegate(address delegator, address delegatee) internal { + address currentDelegate = _delegates[delegator]; + uint256 delegatorBalance = balanceOf(delegator); + _delegates[delegator] = delegatee; + /* console.log('a-', currentDelegate, delegator, delegatee); */ + emit DelegateChanged(delegator, currentDelegate, delegatee); + + _moveDelegates(currentDelegate, delegatee, delegatorBalance); + } + + /** + An internal function to move delegated vote amounts between addresses. + + @param srcRep the previous representative who received delegated votes. + @param dstRep the new representative to receive these delegated votes. + @param amount the amount of delegated votes to move between representatives. + */ + function _moveDelegates(address srcRep, address dstRep, uint256 amount) internal { + if (srcRep != dstRep && amount > 0) { + + // Decrease the number of votes delegated to the previous representative. + if (srcRep != address(0)) { + uint32 srcRepNum = numCheckpoints[srcRep]; + uint256 srcRepOld = srcRepNum > 0 ? checkpoints[srcRep][srcRepNum - 1].votes : 0; + uint256 srcRepNew = srcRepOld.sub(amount); + _writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew); + } + + // Increase the number of votes delegated to the new representative. + if (dstRep != address(0)) { + uint32 dstRepNum = numCheckpoints[dstRep]; + uint256 dstRepOld = dstRepNum > 0 ? checkpoints[dstRep][dstRepNum - 1].votes : 0; + uint256 dstRepNew = dstRepOld.add(amount); + _writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew); + } + } + } + + /** + An internal function to write a checkpoint of modified vote amounts. + This function is guaranteed to add at most one checkpoint per block. + + @param delegatee The address whose vote count is changed. + @param nCheckpoints The number of checkpoints by address `delegatee`. + @param oldVotes The prior vote count of address `delegatee`. + @param newVotes The new vote count of address `delegatee`. + */ + function _writeCheckpoint(address delegatee, uint32 nCheckpoints, uint256 oldVotes, uint256 newVotes) internal { + uint32 blockNumber = safe32(block.number, "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); + } + + /** + A function to safely limit a number to less than 2^32. + + @param n the number to limit. + @param errorMessage the error message to revert with should `n` be too large. + @return The number `n` limited to 32 bits. + */ + function safe32(uint n, string memory errorMessage) internal pure returns (uint32) { + require(n < 2**32, errorMessage); + return uint32(n); + } + + /** + A function to return the ID of the contract's particular network or chain. + + @return The ID of the contract's network or chain. + */ + function getChainId() internal pure returns (uint) { + uint256 chainId; + assembly { chainId := chainid() } + return chainId; + } +}