From 92638332b4f45a85738c0565576bd24b8ab36b8b Mon Sep 17 00:00:00 2001 From: Austin Green Date: Thu, 14 Dec 2023 14:36:19 -0500 Subject: [PATCH 1/2] fix: standardize `LlamaTokenCaster` functions (#58) **Motivation:** This PR adapts `LlamaTokenCaster` to be more consistent with existing token voting frameworks **Modifications:** - Removed unnecessary boolean return and just returned the statement in `_isClockModeTimestamp` - Removed check for 0 weight when casting (for example Nouns explicitly encourages holders with 0 tokens to vote) - Renamed quantity to weight - Returned weight from castVote and castVeto - Only use uint96 for weight and not uint256. I couldn't see the reason why not to do this but let me know if I missed something. **Result:** Closes #56 --- src/token-voting/LlamaTokenActionCreator.sol | 3 +- src/token-voting/LlamaTokenCaster.sol | 76 ++++++++++--------- test/token-voting/LlamaERC20TokenCaster.t.sol | 30 ++++---- .../token-voting/LlamaERC721TokenCaster.t.sol | 30 ++++---- 4 files changed, 73 insertions(+), 66 deletions(-) diff --git a/src/token-voting/LlamaTokenActionCreator.sol b/src/token-voting/LlamaTokenActionCreator.sol index 1ddd3ac..853b68f 100644 --- a/src/token-voting/LlamaTokenActionCreator.sol +++ b/src/token-voting/LlamaTokenActionCreator.sol @@ -284,8 +284,7 @@ abstract contract LlamaTokenActionCreator is Initializable { // Returns true if the clock mode is timestamp function _isClockModeTimestamp() internal view returns (bool) { string memory clockMode = _getClockMode(); - if (keccak256(abi.encodePacked(clockMode)) == keccak256(abi.encodePacked("mode=timestamp"))) return true; - return false; + return keccak256(abi.encodePacked(clockMode)) == keccak256(abi.encodePacked("mode=timestamp")); } /// @dev Returns the number of votes for a given token holder at a given timestamp. diff --git a/src/token-voting/LlamaTokenCaster.sol b/src/token-voting/LlamaTokenCaster.sol index cd91e26..9b877bb 100644 --- a/src/token-voting/LlamaTokenCaster.sol +++ b/src/token-voting/LlamaTokenCaster.sol @@ -74,9 +74,6 @@ abstract contract LlamaTokenCaster is Initializable { /// @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 a user tries to cast but does not have enough tokens. - error InsufficientBalance(uint256 balance); - /// @dev Thrown when an invalid `voteQuorumPct` is passed to the constructor. error InvalidVoteQuorumPct(uint256 voteQuorumPct); @@ -106,19 +103,19 @@ abstract contract LlamaTokenCaster is Initializable { // ======================== /// @dev Emitted when a vote is cast. - event VoteCast(uint256 id, address indexed tokenholder, uint8 indexed support, uint256 quantity, string reason); + event VoteCast(uint256 id, address indexed tokenholder, uint8 indexed support, uint256 weight, string reason); /// @dev Emitted when a cast approval is submitted to the `LlamaCore` contract. event ApprovalSubmitted( - uint256 id, address indexed caller, uint96 quantityFor, uint96 quantityAgainst, uint96 quantityAbstain + uint256 id, address indexed caller, uint256 weightFor, uint256 weightAgainst, uint256 weightAbstain ); /// @dev Emitted when a veto is cast. - event VetoCast(uint256 id, address indexed tokenholder, uint8 indexed support, uint256 quantity, string reason); + event VetoCast(uint256 id, address indexed tokenholder, uint8 indexed support, uint256 weight, string reason); /// @dev Emitted when a cast disapproval is submitted to the `LlamaCore` contract. event DisapprovalSubmitted( - uint256 id, address indexed caller, uint96 quantityFor, uint96 quantityAgainst, uint96 quantityAbstain + uint256 id, address indexed caller, uint256 weightFor, uint256 weightAgainst, uint256 weightAbstain ); /// @dev Emitted when the voting quorum and/or vetoing quorum is set. @@ -223,8 +220,8 @@ abstract contract LlamaTokenCaster is Initializable { /// 1 = For /// 2 = Abstain, but this is not currently supported. /// @param reason The reason given for the approval by the tokenholder. - function castVote(ActionInfo calldata actionInfo, uint8 support, string calldata reason) external { - _castVote(msg.sender, actionInfo, support, reason); + function castVote(ActionInfo calldata actionInfo, uint8 support, string calldata reason) external returns (uint96) { + return _castVote(msg.sender, actionInfo, support, reason); } function castVoteBySig( @@ -235,11 +232,11 @@ abstract contract LlamaTokenCaster is Initializable { uint8 v, bytes32 r, bytes32 s - ) external { + ) external returns (uint96) { bytes32 digest = _getCastVoteTypedDataHash(caster, support, actionInfo, reason); address signer = ecrecover(digest, v, r, s); if (signer == address(0) || signer != caster) revert InvalidSignature(); - _castVote(signer, actionInfo, support, reason); + return _castVote(signer, actionInfo, support, reason); } /// @notice How tokenholders add their support of the disapproval of an action with a reason. @@ -250,8 +247,8 @@ abstract contract LlamaTokenCaster is Initializable { /// 1 = For /// 2 = Abstain, but this is not currently supported. /// @param reason The reason given for the approval by the tokenholder. - function castVeto(ActionInfo calldata actionInfo, uint8 support, string calldata reason) external { - _castVeto(msg.sender, actionInfo, support, reason); + function castVeto(ActionInfo calldata actionInfo, uint8 support, string calldata reason) external returns (uint96) { + return _castVeto(msg.sender, actionInfo, support, reason); } function castVetoBySig( @@ -262,11 +259,11 @@ abstract contract LlamaTokenCaster is Initializable { uint8 v, bytes32 r, bytes32 s - ) external { + ) external returns (uint96) { bytes32 digest = _getCastVetoTypedDataHash(caster, support, actionInfo, reason); address signer = ecrecover(digest, v, r, s); if (signer == address(0) || signer != caster) revert InvalidSignature(); - _castVeto(signer, actionInfo, support, reason); + return _castVeto(signer, actionInfo, support, reason); } /// @notice Submits a cast approval to the `LlamaCore` contract. @@ -287,7 +284,7 @@ abstract contract LlamaTokenCaster is Initializable { _isClockModeSupported(); // reverts if clock mode is not supported - uint256 totalSupply = _getPastTotalSupply(_timestampToTimepoint(action.creationTime - 1)); + uint256 totalSupply = _getPastTotalSupply(_timestampToTimepoint(action.creationTime) - 1); uint96 votesFor = casts[actionInfo.id].votesFor; uint96 votesAgainst = casts[actionInfo.id].votesAgainst; uint96 votesAbstain = casts[actionInfo.id].votesAbstain; @@ -318,7 +315,7 @@ abstract contract LlamaTokenCaster is Initializable { _isClockModeSupported(); // reverts if clock mode is not supported - uint256 totalSupply = _getPastTotalSupply(_timestampToTimepoint(action.creationTime - 1)); + uint256 totalSupply = _getPastTotalSupply(_timestampToTimepoint(action.creationTime) - 1); uint96 vetoesFor = casts[actionInfo.id].vetoesFor; uint96 vetoesAgainst = casts[actionInfo.id].vetoesAgainst; uint96 vetoesAbstain = casts[actionInfo.id].vetoesAbstain; @@ -345,7 +342,10 @@ abstract contract LlamaTokenCaster is Initializable { // ======== Internal Logic ======== // ================================ - function _castVote(address caster, ActionInfo calldata actionInfo, uint8 support, string calldata reason) internal { + function _castVote(address caster, ActionInfo calldata actionInfo, uint8 support, string calldata reason) + internal + returns (uint96) + { Action memory action = llamaCore.getAction(actionInfo.id); actionInfo.strategy.checkIfApprovalEnabled(actionInfo, address(this), role); // Reverts if not allowed. @@ -356,17 +356,22 @@ abstract contract LlamaTokenCaster is Initializable { > action.creationTime + (actionInfo.strategy.approvalPeriod() * TWO_THIRDS_IN_BPS) / ONE_HUNDRED_IN_BPS ) revert CastingPeriodOver(); - uint256 balance = _getPastVotes(caster, _timestampToTimepoint(action.creationTime - 1)); - _preCastAssertions(balance, support); + uint96 weight = LlamaUtils.toUint96(_getPastVotes(caster, _timestampToTimepoint(action.creationTime) - 1)); + _preCastAssertions(support); - if (support == uint8(VoteType.Against)) casts[actionInfo.id].votesAgainst += LlamaUtils.toUint96(balance); - else if (support == uint8(VoteType.For)) casts[actionInfo.id].votesFor += LlamaUtils.toUint96(balance); - else if (support == uint8(VoteType.Abstain)) casts[actionInfo.id].votesAbstain += LlamaUtils.toUint96(balance); + if (support == uint8(VoteType.Against)) casts[actionInfo.id].votesAgainst += weight; + else if (support == uint8(VoteType.For)) casts[actionInfo.id].votesFor += weight; + else if (support == uint8(VoteType.Abstain)) casts[actionInfo.id].votesAbstain += weight; casts[actionInfo.id].castVote[caster] = true; - emit VoteCast(actionInfo.id, caster, support, balance, reason); + emit VoteCast(actionInfo.id, caster, support, weight, reason); + + return weight; } - function _castVeto(address caster, ActionInfo calldata actionInfo, uint8 support, string calldata reason) internal { + function _castVeto(address caster, ActionInfo calldata actionInfo, uint8 support, string calldata reason) + internal + returns (uint96) + { Action memory action = llamaCore.getAction(actionInfo.id); actionInfo.strategy.checkIfDisapprovalEnabled(actionInfo, address(this), role); // Reverts if not allowed. @@ -377,22 +382,22 @@ abstract contract LlamaTokenCaster is Initializable { > action.minExecutionTime - (actionInfo.strategy.queuingPeriod() * ONE_THIRD_IN_BPS) / ONE_HUNDRED_IN_BPS ) revert CastingPeriodOver(); - uint256 balance = _getPastVotes(caster, _timestampToTimepoint(action.creationTime - 1)); - _preCastAssertions(balance, support); + uint96 weight = LlamaUtils.toUint96(_getPastVotes(caster, _timestampToTimepoint(action.creationTime) - 1)); + _preCastAssertions(support); - if (support == uint8(VoteType.Against)) casts[actionInfo.id].vetoesAgainst += LlamaUtils.toUint96(balance); - else if (support == uint8(VoteType.For)) casts[actionInfo.id].vetoesFor += LlamaUtils.toUint96(balance); - else if (support == uint8(VoteType.Abstain)) casts[actionInfo.id].vetoesAbstain += LlamaUtils.toUint96(balance); + if (support == uint8(VoteType.Against)) casts[actionInfo.id].vetoesAgainst += weight; + else if (support == uint8(VoteType.For)) casts[actionInfo.id].vetoesFor += weight; + else if (support == uint8(VoteType.Abstain)) casts[actionInfo.id].vetoesAbstain += weight; casts[actionInfo.id].castVeto[caster] = true; - emit VetoCast(actionInfo.id, caster, support, balance, reason); + emit VetoCast(actionInfo.id, caster, support, weight, reason); + + return weight; } - function _preCastAssertions(uint256 balance, uint8 support) internal view { + function _preCastAssertions(uint8 support) internal view { if (support > uint8(VoteType.Abstain)) revert InvalidSupport(support); _isClockModeSupported(); // reverts if clock mode is not supported - - if (balance == 0) revert InsufficientBalance(balance); } /// @dev reverts if the clock mode is not supported @@ -419,8 +424,7 @@ abstract contract LlamaTokenCaster is Initializable { /// @dev Returns true if the clock mode is timestamp. function _isClockModeTimestamp() internal view returns (bool) { string memory clockMode = _getClockMode(); - if (keccak256(abi.encodePacked(clockMode)) == keccak256(abi.encodePacked("mode=timestamp"))) return true; - return false; + return keccak256(abi.encodePacked(clockMode)) == keccak256(abi.encodePacked("mode=timestamp")); } /// @dev Returns the number of votes for a given token holder at a given timestamp. diff --git a/test/token-voting/LlamaERC20TokenCaster.t.sol b/test/token-voting/LlamaERC20TokenCaster.t.sol index 32ad755..ffdf4f5 100644 --- a/test/token-voting/LlamaERC20TokenCaster.t.sol +++ b/test/token-voting/LlamaERC20TokenCaster.t.sol @@ -17,13 +17,13 @@ import {LlamaERC20TokenCaster} from "src/token-voting/LlamaERC20TokenCaster.sol" import {LlamaTokenCaster} from "src/token-voting/LlamaTokenCaster.sol"; contract LlamaERC20TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUtils { - event VoteCast(uint256 id, address indexed tokenholder, uint8 indexed support, uint256 quantity, string reason); + event VoteCast(uint256 id, address indexed tokenholder, uint8 indexed support, uint256 weight, string reason); event ApprovalSubmitted( - uint256 id, address indexed caller, uint96 quantityFor, uint96 quantityAgainst, uint96 quantityAbstain + uint256 id, address indexed caller, uint256 weightFor, uint256 weightAgainst, uint256 weightAbstain ); - event VetoCast(uint256 id, address indexed tokenholder, uint8 indexed support, uint256 quantity, string reason); + event VetoCast(uint256 id, address indexed tokenholder, uint8 indexed support, uint256 weight, string reason); event DisapprovalSubmitted( - uint256 id, address indexed caller, uint96 quantityFor, uint96 quantityAgainst, uint96 quantityAbstain + uint256 id, address indexed caller, uint256 weightFor, uint256 weightAgainst, uint256 weightAbstain ); event QuorumSet(uint256 voteQuorumPct, uint256 vetoQuorumPct); @@ -198,12 +198,13 @@ contract CastVote is LlamaERC20TokenCasterTest { llamaERC20TokenCaster.castVote(actionInfo, uint8(VoteType.For), ""); } - function test_RevertsIf_InsufficientBalance() public { - vm.expectRevert(abi.encodeWithSelector(LlamaTokenCaster.InsufficientBalance.selector, 0)); + function test_CanCastWithWeightZero() public { + vm.expectEmit(); + emit VoteCast(actionInfo.id, address(this), uint8(VoteType.For), 0, ""); llamaERC20TokenCaster.castVote(actionInfo, uint8(VoteType.For), ""); } - function test_CastsApprovalCorrectly(uint8 support) public { + function test_CastsVoteCorrectly(uint8 support) public { support = uint8(bound(support, uint8(VoteType.Against), uint8(VoteType.Against))); vm.expectEmit(); emit VoteCast( @@ -213,7 +214,7 @@ contract CastVote is LlamaERC20TokenCasterTest { llamaERC20TokenCaster.castVote(actionInfo, support, ""); } - function test_CastsApprovalCorrectly_WithReason() public { + function test_CastsVoteCorrectly_WithReason() public { vm.expectEmit(); emit VoteCast( actionInfo.id, @@ -252,7 +253,7 @@ contract CastVoteBySig is LlamaERC20TokenCasterTest { llamaERC20TokenCaster.castVoteBySig(tokenHolder1, support, _actionInfo, "", v, r, s); } - function test_CastsApprovalBySig() public { + function test_CastsVoteBySig() public { (uint8 v, bytes32 r, bytes32 s) = createOffchainSignature(actionInfo, tokenHolder1PrivateKey); vm.expectEmit(); @@ -378,12 +379,13 @@ contract CastVeto is LlamaERC20TokenCasterTest { llamaERC20TokenCaster.castVeto(actionInfo, uint8(VoteType.For), ""); } - function test_RevertsIf_InsufficientBalance() public { - vm.expectRevert(abi.encodeWithSelector(LlamaTokenCaster.InsufficientBalance.selector, 0)); + function test_CanCastWithWeightZero() public { + vm.expectEmit(); + emit VetoCast(actionInfo.id, address(this), uint8(VoteType.For), 0, ""); llamaERC20TokenCaster.castVeto(actionInfo, uint8(VoteType.For), ""); } - function test_CastsDisapprovalCorrectly(uint8 support) public { + function test_CastsVetoCorrectly(uint8 support) public { support = uint8(bound(support, uint8(VoteType.Against), uint8(VoteType.Abstain))); vm.expectEmit(); emit VetoCast( @@ -393,7 +395,7 @@ contract CastVeto is LlamaERC20TokenCasterTest { llamaERC20TokenCaster.castVeto(actionInfo, support, ""); } - function test_CastsDisapprovalCorrectly_WithReason() public { + function test_CastsVetoCorrectly_WithReason() public { vm.expectEmit(); emit VetoCast( actionInfo.id, @@ -439,7 +441,7 @@ contract CastVetoBySig is LlamaERC20TokenCasterTest { llamaERC20TokenCaster.castVetoBySig(tokenHolder1, uint8(VoteType.For), _actionInfo, "", v, r, s); } - function test_CastsDisapprovalBySig() public { + function test_CastsVetoBySig() public { (uint8 v, bytes32 r, bytes32 s) = createOffchainSignature(actionInfo, tokenHolder1PrivateKey); vm.expectEmit(); diff --git a/test/token-voting/LlamaERC721TokenCaster.t.sol b/test/token-voting/LlamaERC721TokenCaster.t.sol index 51316a1..e1aa0d0 100644 --- a/test/token-voting/LlamaERC721TokenCaster.t.sol +++ b/test/token-voting/LlamaERC721TokenCaster.t.sol @@ -17,13 +17,13 @@ import {LlamaERC721TokenCaster} from "src/token-voting/LlamaERC721TokenCaster.so import {LlamaTokenCaster} from "src/token-voting/LlamaTokenCaster.sol"; contract LlamaERC721TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUtils { - event VoteCast(uint256 id, address indexed tokenholder, uint8 indexed support, uint256 quantity, string reason); + event VoteCast(uint256 id, address indexed tokenholder, uint8 indexed support, uint256 weight, string reason); event ApprovalSubmitted( - uint256 id, address indexed caller, uint96 quantityFor, uint96 quantityAgainst, uint96 quantityAbstain + uint256 id, address indexed caller, uint256 weightFor, uint256 weightAgainst, uint256 weightAbstain ); - event VetoCast(uint256 id, address indexed tokenholder, uint8 indexed support, uint256 quantity, string reason); + event VetoCast(uint256 id, address indexed tokenholder, uint8 indexed support, uint256 weight, string reason); event DisapprovalSubmitted( - uint256 id, address indexed caller, uint96 quantityFor, uint96 quantityAgainst, uint96 quantityAbstain + uint256 id, address indexed caller, uint256 weightFor, uint256 weightAgainst, uint256 weightAbstain ); event QuorumSet(uint256 voteQuorumPct, uint256 vetoQuorumPct); @@ -200,12 +200,13 @@ contract CastVote is LlamaERC721TokenCasterTest { llamaERC721TokenCaster.castVote(actionInfo, uint8(VoteType.For), ""); } - function test_RevertsIf_InsufficientBalance() public { - vm.expectRevert(abi.encodeWithSelector(LlamaTokenCaster.InsufficientBalance.selector, 0)); + function test_CanCastWithWeightZero() public { + vm.expectEmit(); + emit VoteCast(actionInfo.id, address(this), uint8(VoteType.For), 0, ""); llamaERC721TokenCaster.castVote(actionInfo, uint8(VoteType.For), ""); } - function test_CastsApprovalCorrectly(uint8 support) public { + function test_CastsVoteCorrectly(uint8 support) public { support = uint8(bound(support, uint8(VoteType.For), uint8(VoteType.Abstain))); vm.expectEmit(); emit VoteCast( @@ -215,7 +216,7 @@ contract CastVote is LlamaERC721TokenCasterTest { llamaERC721TokenCaster.castVote(actionInfo, support, ""); } - function test_CastsApprovalCorrectly_WithReason() public { + function test_CastsVoteCorrectly_WithReason() public { vm.expectEmit(); emit VoteCast( actionInfo.id, @@ -254,7 +255,7 @@ contract CastApprovalBySig is LlamaERC721TokenCasterTest { llamaERC721TokenCaster.castVoteBySig(tokenHolder1, support, _actionInfo, "", v, r, s); } - function test_CastsApprovalBySig() public { + function test_CastsVoteBySig() public { (uint8 v, bytes32 r, bytes32 s) = createOffchainSignature(actionInfo, tokenHolder1PrivateKey); vm.expectEmit(); @@ -380,12 +381,13 @@ contract CastVeto is LlamaERC721TokenCasterTest { llamaERC721TokenCaster.castVeto(actionInfo, uint8(VoteType.For), ""); } - function test_RevertsIf_InsufficientBalance() public { - vm.expectRevert(abi.encodeWithSelector(LlamaTokenCaster.InsufficientBalance.selector, 0)); + function test_CanCastWithWeightZero() public { + vm.expectEmit(); + emit VetoCast(actionInfo.id, address(this), uint8(VoteType.For), 0, ""); llamaERC721TokenCaster.castVeto(actionInfo, uint8(VoteType.For), ""); } - function test_CastsDisapprovalCorrectly(uint8 support) public { + function test_CastsVetoCorrectly(uint8 support) public { support = uint8(bound(support, uint8(VoteType.For), uint8(VoteType.Abstain))); vm.expectEmit(); emit VetoCast( @@ -395,7 +397,7 @@ contract CastVeto is LlamaERC721TokenCasterTest { llamaERC721TokenCaster.castVeto(actionInfo, support, ""); } - function test_CastsDisapprovalCorrectly_WithReason() public { + function test_CastsVetoCorrectly_WithReason() public { vm.expectEmit(); emit VetoCast( actionInfo.id, @@ -441,7 +443,7 @@ contract CastVetoBySig is LlamaERC721TokenCasterTest { llamaERC721TokenCaster.castVetoBySig(tokenHolder1, uint8(VoteType.For), _actionInfo, "", v, r, s); } - function test_CastsDisapprovalBySig() public { + function test_CastsVetoBySig() public { (uint8 v, bytes32 r, bytes32 s) = createOffchainSignature(actionInfo, tokenHolder1PrivateKey); vm.expectEmit(); From 516a332777b49290374853fad12b8a7800a44fbc Mon Sep 17 00:00:00 2001 From: Austin Green Date: Thu, 14 Dec 2023 14:39:29 -0500 Subject: [PATCH 2/2] fix: clock adapter improvements (#59) **Motivation:** This PR implements improvements to the clock adapter functionality. **Modifications:** - Changed the type of all uses of the timepoint variables to `uint48` to follow the EIP `https://eips.ethereum.org/EIPS/eip-6372` - Added a `toUint48` function to utils to safely downcast (note: we should never have to worry about block.timestamp or block number overflowing) - Added a `clock()` and `CLOCK_MODE()` function to the adapter so the caster and creator contracts can reference the adapter for these view functions if they're not defined on the token directly - Removed `currentTimepointMinusOne()` from the adapter. Contracts can just use `clock()` and subtract one from the result - ~~Updated all implementations of `_getClockMode` to first try to get the clock mode from the adapter if it is present, otherwise fallback to the token, and otherwise fallback to the default timestamp clock mode~~ - When fetching previous timepoints for getting the past total supply or get past votes, I changed it to we first convert action creation time to the timepoint and then subtract one **Result:** Closes #54 --- src/lib/LlamaUtils.sol | 6 ++++++ src/token-voting/ILlamaTokenClockAdapter.sol | 11 ++++++++--- src/token-voting/LlamaERC20TokenActionCreator.sol | 6 +++--- src/token-voting/LlamaERC20TokenCaster.sol | 6 +++--- .../LlamaERC721TokenActionCreator.sol | 6 +++--- src/token-voting/LlamaERC721TokenCaster.sol | 6 +++--- src/token-voting/LlamaTokenActionCreator.sol | 10 +++++----- src/token-voting/LlamaTokenCaster.sol | 15 ++++++++------- 8 files changed, 39 insertions(+), 27 deletions(-) diff --git a/src/lib/LlamaUtils.sol b/src/lib/LlamaUtils.sol index 9ae5d4f..7ac80e5 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 `uint48`. + function toUint48(uint256 n) internal pure returns (uint48) { + if (n > type(uint48).max) revert UnsafeCast(n); + return uint48(n); + } + /// @dev Reverts if `n` does not fit in a `uint64`. function toUint64(uint256 n) internal pure returns (uint64) { if (n > type(uint64).max) revert UnsafeCast(n); diff --git a/src/token-voting/ILlamaTokenClockAdapter.sol b/src/token-voting/ILlamaTokenClockAdapter.sol index cd4a598..0d6fef0 100644 --- a/src/token-voting/ILlamaTokenClockAdapter.sol +++ b/src/token-voting/ILlamaTokenClockAdapter.sol @@ -6,12 +6,17 @@ pragma solidity ^0.8.23; /// @notice This contract provides an interface for clock adapters. Clock adapters enable voting tokens that don't use /// timestamp-based checkpointing to work with the Llama token voting module. interface ILlamaTokenClockAdapter { - /// @notice Returns the most recent timepoint in the past. - function currentTimepointMinusOne() external view returns (uint256 timepoint); + /// @notice Returns the current timepoint according to the token's clock. + /// @return timepoint the current timepoint + function clock() external view returns (uint48 timepoint); + + /// @notice Machine-readable description of the clock as specified in ERC-6372. + function CLOCK_MODE() external view returns (string memory); /// @notice Converts a timestamp to timepoint units. /// @param timestamp The timestamp to convert. - function timestampToTimepoint(uint256 timestamp) external view returns (uint256 timepoint); + /// @return timepoint the current timepoint + function timestampToTimepoint(uint256 timestamp) external view returns (uint48 timepoint); /// @notice Returns true if the clock mode is supported and false if it is unsupported. /// @param clockMode The clock mode to check. diff --git a/src/token-voting/LlamaERC20TokenActionCreator.sol b/src/token-voting/LlamaERC20TokenActionCreator.sol index 382298f..bc40128 100644 --- a/src/token-voting/LlamaERC20TokenActionCreator.sol +++ b/src/token-voting/LlamaERC20TokenActionCreator.sol @@ -45,17 +45,17 @@ contract LlamaERC20TokenActionCreator is LlamaTokenActionCreator { } /// @inheritdoc LlamaTokenActionCreator - function _getPastVotes(address account, uint256 timepoint) internal view virtual override returns (uint256) { + function _getPastVotes(address account, uint48 timepoint) internal view virtual override returns (uint256) { return token.getPastVotes(account, timepoint); } /// @inheritdoc LlamaTokenActionCreator - function _getPastTotalSupply(uint256 timepoint) internal view virtual override returns (uint256) { + function _getPastTotalSupply(uint48 timepoint) internal view virtual override returns (uint256) { return token.getPastTotalSupply(timepoint); } /// @inheritdoc LlamaTokenActionCreator - function _getClockMode() internal view virtual override returns (string memory) { + function _getClockMode() internal view virtual override returns (string memory clockmode) { return token.CLOCK_MODE(); } } diff --git a/src/token-voting/LlamaERC20TokenCaster.sol b/src/token-voting/LlamaERC20TokenCaster.sol index 8223c14..bbbd0f0 100644 --- a/src/token-voting/LlamaERC20TokenCaster.sol +++ b/src/token-voting/LlamaERC20TokenCaster.sol @@ -44,17 +44,17 @@ contract LlamaERC20TokenCaster is LlamaTokenCaster { } /// @inheritdoc LlamaTokenCaster - function _getPastVotes(address account, uint256 timepoint) internal view virtual override returns (uint256) { + function _getPastVotes(address account, uint48 timepoint) internal view virtual override returns (uint256) { return token.getPastVotes(account, timepoint); } /// @inheritdoc LlamaTokenCaster - function _getPastTotalSupply(uint256 timepoint) internal view virtual override returns (uint256) { + function _getPastTotalSupply(uint48 timepoint) internal view virtual override returns (uint256) { return token.getPastTotalSupply(timepoint); } /// @inheritdoc LlamaTokenCaster - function _getClockMode() internal view virtual override returns (string memory) { + function _getClockMode() internal view virtual override returns (string memory clockmode) { return token.CLOCK_MODE(); } } diff --git a/src/token-voting/LlamaERC721TokenActionCreator.sol b/src/token-voting/LlamaERC721TokenActionCreator.sol index 5fdd942..07526bf 100644 --- a/src/token-voting/LlamaERC721TokenActionCreator.sol +++ b/src/token-voting/LlamaERC721TokenActionCreator.sol @@ -47,17 +47,17 @@ contract LlamaERC721TokenActionCreator is LlamaTokenActionCreator { } /// @inheritdoc LlamaTokenActionCreator - function _getPastVotes(address account, uint256 timepoint) internal view virtual override returns (uint256) { + function _getPastVotes(address account, uint48 timepoint) internal view virtual override returns (uint256) { return token.getPastVotes(account, timepoint); } /// @inheritdoc LlamaTokenActionCreator - function _getPastTotalSupply(uint256 timepoint) internal view virtual override returns (uint256) { + function _getPastTotalSupply(uint48 timepoint) internal view virtual override returns (uint256) { return token.getPastTotalSupply(timepoint); } /// @inheritdoc LlamaTokenActionCreator - function _getClockMode() internal view virtual override returns (string memory) { + function _getClockMode() internal view virtual override returns (string memory clockmode) { return token.CLOCK_MODE(); } } diff --git a/src/token-voting/LlamaERC721TokenCaster.sol b/src/token-voting/LlamaERC721TokenCaster.sol index e36c586..6003466 100644 --- a/src/token-voting/LlamaERC721TokenCaster.sol +++ b/src/token-voting/LlamaERC721TokenCaster.sol @@ -46,17 +46,17 @@ contract LlamaERC721TokenCaster is LlamaTokenCaster { } /// @inheritdoc LlamaTokenCaster - function _getPastVotes(address account, uint256 timepoint) internal view virtual override returns (uint256) { + function _getPastVotes(address account, uint48 timepoint) internal view virtual override returns (uint256) { return token.getPastVotes(account, timepoint); } /// @inheritdoc LlamaTokenCaster - function _getPastTotalSupply(uint256 timepoint) internal view virtual override returns (uint256) { + function _getPastTotalSupply(uint48 timepoint) internal view virtual override returns (uint256) { return token.getPastTotalSupply(timepoint); } /// @inheritdoc LlamaTokenCaster - function _getClockMode() internal view virtual override returns (string memory) { + function _getClockMode() internal view virtual override returns (string memory clockmode) { return token.CLOCK_MODE(); } } diff --git a/src/token-voting/LlamaTokenActionCreator.sol b/src/token-voting/LlamaTokenActionCreator.sol index 853b68f..48faf5e 100644 --- a/src/token-voting/LlamaTokenActionCreator.sol +++ b/src/token-voting/LlamaTokenActionCreator.sol @@ -276,9 +276,9 @@ abstract contract LlamaTokenActionCreator is Initializable { } /// @dev Returns the current timepoint minus one. - function _currentTimepointMinusOne() internal view returns (uint256) { - if (_isClockModeTimestamp()) return block.timestamp - 1; - return clockAdapter.currentTimepointMinusOne(); + function _currentTimepointMinusOne() internal view returns (uint48) { + if (_isClockModeTimestamp()) return LlamaUtils.toUint48(block.timestamp - 1); + return clockAdapter.clock() - 1; } // Returns true if the clock mode is timestamp @@ -288,10 +288,10 @@ abstract contract LlamaTokenActionCreator is Initializable { } /// @dev Returns the number of votes for a given token holder at a given timestamp. - function _getPastVotes(address account, uint256 timepoint) internal view virtual returns (uint256) {} + function _getPastVotes(address account, uint48 timepoint) internal view virtual returns (uint256) {} /// @dev Returns the total supply of the token at a given timestamp. - function _getPastTotalSupply(uint256 timepoint) internal view virtual returns (uint256) {} + function _getPastTotalSupply(uint48 timepoint) internal view virtual returns (uint256) {} /// @dev Returns the clock mode of the token (https://eips.ethereum.org/EIPS/eip-6372). function _getClockMode() internal view virtual returns (string memory) {} diff --git a/src/token-voting/LlamaTokenCaster.sol b/src/token-voting/LlamaTokenCaster.sol index 9b877bb..22e7229 100644 --- a/src/token-voting/LlamaTokenCaster.sol +++ b/src/token-voting/LlamaTokenCaster.sol @@ -203,6 +203,7 @@ abstract contract LlamaTokenCaster is Initializable { role = _role; voteQuorumPct = _voteQuorumPct; vetoQuorumPct = _vetoQuorumPct; + emit QuorumSet(_voteQuorumPct, _vetoQuorumPct); } @@ -410,15 +411,15 @@ abstract contract LlamaTokenCaster is Initializable { } /// @dev Returns the timestamp or timepoint depending on the clock mode. - function _timestampToTimepoint(uint256 timestamp) internal view returns (uint256) { - if (_isClockModeTimestamp()) return timestamp; + function _timestampToTimepoint(uint256 timestamp) internal view returns (uint48) { + if (_isClockModeTimestamp()) return LlamaUtils.toUint48(timestamp); return clockAdapter.timestampToTimepoint(timestamp); } /// @dev Returns the current timepoint minus one. - function _currentTimepointMinusOne() internal view returns (uint256) { - if (_isClockModeTimestamp()) return block.timestamp - 1; - return clockAdapter.currentTimepointMinusOne(); + function _currentTimepointMinusOne() internal view returns (uint48) { + if (_isClockModeTimestamp()) return LlamaUtils.toUint48(block.timestamp - 1); + return clockAdapter.clock() - 1; } /// @dev Returns true if the clock mode is timestamp. @@ -428,10 +429,10 @@ abstract contract LlamaTokenCaster is Initializable { } /// @dev Returns the number of votes for a given token holder at a given timestamp. - function _getPastVotes(address account, uint256 timepoint) internal view virtual returns (uint256) {} + function _getPastVotes(address account, uint48 timepoint) internal view virtual returns (uint256) {} /// @dev Returns the total supply of the token at a given timestamp. - function _getPastTotalSupply(uint256 timepoint) internal view virtual returns (uint256) {} + function _getPastTotalSupply(uint48 timepoint) internal view virtual returns (uint256) {} /// @dev Returns the clock mode of the token (https://eips.ethereum.org/EIPS/eip-6372). function _getClockMode() internal view virtual returns (string memory) {}