diff --git a/src/lib/ERC721NonTransferableMinimalProxy.sol b/src/lib/ERC721NonTransferableMinimalProxy.sol deleted file mode 100644 index 15b6ea8..0000000 --- a/src/lib/ERC721NonTransferableMinimalProxy.sol +++ /dev/null @@ -1,192 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// forgefmt: disable-start -pragma solidity ^0.8.0; - -import {Initializable} from "@openzeppelin/proxy/utils/Initializable.sol"; - -/// @title ERC721 Non-Transferable Minimal Proxy -/// @notice This contract is a modified version of Solmate's ERC721 contract -/// @notice Modern, minimalist, and gas efficient ERC-721 implementation as a minimal proxy. -/// @author Solmate / Llama (https://github.com/transmissions11/solmate/blob/34d20fc027fe8d50da71428687024a29dc01748b/src/tokens/ERC721.sol) -abstract contract ERC721NonTransferableMinimalProxy is Initializable { - /*////////////////////////////////////////////////////////////// - EVENTS - //////////////////////////////////////////////////////////////*/ - - event Transfer(address indexed from, address indexed to, uint256 indexed id); - - event Approval(address indexed owner, address indexed spender, uint256 indexed id); - - event ApprovalForAll(address indexed owner, address indexed operator, bool approved); - - /*////////////////////////////////////////////////////////////// - METADATA STORAGE/LOGIC - //////////////////////////////////////////////////////////////*/ - - string public name; - - string public symbol; - - function tokenURI(uint256 id) public view virtual returns (string memory); - - /*////////////////////////////////////////////////////////////// - ERC721 BALANCE/OWNER STORAGE - //////////////////////////////////////////////////////////////*/ - - mapping(uint256 => address) internal _ownerOf; - - mapping(address => uint256) internal _balanceOf; - - function ownerOf(uint256 id) public view virtual returns (address owner) { - require((owner = _ownerOf[id]) != address(0), "NOT_MINTED"); - } - - function balanceOf(address owner) public view virtual returns (uint256) { - require(owner != address(0), "ZERO_ADDRESS"); - - return _balanceOf[owner]; - } - - /*////////////////////////////////////////////////////////////// - ERC721 APPROVAL STORAGE - //////////////////////////////////////////////////////////////*/ - - mapping(uint256 => address) public getApproved; - - mapping(address => mapping(address => bool)) public isApprovedForAll; - - /*////////////////////////////////////////////////////////////// - CONSTRUCTOR - //////////////////////////////////////////////////////////////*/ - - function __initializeERC721MinimalProxy (string memory _name, string memory _symbol) internal { - name = _name; - symbol = _symbol; - } - - /*////////////////////////////////////////////////////////////// - ERC721 LOGIC - //////////////////////////////////////////////////////////////*/ - - function approve(address spender, uint256 id) public virtual { - address owner = _ownerOf[id]; - - require(msg.sender == owner || isApprovedForAll[owner][msg.sender], "NOT_AUTHORIZED"); - - getApproved[id] = spender; - - emit Approval(owner, spender, id); - } - - function setApprovalForAll(address operator, bool approved) public virtual { - isApprovedForAll[msg.sender][operator] = approved; - - emit ApprovalForAll(msg.sender, operator, approved); - } - - function transferFrom(address from, address to, uint256 id) public virtual { - require(from == _ownerOf[id], "WRONG_FROM"); - - require(to != address(0), "INVALID_RECIPIENT"); - - require(msg.sender == from || isApprovedForAll[from][msg.sender] || msg.sender == getApproved[id], "NOT_AUTHORIZED"); - - // Underflow of the sender's balance is impossible because we check for - // ownership above and the recipient's balance can't realistically overflow. - unchecked { - _balanceOf[from]--; - - _balanceOf[to]++; - } - - _ownerOf[id] = to; - - delete getApproved[id]; - - emit Transfer(from, to, id); - } - - function safeTransferFrom(address from, address to, uint256 id) public virtual; - - function safeTransferFrom(address from, address to, uint256 id, bytes calldata data) public virtual; - - /*////////////////////////////////////////////////////////////// - ERC165 LOGIC - //////////////////////////////////////////////////////////////*/ - - function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { - return interfaceId == 0x01ffc9a7 // ERC165 Interface ID for ERC165 - || interfaceId == 0x80ac58cd // ERC165 Interface ID for ERC721 - || interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata - } - - /*////////////////////////////////////////////////////////////// - INTERNAL MINT/BURN LOGIC - //////////////////////////////////////////////////////////////*/ - - function _mint(address to, uint256 id) internal virtual { - require(to != address(0), "INVALID_RECIPIENT"); - - require(_ownerOf[id] == address(0), "ALREADY_MINTED"); - - // Counter overflow is incredibly unrealistic. - unchecked { - _balanceOf[to]++; - } - - _ownerOf[id] = to; - - emit Transfer(address(0), to, id); - } - - function _burn(uint256 id) internal virtual { - address owner = _ownerOf[id]; - - require(owner != address(0), "NOT_MINTED"); - - // Ownership check above ensures no underflow. - unchecked { - _balanceOf[owner]--; - } - - delete _ownerOf[id]; - - delete getApproved[id]; - - emit Transfer(owner, address(0), id); - } - - /*////////////////////////////////////////////////////////////// - INTERNAL SAFE MINT LOGIC - //////////////////////////////////////////////////////////////*/ - - function _safeMint(address to, uint256 id) internal virtual { - _mint(to, id); - - require( - to.code.length == 0 - || ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, "") - == ERC721TokenReceiver.onERC721Received.selector, - "UNSAFE_RECIPIENT" - ); - } - - function _safeMint(address to, uint256 id, bytes memory data) internal virtual { - _mint(to, id); - - require( - to.code.length == 0 - || ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, data) - == ERC721TokenReceiver.onERC721Received.selector, - "UNSAFE_RECIPIENT" - ); - } -} - -/// @notice A generic interface for a contract which properly accepts ERC721 tokens. -/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol) -abstract contract ERC721TokenReceiver { - function onERC721Received(address, address, uint256, bytes calldata) external virtual returns (bytes4) { - return ERC721TokenReceiver.onERC721Received.selector; - } -} diff --git a/src/lib/LlamaUtils.sol b/src/lib/LlamaUtils.sol index 7ac80e5..72b127d 100644 --- a/src/lib/LlamaUtils.sol +++ b/src/lib/LlamaUtils.sol @@ -8,6 +8,12 @@ library LlamaUtils { /// @dev Thrown when a value cannot be safely casted to a smaller type. error UnsafeCast(uint256 n); + /// @dev Reverts if `n` does not fit in a `uint16`. + function toUint16(uint256 n) internal pure returns (uint16) { + if (n > type(uint16).max) revert UnsafeCast(n); + return uint16(n); + } + /// @dev Reverts if `n` does not fit in a `uint48`. function toUint48(uint256 n) internal pure returns (uint48) { if (n > type(uint48).max) revert UnsafeCast(n); @@ -26,6 +32,12 @@ library LlamaUtils { return uint96(n); } + /// @dev Reverts if `n` does not fit in a `uint224`. + function toUint224(uint256 n) internal pure returns (uint224) { + if (n > type(uint224).max) revert UnsafeCast(n); + return uint224(n); + } + /// @dev Increments a `uint256` without checking for overflow. function uncheckedIncrement(uint256 i) internal pure returns (uint256) { unchecked { diff --git a/src/lib/PolicyholderCheckpoints.sol b/src/lib/QuorumCheckpoints.sol similarity index 78% rename from src/lib/PolicyholderCheckpoints.sol rename to src/lib/QuorumCheckpoints.sol index ca77ae6..1ca3b2f 100644 --- a/src/lib/PolicyholderCheckpoints.sol +++ b/src/lib/QuorumCheckpoints.sol @@ -13,30 +13,30 @@ import {LlamaUtils} from "src/lib/LlamaUtils.sol"; * * @dev This was created by modifying then running the OpenZeppelin `Checkpoints.js` script, which generated a version * of this library that uses a 64 bit `timestamp` and 96 bit `quantity` field in the `Checkpoint` struct. The struct - * was then modified to add a 64 bit `expiration` field. For simplicity, safe cast and math methods were inlined from + * was then modified to add two uint16 quorum fields. For simplicity, safe cast and math methods were inlined from * the OpenZeppelin versions at the same commit. We disable forge-fmt for this file to simplify diffing against the * original OpenZeppelin version: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/d00acef4059807535af0bd0dd0ddf619747a044b/contracts/utils/Checkpoints.sol */ -library PolicyholderCheckpoints { +library QuorumCheckpoints { struct History { Checkpoint[] _checkpoints; } struct Checkpoint { - uint64 timestamp; - uint64 expiration; - uint96 quantity; + uint224 timestamp; + uint16 voteQuorumPct; + uint16 vetoQuorumPct; } /** - * @dev Returns the quantity at a given block timestamp. If a checkpoint is not available at that time, the closest + * @dev Returns the quorums at a given block timestamp. If a checkpoint is not available at that time, the closest * one before it is returned, or zero otherwise. Similar to {upperLookup} but optimized for the case when the * searched checkpoint is probably "recent", defined as being among the last sqrt(N) checkpoints where N is the * timestamp of checkpoints. */ - function getAtProbablyRecentTimestamp(History storage self, uint256 timestamp) internal view returns (uint96) { + function getAtProbablyRecentTimestamp(History storage self, uint256 timestamp) internal view returns (uint16, uint16) { require(timestamp < block.timestamp, "PolicyholderCheckpoints: timestamp is not in the past"); - uint64 _timestamp = LlamaUtils.toUint64(timestamp); + uint224 _timestamp = LlamaUtils.toUint224(timestamp); uint256 len = self._checkpoints.length; @@ -54,44 +54,48 @@ library PolicyholderCheckpoints { uint256 pos = _upperBinaryLookup(self._checkpoints, _timestamp, low, high); - return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1).quantity; + if (pos == 0) return (0, 0); + Checkpoint memory ckpt = _unsafeAccess(self._checkpoints, pos - 1); + return (ckpt.voteQuorumPct, ckpt.vetoQuorumPct); } /** - * @dev Pushes a `quantity` and `expiration` onto a History so that it is stored as the checkpoint for the current + * @dev Pushes a `voteQuorumPct` and `vetoQuorumPct` onto a History so that it is stored as the checkpoint for the current * `timestamp`. * - * Returns previous quantity and new quantity. + * Returns previous quorum and new quorum. * - * @dev Note that the order of the `expiration` and `quantity` parameters is reversed from the ordering used - * everywhere else in this file. The struct and other methods have the order as `(expiration, quantity)` but this - * method has it as `(quantity, expiration)`. As a result, use caution when editing this method to avoid + * @dev Note that the order of the `voteQuorumPct` and `vetoQuorumPct` parameters is reversed from the ordering used + * everywhere else in this file. The struct and other methods have the order as `(voteQuorumPct, vetoQuorumPct)` but this + * method has it as `(voteQuorumPct, vetoQuorumPct)`. As a result, use caution when editing this method to avoid * accidentally introducing a bug or breaking change. */ - function push(History storage self, uint256 quantity, uint256 expiration) internal returns (uint96, uint96) { - return _insert(self._checkpoints, LlamaUtils.toUint64(block.timestamp), LlamaUtils.toUint64(expiration), LlamaUtils.toUint96(quantity)); + function push(History storage self, uint256 vetoQuorumPct, uint256 voteQuorumPct) internal returns (uint16, uint16) { + return _insert(self._checkpoints, LlamaUtils.toUint224(block.timestamp), LlamaUtils.toUint16(voteQuorumPct), LlamaUtils.toUint16(vetoQuorumPct)); } /** - * @dev Returns the quantity in the most recent checkpoint, or zero if there are no checkpoints. + * @dev Returns the quorum in the most recent checkpoint, or zero if there are no checkpoints. */ - function latest(History storage self) internal view returns (uint96) { + function latest(History storage self) internal view returns (uint16, uint16) { uint256 pos = self._checkpoints.length; - return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1).quantity; + if (pos == 0) return (0, 0); + Checkpoint memory ckpt = _unsafeAccess(self._checkpoints, pos - 1); + return (ckpt.voteQuorumPct, ckpt.vetoQuorumPct); } /** * @dev Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the timestamp and - * quantity in the most recent checkpoint. + * quorum in the most recent checkpoint. */ function latestCheckpoint(History storage self) internal view returns ( bool exists, - uint64 timestamp, - uint64 expiration, - uint96 quantity + uint224 timestamp, + uint16 voteQuorumPct, + uint16 vetoQuorumPct ) { uint256 pos = self._checkpoints.length; @@ -99,7 +103,7 @@ library PolicyholderCheckpoints { return (false, 0, 0, 0); } else { Checkpoint memory ckpt = _unsafeAccess(self._checkpoints, pos - 1); - return (true, ckpt.timestamp, ckpt.expiration, ckpt.quantity); + return (true, ckpt.timestamp, ckpt.voteQuorumPct, ckpt.vetoQuorumPct); } } @@ -111,15 +115,15 @@ library PolicyholderCheckpoints { } /** - * @dev Pushes a (`timestamp`, `expiration`, `quantity`) pair into an ordered list of checkpoints, either by inserting a new + * @dev Pushes a (`timestamp`, `voteQuorumPct`, `vetoQuorumPct`) pair into an ordered list of checkpoints, either by inserting a new * checkpoint, or by updating the last one. */ function _insert( Checkpoint[] storage self, - uint64 timestamp, - uint64 expiration, - uint96 quantity - ) private returns (uint96, uint96) { + uint224 timestamp, + uint16 voteQuorumPct, + uint16 vetoQuorumPct + ) private returns (uint16, uint16) { uint256 pos = self.length; if (pos > 0) { @@ -132,15 +136,15 @@ library PolicyholderCheckpoints { // Update or push new checkpoint if (last.timestamp == timestamp) { Checkpoint storage ckpt = _unsafeAccess(self, pos - 1); - ckpt.quantity = quantity; - ckpt.expiration = expiration; + ckpt.voteQuorumPct = voteQuorumPct; + ckpt.vetoQuorumPct = vetoQuorumPct; } else { - self.push(Checkpoint({timestamp: timestamp, expiration: expiration, quantity: quantity})); + self.push(Checkpoint({timestamp: timestamp, voteQuorumPct: voteQuorumPct, vetoQuorumPct: vetoQuorumPct})); } - return (last.quantity, quantity); + return (last.vetoQuorumPct, vetoQuorumPct); } else { - self.push(Checkpoint({timestamp: timestamp, expiration: expiration, quantity: quantity})); - return (0, quantity); + self.push(Checkpoint({timestamp: timestamp, voteQuorumPct: voteQuorumPct, vetoQuorumPct: vetoQuorumPct})); + return (0, vetoQuorumPct); } } @@ -153,7 +157,7 @@ library PolicyholderCheckpoints { */ function _upperBinaryLookup( Checkpoint[] storage self, - uint64 timestamp, + uint224 timestamp, uint256 low, uint256 high ) private view returns (uint256) { @@ -177,7 +181,7 @@ library PolicyholderCheckpoints { */ function _lowerBinaryLookup( Checkpoint[] storage self, - uint64 timestamp, + uint224 timestamp, uint256 low, uint256 high ) private view returns (uint256) { diff --git a/src/lib/SupplyCheckpoints.sol b/src/lib/SupplyCheckpoints.sol deleted file mode 100644 index 3c35591..0000000 --- a/src/lib/SupplyCheckpoints.sol +++ /dev/null @@ -1,294 +0,0 @@ -// SPDX-License-Identifier: MIT -// forgefmt: disable-start -pragma solidity ^0.8.0; - -import {LlamaUtils} from "src/lib/LlamaUtils.sol"; - -/** - * @dev This library defines the `History` struct, for checkpointing values as they change at different points in - * time, and later looking up past values by block timestamp. - * - * To create a history of checkpoints define a variable type `SupplyCheckpoints.History` in your contract, and store a - * new checkpoint for the current transaction timestamp using the {push} function. - * - * @dev This was created by modifying then running the OpenZeppelin `Checkpoints.js` script, which generated a version - * of this library that uses a 64 bit `timestamp` and 96 bit `quantity` field in the `Checkpoint` struct. The struct - * was then modified to work with the below `Checkpoint` struct. For simplicity, safe cast and math methods were inlined - * from the OpenZeppelin versions at the same commit. We disable forge-fmt for this file to simplify diffing against the - * original OpenZeppelin version: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/d00acef4059807535af0bd0dd0ddf619747a044b/contracts/utils/Checkpoints.sol - */ -library SupplyCheckpoints { - struct History { - Checkpoint[] _checkpoints; - } - - struct Checkpoint { - uint64 timestamp; - uint96 numberOfHolders; - uint96 totalQuantity; - } - - /** - * @dev Returns the supply quantities at a given block timestamp. If a checkpoint is not available at that time, the closest - * one before it is returned, or zero otherwise. Similar to {upperLookup} but optimized for the case when the - * searched checkpoint is probably "recent", defined as being among the last sqrt(N) checkpoints where N is the - * timestamp of checkpoints. - */ - function getAtProbablyRecentTimestamp(History storage self, uint256 timestamp) - internal - view - returns (uint96 numberOfHolders, uint96 totalQuantity) - { - require(timestamp < block.timestamp, "SupplyCheckpoints: timestamp is not in the past"); - uint64 _timestamp = LlamaUtils.toUint64(timestamp); - - uint256 len = self._checkpoints.length; - - uint256 low = 0; - uint256 high = len; - - if (len > 5) { - uint256 mid = len - sqrt(len); - if (_timestamp < _unsafeAccess(self._checkpoints, mid).timestamp) { - high = mid; - } else { - low = mid + 1; - } - } - - uint256 pos = _upperBinaryLookup(self._checkpoints, _timestamp, low, high); - - return pos == 0 ? (0, 0) : _unsafeSupplyAccess(self._checkpoints, pos - 1); - } - - /** - * @dev Pushes the `numberOfHolders` and `totalQuantity` supplies onto a History so that it is stored as the - * checkpoint for the current `timestamp`. - * - * For simplicity, this method does not return anything, since the return values are not needed by Llama. - */ - function push(History storage self, uint256 numberOfHolders, uint256 totalQuantity) internal { - _insert(self._checkpoints, LlamaUtils.toUint64(block.timestamp), LlamaUtils.toUint96(numberOfHolders), LlamaUtils.toUint96(totalQuantity)); - } - - /** - * @dev Returns the supplies in the most recent checkpoint, or zeros if there are no checkpoints. - */ - function latest(History storage self) internal view returns (uint96 numberOfHolders, uint96 totalQuantity) { - uint256 pos = self._checkpoints.length; - return pos == 0 ? (0, 0) : _unsafeSupplyAccess(self._checkpoints, pos - 1); - } - - /** - * @dev Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the timestamp and - * supplies in the most recent checkpoint. - */ - function latestCheckpoint(History storage self) - internal - view - returns ( - bool exists, - uint64 timestamp, - uint96 numberOfHolders, - uint96 totalQuantity - ) - { - uint256 pos = self._checkpoints.length; - if (pos == 0) { - return (false, 0, 0, 0); - } else { - Checkpoint memory ckpt = _unsafeAccess(self._checkpoints, pos - 1); - return (true, ckpt.timestamp, ckpt.numberOfHolders, ckpt.totalQuantity); - } - } - - /** - * @dev Returns the number of checkpoints. - */ - function length(History storage self) internal view returns (uint256) { - return self._checkpoints.length; - } - - /** - * @dev Pushes a (`timestamp`, `numberOfHolders`, `totalQuantity`) pair into an ordered list of checkpoints, either - * by inserting a new checkpoint, or by updating the last one. - * - * For simplicity, this method does not return anything, since the return values are not needed by Llama. - */ - function _insert( - Checkpoint[] storage self, - uint64 timestamp, - uint96 numberOfHolders, - uint96 totalQuantity - ) private { - uint256 pos = self.length; - - if (pos > 0) { - // Copying to memory is important here. - Checkpoint memory last = _unsafeAccess(self, pos - 1); - - // Checkpoints timestamps must be increasing. - require(last.timestamp <= timestamp, "Supply Checkpoint: invalid timestamp"); - - // Update or push new checkpoint - if (last.timestamp == timestamp) { - Checkpoint storage ckpt = _unsafeAccess(self, pos - 1); - ckpt.numberOfHolders = numberOfHolders; - ckpt.totalQuantity = totalQuantity; - } else { - self.push(Checkpoint({timestamp: timestamp, numberOfHolders: numberOfHolders, totalQuantity: totalQuantity})); - } - } else { - self.push(Checkpoint({timestamp: timestamp, numberOfHolders: numberOfHolders, totalQuantity: totalQuantity})); - } - } - - /** - * @dev Return the index of the oldest checkpoint whose timestamp is greater than the search timestamp, or `high` - * if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and exclusive - * `high`. - * - * WARNING: `high` should not be greater than the array's length. - */ - function _upperBinaryLookup( - Checkpoint[] storage self, - uint64 timestamp, - uint256 low, - uint256 high - ) private view returns (uint256) { - while (low < high) { - uint256 mid = average(low, high); - if (_unsafeAccess(self, mid).timestamp > timestamp) { - high = mid; - } else { - low = mid + 1; - } - } - return high; - } - - /** - * @dev Return the index of the oldest checkpoint whose timestamp is greater or equal than the search timestamp, or - * `high` if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and - * exclusive `high`. - * - * WARNING: `high` should not be greater than the array's length. - */ - function _lowerBinaryLookup( - Checkpoint[] storage self, - uint64 timestamp, - uint256 low, - uint256 high - ) private view returns (uint256) { - while (low < high) { - uint256 mid = average(low, high); - if (_unsafeAccess(self, mid).timestamp < timestamp) { - low = mid + 1; - } else { - high = mid; - } - } - return high; - } - - function _unsafeAccess(Checkpoint[] storage self, uint256 pos) - private - pure - returns (Checkpoint storage result) - { - assembly { - mstore(0, self.slot) - result.slot := add(keccak256(0, 0x20), pos) - } - } - - function _unsafeSupplyAccess(Checkpoint[] storage self, uint256 pos) - private - view - returns (uint96 numberOfHolders, uint96 totalQuantity) - { - Checkpoint storage result; - assembly { - mstore(0, self.slot) - result.slot := add(keccak256(0, 0x20), pos) - } - numberOfHolders = result.numberOfHolders; - totalQuantity = result.totalQuantity; - } - - /** - * @dev Returns the average of two numbers. The result is rounded towards - * zero. - */ - function average(uint256 a, uint256 b) private pure returns (uint256) { - return (a & b) + (a ^ b) / 2; // (a + b) / 2 can overflow. - } - - /** - * @dev This was copied from Solmate v7 https://github.com/transmissions11/solmate/blob/e8f96f25d48fe702117ce76c79228ca4f20206cb/src/utils/FixedPointMathLib.sol - * @notice The math utils in solmate v7 were reviewed/audited by spearbit as part of the art gobblers audit, and are more efficient than the v6 versions. - */ - function sqrt(uint256 x) internal pure returns (uint256 z) { - assembly { - let y := x // We start y at x, which will help us make our initial estimate. - - z := 181 // The "correct" value is 1, but this saves a multiplication later. - - // This segment is to get a reasonable initial estimate for the Babylonian method. With a bad - // start, the correct # of bits increases ~linearly each iteration instead of ~quadratically. - - // We check y >= 2^(k + 8) but shift right by k bits - // each branch to ensure that if x >= 256, then y >= 256. - if iszero(lt(y, 0x10000000000000000000000000000000000)) { - y := shr(128, y) - z := shl(64, z) - } - if iszero(lt(y, 0x1000000000000000000)) { - y := shr(64, y) - z := shl(32, z) - } - if iszero(lt(y, 0x10000000000)) { - y := shr(32, y) - z := shl(16, z) - } - if iszero(lt(y, 0x1000000)) { - y := shr(16, y) - z := shl(8, z) - } - - // Goal was to get z*z*y within a small factor of x. More iterations could - // get y in a tighter range. Currently, we will have y in [256, 256*2^16). - // We ensured y >= 256 so that the relative difference between y and y+1 is small. - // That's not possible if x < 256 but we can just verify those cases exhaustively. - - // Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256. - // Correctness can be checked exhaustively for x < 256, so we assume y >= 256. - // Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps. - - // For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range - // (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256. - - // Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate - // sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18. - - // There is no overflow risk here since y < 2^136 after the first branch above. - z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181. - - // Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough. - z := shr(1, add(z, div(x, z))) - z := shr(1, add(z, div(x, z))) - z := shr(1, add(z, div(x, z))) - z := shr(1, add(z, div(x, z))) - z := shr(1, add(z, div(x, z))) - z := shr(1, add(z, div(x, z))) - z := shr(1, add(z, div(x, z))) - - // If x+1 is a perfect square, the Babylonian method cycles between - // floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor. - // See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division - // Since the ceil is rare, we save gas on the assignment and repeat division in the rare case. - // If you don't care whether the floor or ceil square root is returned, you can remove this statement. - z := sub(z, lt(div(x, z), z)) - } - } -} diff --git a/src/token-voting/LlamaERC20TokenCaster.sol b/src/token-voting/LlamaERC20TokenCaster.sol index bbbd0f0..5017f36 100644 --- a/src/token-voting/LlamaERC20TokenCaster.sol +++ b/src/token-voting/LlamaERC20TokenCaster.sol @@ -34,8 +34,8 @@ contract LlamaERC20TokenCaster is LlamaTokenCaster { ILlamaCore _llamaCore, ILlamaTokenClockAdapter _clockAdapter, uint8 _role, - uint256 _voteQuorumPct, - uint256 _vetoQuorumPct + uint16 _voteQuorumPct, + uint16 _vetoQuorumPct ) external initializer { __initializeLlamaTokenCasterMinimalProxy(_llamaCore, _clockAdapter, _role, _voteQuorumPct, _vetoQuorumPct); token = _token; diff --git a/src/token-voting/LlamaERC721TokenCaster.sol b/src/token-voting/LlamaERC721TokenCaster.sol index 6003466..52f52d0 100644 --- a/src/token-voting/LlamaERC721TokenCaster.sol +++ b/src/token-voting/LlamaERC721TokenCaster.sol @@ -35,8 +35,8 @@ contract LlamaERC721TokenCaster is LlamaTokenCaster { ILlamaCore _llamaCore, ILlamaTokenClockAdapter _clockAdapter, uint8 _role, - uint256 _voteQuorumPct, - uint256 _vetoQuorumPct + uint16 _voteQuorumPct, + uint16 _vetoQuorumPct ) external initializer { __initializeLlamaTokenCasterMinimalProxy(_llamaCore, _clockAdapter, _role, _voteQuorumPct, _vetoQuorumPct); token = _token; diff --git a/src/token-voting/LlamaTokenCaster.sol b/src/token-voting/LlamaTokenCaster.sol index 22e7229..b960f09 100644 --- a/src/token-voting/LlamaTokenCaster.sol +++ b/src/token-voting/LlamaTokenCaster.sol @@ -2,12 +2,13 @@ pragma solidity ^0.8.23; import {Initializable} from "@openzeppelin/proxy/utils/Initializable.sol"; - import {FixedPointMathLib} from "@solmate/utils/FixedPointMathLib.sol"; + import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; import {ILlamaTokenClockAdapter} from "src/token-voting/ILlamaTokenClockAdapter.sol"; import {ActionState, VoteType} from "src/lib/Enums.sol"; import {LlamaUtils} from "src/lib/LlamaUtils.sol"; +import {QuorumCheckpoints} from "src/lib/QuorumCheckpoints.sol"; import {Action, ActionInfo} from "src/lib/Structs.sol"; /// @title LlamaTokenCaster @@ -19,6 +20,7 @@ import {Action, ActionInfo} from "src/lib/Structs.sol"; /// contract does not verify that it holds the correct policy when voting and relies on `LlamaCore` to /// verify that during submission. abstract contract LlamaTokenCaster is Initializable { + using QuorumCheckpoints for QuorumCheckpoints.History; // ========================= // ======== Structs ======== // ========================= @@ -71,14 +73,17 @@ abstract contract LlamaTokenCaster is Initializable { /// @dev Thrown when a user tries to cast a vote or veto but the against surpasses for. error ForDoesNotSurpassAgainst(uint256 castsFor, uint256 castsAgainst); + /// @dev Thrown when a user tries to query checkpoints at non-existant indices. + error InvalidIndices(); + /// @dev Thrown when a user tries to submit an approval but there are not enough votes. error InsufficientVotes(uint256 votes, uint256 threshold); /// @dev Thrown when an invalid `voteQuorumPct` is passed to the constructor. - error InvalidVoteQuorumPct(uint256 voteQuorumPct); + error InvalidVoteQuorumPct(uint16 voteQuorumPct); - /// @dev Thrown when an invalid `veteQuorumPct` is passed to the constructor. - error InvalidVetoQuorumPct(uint256 veteQuorumPct); + /// @dev Thrown when an invalid `vetoQuorumPct` is passed to the constructor. + error InvalidVetoQuorumPct(uint16 vetoQuorumPct); /// @dev Thrown when an invalid `llamaCore` address is passed to the constructor. error InvalidLlamaCoreAddress(); @@ -92,6 +97,9 @@ abstract contract LlamaTokenCaster is Initializable { /// @dev Thrown when an invalid `support` value is used when casting. error InvalidSupport(uint8 support); + /// @dev Thrown when an address other than the `LlamaExecutor` tries to call a function. + error OnlyLlamaExecutor(); + /// @dev Thrown when an invalid `role` is passed to the constructor. error RoleNotInitialized(uint8 role); @@ -119,7 +127,7 @@ abstract contract LlamaTokenCaster is Initializable { ); /// @dev Emitted when the voting quorum and/or vetoing quorum is set. - event QuorumSet(uint256 voteQuorumPct, uint256 vetoQuorumPct); + event QuorumSet(uint16 voteQuorumPct, uint16 vetoQuorumPct); // ================================================= // ======== Constants and Storage Variables ======== @@ -156,15 +164,11 @@ abstract contract LlamaTokenCaster is Initializable { /// @notice The core contract for this Llama instance. ILlamaCore public llamaCore; + QuorumCheckpoints.History internal quorumCheckpoints; + /// @notice The contract that manages the timepoints for this token voting module. ILlamaTokenClockAdapter public clockAdapter; - /// @notice The minimum % of approvals required to submit approvals to `LlamaCore`. - uint256 public voteQuorumPct; - - /// @notice The minimum % of disapprovals required to submit disapprovals to `LlamaCore`. - uint256 public vetoQuorumPct; - /// @notice The role used by this contract to cast approvals and disapprovals. /// @dev This role is expected to have the ability to force approve and disapprove actions. uint8 public role; @@ -190,21 +194,16 @@ abstract contract LlamaTokenCaster is Initializable { ILlamaCore _llamaCore, ILlamaTokenClockAdapter _clockAdapter, uint8 _role, - uint256 _voteQuorumPct, - uint256 _vetoQuorumPct + uint16 _voteQuorumPct, + uint16 _vetoQuorumPct ) internal { if (_llamaCore.actionsCount() < 0) revert InvalidLlamaCoreAddress(); if (_role > _llamaCore.policy().numRoles()) revert RoleNotInitialized(_role); - if (_voteQuorumPct > ONE_HUNDRED_IN_BPS || _voteQuorumPct <= 0) revert InvalidVoteQuorumPct(_voteQuorumPct); - if (_vetoQuorumPct > ONE_HUNDRED_IN_BPS || _vetoQuorumPct <= 0) revert InvalidVetoQuorumPct(_vetoQuorumPct); llamaCore = _llamaCore; clockAdapter = _clockAdapter; role = _role; - voteQuorumPct = _voteQuorumPct; - vetoQuorumPct = _vetoQuorumPct; - - emit QuorumSet(_voteQuorumPct, _vetoQuorumPct); + _setQuorumPct(_voteQuorumPct, _vetoQuorumPct); } // =========================================== @@ -289,6 +288,7 @@ abstract contract LlamaTokenCaster is Initializable { uint96 votesFor = casts[actionInfo.id].votesFor; uint96 votesAgainst = casts[actionInfo.id].votesAgainst; uint96 votesAbstain = casts[actionInfo.id].votesAbstain; + (uint16 voteQuorumPct,) = quorumCheckpoints.getAtProbablyRecentTimestamp(action.creationTime - 1); uint256 threshold = FixedPointMathLib.mulDivUp(totalSupply, voteQuorumPct, ONE_HUNDRED_IN_BPS); if (votesFor < threshold) revert InsufficientVotes(votesFor, threshold); if (votesFor <= votesAgainst) revert ForDoesNotSurpassAgainst(votesFor, votesAgainst); @@ -320,6 +320,7 @@ abstract contract LlamaTokenCaster is Initializable { uint96 vetoesFor = casts[actionInfo.id].vetoesFor; uint96 vetoesAgainst = casts[actionInfo.id].vetoesAgainst; uint96 vetoesAbstain = casts[actionInfo.id].vetoesAbstain; + (, uint16 vetoQuorumPct) = quorumCheckpoints.getAtProbablyRecentTimestamp(action.creationTime - 1); uint256 threshold = FixedPointMathLib.mulDivUp(totalSupply, vetoQuorumPct, ONE_HUNDRED_IN_BPS); if (vetoesFor < threshold) revert InsufficientVotes(vetoesFor, threshold); if (vetoesFor <= vetoesAgainst) revert ForDoesNotSurpassAgainst(vetoesFor, vetoesAgainst); @@ -329,6 +330,16 @@ abstract contract LlamaTokenCaster is Initializable { emit DisapprovalSubmitted(actionInfo.id, msg.sender, vetoesFor, vetoesAgainst, vetoesAbstain); } + // -------- Instance Management -------- + + /// @notice Sets the voting quorum and vetoing quorum. + /// @param _voteQuorumPct The minimum % of votes required to submit an approval to `LlamaCore`. + /// @param _vetoQuorumPct The minimum % of vetoes required to submit a disapproval to `LlamaCore`. + function setQuorumPct(uint16 _voteQuorumPct, uint16 _vetoQuorumPct) external { + if (msg.sender != llamaCore.executor()) revert OnlyLlamaExecutor(); + _setQuorumPct(_voteQuorumPct, _vetoQuorumPct); + } + // -------- User Nonce Management -------- /// @notice Increments the caller's nonce for the given `selector`. This is useful for revoking @@ -339,6 +350,28 @@ abstract contract LlamaTokenCaster is Initializable { nonces[msg.sender][selector] = LlamaUtils.uncheckedIncrement(nonces[msg.sender][selector]); } + // -------- Getters -------- + /// @notice Returns the current voting quorum and vetoing quorum. + function getQuorum() external view returns (uint16 voteQuorumPct, uint16 vetoQuorumPct) { + return quorumCheckpoints.latest(); + } + + function getPastQuorum(uint256 timestamp) external view returns (uint16 voteQuorumPct, uint16 vetoQuorumPct) { + return quorumCheckpoints.getAtProbablyRecentTimestamp(timestamp); + } + + function getQuorumCheckpoints(uint256 start, uint256 end) external view returns (QuorumCheckpoints.History memory) { + if (start > end) revert InvalidIndices(); + uint256 checkpointsLength = quorumCheckpoints._checkpoints.length; + if (end > checkpointsLength) revert InvalidIndices(); + uint256 sliceLength = end - start; + QuorumCheckpoints.Checkpoint[] memory checkpoints = new QuorumCheckpoints.Checkpoint[](sliceLength); + for (uint256 i = start; i < end; i = LlamaUtils.uncheckedIncrement(i)) { + checkpoints[i - start] = quorumCheckpoints._checkpoints[i]; + } + return QuorumCheckpoints.History(checkpoints); + } + // ================================ // ======== Internal Logic ======== // ================================ @@ -401,6 +434,14 @@ abstract contract LlamaTokenCaster is Initializable { _isClockModeSupported(); // reverts if clock mode is not supported } + /// @dev Sets the voting quorum and vetoing quorum. + function _setQuorumPct(uint16 _voteQuorumPct, uint16 _vetoQuorumPct) internal { + if (_voteQuorumPct > ONE_HUNDRED_IN_BPS || _voteQuorumPct <= 0) revert InvalidVoteQuorumPct(_voteQuorumPct); + if (_vetoQuorumPct > ONE_HUNDRED_IN_BPS || _vetoQuorumPct <= 0) revert InvalidVetoQuorumPct(_vetoQuorumPct); + quorumCheckpoints.push(_voteQuorumPct, _vetoQuorumPct); + emit QuorumSet(_voteQuorumPct, _vetoQuorumPct); + } + /// @dev reverts if the clock mode is not supported function _isClockModeSupported() internal view { if (!_isClockModeTimestamp()) { diff --git a/src/token-voting/LlamaTokenVotingFactory.sol b/src/token-voting/LlamaTokenVotingFactory.sol index 20c91c8..1973642 100644 --- a/src/token-voting/LlamaTokenVotingFactory.sol +++ b/src/token-voting/LlamaTokenVotingFactory.sol @@ -75,8 +75,8 @@ contract LlamaTokenVotingFactory { uint8 actionCreatorRole, uint8 casterRole, uint256 creationThreshold, - uint256 voteQuorumPct, - uint256 vetoQuorumPct + uint16 voteQuorumPct, + uint16 vetoQuorumPct ) external returns (address actionCreator, address caster) { if (isERC20) { actionCreator = address( @@ -164,8 +164,8 @@ contract LlamaTokenVotingFactory { ILlamaTokenClockAdapter clockAdapter, uint256 nonce, uint8 role, - uint256 voteQuorumPct, - uint256 vetoQuorumPct + uint16 voteQuorumPct, + uint16 vetoQuorumPct ) internal returns (LlamaERC20TokenCaster caster) { caster = LlamaERC20TokenCaster( Clones.cloneDeterministic( @@ -183,8 +183,8 @@ contract LlamaTokenVotingFactory { ILlamaTokenClockAdapter clockAdapter, uint256 nonce, uint8 role, - uint256 voteQuorumPct, - uint256 vetoQuorumPct + uint16 voteQuorumPct, + uint16 vetoQuorumPct ) internal returns (LlamaERC721TokenCaster caster) { caster = LlamaERC721TokenCaster( Clones.cloneDeterministic( diff --git a/test/token-voting/LlamaERC20TokenCaster.t.sol b/test/token-voting/LlamaERC20TokenCaster.t.sol index ffdf4f5..d35ae8a 100644 --- a/test/token-voting/LlamaERC20TokenCaster.t.sol +++ b/test/token-voting/LlamaERC20TokenCaster.t.sol @@ -25,7 +25,7 @@ contract LlamaERC20TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUti event DisapprovalSubmitted( uint256 id, address indexed caller, uint256 weightFor, uint256 weightAgainst, uint256 weightAbstain ); - event QuorumSet(uint256 voteQuorumPct, uint256 vetoQuorumPct); + event QuorumSet(uint16 voteQuorumPct, uint16 vetoQuorumPct); ActionInfo actionInfo; LlamaERC20TokenCaster llamaERC20TokenCaster; @@ -712,3 +712,34 @@ contract SubmitDisapprovals is LlamaERC20TokenCasterTest { llamaERC20TokenCaster.submitDisapproval(actionInfo); } } + +contract SetQuorumPct is LlamaERC20TokenCasterTest { + function test_RevertsIf_NotLlamaExecutor(address notLlamaExecutor) public { + vm.assume(notLlamaExecutor != address(EXECUTOR)); + vm.expectRevert(LlamaTokenCaster.OnlyLlamaExecutor.selector); + vm.prank(notLlamaExecutor); + llamaERC20TokenCaster.setQuorumPct(ERC20_VOTE_QUORUM_PCT, ERC20_VETO_QUORUM_PCT); + } + + function test_RevertsIf_InvalidQuorumPct() public { + vm.startPrank(address(EXECUTOR)); + vm.expectRevert(abi.encodeWithSelector(LlamaTokenCaster.InvalidVetoQuorumPct.selector, uint256(0))); + llamaERC20TokenCaster.setQuorumPct(ERC20_VOTE_QUORUM_PCT, 0); + vm.expectRevert(abi.encodeWithSelector(LlamaTokenCaster.InvalidVoteQuorumPct.selector, uint256(0))); + llamaERC20TokenCaster.setQuorumPct(0, ERC20_VETO_QUORUM_PCT); + vm.expectRevert(abi.encodeWithSelector(LlamaTokenCaster.InvalidVetoQuorumPct.selector, uint256(10_001))); + llamaERC20TokenCaster.setQuorumPct(ERC20_VOTE_QUORUM_PCT, 10_001); + vm.expectRevert(abi.encodeWithSelector(LlamaTokenCaster.InvalidVoteQuorumPct.selector, uint256(10_001))); + llamaERC20TokenCaster.setQuorumPct(10_001, ERC20_VETO_QUORUM_PCT); + vm.stopPrank(); + } + + function test_SetsQuorumPctCorrectly(uint16 _voteQuorum, uint16 _vetoQuorum) public { + _voteQuorum = uint16(bound(_voteQuorum, 1, ONE_HUNDRED_IN_BPS)); + _vetoQuorum = uint16(bound(_vetoQuorum, 1, ONE_HUNDRED_IN_BPS)); + vm.expectEmit(); + emit QuorumSet(_voteQuorum, _vetoQuorum); + vm.prank(address(EXECUTOR)); + llamaERC20TokenCaster.setQuorumPct(_voteQuorum, _vetoQuorum); + } +} diff --git a/test/token-voting/LlamaERC721TokenCaster.t.sol b/test/token-voting/LlamaERC721TokenCaster.t.sol index e1aa0d0..1fcb661 100644 --- a/test/token-voting/LlamaERC721TokenCaster.t.sol +++ b/test/token-voting/LlamaERC721TokenCaster.t.sol @@ -25,7 +25,7 @@ contract LlamaERC721TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUt event DisapprovalSubmitted( uint256 id, address indexed caller, uint256 weightFor, uint256 weightAgainst, uint256 weightAbstain ); - event QuorumSet(uint256 voteQuorumPct, uint256 vetoQuorumPct); + event QuorumSet(uint16 voteQuorumPct, uint16 vetoQuorumPct); ActionInfo actionInfo; LlamaERC721TokenCaster llamaERC721TokenCaster; @@ -714,3 +714,34 @@ contract SubmitDisapprovals is LlamaERC721TokenCasterTest { llamaERC721TokenCaster.submitDisapproval(actionInfo); } } + +contract SetQuorumPct is LlamaERC721TokenCasterTest { + function test_RevertsIf_NotLlamaExecutor(address notLlamaExecutor) public { + vm.assume(notLlamaExecutor != address(EXECUTOR)); + vm.expectRevert(LlamaTokenCaster.OnlyLlamaExecutor.selector); + vm.prank(notLlamaExecutor); + llamaERC721TokenCaster.setQuorumPct(ERC20_VOTE_QUORUM_PCT, ERC20_VETO_QUORUM_PCT); + } + + function test_RevertsIf_InvalidQuorumPct() public { + vm.startPrank(address(EXECUTOR)); + vm.expectRevert(abi.encodeWithSelector(LlamaTokenCaster.InvalidVetoQuorumPct.selector, uint256(0))); + llamaERC721TokenCaster.setQuorumPct(ERC20_VOTE_QUORUM_PCT, 0); + vm.expectRevert(abi.encodeWithSelector(LlamaTokenCaster.InvalidVoteQuorumPct.selector, uint256(0))); + llamaERC721TokenCaster.setQuorumPct(0, ERC20_VETO_QUORUM_PCT); + vm.expectRevert(abi.encodeWithSelector(LlamaTokenCaster.InvalidVetoQuorumPct.selector, uint256(10_001))); + llamaERC721TokenCaster.setQuorumPct(ERC20_VOTE_QUORUM_PCT, 10_001); + vm.expectRevert(abi.encodeWithSelector(LlamaTokenCaster.InvalidVoteQuorumPct.selector, uint256(10_001))); + llamaERC721TokenCaster.setQuorumPct(10_001, ERC20_VETO_QUORUM_PCT); + vm.stopPrank(); + } + + function test_SetsQuorumPctCorrectly(uint16 _voteQuorum, uint16 _vetoQuorum) public { + _voteQuorum = uint16(bound(_voteQuorum, 1, ONE_HUNDRED_IN_BPS)); + _vetoQuorum = uint16(bound(_vetoQuorum, 1, ONE_HUNDRED_IN_BPS)); + vm.expectEmit(); + emit QuorumSet(_voteQuorum, _vetoQuorum); + vm.prank(address(EXECUTOR)); + llamaERC721TokenCaster.setQuorumPct(_voteQuorum, _vetoQuorum); + } +} diff --git a/test/token-voting/LlamaTokenVotingFactory.t.sol b/test/token-voting/LlamaTokenVotingFactory.t.sol index 2360b0c..5c97cdc 100644 --- a/test/token-voting/LlamaTokenVotingFactory.t.sol +++ b/test/token-voting/LlamaTokenVotingFactory.t.sol @@ -32,7 +32,7 @@ contract LlamaTokenVotingFactoryTest is LlamaTokenVotingTestSetup { uint256 chainId ); event ActionThresholdSet(uint256 newThreshold); - event QuorumSet(uint256 voteQuorumPct, uint256 vetoQuorumPct); + event QuorumSet(uint16 voteQuorumPct, uint16 vetoQuorumPct); function setUp() public override { LlamaTokenVotingTestSetup.setUp(); @@ -146,13 +146,14 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { assertEq(address(llamaERC20TokenActionCreator.token()), address(erc20VotesToken)); assertEq(address(llamaERC20TokenActionCreator.llamaCore()), address(CORE)); + (uint16 voteQuorumPct, uint16 vetoQuorumPct) = llamaERC20TokenCaster.getQuorum(); + assertEq(ERC20_VOTE_QUORUM_PCT, voteQuorumPct); + assertEq(ERC20_VETO_QUORUM_PCT, vetoQuorumPct); assertEq(llamaERC20TokenActionCreator.role(), tokenVotingActionCreatorRole); assertEq(llamaERC20TokenActionCreator.creationThreshold(), ERC20_CREATION_THRESHOLD); assertEq(address(llamaERC20TokenCaster.token()), address(erc20VotesToken)); assertEq(address(llamaERC20TokenCaster.llamaCore()), address(CORE)); assertEq(llamaERC20TokenCaster.role(), tokenVotingCasterRole); - assertEq(llamaERC20TokenCaster.voteQuorumPct(), ERC20_VOTE_QUORUM_PCT); - assertEq(llamaERC20TokenCaster.vetoQuorumPct(), ERC20_VETO_QUORUM_PCT); } function test_CanDeployERC721TokenVotingModule() public { @@ -211,13 +212,14 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { assertEq(address(llamaERC721TokenActionCreator.token()), address(erc721VotesToken)); assertEq(address(llamaERC721TokenActionCreator.llamaCore()), address(CORE)); + (uint16 voteQuorumPct, uint16 vetoQuorumPct) = llamaERC721TokenCaster.getQuorum(); + assertEq(ERC721_VOTE_QUORUM_PCT, voteQuorumPct); + assertEq(ERC721_VETO_QUORUM_PCT, vetoQuorumPct); assertEq(llamaERC721TokenActionCreator.role(), tokenVotingActionCreatorRole); assertEq(llamaERC721TokenActionCreator.creationThreshold(), ERC721_CREATION_THRESHOLD); assertEq(address(llamaERC721TokenCaster.token()), address(erc721VotesToken)); assertEq(address(llamaERC721TokenCaster.llamaCore()), address(CORE)); assertEq(llamaERC721TokenCaster.role(), tokenVotingCasterRole); - assertEq(llamaERC721TokenCaster.voteQuorumPct(), ERC721_VOTE_QUORUM_PCT); - assertEq(llamaERC721TokenCaster.vetoQuorumPct(), ERC721_VETO_QUORUM_PCT); } function test_CanBeDeployedByAnyone(address randomCaller) public { @@ -275,13 +277,14 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { assertEq(address(llamaERC20TokenActionCreator.token()), address(erc20VotesToken)); assertEq(address(llamaERC20TokenActionCreator.llamaCore()), address(CORE)); + (uint16 voteQuorumPct, uint16 vetoQuorumPct) = llamaERC20TokenCaster.getQuorum(); + assertEq(ERC20_VOTE_QUORUM_PCT, voteQuorumPct); + assertEq(ERC20_VETO_QUORUM_PCT, vetoQuorumPct); assertEq(llamaERC20TokenActionCreator.role(), tokenVotingActionCreatorRole); assertEq(llamaERC20TokenActionCreator.creationThreshold(), ERC20_CREATION_THRESHOLD); assertEq(address(llamaERC20TokenCaster.token()), address(erc20VotesToken)); assertEq(address(llamaERC20TokenCaster.llamaCore()), address(CORE)); assertEq(llamaERC20TokenCaster.role(), tokenVotingCasterRole); - assertEq(llamaERC20TokenCaster.voteQuorumPct(), ERC20_VOTE_QUORUM_PCT); - assertEq(llamaERC20TokenCaster.vetoQuorumPct(), ERC20_VETO_QUORUM_PCT); } function test_CanBeDeployedMoreThanOnceBySameDeployer() public { diff --git a/test/token-voting/LlamaTokenVotingTestSetup.sol b/test/token-voting/LlamaTokenVotingTestSetup.sol index f45e679..19786eb 100644 --- a/test/token-voting/LlamaTokenVotingTestSetup.sol +++ b/test/token-voting/LlamaTokenVotingTestSetup.sol @@ -29,13 +29,13 @@ contract LlamaTokenVotingTestSetup is LlamaPeripheryTestSetup, DeployLlamaTokenV // ERC20 Token Voting Constants. uint256 public constant ERC20_CREATION_THRESHOLD = 500_000e18; - uint256 public constant ERC20_VOTE_QUORUM_PCT = 1000; - uint256 public constant ERC20_VETO_QUORUM_PCT = 1000; + uint16 public constant ERC20_VOTE_QUORUM_PCT = 1000; + uint16 public constant ERC20_VETO_QUORUM_PCT = 1000; // ERC721 Token Voting Constants. uint256 public constant ERC721_CREATION_THRESHOLD = 1; - uint256 public constant ERC721_VOTE_QUORUM_PCT = 1000; - uint256 public constant ERC721_VETO_QUORUM_PCT = 1000; + uint16 public constant ERC721_VOTE_QUORUM_PCT = 1000; + uint16 public constant ERC721_VETO_QUORUM_PCT = 1000; // When deploying a token-voting module with timestamp checkpointing on the token, we pass in address(0) for the clock // adapter.