From 7ada7c6e2b8dda6975c50b6cc922c2c1d13cb572 Mon Sep 17 00:00:00 2001 From: Austin Green Date: Thu, 14 Dec 2023 19:08:49 -0500 Subject: [PATCH 01/23] adapter --- .../LlamaERC20TokenActionCreator.sol | 23 +----- src/token-voting/LlamaERC20TokenCaster.sol | 23 +----- .../LlamaERC721TokenActionCreator.sol | 23 +----- src/token-voting/LlamaERC721TokenCaster.sol | 23 +----- src/token-voting/LlamaTokenActionCreator.sol | 47 ++---------- src/token-voting/LlamaTokenAdapter.sol | 75 +++++++++++++++++++ src/token-voting/LlamaTokenCaster.sol | 62 +++------------ src/token-voting/LlamaTokenVotingFactory.sol | 32 ++++---- .../ILlamaTokenAdapter.sol} | 16 ++-- .../LlamaTokenVotingFactory.t.sol | 4 +- .../LlamaTokenVotingTestSetup.sol | 6 +- 11 files changed, 139 insertions(+), 195 deletions(-) create mode 100644 src/token-voting/LlamaTokenAdapter.sol rename src/token-voting/{ILlamaTokenClockAdapter.sol => interfaces/ILlamaTokenAdapter.sol} (52%) diff --git a/src/token-voting/LlamaERC20TokenActionCreator.sol b/src/token-voting/LlamaERC20TokenActionCreator.sol index bc40128..d1522b5 100644 --- a/src/token-voting/LlamaERC20TokenActionCreator.sol +++ b/src/token-voting/LlamaERC20TokenActionCreator.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.23; import {ERC20Votes} from "@openzeppelin/token/ERC20/extensions/ERC20Votes.sol"; import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; -import {ILlamaTokenClockAdapter} from "src/token-voting/ILlamaTokenClockAdapter.sol"; +import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; import {LlamaTokenActionCreator} from "src/token-voting/LlamaTokenActionCreator.sol"; /// @title LlamaERC20TokenActionCreator @@ -33,29 +33,14 @@ contract LlamaERC20TokenActionCreator is LlamaTokenActionCreator { function initialize( ERC20Votes _token, ILlamaCore _llamaCore, - ILlamaTokenClockAdapter _clockAdapter, + ILlamaTokenAdapter _tokenAdapter, uint8 _role, uint256 _creationThreshold ) external initializer { - __initializeLlamaTokenActionCreatorMinimalProxy(_llamaCore, _clockAdapter, _role, _creationThreshold); + __initializeLlamaTokenActionCreatorMinimalProxy(_llamaCore, _tokenAdapter, _role, _creationThreshold); token = _token; - uint256 totalSupply = token.getPastTotalSupply(_currentTimepointMinusOne()); + uint256 totalSupply = tokenAdapter.getPastTotalSupply(tokenAdapter.clock() - 1); if (totalSupply == 0) revert InvalidTokenAddress(); if (_creationThreshold > totalSupply) revert InvalidCreationThreshold(); } - - /// @inheritdoc LlamaTokenActionCreator - function _getPastVotes(address account, uint48 timepoint) internal view virtual override returns (uint256) { - return token.getPastVotes(account, timepoint); - } - - /// @inheritdoc LlamaTokenActionCreator - 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 clockmode) { - return token.CLOCK_MODE(); - } } diff --git a/src/token-voting/LlamaERC20TokenCaster.sol b/src/token-voting/LlamaERC20TokenCaster.sol index 5017f36..c41b3f2 100644 --- a/src/token-voting/LlamaERC20TokenCaster.sol +++ b/src/token-voting/LlamaERC20TokenCaster.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.23; import {ERC20Votes} from "@openzeppelin/token/ERC20/extensions/ERC20Votes.sol"; import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; -import {ILlamaTokenClockAdapter} from "src/token-voting/ILlamaTokenClockAdapter.sol"; +import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; import {LlamaTokenCaster} from "src/token-voting/LlamaTokenCaster.sol"; /// @title LlamaERC20TokenCaster @@ -32,29 +32,14 @@ contract LlamaERC20TokenCaster is LlamaTokenCaster { function initialize( ERC20Votes _token, ILlamaCore _llamaCore, - ILlamaTokenClockAdapter _clockAdapter, + ILlamaTokenAdapter _tokenAdapter, uint8 _role, uint16 _voteQuorumPct, uint16 _vetoQuorumPct ) external initializer { - __initializeLlamaTokenCasterMinimalProxy(_llamaCore, _clockAdapter, _role, _voteQuorumPct, _vetoQuorumPct); + __initializeLlamaTokenCasterMinimalProxy(_llamaCore, _tokenAdapter, _role, _voteQuorumPct, _vetoQuorumPct); token = _token; - uint256 totalSupply = token.getPastTotalSupply(_currentTimepointMinusOne()); + uint256 totalSupply = tokenAdapter.getPastTotalSupply(tokenAdapter.clock() - 1); if (totalSupply == 0) revert InvalidTokenAddress(); } - - /// @inheritdoc LlamaTokenCaster - function _getPastVotes(address account, uint48 timepoint) internal view virtual override returns (uint256) { - return token.getPastVotes(account, timepoint); - } - - /// @inheritdoc LlamaTokenCaster - 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 clockmode) { - return token.CLOCK_MODE(); - } } diff --git a/src/token-voting/LlamaERC721TokenActionCreator.sol b/src/token-voting/LlamaERC721TokenActionCreator.sol index 07526bf..5bee9f9 100644 --- a/src/token-voting/LlamaERC721TokenActionCreator.sol +++ b/src/token-voting/LlamaERC721TokenActionCreator.sol @@ -5,7 +5,7 @@ import {ERC721Votes} from "@openzeppelin/token/ERC721/extensions/ERC721Votes.sol import {IERC721} from "@openzeppelin/token/ERC721/IERC721.sol"; import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; -import {ILlamaTokenClockAdapter} from "src/token-voting/ILlamaTokenClockAdapter.sol"; +import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; import {LlamaTokenActionCreator} from "src/token-voting/LlamaTokenActionCreator.sol"; /// @title LlamaERC721TokenActionCreator @@ -34,30 +34,15 @@ contract LlamaERC721TokenActionCreator is LlamaTokenActionCreator { function initialize( ERC721Votes _token, ILlamaCore _llamaCore, - ILlamaTokenClockAdapter _clockAdapter, + ILlamaTokenAdapter _tokenAdapter, uint8 _role, uint256 _creationThreshold ) external initializer { - __initializeLlamaTokenActionCreatorMinimalProxy(_llamaCore, _clockAdapter, _role, _creationThreshold); + __initializeLlamaTokenActionCreatorMinimalProxy(_llamaCore, _tokenAdapter, _role, _creationThreshold); token = _token; if (!token.supportsInterface(type(IERC721).interfaceId)) revert InvalidTokenAddress(); - uint256 totalSupply = token.getPastTotalSupply(_currentTimepointMinusOne()); + uint256 totalSupply = tokenAdapter.getPastTotalSupply(tokenAdapter.clock() - 1); if (totalSupply == 0) revert InvalidTokenAddress(); if (_creationThreshold > totalSupply) revert InvalidCreationThreshold(); } - - /// @inheritdoc LlamaTokenActionCreator - function _getPastVotes(address account, uint48 timepoint) internal view virtual override returns (uint256) { - return token.getPastVotes(account, timepoint); - } - - /// @inheritdoc LlamaTokenActionCreator - 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 clockmode) { - return token.CLOCK_MODE(); - } } diff --git a/src/token-voting/LlamaERC721TokenCaster.sol b/src/token-voting/LlamaERC721TokenCaster.sol index 52f52d0..26dcede 100644 --- a/src/token-voting/LlamaERC721TokenCaster.sol +++ b/src/token-voting/LlamaERC721TokenCaster.sol @@ -5,7 +5,7 @@ import {ERC721Votes} from "@openzeppelin/token/ERC721/extensions/ERC721Votes.sol import {IERC721} from "@openzeppelin/token/ERC721/IERC721.sol"; import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; -import {ILlamaTokenClockAdapter} from "src/token-voting/ILlamaTokenClockAdapter.sol"; +import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; import {LlamaTokenCaster} from "src/token-voting/LlamaTokenCaster.sol"; /// @title LlamaERC721TokenCaster /// @author Llama (devsdosomething@llama.xyz) @@ -33,30 +33,15 @@ contract LlamaERC721TokenCaster is LlamaTokenCaster { function initialize( ERC721Votes _token, ILlamaCore _llamaCore, - ILlamaTokenClockAdapter _clockAdapter, + ILlamaTokenAdapter _tokenAdapter, uint8 _role, uint16 _voteQuorumPct, uint16 _vetoQuorumPct ) external initializer { - __initializeLlamaTokenCasterMinimalProxy(_llamaCore, _clockAdapter, _role, _voteQuorumPct, _vetoQuorumPct); + __initializeLlamaTokenCasterMinimalProxy(_llamaCore, _tokenAdapter, _role, _voteQuorumPct, _vetoQuorumPct); token = _token; if (!token.supportsInterface(type(IERC721).interfaceId)) revert InvalidTokenAddress(); - uint256 totalSupply = token.getPastTotalSupply(_currentTimepointMinusOne()); + uint256 totalSupply = tokenAdapter.getPastTotalSupply(tokenAdapter.clock() - 1); if (totalSupply == 0) revert InvalidTokenAddress(); } - - /// @inheritdoc LlamaTokenCaster - function _getPastVotes(address account, uint48 timepoint) internal view virtual override returns (uint256) { - return token.getPastVotes(account, timepoint); - } - - /// @inheritdoc LlamaTokenCaster - 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 clockmode) { - return token.CLOCK_MODE(); - } } diff --git a/src/token-voting/LlamaTokenActionCreator.sol b/src/token-voting/LlamaTokenActionCreator.sol index 48faf5e..326b774 100644 --- a/src/token-voting/LlamaTokenActionCreator.sol +++ b/src/token-voting/LlamaTokenActionCreator.sol @@ -5,7 +5,7 @@ import {Initializable} from "@openzeppelin/proxy/utils/Initializable.sol"; import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; import {ILlamaStrategy} from "src/interfaces/ILlamaStrategy.sol"; -import {ILlamaTokenClockAdapter} from "src/token-voting/ILlamaTokenClockAdapter.sol"; +import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; import {Action, ActionInfo} from "src/lib/Structs.sol"; import {LlamaUtils} from "src/lib/LlamaUtils.sol"; @@ -89,7 +89,7 @@ abstract contract LlamaTokenActionCreator is Initializable { ILlamaCore public llamaCore; /// @notice The contract that manages the timepoints for this token voting module. - ILlamaTokenClockAdapter public clockAdapter; + ILlamaTokenAdapter public tokenAdapter; /// @notice The default number of tokens required to create an action. uint256 public creationThreshold; @@ -118,7 +118,7 @@ abstract contract LlamaTokenActionCreator is Initializable { /// creation threshold of 1000 tokens, pass in 1000e18. function __initializeLlamaTokenActionCreatorMinimalProxy( ILlamaCore _llamaCore, - ILlamaTokenClockAdapter _clockAdapter, + ILlamaTokenAdapter _tokenAdapter, uint8 _role, uint256 _creationThreshold ) internal { @@ -126,7 +126,7 @@ abstract contract LlamaTokenActionCreator is Initializable { if (_role > _llamaCore.policy().numRoles()) revert RoleNotInitialized(_role); llamaCore = _llamaCore; - clockAdapter = _clockAdapter; + tokenAdapter = _tokenAdapter; role = _role; _setActionThreshold(_creationThreshold); } @@ -215,7 +215,9 @@ abstract contract LlamaTokenActionCreator is Initializable { /// @dev This must be in the same decimals as the token. function setActionThreshold(uint256 _creationThreshold) external { if (msg.sender != address(llamaCore.executor())) revert OnlyLlamaExecutor(); - if (_creationThreshold > _getPastTotalSupply(_currentTimepointMinusOne())) revert InvalidCreationThreshold(); + if (_creationThreshold > tokenAdapter.getPastTotalSupply(tokenAdapter.clock() - 1)) { + revert InvalidCreationThreshold(); + } _setActionThreshold(_creationThreshold); } @@ -242,10 +244,7 @@ abstract contract LlamaTokenActionCreator is Initializable { bytes calldata data, string memory description ) internal returns (uint256 actionId) { - /// @dev only timestamp mode is supported for now - _isClockModeSupported(); // reverts if clock mode is not supported - - uint256 balance = _getPastVotes(tokenHolder, _currentTimepointMinusOne()); + uint256 balance = tokenAdapter.getPastVotes(tokenHolder, tokenAdapter.clock() - 1); if (balance < creationThreshold) revert InsufficientBalance(balance); actionId = llamaCore.createAction(role, strategy, target, value, data, description); @@ -266,36 +265,6 @@ abstract contract LlamaTokenActionCreator is Initializable { emit ActionThresholdSet(_creationThreshold); } - ///@dev Reverts if the clock mode is not supported. - function _isClockModeSupported() internal view { - if (!_isClockModeTimestamp()) { - string memory clockMode = _getClockMode(); - bool supported = clockAdapter.isClockModeSupported(clockMode); - if (!supported) revert ClockModeNotSupported(clockMode); - } - } - - /// @dev Returns the current timepoint minus one. - 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 - function _isClockModeTimestamp() internal view returns (bool) { - string memory clockMode = _getClockMode(); - 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. - 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(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) {} - /// @dev Returns the current nonce for a given tokenHolder and selector, and increments it. Used to prevent /// replay attacks. function _useNonce(address tokenHolder, bytes4 selector) internal returns (uint256 nonce) { diff --git a/src/token-voting/LlamaTokenAdapter.sol b/src/token-voting/LlamaTokenAdapter.sol new file mode 100644 index 0000000..d0ce295 --- /dev/null +++ b/src/token-voting/LlamaTokenAdapter.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {IVotes} from "@openzeppelin/governance/utils/IVotes.sol"; +import {IERC6372} from "@openzeppelin/interfaces/IERC6372.sol"; + +import {LlamaUtils} from "src/lib/LlamaUtils.sol"; +import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; + +contract LlamaTokenAdapter is ILlamaTokenAdapter { + /// @dev The clock was incorrectly modified. + error ERC6372InconsistentClock(); + + /// @notice The token to be used for voting. + IVotes public token; + + string CLOCK_MODE; + + constructor(IVotes _token, string memory _clockMode) { + token = _token; + + if (keccak256(abi.encodePacked(_clockMode)) != keccak256(abi.encodePacked(""))) { + CLOCK_MODE = _clockMode; + } else { + try IERC6372(address(token)).CLOCK_MODE() returns (string memory mode) { + CLOCK_MODE = mode; + } catch { + CLOCK_MODE = "mode=timestamp"; + } + } + } + + function clock() public view returns (uint48 timepoint) { + try IERC6372(address(token)).clock() returns (uint48 tokenTimepoint) { + timepoint = tokenTimepoint; + } catch { + timepoint = LlamaUtils.toUint48(block.timestamp); + } + } + + function checkIfInconsistentClock() external view { + bool hasClockChanged = _hasClockChanged(); + bool hasClockModeChanged = _hasClockModeChanged(); + + if (hasClockChanged || hasClockModeChanged) revert ERC6372InconsistentClock(); + } + + function timestampToTimepoint(uint256 timestamp) external view returns (uint48 timepoint) { + return LlamaUtils.toUint48(timestamp); + } + + function getPastVotes(address account, uint48 timepoint) external view returns (uint256) { + return token.getPastVotes(account, timepoint); + } + + function getPastTotalSupply(uint48 timepoint) external view returns (uint256) { + return token.getPastTotalSupply(timepoint); + } + + function _hasClockModeChanged() internal view returns (bool) { + try IERC6372(address(token)).CLOCK_MODE() returns (string memory mode) { + return keccak256(abi.encodePacked(mode)) != keccak256(abi.encodePacked(CLOCK_MODE)); + } catch { + return false; + } + } + + function _hasClockChanged() internal view returns (bool) { + if (keccak256(abi.encodePacked(CLOCK_MODE)) == keccak256(abi.encodePacked("mode=timestamp"))) { + return clock() != LlamaUtils.toUint48(block.timestamp); + } else { + return clock() != LlamaUtils.toUint48(block.number); + } + } +} diff --git a/src/token-voting/LlamaTokenCaster.sol b/src/token-voting/LlamaTokenCaster.sol index b960f09..0325c5a 100644 --- a/src/token-voting/LlamaTokenCaster.sol +++ b/src/token-voting/LlamaTokenCaster.sol @@ -5,7 +5,7 @@ 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 {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; import {ActionState, VoteType} from "src/lib/Enums.sol"; import {LlamaUtils} from "src/lib/LlamaUtils.sol"; import {QuorumCheckpoints} from "src/lib/QuorumCheckpoints.sol"; @@ -167,7 +167,7 @@ abstract contract LlamaTokenCaster is Initializable { QuorumCheckpoints.History internal quorumCheckpoints; /// @notice The contract that manages the timepoints for this token voting module. - ILlamaTokenClockAdapter public clockAdapter; + ILlamaTokenAdapter public tokenAdapter; /// @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. @@ -192,7 +192,7 @@ abstract contract LlamaTokenCaster is Initializable { /// @param _vetoQuorumPct The minimum % of vetoes required to submit a disapproval to `LlamaCore`. function __initializeLlamaTokenCasterMinimalProxy( ILlamaCore _llamaCore, - ILlamaTokenClockAdapter _clockAdapter, + ILlamaTokenAdapter _tokenAdapter, uint8 _role, uint16 _voteQuorumPct, uint16 _vetoQuorumPct @@ -201,7 +201,7 @@ abstract contract LlamaTokenCaster is Initializable { if (_role > _llamaCore.policy().numRoles()) revert RoleNotInitialized(_role); llamaCore = _llamaCore; - clockAdapter = _clockAdapter; + tokenAdapter = _tokenAdapter; role = _role; _setQuorumPct(_voteQuorumPct, _vetoQuorumPct); } @@ -282,9 +282,7 @@ abstract contract LlamaTokenCaster is Initializable { if (block.timestamp > action.creationTime + approvalPeriod) revert SubmissionPeriodOver(); - _isClockModeSupported(); // reverts if clock mode is not supported - - uint256 totalSupply = _getPastTotalSupply(_timestampToTimepoint(action.creationTime) - 1); + uint256 totalSupply = tokenAdapter.getPastTotalSupply(tokenAdapter.timestampToTimepoint(action.creationTime) - 1); uint96 votesFor = casts[actionInfo.id].votesFor; uint96 votesAgainst = casts[actionInfo.id].votesAgainst; uint96 votesAbstain = casts[actionInfo.id].votesAbstain; @@ -314,9 +312,7 @@ abstract contract LlamaTokenCaster is Initializable { } if (block.timestamp >= action.minExecutionTime) revert SubmissionPeriodOver(); - _isClockModeSupported(); // reverts if clock mode is not supported - - uint256 totalSupply = _getPastTotalSupply(_timestampToTimepoint(action.creationTime) - 1); + uint256 totalSupply = tokenAdapter.getPastTotalSupply(tokenAdapter.timestampToTimepoint(action.creationTime) - 1); uint96 vetoesFor = casts[actionInfo.id].vetoesFor; uint96 vetoesAgainst = casts[actionInfo.id].vetoesAgainst; uint96 vetoesAbstain = casts[actionInfo.id].vetoesAbstain; @@ -390,7 +386,8 @@ abstract contract LlamaTokenCaster is Initializable { > action.creationTime + (actionInfo.strategy.approvalPeriod() * TWO_THIRDS_IN_BPS) / ONE_HUNDRED_IN_BPS ) revert CastingPeriodOver(); - uint96 weight = LlamaUtils.toUint96(_getPastVotes(caster, _timestampToTimepoint(action.creationTime) - 1)); + uint96 weight = + LlamaUtils.toUint96(tokenAdapter.getPastVotes(caster, tokenAdapter.timestampToTimepoint(action.creationTime) - 1)); _preCastAssertions(support); if (support == uint8(VoteType.Against)) casts[actionInfo.id].votesAgainst += weight; @@ -416,7 +413,8 @@ abstract contract LlamaTokenCaster is Initializable { > action.minExecutionTime - (actionInfo.strategy.queuingPeriod() * ONE_THIRD_IN_BPS) / ONE_HUNDRED_IN_BPS ) revert CastingPeriodOver(); - uint96 weight = LlamaUtils.toUint96(_getPastVotes(caster, _timestampToTimepoint(action.creationTime) - 1)); + uint96 weight = + LlamaUtils.toUint96(tokenAdapter.getPastVotes(caster, tokenAdapter.timestampToTimepoint(action.creationTime) - 1)); _preCastAssertions(support); if (support == uint8(VoteType.Against)) casts[actionInfo.id].vetoesAgainst += weight; @@ -428,10 +426,8 @@ abstract contract LlamaTokenCaster is Initializable { return weight; } - function _preCastAssertions(uint8 support) internal view { + function _preCastAssertions(uint8 support) internal pure { if (support > uint8(VoteType.Abstain)) revert InvalidSupport(support); - - _isClockModeSupported(); // reverts if clock mode is not supported } /// @dev Sets the voting quorum and vetoing quorum. @@ -442,42 +438,6 @@ abstract contract LlamaTokenCaster is Initializable { emit QuorumSet(_voteQuorumPct, _vetoQuorumPct); } - /// @dev reverts if the clock mode is not supported - function _isClockModeSupported() internal view { - if (!_isClockModeTimestamp()) { - string memory clockMode = _getClockMode(); - bool supported = clockAdapter.isClockModeSupported(clockMode); - if (!supported) revert ClockModeNotSupported(clockMode); - } - } - - /// @dev Returns the timestamp or timepoint depending on the clock mode. - 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 (uint48) { - if (_isClockModeTimestamp()) return LlamaUtils.toUint48(block.timestamp - 1); - return clockAdapter.clock() - 1; - } - - /// @dev Returns true if the clock mode is timestamp. - function _isClockModeTimestamp() internal view returns (bool) { - string memory clockMode = _getClockMode(); - 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. - 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(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) {} - /// @dev Returns the current nonce for a given tokenholder and selector, and increments it. Used to prevent /// replay attacks. function _useNonce(address tokenholder, bytes4 selector) internal returns (uint256 nonce) { diff --git a/src/token-voting/LlamaTokenVotingFactory.sol b/src/token-voting/LlamaTokenVotingFactory.sol index 1973642..0e76856 100644 --- a/src/token-voting/LlamaTokenVotingFactory.sol +++ b/src/token-voting/LlamaTokenVotingFactory.sol @@ -6,7 +6,7 @@ import {ERC20Votes} from "@openzeppelin/token/ERC20/extensions/ERC20Votes.sol"; import {ERC721Votes} from "@openzeppelin/token/ERC721/extensions/ERC721Votes.sol"; import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; -import {ILlamaTokenClockAdapter} from "src/token-voting/ILlamaTokenClockAdapter.sol"; +import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; import {LlamaERC20TokenActionCreator} from "src/token-voting/LlamaERC20TokenActionCreator.sol"; import {LlamaERC20TokenCaster} from "src/token-voting/LlamaERC20TokenCaster.sol"; import {LlamaERC721TokenActionCreator} from "src/token-voting/LlamaERC721TokenActionCreator.sol"; @@ -21,7 +21,7 @@ contract LlamaTokenVotingFactory { address indexed deployer, ILlamaCore indexed llamaCore, address indexed token, - ILlamaTokenClockAdapter clockAdapter, + ILlamaTokenAdapter tokenAdapter, uint256 nonce, bool isERC20, uint8 actionCreatorRole, @@ -69,7 +69,7 @@ contract LlamaTokenVotingFactory { function deploy( ILlamaCore llamaCore, address token, - ILlamaTokenClockAdapter clockAdapter, + ILlamaTokenAdapter tokenAdapter, uint256 nonce, bool isERC20, uint8 actionCreatorRole, @@ -81,23 +81,23 @@ contract LlamaTokenVotingFactory { if (isERC20) { actionCreator = address( _deployLlamaERC20TokenActionCreator( - ERC20Votes(token), llamaCore, clockAdapter, nonce, actionCreatorRole, creationThreshold + ERC20Votes(token), llamaCore, tokenAdapter, nonce, actionCreatorRole, creationThreshold ) ); caster = address( _deployLlamaERC20TokenCaster( - ERC20Votes(token), llamaCore, clockAdapter, nonce, casterRole, voteQuorumPct, vetoQuorumPct + ERC20Votes(token), llamaCore, tokenAdapter, nonce, casterRole, voteQuorumPct, vetoQuorumPct ) ); } else { actionCreator = address( _deployLlamaERC721TokenActionCreator( - ERC721Votes(token), llamaCore, clockAdapter, nonce, actionCreatorRole, creationThreshold + ERC721Votes(token), llamaCore, tokenAdapter, nonce, actionCreatorRole, creationThreshold ) ); caster = address( _deployLlamaERC721TokenCaster( - ERC721Votes(token), llamaCore, clockAdapter, nonce, casterRole, voteQuorumPct, vetoQuorumPct + ERC721Votes(token), llamaCore, tokenAdapter, nonce, casterRole, voteQuorumPct, vetoQuorumPct ) ); } @@ -106,7 +106,7 @@ contract LlamaTokenVotingFactory { msg.sender, llamaCore, token, - clockAdapter, + tokenAdapter, nonce, isERC20, actionCreatorRole, @@ -125,7 +125,7 @@ contract LlamaTokenVotingFactory { function _deployLlamaERC20TokenActionCreator( ERC20Votes token, ILlamaCore llamaCore, - ILlamaTokenClockAdapter clockAdapter, + ILlamaTokenAdapter tokenAdapter, uint256 nonce, uint8 role, uint256 creationThreshold @@ -136,14 +136,14 @@ contract LlamaTokenVotingFactory { keccak256(abi.encodePacked(msg.sender, address(llamaCore), address(token), nonce)) ) ); - actionCreator.initialize(token, llamaCore, clockAdapter, role, creationThreshold); + actionCreator.initialize(token, llamaCore, tokenAdapter, role, creationThreshold); } /// @dev Deploys and initiliazes a new `LlamaERC721TokenActionCreator` clone. function _deployLlamaERC721TokenActionCreator( ERC721Votes token, ILlamaCore llamaCore, - ILlamaTokenClockAdapter clockAdapter, + ILlamaTokenAdapter tokenAdapter, uint256 nonce, uint8 role, uint256 creationThreshold @@ -154,14 +154,14 @@ contract LlamaTokenVotingFactory { keccak256(abi.encodePacked(msg.sender, address(llamaCore), address(token), nonce)) ) ); - actionCreator.initialize(token, llamaCore, clockAdapter, role, creationThreshold); + actionCreator.initialize(token, llamaCore, tokenAdapter, role, creationThreshold); } /// @dev Deploys and initiliazes a new `LlamaERC20TokenCaster` clone. function _deployLlamaERC20TokenCaster( ERC20Votes token, ILlamaCore llamaCore, - ILlamaTokenClockAdapter clockAdapter, + ILlamaTokenAdapter tokenAdapter, uint256 nonce, uint8 role, uint16 voteQuorumPct, @@ -173,14 +173,14 @@ contract LlamaTokenVotingFactory { keccak256(abi.encodePacked(msg.sender, address(llamaCore), address(token), nonce)) ) ); - caster.initialize(token, llamaCore, clockAdapter, role, voteQuorumPct, vetoQuorumPct); + caster.initialize(token, llamaCore, tokenAdapter, role, voteQuorumPct, vetoQuorumPct); } /// @dev Deploys and initiliazes a new `LlamaERC721TokenCaster` clone. function _deployLlamaERC721TokenCaster( ERC721Votes token, ILlamaCore llamaCore, - ILlamaTokenClockAdapter clockAdapter, + ILlamaTokenAdapter tokenAdapter, uint256 nonce, uint8 role, uint16 voteQuorumPct, @@ -192,6 +192,6 @@ contract LlamaTokenVotingFactory { keccak256(abi.encodePacked(msg.sender, address(llamaCore), address(token), nonce)) ) ); - caster.initialize(token, llamaCore, clockAdapter, role, voteQuorumPct, vetoQuorumPct); + caster.initialize(token, llamaCore, tokenAdapter, role, voteQuorumPct, vetoQuorumPct); } } diff --git a/src/token-voting/ILlamaTokenClockAdapter.sol b/src/token-voting/interfaces/ILlamaTokenAdapter.sol similarity index 52% rename from src/token-voting/ILlamaTokenClockAdapter.sol rename to src/token-voting/interfaces/ILlamaTokenAdapter.sol index 0d6fef0..3a4a48e 100644 --- a/src/token-voting/ILlamaTokenClockAdapter.sol +++ b/src/token-voting/interfaces/ILlamaTokenAdapter.sol @@ -1,24 +1,24 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.23; -/// @title ILlamaTokenClockAdapter +/// @title ILlamaTokenAdapter /// @author Llama (devsdosomething@llama.xyz) -/// @notice This contract provides an interface for clock adapters. Clock adapters enable voting tokens that don't use +/// @notice This contract provides an interface for clock _tokenAdapters. Clock _tokenAdapters enable voting tokens that +/// don't use /// timestamp-based checkpointing to work with the Llama token voting module. -interface ILlamaTokenClockAdapter { +interface ILlamaTokenAdapter { /// @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); + function checkIfInconsistentClock() external view; /// @notice Converts a timestamp to timepoint units. /// @param timestamp The timestamp to convert. /// @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. - function isClockModeSupported(string memory clockMode) external pure returns (bool); + function getPastVotes(address account, uint48 timepoint) external view returns (uint256); + + function getPastTotalSupply(uint48 timepoint) external view returns (uint256); } diff --git a/test/token-voting/LlamaTokenVotingFactory.t.sol b/test/token-voting/LlamaTokenVotingFactory.t.sol index 5c97cdc..1f50bd7 100644 --- a/test/token-voting/LlamaTokenVotingFactory.t.sol +++ b/test/token-voting/LlamaTokenVotingFactory.t.sol @@ -10,7 +10,7 @@ import {LlamaTokenVotingTestSetup} from "test/token-voting/LlamaTokenVotingTestS import {ActionInfo} from "src/lib/Structs.sol"; import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; import {ILlamaPolicy} from "src/interfaces/ILlamaPolicy.sol"; -import {ILlamaTokenClockAdapter} from "src/token-voting/ILlamaTokenClockAdapter.sol"; +import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; import {LlamaERC20TokenActionCreator} from "src/token-voting/LlamaERC20TokenActionCreator.sol"; import {LlamaERC20TokenCaster} from "src/token-voting/LlamaERC20TokenCaster.sol"; import {LlamaERC721TokenActionCreator} from "src/token-voting/LlamaERC721TokenActionCreator.sol"; @@ -22,7 +22,7 @@ contract LlamaTokenVotingFactoryTest is LlamaTokenVotingTestSetup { address indexed deployer, ILlamaCore indexed llamaCore, address indexed token, - ILlamaTokenClockAdapter clockAdapter, + ILlamaTokenAdapter tokenAdapter, uint256 nonce, bool isERC20, uint8 actionCreatorRole, diff --git a/test/token-voting/LlamaTokenVotingTestSetup.sol b/test/token-voting/LlamaTokenVotingTestSetup.sol index 19786eb..82ff260 100644 --- a/test/token-voting/LlamaTokenVotingTestSetup.sol +++ b/test/token-voting/LlamaTokenVotingTestSetup.sol @@ -15,7 +15,7 @@ import {ILlamaPolicy} from "src/interfaces/ILlamaPolicy.sol"; import {ILlamaRelativeStrategyBase} from "src/interfaces/ILlamaRelativeStrategyBase.sol"; import {ILlamaStrategy} from "src/interfaces/ILlamaStrategy.sol"; import {RoleDescription} from "src/lib/UDVTs.sol"; -import {ILlamaTokenClockAdapter} from "src/token-voting/ILlamaTokenClockAdapter.sol"; +import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; import {LlamaERC20TokenActionCreator} from "src/token-voting/LlamaERC20TokenActionCreator.sol"; import {LlamaERC20TokenCaster} from "src/token-voting/LlamaERC20TokenCaster.sol"; import {LlamaERC721TokenActionCreator} from "src/token-voting/LlamaERC721TokenActionCreator.sol"; @@ -38,8 +38,8 @@ contract LlamaTokenVotingTestSetup is LlamaPeripheryTestSetup, DeployLlamaTokenV 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. - ILlamaTokenClockAdapter constant LLAMA_TOKEN_TIMESTAMP_ADAPTER = ILlamaTokenClockAdapter(address(0)); + // tokenAdapter. + ILlamaTokenAdapter constant LLAMA_TOKEN_TIMESTAMP_ADAPTER = ILlamaTokenAdapter(address(0)); // Votes Tokens MockERC20Votes public erc20VotesToken; From 1da468b6def65ba0633d7fff43eb77d47e09ab59 Mon Sep 17 00:00:00 2001 From: Austin Green Date: Thu, 14 Dec 2023 19:13:01 -0500 Subject: [PATCH 02/23] add clock mode changed check --- src/token-voting/LlamaTokenActionCreator.sol | 3 +++ src/token-voting/LlamaTokenAdapter.sol | 2 +- src/token-voting/LlamaTokenCaster.sol | 11 ++++++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/token-voting/LlamaTokenActionCreator.sol b/src/token-voting/LlamaTokenActionCreator.sol index 326b774..915095c 100644 --- a/src/token-voting/LlamaTokenActionCreator.sol +++ b/src/token-voting/LlamaTokenActionCreator.sol @@ -244,6 +244,9 @@ abstract contract LlamaTokenActionCreator is Initializable { bytes calldata data, string memory description ) internal returns (uint256 actionId) { + // Reverts if clock or CLOCK_MODE() has changed + tokenAdapter.checkIfInconsistentClock(); + uint256 balance = tokenAdapter.getPastVotes(tokenHolder, tokenAdapter.clock() - 1); if (balance < creationThreshold) revert InsufficientBalance(balance); diff --git a/src/token-voting/LlamaTokenAdapter.sol b/src/token-voting/LlamaTokenAdapter.sol index d0ce295..8bfadb7 100644 --- a/src/token-voting/LlamaTokenAdapter.sol +++ b/src/token-voting/LlamaTokenAdapter.sol @@ -45,7 +45,7 @@ contract LlamaTokenAdapter is ILlamaTokenAdapter { if (hasClockChanged || hasClockModeChanged) revert ERC6372InconsistentClock(); } - function timestampToTimepoint(uint256 timestamp) external view returns (uint48 timepoint) { + function timestampToTimepoint(uint256 timestamp) external pure returns (uint48 timepoint) { return LlamaUtils.toUint48(timestamp); } diff --git a/src/token-voting/LlamaTokenCaster.sol b/src/token-voting/LlamaTokenCaster.sol index 0325c5a..da2a901 100644 --- a/src/token-voting/LlamaTokenCaster.sol +++ b/src/token-voting/LlamaTokenCaster.sol @@ -282,6 +282,9 @@ abstract contract LlamaTokenCaster is Initializable { if (block.timestamp > action.creationTime + approvalPeriod) revert SubmissionPeriodOver(); + // Reverts if clock or CLOCK_MODE() has changed + tokenAdapter.checkIfInconsistentClock(); + uint256 totalSupply = tokenAdapter.getPastTotalSupply(tokenAdapter.timestampToTimepoint(action.creationTime) - 1); uint96 votesFor = casts[actionInfo.id].votesFor; uint96 votesAgainst = casts[actionInfo.id].votesAgainst; @@ -312,6 +315,9 @@ abstract contract LlamaTokenCaster is Initializable { } if (block.timestamp >= action.minExecutionTime) revert SubmissionPeriodOver(); + // Reverts if clock or CLOCK_MODE() has changed + tokenAdapter.checkIfInconsistentClock(); + uint256 totalSupply = tokenAdapter.getPastTotalSupply(tokenAdapter.timestampToTimepoint(action.creationTime) - 1); uint96 vetoesFor = casts[actionInfo.id].vetoesFor; uint96 vetoesAgainst = casts[actionInfo.id].vetoesAgainst; @@ -426,8 +432,11 @@ abstract contract LlamaTokenCaster is Initializable { return weight; } - function _preCastAssertions(uint8 support) internal pure { + function _preCastAssertions(uint8 support) internal view { if (support > uint8(VoteType.Abstain)) revert InvalidSupport(support); + + // Reverts if clock or CLOCK_MODE() has changed + tokenAdapter.checkIfInconsistentClock(); } /// @dev Sets the voting quorum and vetoing quorum. From d90e7f3710ffc0024b9aef3eb8e16bfe80469124 Mon Sep 17 00:00:00 2001 From: Austin Green Date: Fri, 15 Dec 2023 00:19:39 -0500 Subject: [PATCH 03/23] followups --- src/token-voting/LlamaTokenAdapter.sol | 11 ++++++++--- src/token-voting/interfaces/ILlamaTokenAdapter.sol | 11 ++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/token-voting/LlamaTokenAdapter.sol b/src/token-voting/LlamaTokenAdapter.sol index 8bfadb7..3d2eb43 100644 --- a/src/token-voting/LlamaTokenAdapter.sol +++ b/src/token-voting/LlamaTokenAdapter.sol @@ -19,17 +19,18 @@ contract LlamaTokenAdapter is ILlamaTokenAdapter { constructor(IVotes _token, string memory _clockMode) { token = _token; - if (keccak256(abi.encodePacked(_clockMode)) != keccak256(abi.encodePacked(""))) { - CLOCK_MODE = _clockMode; - } else { + if (keccak256(abi.encodePacked(_clockMode)) == keccak256(abi.encodePacked(""))) { try IERC6372(address(token)).CLOCK_MODE() returns (string memory mode) { CLOCK_MODE = mode; } catch { CLOCK_MODE = "mode=timestamp"; } + } else { + CLOCK_MODE = _clockMode; } } + /// @inheritdoc ILlamaTokenAdapter function clock() public view returns (uint48 timepoint) { try IERC6372(address(token)).clock() returns (uint48 tokenTimepoint) { timepoint = tokenTimepoint; @@ -38,6 +39,7 @@ contract LlamaTokenAdapter is ILlamaTokenAdapter { } } + /// @inheritdoc ILlamaTokenAdapter function checkIfInconsistentClock() external view { bool hasClockChanged = _hasClockChanged(); bool hasClockModeChanged = _hasClockModeChanged(); @@ -45,14 +47,17 @@ contract LlamaTokenAdapter is ILlamaTokenAdapter { if (hasClockChanged || hasClockModeChanged) revert ERC6372InconsistentClock(); } + /// @inheritdoc ILlamaTokenAdapter function timestampToTimepoint(uint256 timestamp) external pure returns (uint48 timepoint) { return LlamaUtils.toUint48(timestamp); } + /// @inheritdoc ILlamaTokenAdapter function getPastVotes(address account, uint48 timepoint) external view returns (uint256) { return token.getPastVotes(account, timepoint); } + /// @inheritdoc ILlamaTokenAdapter function getPastTotalSupply(uint48 timepoint) external view returns (uint256) { return token.getPastTotalSupply(timepoint); } diff --git a/src/token-voting/interfaces/ILlamaTokenAdapter.sol b/src/token-voting/interfaces/ILlamaTokenAdapter.sol index 3a4a48e..40dd983 100644 --- a/src/token-voting/interfaces/ILlamaTokenAdapter.sol +++ b/src/token-voting/interfaces/ILlamaTokenAdapter.sol @@ -3,14 +3,14 @@ pragma solidity ^0.8.23; /// @title ILlamaTokenAdapter /// @author Llama (devsdosomething@llama.xyz) -/// @notice This contract provides an interface for clock _tokenAdapters. Clock _tokenAdapters enable voting tokens that -/// don't use -/// timestamp-based checkpointing to work with the Llama token voting module. +/// @notice This contract provides an interface for voting token adapters. interface ILlamaTokenAdapter { /// @notice Returns the current timepoint according to the token's clock. /// @return timepoint the current timepoint function clock() external view returns (uint48 timepoint); + /// @notice Reverts if the token's CLOCK_MODE changes from what's in the adapter or if the clock() function doesn't + /// return the correct timepoint based on CLOCK_MODE. function checkIfInconsistentClock() external view; /// @notice Converts a timestamp to timepoint units. @@ -18,7 +18,12 @@ interface ILlamaTokenAdapter { /// @return timepoint the current timepoint function timestampToTimepoint(uint256 timestamp) external view returns (uint48 timepoint); + /// @notice Get the voting balance of a token holder at a specified past timepoint. + /// @param account The token holder's address. + /// @param timepoint The timepoint at which to get the voting balance. function getPastVotes(address account, uint48 timepoint) external view returns (uint256); + /// @notice Get the total supply of a token at a specified past timepoint. + /// @param timepoint The timepoint at which to get the total supply. function getPastTotalSupply(uint48 timepoint) external view returns (uint256); } From 5915799a3e7832b572c0cb846c8293681924e540 Mon Sep 17 00:00:00 2001 From: Austin Green Date: Fri, 15 Dec 2023 00:27:44 -0500 Subject: [PATCH 04/23] followups --- .../LlamaERC20TokenActionCreator.sol | 16 ++++------------ src/token-voting/LlamaERC20TokenCaster.sol | 6 ------ .../LlamaERC721TokenActionCreator.sol | 17 ++++------------- src/token-voting/LlamaERC721TokenCaster.sol | 7 ------- .../LlamaTokenAdapterTimestamp.sol} | 17 ++++------------- 5 files changed, 12 insertions(+), 51 deletions(-) rename src/token-voting/{LlamaTokenAdapter.sol => token-adapters/LlamaTokenAdapterTimestamp.sol} (83%) diff --git a/src/token-voting/LlamaERC20TokenActionCreator.sol b/src/token-voting/LlamaERC20TokenActionCreator.sol index d1522b5..f2a145e 100644 --- a/src/token-voting/LlamaERC20TokenActionCreator.sol +++ b/src/token-voting/LlamaERC20TokenActionCreator.sol @@ -12,9 +12,6 @@ import {LlamaTokenActionCreator} from "src/token-voting/LlamaTokenActionCreator. /// @notice This contract lets holders of a specified `ERC20Votes` token create actions on a llama instance if their /// token balance is greater than or equal to the creation threshold. contract LlamaERC20TokenActionCreator is LlamaTokenActionCreator { - /// @notice The ERC20 token to be used for voting. - ERC20Votes public token; - /// @dev This contract is deployed as a minimal proxy from the factory's `deploy` function. The /// `_disableInitializers` locks the implementation (logic) contract, preventing any future initialization of it. constructor() { @@ -24,21 +21,16 @@ contract LlamaERC20TokenActionCreator is LlamaTokenActionCreator { /// @notice Initializes a new `LlamaERC20TokenActionCreator` clone. /// @dev This function is called by the `deploy` function in the `LlamaTokenVotingFactory` contract. /// The `initializer` modifier ensures that this function can be invoked at most once. - /// @param _token The ERC20 token to be used for voting. /// @param _llamaCore The `LlamaCore` contract for this Llama instance. /// @param _role The role used by this contract to cast approvals and disapprovals. /// @param _creationThreshold The default number of tokens required to create an action. This must /// be in the same decimals as the token. For example, if the token has 18 decimals and you want a /// creation threshold of 1000 tokens, pass in 1000e18. - function initialize( - ERC20Votes _token, - ILlamaCore _llamaCore, - ILlamaTokenAdapter _tokenAdapter, - uint8 _role, - uint256 _creationThreshold - ) external initializer { + function initialize(ILlamaCore _llamaCore, ILlamaTokenAdapter _tokenAdapter, uint8 _role, uint256 _creationThreshold) + external + initializer + { __initializeLlamaTokenActionCreatorMinimalProxy(_llamaCore, _tokenAdapter, _role, _creationThreshold); - token = _token; uint256 totalSupply = tokenAdapter.getPastTotalSupply(tokenAdapter.clock() - 1); if (totalSupply == 0) revert InvalidTokenAddress(); if (_creationThreshold > totalSupply) revert InvalidCreationThreshold(); diff --git a/src/token-voting/LlamaERC20TokenCaster.sol b/src/token-voting/LlamaERC20TokenCaster.sol index c41b3f2..e27ec5a 100644 --- a/src/token-voting/LlamaERC20TokenCaster.sol +++ b/src/token-voting/LlamaERC20TokenCaster.sol @@ -12,9 +12,6 @@ import {LlamaTokenCaster} from "src/token-voting/LlamaTokenCaster.sol"; /// @notice This contract lets holders of a given governance `ERC20Votes` token collectively cast an approval or /// disapproval on created actions. contract LlamaERC20TokenCaster is LlamaTokenCaster { - /// @notice The ERC20 token to be used for voting. - ERC20Votes public token; - /// @dev This contract is deployed as a minimal proxy from the factory's `deploy` function. The /// `_disableInitializers` locks the implementation (logic) contract, preventing any future initialization of it. constructor() { @@ -24,13 +21,11 @@ contract LlamaERC20TokenCaster is LlamaTokenCaster { /// @notice Initializes a new `LlamaERC20TokenCaster` clone. /// @dev This function is called by the `deploy` function in the `LlamaTokenVotingFactory` contract. /// The `initializer` modifier ensures that this function can be invoked at most once. - /// @param _token The ERC20 token to be used for voting. /// @param _llamaCore The `LlamaCore` contract for this Llama instance. /// @param _role The role used by this contract to cast approvals and disapprovals. /// @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 initialize( - ERC20Votes _token, ILlamaCore _llamaCore, ILlamaTokenAdapter _tokenAdapter, uint8 _role, @@ -38,7 +33,6 @@ contract LlamaERC20TokenCaster is LlamaTokenCaster { uint16 _vetoQuorumPct ) external initializer { __initializeLlamaTokenCasterMinimalProxy(_llamaCore, _tokenAdapter, _role, _voteQuorumPct, _vetoQuorumPct); - token = _token; uint256 totalSupply = tokenAdapter.getPastTotalSupply(tokenAdapter.clock() - 1); if (totalSupply == 0) revert InvalidTokenAddress(); } diff --git a/src/token-voting/LlamaERC721TokenActionCreator.sol b/src/token-voting/LlamaERC721TokenActionCreator.sol index 5bee9f9..eae3f50 100644 --- a/src/token-voting/LlamaERC721TokenActionCreator.sol +++ b/src/token-voting/LlamaERC721TokenActionCreator.sol @@ -13,9 +13,6 @@ import {LlamaTokenActionCreator} from "src/token-voting/LlamaTokenActionCreator. /// @notice This contract lets holders of a given governance ERC721Votes token create actions on the llama instance if /// they hold enough tokens. contract LlamaERC721TokenActionCreator is LlamaTokenActionCreator { - /// @notice The ERC721 token to be used for voting. - ERC721Votes public token; - /// @dev This contract is deployed as a minimal proxy from the factory's `deploy` function. The /// `_disableInitializers` locks the implementation (logic) contract, preventing any future initialization of it. constructor() { @@ -25,22 +22,16 @@ contract LlamaERC721TokenActionCreator is LlamaTokenActionCreator { /// @notice Initializes a new `LlamaERC721TokenActionCreator` clone. /// @dev This function is called by the `deploy` function in the `LlamaTokenVotingFactory` contract. /// The `initializer` modifier ensures that this function can be invoked at most once. - /// @param _token The ERC721 token to be used for voting. /// @param _llamaCore The `LlamaCore` contract for this Llama instance. /// @param _role The role used by this contract to cast approvals and disapprovals. /// @param _creationThreshold The default number of tokens required to create an action. This must /// be in the same decimals as the token. For example, if the token has 18 decimals and you want a /// creation threshold of 1000 tokens, pass in 1000e18. - function initialize( - ERC721Votes _token, - ILlamaCore _llamaCore, - ILlamaTokenAdapter _tokenAdapter, - uint8 _role, - uint256 _creationThreshold - ) external initializer { + function initialize(ILlamaCore _llamaCore, ILlamaTokenAdapter _tokenAdapter, uint8 _role, uint256 _creationThreshold) + external + initializer + { __initializeLlamaTokenActionCreatorMinimalProxy(_llamaCore, _tokenAdapter, _role, _creationThreshold); - token = _token; - if (!token.supportsInterface(type(IERC721).interfaceId)) revert InvalidTokenAddress(); uint256 totalSupply = tokenAdapter.getPastTotalSupply(tokenAdapter.clock() - 1); if (totalSupply == 0) revert InvalidTokenAddress(); if (_creationThreshold > totalSupply) revert InvalidCreationThreshold(); diff --git a/src/token-voting/LlamaERC721TokenCaster.sol b/src/token-voting/LlamaERC721TokenCaster.sol index 26dcede..9b173be 100644 --- a/src/token-voting/LlamaERC721TokenCaster.sol +++ b/src/token-voting/LlamaERC721TokenCaster.sol @@ -13,9 +13,6 @@ import {LlamaTokenCaster} from "src/token-voting/LlamaTokenCaster.sol"; /// disapproval on created actions. contract LlamaERC721TokenCaster is LlamaTokenCaster { - /// @notice The ERC721 token to be used for voting. - ERC721Votes public token; - /// @dev This contract is deployed as a minimal proxy from the factory's `deploy` function. The /// `_disableInitializers` locks the implementation (logic) contract, preventing any future initialization of it. constructor() { @@ -25,13 +22,11 @@ contract LlamaERC721TokenCaster is LlamaTokenCaster { /// @notice Initializes a new `LlamaERC721TokenCaster` clone. /// @dev This function is called by the `deploy` function in the `LlamaTokenVotingFactory` contract. /// The `initializer` modifier ensures that this function can be invoked at most once. - /// @param _token The ERC721 token to be used for voting. /// @param _llamaCore The `LlamaCore` contract for this Llama instance. /// @param _role The role used by this contract to cast approvals and disapprovals. /// @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 initialize( - ERC721Votes _token, ILlamaCore _llamaCore, ILlamaTokenAdapter _tokenAdapter, uint8 _role, @@ -39,8 +34,6 @@ contract LlamaERC721TokenCaster is LlamaTokenCaster { uint16 _vetoQuorumPct ) external initializer { __initializeLlamaTokenCasterMinimalProxy(_llamaCore, _tokenAdapter, _role, _voteQuorumPct, _vetoQuorumPct); - token = _token; - if (!token.supportsInterface(type(IERC721).interfaceId)) revert InvalidTokenAddress(); uint256 totalSupply = tokenAdapter.getPastTotalSupply(tokenAdapter.clock() - 1); if (totalSupply == 0) revert InvalidTokenAddress(); } diff --git a/src/token-voting/LlamaTokenAdapter.sol b/src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol similarity index 83% rename from src/token-voting/LlamaTokenAdapter.sol rename to src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol index 3d2eb43..16d56f2 100644 --- a/src/token-voting/LlamaTokenAdapter.sol +++ b/src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol @@ -7,27 +7,18 @@ import {IERC6372} from "@openzeppelin/interfaces/IERC6372.sol"; import {LlamaUtils} from "src/lib/LlamaUtils.sol"; import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; -contract LlamaTokenAdapter is ILlamaTokenAdapter { +contract LlamaTokenAdapterTimestamp is ILlamaTokenAdapter { /// @dev The clock was incorrectly modified. error ERC6372InconsistentClock(); /// @notice The token to be used for voting. - IVotes public token; + IVotes public immutable token; string CLOCK_MODE; - constructor(IVotes _token, string memory _clockMode) { + constructor(IVotes _token) { token = _token; - - if (keccak256(abi.encodePacked(_clockMode)) == keccak256(abi.encodePacked(""))) { - try IERC6372(address(token)).CLOCK_MODE() returns (string memory mode) { - CLOCK_MODE = mode; - } catch { - CLOCK_MODE = "mode=timestamp"; - } - } else { - CLOCK_MODE = _clockMode; - } + CLOCK_MODE = "mode=timestamp"; } /// @inheritdoc ILlamaTokenAdapter From 2f4de426ae0a7d4da76cec5e02517da962656743 Mon Sep 17 00:00:00 2001 From: Austin Green Date: Fri, 15 Dec 2023 12:07:09 -0500 Subject: [PATCH 05/23] refactor: remove seperate ERC-20 and ERC-721 contracts and turn the adapter into a proxy contract --- .../LlamaERC20TokenActionCreator.sol | 38 --- src/token-voting/LlamaERC20TokenCaster.sol | 39 --- .../LlamaERC721TokenActionCreator.sol | 39 --- src/token-voting/LlamaERC721TokenCaster.sol | 40 --- src/token-voting/LlamaTokenActionCreator.sol | 26 +- src/token-voting/LlamaTokenCaster.sol | 20 +- src/token-voting/LlamaTokenVotingFactory.sol | 259 +++++++----------- .../interfaces/ILlamaTokenAdapter.sol | 15 + .../LlamaTokenAdapterTimestamp.sol | 24 +- 9 files changed, 174 insertions(+), 326 deletions(-) delete mode 100644 src/token-voting/LlamaERC20TokenActionCreator.sol delete mode 100644 src/token-voting/LlamaERC20TokenCaster.sol delete mode 100644 src/token-voting/LlamaERC721TokenActionCreator.sol delete mode 100644 src/token-voting/LlamaERC721TokenCaster.sol diff --git a/src/token-voting/LlamaERC20TokenActionCreator.sol b/src/token-voting/LlamaERC20TokenActionCreator.sol deleted file mode 100644 index f2a145e..0000000 --- a/src/token-voting/LlamaERC20TokenActionCreator.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {ERC20Votes} from "@openzeppelin/token/ERC20/extensions/ERC20Votes.sol"; - -import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; -import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; -import {LlamaTokenActionCreator} from "src/token-voting/LlamaTokenActionCreator.sol"; - -/// @title LlamaERC20TokenActionCreator -/// @author Llama (devsdosomething@llama.xyz) -/// @notice This contract lets holders of a specified `ERC20Votes` token create actions on a llama instance if their -/// token balance is greater than or equal to the creation threshold. -contract LlamaERC20TokenActionCreator is LlamaTokenActionCreator { - /// @dev This contract is deployed as a minimal proxy from the factory's `deploy` function. The - /// `_disableInitializers` locks the implementation (logic) contract, preventing any future initialization of it. - constructor() { - _disableInitializers(); - } - - /// @notice Initializes a new `LlamaERC20TokenActionCreator` clone. - /// @dev This function is called by the `deploy` function in the `LlamaTokenVotingFactory` contract. - /// The `initializer` modifier ensures that this function can be invoked at most once. - /// @param _llamaCore The `LlamaCore` contract for this Llama instance. - /// @param _role The role used by this contract to cast approvals and disapprovals. - /// @param _creationThreshold The default number of tokens required to create an action. This must - /// be in the same decimals as the token. For example, if the token has 18 decimals and you want a - /// creation threshold of 1000 tokens, pass in 1000e18. - function initialize(ILlamaCore _llamaCore, ILlamaTokenAdapter _tokenAdapter, uint8 _role, uint256 _creationThreshold) - external - initializer - { - __initializeLlamaTokenActionCreatorMinimalProxy(_llamaCore, _tokenAdapter, _role, _creationThreshold); - uint256 totalSupply = tokenAdapter.getPastTotalSupply(tokenAdapter.clock() - 1); - if (totalSupply == 0) revert InvalidTokenAddress(); - if (_creationThreshold > totalSupply) revert InvalidCreationThreshold(); - } -} diff --git a/src/token-voting/LlamaERC20TokenCaster.sol b/src/token-voting/LlamaERC20TokenCaster.sol deleted file mode 100644 index e27ec5a..0000000 --- a/src/token-voting/LlamaERC20TokenCaster.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {ERC20Votes} from "@openzeppelin/token/ERC20/extensions/ERC20Votes.sol"; - -import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; -import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; -import {LlamaTokenCaster} from "src/token-voting/LlamaTokenCaster.sol"; - -/// @title LlamaERC20TokenCaster -/// @author Llama (devsdosomething@llama.xyz) -/// @notice This contract lets holders of a given governance `ERC20Votes` token collectively cast an approval or -/// disapproval on created actions. -contract LlamaERC20TokenCaster is LlamaTokenCaster { - /// @dev This contract is deployed as a minimal proxy from the factory's `deploy` function. The - /// `_disableInitializers` locks the implementation (logic) contract, preventing any future initialization of it. - constructor() { - _disableInitializers(); - } - - /// @notice Initializes a new `LlamaERC20TokenCaster` clone. - /// @dev This function is called by the `deploy` function in the `LlamaTokenVotingFactory` contract. - /// The `initializer` modifier ensures that this function can be invoked at most once. - /// @param _llamaCore The `LlamaCore` contract for this Llama instance. - /// @param _role The role used by this contract to cast approvals and disapprovals. - /// @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 initialize( - ILlamaCore _llamaCore, - ILlamaTokenAdapter _tokenAdapter, - uint8 _role, - uint16 _voteQuorumPct, - uint16 _vetoQuorumPct - ) external initializer { - __initializeLlamaTokenCasterMinimalProxy(_llamaCore, _tokenAdapter, _role, _voteQuorumPct, _vetoQuorumPct); - uint256 totalSupply = tokenAdapter.getPastTotalSupply(tokenAdapter.clock() - 1); - if (totalSupply == 0) revert InvalidTokenAddress(); - } -} diff --git a/src/token-voting/LlamaERC721TokenActionCreator.sol b/src/token-voting/LlamaERC721TokenActionCreator.sol deleted file mode 100644 index eae3f50..0000000 --- a/src/token-voting/LlamaERC721TokenActionCreator.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {ERC721Votes} from "@openzeppelin/token/ERC721/extensions/ERC721Votes.sol"; -import {IERC721} from "@openzeppelin/token/ERC721/IERC721.sol"; - -import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; -import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; -import {LlamaTokenActionCreator} from "src/token-voting/LlamaTokenActionCreator.sol"; - -/// @title LlamaERC721TokenActionCreator -/// @author Llama (devsdosomething@llama.xyz) -/// @notice This contract lets holders of a given governance ERC721Votes token create actions on the llama instance if -/// they hold enough tokens. -contract LlamaERC721TokenActionCreator is LlamaTokenActionCreator { - /// @dev This contract is deployed as a minimal proxy from the factory's `deploy` function. The - /// `_disableInitializers` locks the implementation (logic) contract, preventing any future initialization of it. - constructor() { - _disableInitializers(); - } - - /// @notice Initializes a new `LlamaERC721TokenActionCreator` clone. - /// @dev This function is called by the `deploy` function in the `LlamaTokenVotingFactory` contract. - /// The `initializer` modifier ensures that this function can be invoked at most once. - /// @param _llamaCore The `LlamaCore` contract for this Llama instance. - /// @param _role The role used by this contract to cast approvals and disapprovals. - /// @param _creationThreshold The default number of tokens required to create an action. This must - /// be in the same decimals as the token. For example, if the token has 18 decimals and you want a - /// creation threshold of 1000 tokens, pass in 1000e18. - function initialize(ILlamaCore _llamaCore, ILlamaTokenAdapter _tokenAdapter, uint8 _role, uint256 _creationThreshold) - external - initializer - { - __initializeLlamaTokenActionCreatorMinimalProxy(_llamaCore, _tokenAdapter, _role, _creationThreshold); - uint256 totalSupply = tokenAdapter.getPastTotalSupply(tokenAdapter.clock() - 1); - if (totalSupply == 0) revert InvalidTokenAddress(); - if (_creationThreshold > totalSupply) revert InvalidCreationThreshold(); - } -} diff --git a/src/token-voting/LlamaERC721TokenCaster.sol b/src/token-voting/LlamaERC721TokenCaster.sol deleted file mode 100644 index 9b173be..0000000 --- a/src/token-voting/LlamaERC721TokenCaster.sol +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {ERC721Votes} from "@openzeppelin/token/ERC721/extensions/ERC721Votes.sol"; -import {IERC721} from "@openzeppelin/token/ERC721/IERC721.sol"; - -import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; -import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; -import {LlamaTokenCaster} from "src/token-voting/LlamaTokenCaster.sol"; -/// @title LlamaERC721TokenCaster -/// @author Llama (devsdosomething@llama.xyz) -/// @notice This contract lets holders of a given governance `ERC721Votes` token collectively cast an approval or -/// disapproval on created actions. - -contract LlamaERC721TokenCaster is LlamaTokenCaster { - /// @dev This contract is deployed as a minimal proxy from the factory's `deploy` function. The - /// `_disableInitializers` locks the implementation (logic) contract, preventing any future initialization of it. - constructor() { - _disableInitializers(); - } - - /// @notice Initializes a new `LlamaERC721TokenCaster` clone. - /// @dev This function is called by the `deploy` function in the `LlamaTokenVotingFactory` contract. - /// The `initializer` modifier ensures that this function can be invoked at most once. - /// @param _llamaCore The `LlamaCore` contract for this Llama instance. - /// @param _role The role used by this contract to cast approvals and disapprovals. - /// @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 initialize( - ILlamaCore _llamaCore, - ILlamaTokenAdapter _tokenAdapter, - uint8 _role, - uint16 _voteQuorumPct, - uint16 _vetoQuorumPct - ) external initializer { - __initializeLlamaTokenCasterMinimalProxy(_llamaCore, _tokenAdapter, _role, _voteQuorumPct, _vetoQuorumPct); - uint256 totalSupply = tokenAdapter.getPastTotalSupply(tokenAdapter.clock() - 1); - if (totalSupply == 0) revert InvalidTokenAddress(); - } -} diff --git a/src/token-voting/LlamaTokenActionCreator.sol b/src/token-voting/LlamaTokenActionCreator.sol index 915095c..5e1911e 100644 --- a/src/token-voting/LlamaTokenActionCreator.sol +++ b/src/token-voting/LlamaTokenActionCreator.sol @@ -17,7 +17,7 @@ import {LlamaUtils} from "src/lib/LlamaUtils.sol"; /// it must hold a Policy from the specified `LlamaCore` instance to actually be able to create an action. The /// instance's policy encodes what actions this contract is allowed to create, and attempting to create an action that /// is not allowed by the policy will result in a revert. -abstract contract LlamaTokenActionCreator is Initializable { +contract LlamaTokenActionCreator is Initializable { // ======================== // ======== Errors ======== // ======================== @@ -110,18 +110,24 @@ abstract contract LlamaTokenActionCreator is Initializable { // ======== Initialization ======== // ================================ - /// @dev This will be called by the `initialize` of the inheriting contract. + /// @dev This contract is deployed as a minimal proxy from the factory's `deploy` function. The + /// `_disableInitializers` locks the implementation (logic) contract, preventing any future initialization of it. + constructor() { + _disableInitializers(); + } + + /// @notice Initializes a new `LlamaERC20TokenActionCreator` clone. + /// @dev This function is called by the `deploy` function in the `LlamaTokenVotingFactory` contract. + /// The `initializer` modifier ensures that this function can be invoked at most once. /// @param _llamaCore The `LlamaCore` contract for this Llama instance. /// @param _role The role used by this contract to cast approvals and disapprovals. /// @param _creationThreshold The default number of tokens required to create an action. This must /// be in the same decimals as the token. For example, if the token has 18 decimals and you want a /// creation threshold of 1000 tokens, pass in 1000e18. - function __initializeLlamaTokenActionCreatorMinimalProxy( - ILlamaCore _llamaCore, - ILlamaTokenAdapter _tokenAdapter, - uint8 _role, - uint256 _creationThreshold - ) internal { + function initialize(ILlamaCore _llamaCore, ILlamaTokenAdapter _tokenAdapter, uint8 _role, uint256 _creationThreshold) + external + initializer + { if (_llamaCore.actionsCount() < 0) revert InvalidLlamaCoreAddress(); if (_role > _llamaCore.policy().numRoles()) revert RoleNotInitialized(_role); @@ -129,6 +135,10 @@ abstract contract LlamaTokenActionCreator is Initializable { tokenAdapter = _tokenAdapter; role = _role; _setActionThreshold(_creationThreshold); + + uint256 totalSupply = tokenAdapter.getPastTotalSupply(tokenAdapter.clock() - 1); + if (totalSupply == 0) revert InvalidTokenAddress(); + if (_creationThreshold > totalSupply) revert InvalidCreationThreshold(); } // =========================================== diff --git a/src/token-voting/LlamaTokenCaster.sol b/src/token-voting/LlamaTokenCaster.sol index da2a901..c42448f 100644 --- a/src/token-voting/LlamaTokenCaster.sol +++ b/src/token-voting/LlamaTokenCaster.sol @@ -19,7 +19,7 @@ import {Action, ActionInfo} from "src/lib/Structs.sol"; /// it must hold a Policy from the specified `LlamaCore` instance to actually be able to cast on an action. This /// 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 { +contract LlamaTokenCaster is Initializable { using QuorumCheckpoints for QuorumCheckpoints.History; // ========================= // ======== Structs ======== @@ -185,18 +185,27 @@ abstract contract LlamaTokenCaster is Initializable { // ======== Initialization ======== // ================================ - /// @dev This will be called by the `initialize` of the inheriting contract. + /// @dev This contract is deployed as a minimal proxy from the factory's `deploy` function. The + /// `_disableInitializers` locks the implementation (logic) contract, preventing any future initialization of it. + constructor() { + _disableInitializers(); + } + + /// @notice Initializes a new `LlamaERC20TokenCaster` clone. + /// @dev This function is called by the `deploy` function in the `LlamaTokenVotingFactory` contract. + /// The `initializer` modifier ensures that this function can be invoked at most once. /// @param _llamaCore The `LlamaCore` contract for this Llama instance. /// @param _role The role used by this contract to cast approvals and disapprovals. /// @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 __initializeLlamaTokenCasterMinimalProxy( + + function initialize( ILlamaCore _llamaCore, ILlamaTokenAdapter _tokenAdapter, uint8 _role, uint16 _voteQuorumPct, uint16 _vetoQuorumPct - ) internal { + ) external initializer { if (_llamaCore.actionsCount() < 0) revert InvalidLlamaCoreAddress(); if (_role > _llamaCore.policy().numRoles()) revert RoleNotInitialized(_role); @@ -204,6 +213,9 @@ abstract contract LlamaTokenCaster is Initializable { tokenAdapter = _tokenAdapter; role = _role; _setQuorumPct(_voteQuorumPct, _vetoQuorumPct); + + uint256 totalSupply = tokenAdapter.getPastTotalSupply(tokenAdapter.clock() - 1); + if (totalSupply == 0) revert InvalidTokenAddress(); } // =========================================== diff --git a/src/token-voting/LlamaTokenVotingFactory.sol b/src/token-voting/LlamaTokenVotingFactory.sol index 0e76856..6bb2201 100644 --- a/src/token-voting/LlamaTokenVotingFactory.sol +++ b/src/token-voting/LlamaTokenVotingFactory.sol @@ -2,196 +2,149 @@ pragma solidity ^0.8.23; import {Clones} from "@openzeppelin/proxy/Clones.sol"; -import {ERC20Votes} from "@openzeppelin/token/ERC20/extensions/ERC20Votes.sol"; -import {ERC721Votes} from "@openzeppelin/token/ERC721/extensions/ERC721Votes.sol"; +import {IVotes} from "@openzeppelin/governance/utils/IVotes.sol"; import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; -import {LlamaERC20TokenActionCreator} from "src/token-voting/LlamaERC20TokenActionCreator.sol"; -import {LlamaERC20TokenCaster} from "src/token-voting/LlamaERC20TokenCaster.sol"; -import {LlamaERC721TokenActionCreator} from "src/token-voting/LlamaERC721TokenActionCreator.sol"; -import {LlamaERC721TokenCaster} from "src/token-voting/LlamaERC721TokenCaster.sol"; +import {LlamaTokenActionCreator} from "src/token-voting/LlamaTokenActionCreator.sol"; +import {LlamaTokenCaster} from "src/token-voting/LlamaTokenCaster.sol"; /// @title LlamaTokenVotingFactory /// @author Llama (devsdosomething@llama.xyz) -/// @notice This contract lets llama instances deploy a token voting module in a single llama action. +/// @notice This contract enables Llama instances to deploy a token voting module. contract LlamaTokenVotingFactory { + // ========================= + // ======== Structs ======== + // ========================= + + /// @dev Configuration of a new Llama token voting module. + struct LlamaTokenVotingConfig { + ILlamaCore llamaCore; // The address of the Llama core. + IVotes token; // The address of the token to be used for voting. + ILlamaTokenAdapter tokenAdapterLogic; // The logic contract of the token adapter. + bytes adapterConfig; // The configuration of the token adapter. + uint256 nonce; // The nonce to be used in the salt of the deterministic deployment. + uint8 actionCreatorRole; // The role required by the `LlamaTokenActionCreator` to create an action. + uint8 casterRole; // The role required by the `LlamaTokenCaster` to cast approvals and disapprovals. + uint256 creationThreshold; // The number of tokens required to create an action. + uint16 voteQuorumPct; // The minimum percentage of tokens required to approve an action. + uint16 vetoQuorumPct; // The minimum percentage of tokens required to disapprove an action. + } + + // ======================== + // ======== Errors ======== + // ======================== + + /// @dev Thrown when a token adapter has been incorrectly configured. + error InvalidTokenAdapterConfig(); + + // ======================== + // ======== Events ======== + // ======================== + /// @dev Emitted when a new Llama token voting module is created. event LlamaTokenVotingInstanceCreated( address indexed deployer, ILlamaCore indexed llamaCore, - address indexed token, + IVotes indexed token, ILlamaTokenAdapter tokenAdapter, uint256 nonce, - bool isERC20, uint8 actionCreatorRole, uint8 casterRole, - address llamaTokenActionCreator, - address llamaTokenCaster, + LlamaTokenActionCreator llamaTokenActionCreator, + LlamaTokenCaster llamaTokenCaster, uint256 chainId ); - /// @notice The ERC20 Tokenholder Action Creator (logic) contract. - LlamaERC20TokenActionCreator public immutable ERC20_TOKEN_ACTION_CREATOR_LOGIC; - - /// @notice The ERC20 Tokenholder Caster (logic) contract. - LlamaERC20TokenCaster public immutable ERC20_TOKEN_CASTER_LOGIC; + // ================================================= + // ======== Constants and Storage Variables ======== + // ================================================= - /// @notice The ERC721 Tokenholder Action Creator (logic) contract. - LlamaERC721TokenActionCreator public immutable ERC721_TOKEN_ACTION_CREATOR_LOGIC; + /// @notice The Token Action Creator implementation (logic) contract. + LlamaTokenActionCreator public immutable LLAMA_TOKEN_ACTION_CREATOR_LOGIC; - /// @notice The ERC721 Tokenholder Caster (logic) contract. - LlamaERC721TokenCaster public immutable ERC721_TOKEN_CASTER_LOGIC; + /// @notice The Token Caster implementation (logic) contract. + LlamaTokenCaster public immutable LLAMA_TOKEN_CASTER_LOGIC; /// @dev Set the logic contracts used to deploy Token Voting modules. - constructor( - LlamaERC20TokenActionCreator llamaERC20TokenActionCreatorLogic, - LlamaERC20TokenCaster llamaERC20TokenCasterLogic, - LlamaERC721TokenActionCreator llamaERC721TokenActionCreatorLogic, - LlamaERC721TokenCaster llamaERC721TokenCasterLogic - ) { - ERC20_TOKEN_ACTION_CREATOR_LOGIC = llamaERC20TokenActionCreatorLogic; - ERC20_TOKEN_CASTER_LOGIC = llamaERC20TokenCasterLogic; - ERC721_TOKEN_ACTION_CREATOR_LOGIC = llamaERC721TokenActionCreatorLogic; - ERC721_TOKEN_CASTER_LOGIC = llamaERC721TokenCasterLogic; + constructor(LlamaTokenActionCreator LlamaTokenActionCreatorLogic, LlamaTokenCaster LlamaTokenCasterLogic) { + LLAMA_TOKEN_ACTION_CREATOR_LOGIC = LlamaTokenActionCreatorLogic; + LLAMA_TOKEN_CASTER_LOGIC = LlamaTokenCasterLogic; } - ///@notice Deploys a token voting module in a single function so it can be deployed in a llama action. - ///@param llamaCore The address of the Llama core. - ///@param token The address of the token to be used for voting. - ///@param nonce The nonce to be used in the salt of the deterministic deployment. - ///@param isERC20 Whether the token is an ERC20 or ERC721. - ///@param actionCreatorRole The role required by the `LlamaTokenActionCreator` to create an action. - ///@param casterRole The role required by the `LlamaTokenCaster` to cast approvals and disapprovals. - ///@param creationThreshold The number of tokens required to create an action. - ///@param voteQuorumPct The minimum percentage of tokens required to approve an action. - ///@param vetoQuorumPct The minimum percentage of tokens required to disapprove an action. - function deploy( - ILlamaCore llamaCore, - address token, - ILlamaTokenAdapter tokenAdapter, - uint256 nonce, - bool isERC20, - uint8 actionCreatorRole, - uint8 casterRole, - uint256 creationThreshold, - uint16 voteQuorumPct, - uint16 vetoQuorumPct - ) external returns (address actionCreator, address caster) { - if (isERC20) { - actionCreator = address( - _deployLlamaERC20TokenActionCreator( - ERC20Votes(token), llamaCore, tokenAdapter, nonce, actionCreatorRole, creationThreshold - ) - ); - caster = address( - _deployLlamaERC20TokenCaster( - ERC20Votes(token), llamaCore, tokenAdapter, nonce, casterRole, voteQuorumPct, vetoQuorumPct - ) - ); - } else { - actionCreator = address( - _deployLlamaERC721TokenActionCreator( - ERC721Votes(token), llamaCore, tokenAdapter, nonce, actionCreatorRole, creationThreshold - ) - ); - caster = address( - _deployLlamaERC721TokenCaster( - ERC721Votes(token), llamaCore, tokenAdapter, nonce, casterRole, voteQuorumPct, vetoQuorumPct - ) - ); - } - - emit LlamaTokenVotingInstanceCreated( - msg.sender, - llamaCore, - token, - tokenAdapter, - nonce, - isERC20, - actionCreatorRole, - casterRole, - actionCreator, - caster, - block.chainid + /// @notice Deploys a new Llama token voting module. + /// @param tokenVotingConfig The configuration of the new Llama token voting module. + /// @return actionCreator The address of the `LlamaTokenActionCreator` of the deployed token voting module. + /// @return caster The address of the `LlamaTokenCaster` of the deployed token voting module. + function deploy(LlamaTokenVotingConfig memory tokenVotingConfig) + external + returns (LlamaTokenActionCreator actionCreator, LlamaTokenCaster caster) + { + // Initialize token adapter based on provided logic address and config + ILlamaTokenAdapter tokenAdapter = ILlamaTokenAdapter( + Clones.cloneDeterministic( + address(tokenVotingConfig.tokenAdapterLogic), keccak256(tokenVotingConfig.adapterConfig) + ) ); - } + tokenAdapter.initialize(tokenVotingConfig.adapterConfig); - // ==================================== - // ======== Internal Functions ======== - // ==================================== + // Check to see if token adapter was correctly initialized + if (address(tokenAdapter.token()) == address(0)) revert InvalidTokenAdapterConfig(); + if (tokenAdapter.timestampToTimepoint(block.timestamp) >= 0) revert InvalidTokenAdapterConfig(); - /// @dev Deploys and initiliazes a new `LlamaERC20TokenActionCreator` clone. - function _deployLlamaERC20TokenActionCreator( - ERC20Votes token, - ILlamaCore llamaCore, - ILlamaTokenAdapter tokenAdapter, - uint256 nonce, - uint8 role, - uint256 creationThreshold - ) internal returns (LlamaERC20TokenActionCreator actionCreator) { - actionCreator = LlamaERC20TokenActionCreator( + // Reverts if clock is inconsistent + tokenAdapter.checkIfInconsistentClock(); + + // Deploy and initialize `LlamaTokenActionCreator` contract + actionCreator = LlamaTokenActionCreator( Clones.cloneDeterministic( - address(ERC20_TOKEN_ACTION_CREATOR_LOGIC), - keccak256(abi.encodePacked(msg.sender, address(llamaCore), address(token), nonce)) + address(LLAMA_TOKEN_ACTION_CREATOR_LOGIC), + keccak256( + abi.encodePacked( + msg.sender, address(tokenVotingConfig.llamaCore), address(tokenAdapter), tokenVotingConfig.nonce + ) + ) ) ); - actionCreator.initialize(token, llamaCore, tokenAdapter, role, creationThreshold); - } - /// @dev Deploys and initiliazes a new `LlamaERC721TokenActionCreator` clone. - function _deployLlamaERC721TokenActionCreator( - ERC721Votes token, - ILlamaCore llamaCore, - ILlamaTokenAdapter tokenAdapter, - uint256 nonce, - uint8 role, - uint256 creationThreshold - ) internal returns (LlamaERC721TokenActionCreator actionCreator) { - actionCreator = LlamaERC721TokenActionCreator( - Clones.cloneDeterministic( - address(ERC721_TOKEN_ACTION_CREATOR_LOGIC), - keccak256(abi.encodePacked(msg.sender, address(llamaCore), address(token), nonce)) - ) + actionCreator.initialize( + tokenVotingConfig.llamaCore, + tokenAdapter, + tokenVotingConfig.actionCreatorRole, + tokenVotingConfig.creationThreshold ); - actionCreator.initialize(token, llamaCore, tokenAdapter, role, creationThreshold); - } - /// @dev Deploys and initiliazes a new `LlamaERC20TokenCaster` clone. - function _deployLlamaERC20TokenCaster( - ERC20Votes token, - ILlamaCore llamaCore, - ILlamaTokenAdapter tokenAdapter, - uint256 nonce, - uint8 role, - uint16 voteQuorumPct, - uint16 vetoQuorumPct - ) internal returns (LlamaERC20TokenCaster caster) { - caster = LlamaERC20TokenCaster( + // Deploy and initialize `LlamaTokenCaster` contract + caster = LlamaTokenCaster( Clones.cloneDeterministic( - address(ERC20_TOKEN_CASTER_LOGIC), - keccak256(abi.encodePacked(msg.sender, address(llamaCore), address(token), nonce)) + address(LLAMA_TOKEN_CASTER_LOGIC), + keccak256( + abi.encodePacked( + msg.sender, address(tokenVotingConfig.llamaCore), address(tokenAdapter), tokenVotingConfig.nonce + ) + ) ) ); - caster.initialize(token, llamaCore, tokenAdapter, role, voteQuorumPct, vetoQuorumPct); - } - /// @dev Deploys and initiliazes a new `LlamaERC721TokenCaster` clone. - function _deployLlamaERC721TokenCaster( - ERC721Votes token, - ILlamaCore llamaCore, - ILlamaTokenAdapter tokenAdapter, - uint256 nonce, - uint8 role, - uint16 voteQuorumPct, - uint16 vetoQuorumPct - ) internal returns (LlamaERC721TokenCaster caster) { - caster = LlamaERC721TokenCaster( - Clones.cloneDeterministic( - address(ERC721_TOKEN_CASTER_LOGIC), - keccak256(abi.encodePacked(msg.sender, address(llamaCore), address(token), nonce)) - ) + caster.initialize( + tokenVotingConfig.llamaCore, + tokenAdapter, + tokenVotingConfig.casterRole, + tokenVotingConfig.voteQuorumPct, + tokenVotingConfig.vetoQuorumPct + ); + + emit LlamaTokenVotingInstanceCreated( + msg.sender, + tokenVotingConfig.llamaCore, + tokenAdapter.token(), + tokenAdapter, + tokenVotingConfig.nonce, + tokenVotingConfig.actionCreatorRole, + tokenVotingConfig.casterRole, + actionCreator, + caster, + block.chainid ); - caster.initialize(token, llamaCore, tokenAdapter, role, voteQuorumPct, vetoQuorumPct); } } diff --git a/src/token-voting/interfaces/ILlamaTokenAdapter.sol b/src/token-voting/interfaces/ILlamaTokenAdapter.sol index 40dd983..a3cf5b7 100644 --- a/src/token-voting/interfaces/ILlamaTokenAdapter.sol +++ b/src/token-voting/interfaces/ILlamaTokenAdapter.sol @@ -1,10 +1,25 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.23; +import {IVotes} from "@openzeppelin/governance/utils/IVotes.sol"; + /// @title ILlamaTokenAdapter /// @author Llama (devsdosomething@llama.xyz) /// @notice This contract provides an interface for voting token adapters. interface ILlamaTokenAdapter { + /// @notice Initializes a new clone of the token adapter. + /// @dev This function is called by the `deploy` function in the `LlamaTokenVotingFactory` contract. The `initializer` + /// modifier ensures that this function can be invoked at most once. + /// @param config The token adapter configuration, encoded as bytes to support differing constructor arguments in + /// different token adapters. + /// @return This return statement must be hardcoded to `true` to ensure that initializing an EOA + /// (like the zero address) will revert. + function initialize(bytes memory config) external returns (bool); + + /// @notice Returns the token voting module's `IVotes` voting token. + /// @return token The voting token. + function token() external view returns (IVotes token); + /// @notice Returns the current timepoint according to the token's clock. /// @return timepoint the current timepoint function clock() external view returns (uint48 timepoint); diff --git a/src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol b/src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol index 16d56f2..ab6a8ee 100644 --- a/src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol +++ b/src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol @@ -1,23 +1,37 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.23; +import {Initializable} from "@openzeppelin/proxy/utils/Initializable.sol"; import {IVotes} from "@openzeppelin/governance/utils/IVotes.sol"; import {IERC6372} from "@openzeppelin/interfaces/IERC6372.sol"; import {LlamaUtils} from "src/lib/LlamaUtils.sol"; import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; -contract LlamaTokenAdapterTimestamp is ILlamaTokenAdapter { +/// @dev Llama token adapter initialization configuration. +struct Config { + IVotes token; // The address of the voting token. +} + +contract LlamaTokenAdapterTimestamp is ILlamaTokenAdapter, Initializable { /// @dev The clock was incorrectly modified. error ERC6372InconsistentClock(); /// @notice The token to be used for voting. - IVotes public immutable token; + IVotes public token; - string CLOCK_MODE; + string public CLOCK_MODE; - constructor(IVotes _token) { - token = _token; + /// @dev This contract is deployed as a minimal proxy from the factory's `deploy` function. The + /// `_disableInitializers` locks the implementation (logic) contract, preventing any future initialization of it. + constructor() { + _disableInitializers(); + } + + /// @inheritdoc ILlamaTokenAdapter + function initialize(bytes memory config) external initializer returns (bool) { + Config memory adapterConfig = abi.decode(config, (Config)); + token = adapterConfig.token; CLOCK_MODE = "mode=timestamp"; } From 8e3fe381293243072151a4ea17106237dab8ab92 Mon Sep 17 00:00:00 2001 From: Austin Green Date: Fri, 15 Dec 2023 12:14:13 -0500 Subject: [PATCH 06/23] remove token reference --- src/token-voting/LlamaTokenVotingFactory.sol | 4 +--- src/token-voting/interfaces/ILlamaTokenAdapter.sol | 2 +- .../token-adapters/LlamaTokenAdapterTimestamp.sol | 12 +++++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/token-voting/LlamaTokenVotingFactory.sol b/src/token-voting/LlamaTokenVotingFactory.sol index 6bb2201..bcf3c65 100644 --- a/src/token-voting/LlamaTokenVotingFactory.sol +++ b/src/token-voting/LlamaTokenVotingFactory.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.23; import {Clones} from "@openzeppelin/proxy/Clones.sol"; -import {IVotes} from "@openzeppelin/governance/utils/IVotes.sol"; import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; @@ -20,7 +19,6 @@ contract LlamaTokenVotingFactory { /// @dev Configuration of a new Llama token voting module. struct LlamaTokenVotingConfig { ILlamaCore llamaCore; // The address of the Llama core. - IVotes token; // The address of the token to be used for voting. ILlamaTokenAdapter tokenAdapterLogic; // The logic contract of the token adapter. bytes adapterConfig; // The configuration of the token adapter. uint256 nonce; // The nonce to be used in the salt of the deterministic deployment. @@ -46,7 +44,7 @@ contract LlamaTokenVotingFactory { event LlamaTokenVotingInstanceCreated( address indexed deployer, ILlamaCore indexed llamaCore, - IVotes indexed token, + address indexed token, ILlamaTokenAdapter tokenAdapter, uint256 nonce, uint8 actionCreatorRole, diff --git a/src/token-voting/interfaces/ILlamaTokenAdapter.sol b/src/token-voting/interfaces/ILlamaTokenAdapter.sol index a3cf5b7..ffa4060 100644 --- a/src/token-voting/interfaces/ILlamaTokenAdapter.sol +++ b/src/token-voting/interfaces/ILlamaTokenAdapter.sol @@ -18,7 +18,7 @@ interface ILlamaTokenAdapter { /// @notice Returns the token voting module's `IVotes` voting token. /// @return token The voting token. - function token() external view returns (IVotes token); + function token() external view returns (address token); /// @notice Returns the current timepoint according to the token's clock. /// @return timepoint the current timepoint diff --git a/src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol b/src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol index ab6a8ee..d7f35b4 100644 --- a/src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol +++ b/src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol @@ -10,7 +10,7 @@ import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter /// @dev Llama token adapter initialization configuration. struct Config { - IVotes token; // The address of the voting token. + address token; // The address of the voting token. } contract LlamaTokenAdapterTimestamp is ILlamaTokenAdapter, Initializable { @@ -18,7 +18,7 @@ contract LlamaTokenAdapterTimestamp is ILlamaTokenAdapter, Initializable { error ERC6372InconsistentClock(); /// @notice The token to be used for voting. - IVotes public token; + address public token; string public CLOCK_MODE; @@ -33,6 +33,8 @@ contract LlamaTokenAdapterTimestamp is ILlamaTokenAdapter, Initializable { Config memory adapterConfig = abi.decode(config, (Config)); token = adapterConfig.token; CLOCK_MODE = "mode=timestamp"; + + return true; } /// @inheritdoc ILlamaTokenAdapter @@ -59,16 +61,16 @@ contract LlamaTokenAdapterTimestamp is ILlamaTokenAdapter, Initializable { /// @inheritdoc ILlamaTokenAdapter function getPastVotes(address account, uint48 timepoint) external view returns (uint256) { - return token.getPastVotes(account, timepoint); + return IVotes(token).getPastVotes(account, timepoint); } /// @inheritdoc ILlamaTokenAdapter function getPastTotalSupply(uint48 timepoint) external view returns (uint256) { - return token.getPastTotalSupply(timepoint); + return IVotes(token).getPastTotalSupply(timepoint); } function _hasClockModeChanged() internal view returns (bool) { - try IERC6372(address(token)).CLOCK_MODE() returns (string memory mode) { + try IERC6372(token).CLOCK_MODE() returns (string memory mode) { return keccak256(abi.encodePacked(mode)) != keccak256(abi.encodePacked(CLOCK_MODE)); } catch { return false; From e5432cb7f8d8d5a797c862932f3e26f42364f43f Mon Sep 17 00:00:00 2001 From: Austin Green Date: Fri, 15 Dec 2023 12:17:24 -0500 Subject: [PATCH 07/23] token adapter comments --- .../token-adapters/LlamaTokenAdapterTimestamp.sol | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol b/src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol index d7f35b4..fdff77e 100644 --- a/src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol +++ b/src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol @@ -14,14 +14,27 @@ struct Config { } contract LlamaTokenAdapterTimestamp is ILlamaTokenAdapter, Initializable { + // ======================== + // ======== Errors ======== + // ======================== + /// @dev The clock was incorrectly modified. error ERC6372InconsistentClock(); + // ================================================= + // ======== Constants and Storage Variables ======== + // ================================================= + /// @notice The token to be used for voting. address public token; + /// @notice Machine-readable description of the clock as specified in ERC-6372. string public CLOCK_MODE; + // ================================ + // ======== Initialization ======== + // ================================ + /// @dev This contract is deployed as a minimal proxy from the factory's `deploy` function. The /// `_disableInitializers` locks the implementation (logic) contract, preventing any future initialization of it. constructor() { From 9c21052e3074cc4bd001b1695f7379f0b7049893 Mon Sep 17 00:00:00 2001 From: Austin Green Date: Fri, 15 Dec 2023 12:20:43 -0500 Subject: [PATCH 08/23] factory build --- script/DeployLlamaTokenVotingFactory.s.sol | 43 +++++++--------------- 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/script/DeployLlamaTokenVotingFactory.s.sol b/script/DeployLlamaTokenVotingFactory.s.sol index bb1b645..6a4d11a 100644 --- a/script/DeployLlamaTokenVotingFactory.s.sol +++ b/script/DeployLlamaTokenVotingFactory.s.sol @@ -4,18 +4,16 @@ pragma solidity 0.8.23; import {Script} from "forge-std/Script.sol"; import {DeployUtils} from "script/DeployUtils.sol"; -import {LlamaERC20TokenActionCreator} from "src/token-voting/LlamaERC20TokenActionCreator.sol"; -import {LlamaERC20TokenCaster} from "src/token-voting/LlamaERC20TokenCaster.sol"; -import {LlamaERC721TokenActionCreator} from "src/token-voting/LlamaERC721TokenActionCreator.sol"; -import {LlamaERC721TokenCaster} from "src/token-voting/LlamaERC721TokenCaster.sol"; +import {LlamaTokenActionCreator} from "src/token-voting/LlamaTokenActionCreator.sol"; +import {LlamaTokenCaster} from "src/token-voting/LlamaTokenCaster.sol"; import {LlamaTokenVotingFactory} from "src/token-voting/LlamaTokenVotingFactory.sol"; +import {LlamaTokenAdapterTimestamp} from "src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol"; contract DeployLlamaTokenVotingFactory is Script { // Logic contracts. - LlamaERC20TokenActionCreator llamaERC20TokenActionCreatorLogic; - LlamaERC20TokenCaster llamaERC20TokenCasterLogic; - LlamaERC721TokenActionCreator llamaERC721TokenActionCreatorLogic; - LlamaERC721TokenCaster llamaERC721TokenCasterLogic; + LlamaTokenActionCreator llamaTokenActionCreatorLogic; + LlamaTokenCaster llamaTokenCasterLogic; + LlamaTokenAdapterTimestamp llamaTokenAdapterTimestamp; // Factory contracts. LlamaTokenVotingFactory tokenVotingFactory; @@ -26,36 +24,21 @@ contract DeployLlamaTokenVotingFactory is Script { ); vm.broadcast(); - llamaERC20TokenActionCreatorLogic = new LlamaERC20TokenActionCreator(); + llamaTokenActionCreatorLogic = new LlamaTokenActionCreator(); DeployUtils.print( - string.concat(" LlamaERC20TokenActionCreatorLogic: ", vm.toString(address(llamaERC20TokenActionCreatorLogic))) + string.concat(" LlamaTokenActionCreatorLogic: ", vm.toString(address(llamaTokenActionCreatorLogic))) ); vm.broadcast(); - llamaERC20TokenCasterLogic = new LlamaERC20TokenCaster(); - DeployUtils.print(string.concat(" LlamaERC20TokenCasterLogic: ", vm.toString(address(llamaERC20TokenCasterLogic)))); + llamaTokenCasterLogic = new LlamaTokenCaster(); + DeployUtils.print(string.concat(" LlamaTokenCasterLogic: ", vm.toString(address(llamaTokenCasterLogic)))); vm.broadcast(); - llamaERC721TokenActionCreatorLogic = new LlamaERC721TokenActionCreator(); - DeployUtils.print( - string.concat(" LlamaERC721TokenActionCreatorLogic: ", vm.toString(address(llamaERC721TokenActionCreatorLogic))) - ); - - vm.broadcast(); - llamaERC721TokenCasterLogic = new LlamaERC721TokenCaster(); - DeployUtils.print( - string.concat(" LlamaERC721TokenCasterLogic: ", vm.toString(address(llamaERC721TokenCasterLogic))) - ); + llamaTokenAdapterTimestamp = new LlamaTokenAdapterTimestamp(); + DeployUtils.print(string.concat(" LlamaTokenAdapterTimestamp: ", vm.toString(address(llamaTokenAdapterTimestamp)))); vm.broadcast(); - tokenVotingFactory = new LlamaTokenVotingFactory( - llamaERC20TokenActionCreatorLogic, - llamaERC20TokenCasterLogic, - llamaERC721TokenActionCreatorLogic, - llamaERC721TokenCasterLogic - ); + tokenVotingFactory = new LlamaTokenVotingFactory(llamaTokenActionCreatorLogic, llamaTokenCasterLogic); DeployUtils.print(string.concat(" LlamaTokenVotingFactory: ", vm.toString(address(tokenVotingFactory)))); - - // Deploy the timestamp managers here when we develop them. } } From 067d589270d09391b4bd03291b524fdca8219985 Mon Sep 17 00:00:00 2001 From: Austin Green Date: Fri, 15 Dec 2023 13:08:47 -0500 Subject: [PATCH 09/23] updating tests --- script/DeployLlamaTokenVotingFactory.s.sol | 8 ++- .../LlamaTokenAdapterTimestamp.sol | 14 ++-- test/LlamaPeripheryTestSetup.sol | 1 - test/token-voting/LlamaERC20TokenCaster.t.sol | 39 +++++++---- .../token-voting/LlamaERC721TokenCaster.t.sol | 65 +++++++++-------- .../LlamaTokenVotingTestSetup.sol | 70 +++++++++---------- 6 files changed, 109 insertions(+), 88 deletions(-) diff --git a/script/DeployLlamaTokenVotingFactory.s.sol b/script/DeployLlamaTokenVotingFactory.s.sol index 6a4d11a..e1de3ac 100644 --- a/script/DeployLlamaTokenVotingFactory.s.sol +++ b/script/DeployLlamaTokenVotingFactory.s.sol @@ -13,7 +13,7 @@ contract DeployLlamaTokenVotingFactory is Script { // Logic contracts. LlamaTokenActionCreator llamaTokenActionCreatorLogic; LlamaTokenCaster llamaTokenCasterLogic; - LlamaTokenAdapterTimestamp llamaTokenAdapterTimestamp; + LlamaTokenAdapterTimestamp llamaTokenAdapterTimestampLogic; // Factory contracts. LlamaTokenVotingFactory tokenVotingFactory; @@ -34,8 +34,10 @@ contract DeployLlamaTokenVotingFactory is Script { DeployUtils.print(string.concat(" LlamaTokenCasterLogic: ", vm.toString(address(llamaTokenCasterLogic)))); vm.broadcast(); - llamaTokenAdapterTimestamp = new LlamaTokenAdapterTimestamp(); - DeployUtils.print(string.concat(" LlamaTokenAdapterTimestamp: ", vm.toString(address(llamaTokenAdapterTimestamp)))); + llamaTokenAdapterTimestampLogic = new LlamaTokenAdapterTimestamp(); + DeployUtils.print( + string.concat(" LlamaTokenAdapterTimestamp: ", vm.toString(address(llamaTokenAdapterTimestampLogic))) + ); vm.broadcast(); tokenVotingFactory = new LlamaTokenVotingFactory(llamaTokenActionCreatorLogic, llamaTokenCasterLogic); diff --git a/src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol b/src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol index fdff77e..710bdf6 100644 --- a/src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol +++ b/src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol @@ -8,12 +8,16 @@ import {IERC6372} from "@openzeppelin/interfaces/IERC6372.sol"; import {LlamaUtils} from "src/lib/LlamaUtils.sol"; import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; -/// @dev Llama token adapter initialization configuration. -struct Config { - address token; // The address of the voting token. -} - contract LlamaTokenAdapterTimestamp is ILlamaTokenAdapter, Initializable { + // ========================= + // ======== Structs ======== + // ========================= + + /// @dev Llama token adapter initialization configuration. + struct Config { + address token; // The address of the voting token. + } + // ======================== // ======== Errors ======== // ======================== diff --git a/test/LlamaPeripheryTestSetup.sol b/test/LlamaPeripheryTestSetup.sol index 3d7850d..54f0392 100644 --- a/test/LlamaPeripheryTestSetup.sol +++ b/test/LlamaPeripheryTestSetup.sol @@ -14,7 +14,6 @@ import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; import {ILlamaExecutor} from "src/interfaces/ILlamaExecutor.sol"; import {ILlamaLens} from "src/interfaces/ILlamaLens.sol"; import {ILlamaPolicy} from "src/interfaces/ILlamaPolicy.sol"; -import {ActionInfo, PermissionData, RoleHolderData} from "src/lib/Structs.sol"; contract LlamaPeripheryTestSetup is Test { string MAINNET_RPC_URL = vm.envString("MAINNET_RPC_URL"); // can't use constant here diff --git a/test/token-voting/LlamaERC20TokenCaster.t.sol b/test/token-voting/LlamaERC20TokenCaster.t.sol index d35ae8a..b5ccfe9 100644 --- a/test/token-voting/LlamaERC20TokenCaster.t.sol +++ b/test/token-voting/LlamaERC20TokenCaster.t.sol @@ -13,7 +13,8 @@ import {Action, ActionInfo} from "src/lib/Structs.sol"; import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; import {ILlamaRelativeStrategyBase} from "src/interfaces/ILlamaRelativeStrategyBase.sol"; import {ILlamaStrategy} from "src/interfaces/ILlamaStrategy.sol"; -import {LlamaERC20TokenCaster} from "src/token-voting/LlamaERC20TokenCaster.sol"; +import {LlamaTokenAdapterTimestamp} from "src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol"; +import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; import {LlamaTokenCaster} from "src/token-voting/LlamaTokenCaster.sol"; contract LlamaERC20TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUtils { @@ -28,7 +29,7 @@ contract LlamaERC20TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUti event QuorumSet(uint16 voteQuorumPct, uint16 vetoQuorumPct); ActionInfo actionInfo; - LlamaERC20TokenCaster llamaERC20TokenCaster; + LlamaTokenCaster llamaERC20TokenCaster; ILlamaStrategy tokenVotingStrategy; function setUp() public virtual override { @@ -60,7 +61,7 @@ contract LlamaERC20TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUti tokenVotingStrategy = _deployRelativeQuantityQuorumAndSetRolePermissionToCoreTeam(tokenVotingCasterRole); actionInfo = _createActionWithTokenVotingStrategy(tokenVotingStrategy); - // Setting LlamaERC20TokenCaster's EIP-712 Domain Hash + // Setting LlamaTokenCaster's EIP-712 Domain Hash setDomainHash( LlamaCoreSigUtils.EIP712Domain({ name: CORE.name(), @@ -88,13 +89,21 @@ contract LlamaERC20TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUti vm.prank(tokenHolder3); llamaERC20TokenCaster.castVeto(actionInfo, uint8(VoteType.For), ""); } + + function createTimestampTokenAdapter(address token) public returns (ILlamaTokenAdapter tokenAdapter) { + bytes memory adapterConfig = abi.encode(LlamaTokenAdapterTimestamp.Config(address(token))); + + tokenAdapter = + ILlamaTokenAdapter(Clones.cloneDeterministic(address(llamaTokenAdapterTimestampLogic), keccak256(adapterConfig))); + tokenAdapter.initialize(adapterConfig); + } } // contract Constructor is LlamaERC20TokenCasterTest { // function test_RevertsIf_InvalidLlamaCoreAddress() public { // // With invalid LlamaCore instance, LlamaTokenActionCreator.InvalidLlamaCoreAddress is unreachable // vm.expectRevert(); -// new LlamaERC20TokenCaster( +// new LlamaTokenCaster( // erc20VotesToken, ILlamaCore(makeAddr("invalid-llama-core")), tokenVotingCasterRole, uint256(1), uint256(1) // ); // } @@ -103,7 +112,7 @@ contract LlamaERC20TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUti // vm.assume(notAToken != address(0)); // vm.assume(notAToken != address(erc20VotesToken)); // vm.expectRevert(); // will revert with EvmError: Revert because `totalSupply` is not a function -// new LlamaERC20TokenCaster( +// new LlamaTokenCaster( // ERC20Votes(notAToken), ILlamaCore(address(CORE)), tokenVotingCasterRole, uint256(1), uint256(1) // ); // } @@ -111,26 +120,26 @@ contract LlamaERC20TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUti // function test_RevertsIf_InvalidRole(uint8 role) public { // role = uint8(bound(role, POLICY.numRoles(), 255)); // vm.expectRevert(abi.encodeWithSelector(LlamaTokenCaster.RoleNotInitialized.selector, uint8(255))); -// new LlamaERC20TokenCaster(erc20VotesToken, ILlamaCore(address(CORE)), uint8(255), uint256(1), uint256(1)); +// new LlamaTokenCaster(erc20VotesToken, ILlamaCore(address(CORE)), uint8(255), uint256(1), uint256(1)); // } // function test_RevertsIf_InvalidVoteQuorumPct() public { // vm.expectRevert(abi.encodeWithSelector(LlamaTokenCaster.InvalidVoteQuorumPct.selector, uint256(0))); -// new LlamaERC20TokenCaster(erc20VotesToken, ILlamaCore(address(CORE)), tokenVotingCasterRole, uint256(0), +// new LlamaTokenCaster(erc20VotesToken, ILlamaCore(address(CORE)), tokenVotingCasterRole, uint256(0), // uint256(1)); // vm.expectRevert(abi.encodeWithSelector(LlamaTokenCaster.InvalidVoteQuorumPct.selector, uint256(10_001))); -// new LlamaERC20TokenCaster(erc20VotesToken, ILlamaCore(address(CORE)), tokenVotingCasterRole, +// new LlamaTokenCaster(erc20VotesToken, ILlamaCore(address(CORE)), tokenVotingCasterRole, // uint256(10_001), // uint256(1)); // } // function test_RevertsIf_InvalidVetoQuorumPct() public { // vm.expectRevert(abi.encodeWithSelector(LlamaTokenCaster.InvalidVetoQuorumPct.selector, uint256(0))); -// new LlamaERC20TokenCaster(erc20VotesToken, ILlamaCore(address(CORE)), tokenVotingCasterRole, uint256(1), +// new LlamaTokenCaster(erc20VotesToken, ILlamaCore(address(CORE)), tokenVotingCasterRole, uint256(1), // uint256(0)); // vm.expectRevert(abi.encodeWithSelector(LlamaTokenCaster.InvalidVetoQuorumPct.selector, // uint256(10_001))); -// new LlamaERC20TokenCaster(erc20VotesToken, ILlamaCore(address(CORE)), tokenVotingCasterRole, uint256(1), +// new LlamaTokenCaster(erc20VotesToken, ILlamaCore(address(CORE)), tokenVotingCasterRole, uint256(1), // uint256(10_001)); // } @@ -138,7 +147,7 @@ contract LlamaERC20TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUti // erc20VotesToken.mint(address(this), 1_000_000e18); // we use erc20VotesToken because IVotesToken is an interface // // without the `mint` function -// llamaERC20TokenCaster = new LlamaERC20TokenCaster( +// llamaERC20TokenCaster = new LlamaTokenCaster( // erc20VotesToken, ILlamaCore(address(CORE)), tokenVotingCasterRole, DEFAULT_APPROVAL_THRESHOLD, // DEFAULT_APPROVAL_THRESHOLD // ); @@ -159,7 +168,7 @@ contract CastVote is LlamaERC20TokenCasterTest { } function test_RevertsIf_ApprovalNotEnabled() public { - LlamaERC20TokenCaster casterWithWrongRole = LlamaERC20TokenCaster( + LlamaTokenCaster casterWithWrongRole = LlamaTokenCaster( Clones.cloneDeterministic( address(llamaERC20TokenCasterLogic), keccak256(abi.encodePacked(address(erc20VotesToken), msg.sender)) ) @@ -334,7 +343,7 @@ contract CastVeto is LlamaERC20TokenCasterTest { } function test_RevertsIf_DisapprovalNotEnabled() public { - LlamaERC20TokenCaster casterWithWrongRole = LlamaERC20TokenCaster( + LlamaTokenCaster casterWithWrongRole = LlamaTokenCaster( Clones.cloneDeterministic( address(llamaERC20TokenCasterLogic), keccak256(abi.encodePacked(address(erc20VotesToken), msg.sender)) ) @@ -555,7 +564,7 @@ contract SubmitApprovals is LlamaERC20TokenCasterTest { } function test_RevertsIf_ApprovalNotEnabled() public { - LlamaERC20TokenCaster casterWithWrongRole = LlamaERC20TokenCaster( + LlamaTokenCaster casterWithWrongRole = LlamaTokenCaster( Clones.cloneDeterministic( address(llamaERC20TokenCasterLogic), keccak256(abi.encodePacked(address(erc20VotesToken), msg.sender)) ) @@ -638,7 +647,7 @@ contract SubmitDisapprovals is LlamaERC20TokenCasterTest { function test_RevertsIf_DisapprovalNotEnabled() public { vm.warp(block.timestamp + (1 days * TWO_THIRDS_IN_BPS) / ONE_HUNDRED_IN_BPS); - LlamaERC20TokenCaster casterWithWrongRole = LlamaERC20TokenCaster( + LlamaTokenCaster casterWithWrongRole = LlamaTokenCaster( Clones.cloneDeterministic( address(llamaERC20TokenCasterLogic), keccak256(abi.encodePacked(address(erc20VotesToken), msg.sender)) ) diff --git a/test/token-voting/LlamaERC721TokenCaster.t.sol b/test/token-voting/LlamaERC721TokenCaster.t.sol index 1fcb661..9acb03c 100644 --- a/test/token-voting/LlamaERC721TokenCaster.t.sol +++ b/test/token-voting/LlamaERC721TokenCaster.t.sol @@ -13,7 +13,8 @@ import {Action, ActionInfo} from "src/lib/Structs.sol"; import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; import {ILlamaRelativeStrategyBase} from "src/interfaces/ILlamaRelativeStrategyBase.sol"; import {ILlamaStrategy} from "src/interfaces/ILlamaStrategy.sol"; -import {LlamaERC721TokenCaster} from "src/token-voting/LlamaERC721TokenCaster.sol"; +import {LlamaTokenAdapterTimestamp} from "src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol"; +import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; import {LlamaTokenCaster} from "src/token-voting/LlamaTokenCaster.sol"; contract LlamaERC721TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUtils { @@ -28,7 +29,7 @@ contract LlamaERC721TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUt event QuorumSet(uint16 voteQuorumPct, uint16 vetoQuorumPct); ActionInfo actionInfo; - LlamaERC721TokenCaster llamaERC721TokenCaster; + LlamaTokenCaster llamaERC721TokenCaster; ILlamaStrategy tokenVotingStrategy; function setUp() public virtual override { @@ -60,7 +61,7 @@ contract LlamaERC721TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUt tokenVotingStrategy = _deployRelativeQuantityQuorumAndSetRolePermissionToCoreTeam(tokenVotingCasterRole); actionInfo = _createActionWithTokenVotingStrategy(tokenVotingStrategy); - // Setting LlamaERC721TokenCaster's EIP-712 Domain Hash + // Setting LlamaTokenCaster's EIP-712 Domain Hash setDomainHash( LlamaCoreSigUtils.EIP712Domain({ name: CORE.name(), @@ -88,13 +89,21 @@ contract LlamaERC721TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUt vm.prank(tokenHolder3); llamaERC721TokenCaster.castVeto(actionInfo, uint8(VoteType.For), ""); } + + function createTimestampTokenAdapter(address token) public returns (ILlamaTokenAdapter tokenAdapter) { + bytes memory adapterConfig = abi.encode(LlamaTokenAdapterTimestamp.Config(address(token))); + + tokenAdapter = + ILlamaTokenAdapter(Clones.cloneDeterministic(address(llamaTokenAdapterTimestampLogic), keccak256(adapterConfig))); + tokenAdapter.initialize(adapterConfig); + } } // contract Constructor is LlamaERC721TokenCasterTest { // function test_RevertsIf_InvalidLlamaCoreAddress() public { // // With invalid LlamaCore instance, LlamaTokenActionCreator.InvalidLlamaCoreAddress is unreachable // vm.expectRevert(); -// new LlamaERC721TokenCaster( +// new LlamaTokenCaster( // erc721VotesToken, ILlamaCore(makeAddr("invalid-llama-core")), tokenVotingCasterRole, uint256(1), uint256(1) // ); // } @@ -103,7 +112,7 @@ contract LlamaERC721TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUt // vm.assume(notAToken != address(0)); // vm.assume(notAToken != address(erc721VotesToken)); // vm.expectRevert(); // will revert with EvmError: Revert because `totalSupply` is not a function -// new LlamaERC721TokenCaster( +// new LlamaTokenCaster( // ERC20Votes(notAToken), ILlamaCore(address(CORE)), tokenVotingCasterRole, uint256(1), uint256(1) // ); // } @@ -111,7 +120,7 @@ contract LlamaERC721TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUt // function test_RevertsIf_InvalidRole(uint8 role) public { // role = uint8(bound(role, POLICY.numRoles(), 255)); // vm.expectRevert(abi.encodeWithSelector(LlamaTokenCaster.RoleNotInitialized.selector, uint8(255))); -// new LlamaERC721TokenCaster(erc721VotesToken, ILlamaCore(address(CORE)), uint8(255), uint256(1), +// new LlamaTokenCaster(erc721VotesToken, ILlamaCore(address(CORE)), uint8(255), uint256(1), // uint256(1)); // } @@ -131,7 +140,7 @@ contract LlamaERC721TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUt // uint256(0)); // vm.expectRevert(abi.encodeWithSelector(LlamaTokenCaster.InvalidVetoQuorumPct.selector, // uint256(10_001))); -// new LlamaERC721TokenCaster(erc721VotesToken, ILlamaCore(address(CORE)), tokenVotingCasterRole, uint256(1), +// new LlamaTokenCaster(erc721VotesToken, ILlamaCore(address(CORE)), tokenVotingCasterRole, uint256(1), // uint256(10_001)); // } @@ -140,7 +149,7 @@ contract LlamaERC721TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUt // interface // // without the `mint` function -// llamaERC721TokenCaster = new LlamaERC721TokenCaster( +// llamaERC721TokenCaster = new LlamaTokenCaster( // erc721VotesToken, ILlamaCore(address(CORE)), tokenVotingCasterRole, DEFAULT_APPROVAL_THRESHOLD, // DEFAULT_APPROVAL_THRESHOLD // ); @@ -161,14 +170,15 @@ contract CastVote is LlamaERC721TokenCasterTest { } function test_RevertsIf_ApprovalNotEnabled() public { - LlamaERC721TokenCaster casterWithWrongRole = LlamaERC721TokenCaster( + ILlamaTokenAdapter tokenAdapter = createTimestampTokenAdapter(address(erc721VotesToken)); + + LlamaTokenCaster casterWithWrongRole = LlamaTokenCaster( Clones.cloneDeterministic( - address(llamaERC721TokenCasterLogic), keccak256(abi.encodePacked(address(erc721VotesToken), msg.sender)) + address(llamaTokenCasterLogic), keccak256(abi.encodePacked(address(erc721VotesToken), msg.sender)) ) ); - casterWithWrongRole.initialize( - erc721VotesToken, CORE, LLAMA_TOKEN_TIMESTAMP_ADAPTER, madeUpRole, ERC721_VOTE_QUORUM_PCT, ERC721_VETO_QUORUM_PCT - ); + + casterWithWrongRole.initialize(CORE, tokenAdapter, madeUpRole, ERC721_VOTE_QUORUM_PCT, ERC721_VETO_QUORUM_PCT); vm.expectRevert(abi.encodeWithSelector(ILlamaRelativeStrategyBase.InvalidRole.selector, tokenVotingCasterRole)); casterWithWrongRole.castVote(actionInfo, uint8(VoteType.For), ""); @@ -336,14 +346,14 @@ contract CastVeto is LlamaERC721TokenCasterTest { } function test_RevertsIf_DisapprovalNotEnabled() public { - LlamaERC721TokenCaster casterWithWrongRole = LlamaERC721TokenCaster( + ILlamaTokenAdapter tokenAdapter = createTimestampTokenAdapter(address(erc721VotesToken)); + + LlamaTokenCaster casterWithWrongRole = LlamaTokenCaster( Clones.cloneDeterministic( - address(llamaERC721TokenCasterLogic), keccak256(abi.encodePacked(address(erc721VotesToken), msg.sender)) + address(llamaTokenCasterLogic), keccak256(abi.encodePacked(address(erc721VotesToken), msg.sender)) ) ); - casterWithWrongRole.initialize( - erc721VotesToken, CORE, LLAMA_TOKEN_TIMESTAMP_ADAPTER, madeUpRole, ERC721_VOTE_QUORUM_PCT, ERC721_VETO_QUORUM_PCT - ); + casterWithWrongRole.initialize(CORE, tokenAdapter, madeUpRole, ERC721_VOTE_QUORUM_PCT, ERC721_VETO_QUORUM_PCT); vm.expectRevert(abi.encodeWithSelector(ILlamaRelativeStrategyBase.InvalidRole.selector, tokenVotingCasterRole)); casterWithWrongRole.castVeto(actionInfo, uint8(VoteType.For), ""); @@ -557,14 +567,14 @@ contract SubmitApprovals is LlamaERC721TokenCasterTest { } function test_RevertsIf_ApprovalNotEnabled() public { - LlamaERC721TokenCaster casterWithWrongRole = LlamaERC721TokenCaster( + ILlamaTokenAdapter tokenAdapter = createTimestampTokenAdapter(address(erc721VotesToken)); + + LlamaTokenCaster casterWithWrongRole = LlamaTokenCaster( Clones.cloneDeterministic( - address(llamaERC721TokenCasterLogic), keccak256(abi.encodePacked(address(erc721VotesToken), msg.sender)) + address(llamaTokenCasterLogic), keccak256(abi.encodePacked(address(erc721VotesToken), msg.sender)) ) ); - casterWithWrongRole.initialize( - erc721VotesToken, CORE, LLAMA_TOKEN_TIMESTAMP_ADAPTER, madeUpRole, ERC721_VOTE_QUORUM_PCT, ERC721_VETO_QUORUM_PCT - ); + casterWithWrongRole.initialize(CORE, tokenAdapter, madeUpRole, ERC721_VOTE_QUORUM_PCT, ERC721_VETO_QUORUM_PCT); vm.expectRevert(abi.encodeWithSelector(ILlamaRelativeStrategyBase.InvalidRole.selector, tokenVotingCasterRole)); casterWithWrongRole.submitApproval(actionInfo); } @@ -640,14 +650,13 @@ contract SubmitDisapprovals is LlamaERC721TokenCasterTest { function test_RevertsIf_DisapprovalNotEnabled() public { vm.warp(block.timestamp + (1 days * TWO_THIRDS_IN_BPS) / ONE_HUNDRED_IN_BPS); - LlamaERC721TokenCaster casterWithWrongRole = LlamaERC721TokenCaster( + LlamaTokenCaster casterWithWrongRole = LlamaTokenCaster( Clones.cloneDeterministic( - address(llamaERC721TokenCasterLogic), keccak256(abi.encodePacked(address(erc721VotesToken), msg.sender)) + address(llamaTokenCasterLogic), keccak256(abi.encodePacked(address(erc721VotesToken), msg.sender)) ) ); - casterWithWrongRole.initialize( - erc721VotesToken, CORE, LLAMA_TOKEN_TIMESTAMP_ADAPTER, madeUpRole, ERC721_VOTE_QUORUM_PCT, ERC721_VETO_QUORUM_PCT - ); + ILlamaTokenAdapter tokenAdapter = createTimestampTokenAdapter(address(erc721VotesToken)); + casterWithWrongRole.initialize(CORE, tokenAdapter, madeUpRole, ERC721_VOTE_QUORUM_PCT, ERC721_VETO_QUORUM_PCT); vm.expectRevert(abi.encodeWithSelector(ILlamaRelativeStrategyBase.InvalidRole.selector, tokenVotingCasterRole)); casterWithWrongRole.submitDisapproval(actionInfo); } diff --git a/test/token-voting/LlamaTokenVotingTestSetup.sol b/test/token-voting/LlamaTokenVotingTestSetup.sol index 82ff260..3d67b9a 100644 --- a/test/token-voting/LlamaTokenVotingTestSetup.sol +++ b/test/token-voting/LlamaTokenVotingTestSetup.sol @@ -15,11 +15,10 @@ import {ILlamaPolicy} from "src/interfaces/ILlamaPolicy.sol"; import {ILlamaRelativeStrategyBase} from "src/interfaces/ILlamaRelativeStrategyBase.sol"; import {ILlamaStrategy} from "src/interfaces/ILlamaStrategy.sol"; import {RoleDescription} from "src/lib/UDVTs.sol"; -import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; -import {LlamaERC20TokenActionCreator} from "src/token-voting/LlamaERC20TokenActionCreator.sol"; -import {LlamaERC20TokenCaster} from "src/token-voting/LlamaERC20TokenCaster.sol"; -import {LlamaERC721TokenActionCreator} from "src/token-voting/LlamaERC721TokenActionCreator.sol"; -import {LlamaERC721TokenCaster} from "src/token-voting/LlamaERC721TokenCaster.sol"; +import {LlamaTokenAdapterTimestamp} from "src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol"; +import {LlamaTokenVotingFactory} from "src/token-voting/LlamaTokenVotingFactory.sol"; +import {LlamaTokenActionCreator} from "src/token-voting/LlamaTokenActionCreator.sol"; +import {LlamaTokenCaster} from "src/token-voting/LlamaTokenCaster.sol"; contract LlamaTokenVotingTestSetup is LlamaPeripheryTestSetup, DeployLlamaTokenVotingFactory { // Percentages @@ -37,10 +36,6 @@ contract LlamaTokenVotingTestSetup is LlamaPeripheryTestSetup, DeployLlamaTokenV 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 - // tokenAdapter. - ILlamaTokenAdapter constant LLAMA_TOKEN_TIMESTAMP_ADAPTER = ILlamaTokenAdapter(address(0)); - // Votes Tokens MockERC20Votes public erc20VotesToken; MockERC721Votes public erc721VotesToken; @@ -96,61 +91,64 @@ contract LlamaTokenVotingTestSetup is LlamaPeripheryTestSetup, DeployLlamaTokenV // ======== Helpers ======== // ========================= - function _deployERC20TokenVotingModuleAndSetRole() - internal - returns (LlamaERC20TokenActionCreator, LlamaERC20TokenCaster) - { - vm.startPrank(address(EXECUTOR)); - // Deploy Token Voting Module - (address llamaERC20TokenActionCreator, address llamaERC20TokenCaster) = tokenVotingFactory.deploy( + function _deployERC20TokenVotingModuleAndSetRole() internal returns (LlamaTokenActionCreator, LlamaTokenCaster) { + bytes memory adapterConfig = abi.encode(LlamaTokenAdapterTimestamp.Config(address(erc20VotesToken))); + LlamaTokenVotingFactory.LlamaTokenVotingConfig memory config = LlamaTokenVotingFactory.LlamaTokenVotingConfig( CORE, - address(erc20VotesToken), - LLAMA_TOKEN_TIMESTAMP_ADAPTER, + llamaTokenAdapterTimestampLogic, + adapterConfig, 0, - true, tokenVotingActionCreatorRole, tokenVotingCasterRole, ERC20_CREATION_THRESHOLD, ERC20_VOTE_QUORUM_PCT, ERC20_VETO_QUORUM_PCT ); + + vm.startPrank(address(EXECUTOR)); + // Deploy Token Voting Module + (LlamaTokenActionCreator llamaERC20TokenActionCreator, LlamaTokenCaster llamaERC20TokenCaster) = + tokenVotingFactory.deploy(config); // Assign roles to Token Voting Modules POLICY.setRoleHolder( - tokenVotingActionCreatorRole, llamaERC20TokenActionCreator, DEFAULT_ROLE_QTY, DEFAULT_ROLE_EXPIRATION + tokenVotingActionCreatorRole, address(llamaERC20TokenActionCreator), DEFAULT_ROLE_QTY, DEFAULT_ROLE_EXPIRATION + ); + POLICY.setRoleHolder( + tokenVotingCasterRole, address(llamaERC20TokenCaster), DEFAULT_ROLE_QTY, DEFAULT_ROLE_EXPIRATION ); - POLICY.setRoleHolder(tokenVotingCasterRole, llamaERC20TokenCaster, DEFAULT_ROLE_QTY, DEFAULT_ROLE_EXPIRATION); vm.stopPrank(); - return (LlamaERC20TokenActionCreator(llamaERC20TokenActionCreator), LlamaERC20TokenCaster(llamaERC20TokenCaster)); + return (LlamaTokenActionCreator(llamaERC20TokenActionCreator), LlamaTokenCaster(llamaERC20TokenCaster)); } - function _deployERC721TokenVotingModuleAndSetRole() - internal - returns (LlamaERC721TokenActionCreator, LlamaERC721TokenCaster) - { - vm.startPrank(address(EXECUTOR)); - // Deploy Token Voting Module - (address llamaERC721TokenActionCreator, address llamaERC721TokenCaster) = tokenVotingFactory.deploy( + function _deployERC721TokenVotingModuleAndSetRole() internal returns (LlamaTokenActionCreator, LlamaTokenCaster) { + bytes memory adapterConfig = abi.encode(LlamaTokenAdapterTimestamp.Config(address(erc721VotesToken))); + LlamaTokenVotingFactory.LlamaTokenVotingConfig memory config = LlamaTokenVotingFactory.LlamaTokenVotingConfig( CORE, - address(erc721VotesToken), - LLAMA_TOKEN_TIMESTAMP_ADAPTER, + llamaTokenAdapterTimestampLogic, + adapterConfig, 0, - false, tokenVotingActionCreatorRole, tokenVotingCasterRole, ERC721_CREATION_THRESHOLD, ERC721_VOTE_QUORUM_PCT, ERC721_VETO_QUORUM_PCT ); + + vm.startPrank(address(EXECUTOR)); + // Deploy Token Voting Module + (LlamaTokenActionCreator llamaERC721TokenActionCreator, LlamaTokenCaster llamaERC721TokenCaster) = + tokenVotingFactory.deploy(config); // Assign roles to Token Voting Modules POLICY.setRoleHolder( - tokenVotingActionCreatorRole, llamaERC721TokenActionCreator, DEFAULT_ROLE_QTY, DEFAULT_ROLE_EXPIRATION + tokenVotingActionCreatorRole, address(llamaERC721TokenActionCreator), DEFAULT_ROLE_QTY, DEFAULT_ROLE_EXPIRATION + ); + POLICY.setRoleHolder( + tokenVotingCasterRole, address(llamaERC721TokenCaster), DEFAULT_ROLE_QTY, DEFAULT_ROLE_EXPIRATION ); - POLICY.setRoleHolder(tokenVotingCasterRole, llamaERC721TokenCaster, DEFAULT_ROLE_QTY, DEFAULT_ROLE_EXPIRATION); vm.stopPrank(); - return - (LlamaERC721TokenActionCreator(llamaERC721TokenActionCreator), LlamaERC721TokenCaster(llamaERC721TokenCaster)); + return (LlamaTokenActionCreator(llamaERC721TokenActionCreator), LlamaTokenCaster(llamaERC721TokenCaster)); } function _setRolePermissionToLlamaTokenActionCreator() internal { From d216b2b7ff553b16821250ab5d2b2a2a3fe2e0c3 Mon Sep 17 00:00:00 2001 From: Austin Green Date: Fri, 15 Dec 2023 13:39:59 -0500 Subject: [PATCH 10/23] factory tests --- src/token-voting/LlamaTokenVotingFactory.sol | 12 +- .../LlamaERC20TokenActionCreator.t.sol | 14 +- test/token-voting/LlamaERC20TokenCaster.t.sol | 28 +-- .../LlamaERC721TokenActionCreator.t.sol | 14 +- .../LlamaTokenVotingFactory.t.sol | 238 +++++++++++------- 5 files changed, 175 insertions(+), 131 deletions(-) diff --git a/src/token-voting/LlamaTokenVotingFactory.sol b/src/token-voting/LlamaTokenVotingFactory.sol index bcf3c65..3597c01 100644 --- a/src/token-voting/LlamaTokenVotingFactory.sol +++ b/src/token-voting/LlamaTokenVotingFactory.sol @@ -45,6 +45,7 @@ contract LlamaTokenVotingFactory { address indexed deployer, ILlamaCore indexed llamaCore, address indexed token, + ILlamaTokenAdapter tokenAdapterLogic, ILlamaTokenAdapter tokenAdapter, uint256 nonce, uint8 actionCreatorRole, @@ -99,7 +100,10 @@ contract LlamaTokenVotingFactory { address(LLAMA_TOKEN_ACTION_CREATOR_LOGIC), keccak256( abi.encodePacked( - msg.sender, address(tokenVotingConfig.llamaCore), address(tokenAdapter), tokenVotingConfig.nonce + msg.sender, + address(tokenVotingConfig.llamaCore), + address(tokenVotingConfig.tokenAdapterLogic), + tokenVotingConfig.nonce ) ) ) @@ -118,7 +122,10 @@ contract LlamaTokenVotingFactory { address(LLAMA_TOKEN_CASTER_LOGIC), keccak256( abi.encodePacked( - msg.sender, address(tokenVotingConfig.llamaCore), address(tokenAdapter), tokenVotingConfig.nonce + msg.sender, + address(tokenVotingConfig.llamaCore), + address(tokenVotingConfig.tokenAdapterLogic), + tokenVotingConfig.nonce ) ) ) @@ -136,6 +143,7 @@ contract LlamaTokenVotingFactory { msg.sender, tokenVotingConfig.llamaCore, tokenAdapter.token(), + tokenVotingConfig.tokenAdapterLogic, tokenAdapter, tokenVotingConfig.nonce, tokenVotingConfig.actionCreatorRole, diff --git a/test/token-voting/LlamaERC20TokenActionCreator.t.sol b/test/token-voting/LlamaERC20TokenActionCreator.t.sol index 34090ec..b7f7a9d 100644 --- a/test/token-voting/LlamaERC20TokenActionCreator.t.sol +++ b/test/token-voting/LlamaERC20TokenActionCreator.t.sol @@ -7,10 +7,8 @@ import {LlamaTokenVotingTestSetup} from "test/token-voting/LlamaTokenVotingTestS import {LlamaCoreSigUtils} from "test/utils/LlamaCoreSigUtils.sol"; import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; -import {ILlamaStrategy} from "src/interfaces/ILlamaStrategy.sol"; import {ActionState} from "src/lib/Enums.sol"; import {Action, ActionInfo} from "src/lib/Structs.sol"; -import {LlamaERC20TokenActionCreator} from "src/token-voting/LlamaERC20TokenActionCreator.sol"; import {LlamaTokenActionCreator} from "src/token-voting/LlamaTokenActionCreator.sol"; contract LlamaERC20TokenActionCreatorTest is LlamaTokenVotingTestSetup, LlamaCoreSigUtils { @@ -18,7 +16,7 @@ contract LlamaERC20TokenActionCreatorTest is LlamaTokenVotingTestSetup, LlamaCor event ActionCanceled(uint256 id, address indexed creator); event ActionThresholdSet(uint256 newThreshold); - LlamaERC20TokenActionCreator llamaERC20TokenActionCreator; + LlamaTokenActionCreator llamaERC20TokenActionCreator; function setUp() public virtual override { LlamaTokenVotingTestSetup.setUp(); @@ -49,12 +47,12 @@ contract LlamaERC20TokenActionCreatorTest is LlamaTokenVotingTestSetup, LlamaCor // function test_RevertsIf_InvalidLlamaCore() public { // // With invalid LlamaCore instance, LlamaTokenActionCreator.InvalidLlamaCoreAddress is unreachable // vm.expectRevert(); -// new LlamaERC20TokenActionCreator(erc20VotesToken, ILlamaCore(makeAddr("invalid-llama-core")), uint256(0)); +// new LlamaTokenActionCreator(erc20VotesToken, ILlamaCore(makeAddr("invalid-llama-core")), uint256(0)); // } // function test_RevertsIf_InvalidTokenAddress() public { // vm.expectRevert(); // will EvmError: Revert vecause totalSupply fn does not exist -// new LlamaERC20TokenActionCreator(ERC20Votes(makeAddr("invalid-erc20VotesToken")), CORE, uint256(0)); +// new LlamaTokenActionCreator(ERC20Votes(makeAddr("invalid-erc20VotesToken")), CORE, uint256(0)); // } // function test_RevertsIf_CreationThresholdExceedsTotalSupply() public { @@ -64,7 +62,7 @@ contract LlamaERC20TokenActionCreatorTest is LlamaTokenVotingTestSetup, LlamaCor // vm.warp(block.timestamp + 1); // vm.expectRevert(LlamaTokenActionCreator.InvalidCreationThreshold.selector); -// new LlamaERC20TokenActionCreator(erc20VotesToken, CORE, 17_000_000_000_000_000_000_000_000); +// new LlamaTokenActionCreator(erc20VotesToken, CORE, 17_000_000_000_000_000_000_000_000); // } // function test_ProperlySetsConstructorArguments() public { @@ -74,8 +72,8 @@ contract LlamaERC20TokenActionCreatorTest is LlamaTokenVotingTestSetup, LlamaCor // vm.warp(block.timestamp + 1); -// LlamaERC20TokenActionCreator llamaERC20TokenActionCreator = new -// LlamaERC20TokenActionCreator(erc20VotesToken, +// LlamaTokenActionCreator llamaERC20TokenActionCreator = new +// LlamaTokenActionCreator(erc20VotesToken, // CORE, // threshold); // assertEq(address(llamaERC20TokenActionCreator.TOKEN()), address(erc20VotesToken)); diff --git a/test/token-voting/LlamaERC20TokenCaster.t.sol b/test/token-voting/LlamaERC20TokenCaster.t.sol index b5ccfe9..bfa5189 100644 --- a/test/token-voting/LlamaERC20TokenCaster.t.sol +++ b/test/token-voting/LlamaERC20TokenCaster.t.sol @@ -170,12 +170,11 @@ contract CastVote is LlamaERC20TokenCasterTest { function test_RevertsIf_ApprovalNotEnabled() public { LlamaTokenCaster casterWithWrongRole = LlamaTokenCaster( Clones.cloneDeterministic( - address(llamaERC20TokenCasterLogic), keccak256(abi.encodePacked(address(erc20VotesToken), msg.sender)) + address(llamaTokenCasterLogic), keccak256(abi.encodePacked(address(erc20VotesToken), msg.sender)) ) ); - casterWithWrongRole.initialize( - erc20VotesToken, CORE, LLAMA_TOKEN_TIMESTAMP_ADAPTER, madeUpRole, ERC20_VOTE_QUORUM_PCT, ERC20_VETO_QUORUM_PCT - ); + ILlamaTokenAdapter tokenAdapter = createTimestampTokenAdapter(address(erc20VotesToken)); + casterWithWrongRole.initialize(CORE, tokenAdapter, madeUpRole, ERC20_VOTE_QUORUM_PCT, ERC20_VETO_QUORUM_PCT); vm.expectRevert(abi.encodeWithSelector(ILlamaRelativeStrategyBase.InvalidRole.selector, tokenVotingCasterRole)); casterWithWrongRole.castVote(actionInfo, uint8(VoteType.For), ""); @@ -345,12 +344,11 @@ contract CastVeto is LlamaERC20TokenCasterTest { function test_RevertsIf_DisapprovalNotEnabled() public { LlamaTokenCaster casterWithWrongRole = LlamaTokenCaster( Clones.cloneDeterministic( - address(llamaERC20TokenCasterLogic), keccak256(abi.encodePacked(address(erc20VotesToken), msg.sender)) + address(llamaTokenCasterLogic), keccak256(abi.encodePacked(address(erc20VotesToken), msg.sender)) ) ); - casterWithWrongRole.initialize( - erc20VotesToken, CORE, LLAMA_TOKEN_TIMESTAMP_ADAPTER, madeUpRole, ERC20_VOTE_QUORUM_PCT, ERC20_VETO_QUORUM_PCT - ); + ILlamaTokenAdapter tokenAdapter = createTimestampTokenAdapter(address(erc20VotesToken)); + casterWithWrongRole.initialize(CORE, tokenAdapter, madeUpRole, ERC20_VOTE_QUORUM_PCT, ERC20_VETO_QUORUM_PCT); vm.expectRevert(abi.encodeWithSelector(ILlamaRelativeStrategyBase.InvalidRole.selector, tokenVotingCasterRole)); casterWithWrongRole.castVeto(actionInfo, uint8(VoteType.For), ""); @@ -566,12 +564,11 @@ contract SubmitApprovals is LlamaERC20TokenCasterTest { function test_RevertsIf_ApprovalNotEnabled() public { LlamaTokenCaster casterWithWrongRole = LlamaTokenCaster( Clones.cloneDeterministic( - address(llamaERC20TokenCasterLogic), keccak256(abi.encodePacked(address(erc20VotesToken), msg.sender)) + address(llamaTokenCasterLogic), keccak256(abi.encodePacked(address(erc20VotesToken), msg.sender)) ) ); - casterWithWrongRole.initialize( - erc20VotesToken, CORE, LLAMA_TOKEN_TIMESTAMP_ADAPTER, madeUpRole, ERC20_VOTE_QUORUM_PCT, ERC20_VETO_QUORUM_PCT - ); + ILlamaTokenAdapter tokenAdapter = createTimestampTokenAdapter(address(erc20VotesToken)); + casterWithWrongRole.initialize(CORE, tokenAdapter, madeUpRole, ERC20_VOTE_QUORUM_PCT, ERC20_VETO_QUORUM_PCT); vm.expectRevert(abi.encodeWithSelector(ILlamaRelativeStrategyBase.InvalidRole.selector, tokenVotingCasterRole)); casterWithWrongRole.submitApproval(actionInfo); } @@ -649,12 +646,11 @@ contract SubmitDisapprovals is LlamaERC20TokenCasterTest { vm.warp(block.timestamp + (1 days * TWO_THIRDS_IN_BPS) / ONE_HUNDRED_IN_BPS); LlamaTokenCaster casterWithWrongRole = LlamaTokenCaster( Clones.cloneDeterministic( - address(llamaERC20TokenCasterLogic), keccak256(abi.encodePacked(address(erc20VotesToken), msg.sender)) + address(llamaTokenCasterLogic), keccak256(abi.encodePacked(address(erc20VotesToken), msg.sender)) ) ); - casterWithWrongRole.initialize( - erc20VotesToken, CORE, LLAMA_TOKEN_TIMESTAMP_ADAPTER, madeUpRole, ERC20_VOTE_QUORUM_PCT, ERC20_VETO_QUORUM_PCT - ); + ILlamaTokenAdapter tokenAdapter = createTimestampTokenAdapter(address(erc20VotesToken)); + casterWithWrongRole.initialize(CORE, tokenAdapter, madeUpRole, ERC20_VOTE_QUORUM_PCT, ERC20_VETO_QUORUM_PCT); vm.expectRevert(abi.encodeWithSelector(ILlamaRelativeStrategyBase.InvalidRole.selector, tokenVotingCasterRole)); casterWithWrongRole.submitDisapproval(actionInfo); } diff --git a/test/token-voting/LlamaERC721TokenActionCreator.t.sol b/test/token-voting/LlamaERC721TokenActionCreator.t.sol index 3602762..bb8d5d6 100644 --- a/test/token-voting/LlamaERC721TokenActionCreator.t.sol +++ b/test/token-voting/LlamaERC721TokenActionCreator.t.sol @@ -7,10 +7,8 @@ import {LlamaTokenVotingTestSetup} from "test/token-voting/LlamaTokenVotingTestS import {LlamaCoreSigUtils} from "test/utils/LlamaCoreSigUtils.sol"; import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; -import {ILlamaStrategy} from "src/interfaces/ILlamaStrategy.sol"; import {ActionState} from "src/lib/Enums.sol"; import {Action, ActionInfo} from "src/lib/Structs.sol"; -import {LlamaERC721TokenActionCreator} from "src/token-voting/LlamaERC721TokenActionCreator.sol"; import {LlamaTokenActionCreator} from "src/token-voting/LlamaTokenActionCreator.sol"; contract LlamaERC721TokenActionCreatorTest is LlamaTokenVotingTestSetup, LlamaCoreSigUtils { @@ -18,7 +16,7 @@ contract LlamaERC721TokenActionCreatorTest is LlamaTokenVotingTestSetup, LlamaCo event ActionCanceled(uint256 id, address indexed creator); event ActionThresholdSet(uint256 newThreshold); - LlamaERC721TokenActionCreator llamaERC721TokenActionCreator; + LlamaTokenActionCreator llamaERC721TokenActionCreator; function setUp() public virtual override { LlamaTokenVotingTestSetup.setUp(); @@ -49,13 +47,13 @@ contract LlamaERC721TokenActionCreatorTest is LlamaTokenVotingTestSetup, LlamaCo // function test_RevertsIf_InvalidLlamaCore() public { // // With invalid LlamaCore instance, LlamaTokenActionCreator.InvalidLlamaCoreAddress is unreachable // vm.expectRevert(); -// new LlamaERC721TokenActionCreator(erc721VotesToken, ILlamaCore(makeAddr("invalid-llama-core")), +// new LlamaTokenActionCreator(erc721VotesToken, ILlamaCore(makeAddr("invalid-llama-core")), // uint256(0)); // } // function test_RevertsIf_InvalidTokenAddress() public { // vm.expectRevert(); // will EvmError: Revert vecause totalSupply fn does not exist -// new LlamaERC721TokenActionCreator(ERC20Votes(makeAddr("invalid-erc721VotesToken")), CORE, uint256(0)); +// new LlamaTokenActionCreator(ERC20Votes(makeAddr("invalid-erc721VotesToken")), CORE, uint256(0)); // } // function test_RevertsIf_CreationThresholdExceedsTotalSupply() public { @@ -65,7 +63,7 @@ contract LlamaERC721TokenActionCreatorTest is LlamaTokenVotingTestSetup, LlamaCo // vm.warp(block.timestamp + 1); // vm.expectRevert(LlamaTokenActionCreator.InvalidCreationThreshold.selector); -// new LlamaERC721TokenActionCreator(erc721VotesToken, CORE, 17_000_000_000_000_000_000_000_000); +// new LlamaTokenActionCreator(erc721VotesToken, CORE, 17_000_000_000_000_000_000_000_000); // } // function test_ProperlySetsConstructorArguments() public { @@ -75,8 +73,8 @@ contract LlamaERC721TokenActionCreatorTest is LlamaTokenVotingTestSetup, LlamaCo // vm.warp(block.timestamp + 1); -// LlamaERC721TokenActionCreator llamaERC721TokenActionCreator = new -// LlamaERC721TokenActionCreator(erc721VotesToken, +// LlamaTokenActionCreator llamaERC721TokenActionCreator = new +// LlamaTokenActionCreator(erc721VotesToken, // CORE, // threshold); // assertEq(address(llamaERC721TokenActionCreator.TOKEN()), address(erc721VotesToken)); diff --git a/test/token-voting/LlamaTokenVotingFactory.t.sol b/test/token-voting/LlamaTokenVotingFactory.t.sol index 1f50bd7..6e58da4 100644 --- a/test/token-voting/LlamaTokenVotingFactory.t.sol +++ b/test/token-voting/LlamaTokenVotingFactory.t.sol @@ -11,10 +11,9 @@ import {ActionInfo} from "src/lib/Structs.sol"; import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; import {ILlamaPolicy} from "src/interfaces/ILlamaPolicy.sol"; import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; -import {LlamaERC20TokenActionCreator} from "src/token-voting/LlamaERC20TokenActionCreator.sol"; -import {LlamaERC20TokenCaster} from "src/token-voting/LlamaERC20TokenCaster.sol"; -import {LlamaERC721TokenActionCreator} from "src/token-voting/LlamaERC721TokenActionCreator.sol"; -import {LlamaERC721TokenCaster} from "src/token-voting/LlamaERC721TokenCaster.sol"; +import {LlamaTokenAdapterTimestamp} from "src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol"; +import {LlamaTokenActionCreator} from "src/token-voting/LlamaTokenActionCreator.sol"; +import {LlamaTokenCaster} from "src/token-voting/LlamaTokenCaster.sol"; import {LlamaTokenVotingFactory} from "src/token-voting/LlamaTokenVotingFactory.sol"; contract LlamaTokenVotingFactoryTest is LlamaTokenVotingTestSetup { @@ -22,13 +21,13 @@ contract LlamaTokenVotingFactoryTest is LlamaTokenVotingTestSetup { address indexed deployer, ILlamaCore indexed llamaCore, address indexed token, + ILlamaTokenAdapter tokenAdapterLogic, ILlamaTokenAdapter tokenAdapter, uint256 nonce, - bool isERC20, uint8 actionCreatorRole, uint8 casterRole, - address llamaTokenActionCreator, - address llamaTokenCaster, + LlamaTokenActionCreator llamaTokenActionCreator, + LlamaTokenCaster llamaTokenCaster, uint256 chainId ); event ActionThresholdSet(uint256 newThreshold); @@ -49,21 +48,11 @@ contract LlamaTokenVotingFactoryTest is LlamaTokenVotingTestSetup { contract Constructor is LlamaTokenVotingFactoryTest { function test_SetsLlamaERC20TokenActionCreatorLogicAddress() public { - assertEq(address(tokenVotingFactory.ERC20_TOKEN_ACTION_CREATOR_LOGIC()), address(llamaERC20TokenActionCreatorLogic)); + assertEq(address(tokenVotingFactory.LLAMA_TOKEN_ACTION_CREATOR_LOGIC()), address(llamaTokenActionCreatorLogic)); } function test_SetsLlamaERC20TokenCasterLogicAddress() public { - assertEq(address(tokenVotingFactory.ERC20_TOKEN_CASTER_LOGIC()), address(llamaERC20TokenCasterLogic)); - } - - function test_SetsLlamaERC721TokenActionCreatorLogicAddress() public { - assertEq( - address(tokenVotingFactory.ERC721_TOKEN_ACTION_CREATOR_LOGIC()), address(llamaERC721TokenActionCreatorLogic) - ); - } - - function test_SetsLlamaERC721TokenCasterLogicAddress() public { - assertEq(address(tokenVotingFactory.ERC721_TOKEN_CASTER_LOGIC()), address(llamaERC721TokenCasterLogic)); + assertEq(address(tokenVotingFactory.LLAMA_TOKEN_CASTER_LOGIC()), address(llamaTokenCasterLogic)); } } @@ -91,37 +80,47 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { } function test_CanDeployERC20TokenVotingModule() public { - // Set up action to call `deploy` with the ERC20 token. - bytes memory data = abi.encodeWithSelector( - LlamaTokenVotingFactory.deploy.selector, + bytes memory adapterConfig = abi.encode(LlamaTokenAdapterTimestamp.Config(address(erc20VotesToken))); + LlamaTokenVotingFactory.LlamaTokenVotingConfig memory config = LlamaTokenVotingFactory.LlamaTokenVotingConfig( CORE, - address(erc20VotesToken), - LLAMA_TOKEN_TIMESTAMP_ADAPTER, + llamaTokenAdapterTimestampLogic, + adapterConfig, 0, - true, tokenVotingActionCreatorRole, tokenVotingCasterRole, ERC20_CREATION_THRESHOLD, ERC20_VOTE_QUORUM_PCT, ERC20_VETO_QUORUM_PCT ); + + // Set up action to call `deploy` with the ERC20 token. + bytes memory data = abi.encodeWithSelector(LlamaTokenVotingFactory.deploy.selector, config); ActionInfo memory actionInfo = _setPermissionCreateApproveAndQueueAction(data); // Compute addresses of ERC20 Token Voting Module - LlamaERC20TokenActionCreator llamaERC20TokenActionCreator = LlamaERC20TokenActionCreator( + LlamaTokenActionCreator llamaERC20TokenActionCreator = LlamaTokenActionCreator( Clones.predictDeterministicAddress( - address(llamaERC20TokenActionCreatorLogic), + address(llamaTokenActionCreatorLogic), keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), address(erc20VotesToken), uint256(0))), // salt address(tokenVotingFactory) // deployer ) ); - LlamaERC20TokenCaster llamaERC20TokenCaster = LlamaERC20TokenCaster( + LlamaTokenCaster llamaERC20TokenCaster = LlamaTokenCaster( Clones.predictDeterministicAddress( - address(llamaERC20TokenCasterLogic), + address(llamaTokenCasterLogic), keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), address(erc20VotesToken), uint256(0))), // salt address(tokenVotingFactory) // deployer ) ); + ILlamaTokenAdapter llamaERC20TokenAdapter = ILlamaTokenAdapter( + Clones.predictDeterministicAddress( + address(llamaTokenAdapterTimestampLogic), + keccak256( + abi.encode(address(erc20VotesToken)) // salt + ), + address(tokenVotingFactory) // deployer + ) + ); // Execute call to `deploy`. vm.expectEmit(); @@ -133,58 +132,72 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { address(EXECUTOR), CORE, address(erc20VotesToken), - LLAMA_TOKEN_TIMESTAMP_ADAPTER, + llamaTokenAdapterTimestampLogic, + llamaERC20TokenAdapter, 0, - true, tokenVotingActionCreatorRole, tokenVotingCasterRole, - address(llamaERC20TokenActionCreator), - address(llamaERC20TokenCaster), + llamaERC20TokenActionCreator, + llamaERC20TokenCaster, block.chainid ); CORE.executeAction(actionInfo); - assertEq(address(llamaERC20TokenActionCreator.token()), address(erc20VotesToken)); + assertEq(address(llamaERC20TokenAdapter.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(llamaERC20TokenAdapter.token()), address(erc20VotesToken)); assertEq(address(llamaERC20TokenCaster.llamaCore()), address(CORE)); assertEq(llamaERC20TokenCaster.role(), tokenVotingCasterRole); } function test_CanDeployERC721TokenVotingModule() public { - // Set up action to call `deploy` with the ERC721 token. - bytes memory data = abi.encodeWithSelector( - LlamaTokenVotingFactory.deploy.selector, + bytes memory adapterConfig = abi.encode(LlamaTokenAdapterTimestamp.Config(address(erc721VotesToken))); + LlamaTokenVotingFactory.LlamaTokenVotingConfig memory config = LlamaTokenVotingFactory.LlamaTokenVotingConfig( CORE, - address(erc721VotesToken), - LLAMA_TOKEN_TIMESTAMP_ADAPTER, + llamaTokenAdapterTimestampLogic, + adapterConfig, 0, - false, tokenVotingActionCreatorRole, tokenVotingCasterRole, ERC721_CREATION_THRESHOLD, ERC721_VOTE_QUORUM_PCT, ERC721_VETO_QUORUM_PCT ); + + // Set up action to call `deploy` with the ERC721 token. + bytes memory data = abi.encodeWithSelector(LlamaTokenVotingFactory.deploy.selector, config); ActionInfo memory actionInfo = _setPermissionCreateApproveAndQueueAction(data); // Compute addresses of ERC721 Token Voting Module - LlamaERC721TokenActionCreator llamaERC721TokenActionCreator = LlamaERC721TokenActionCreator( + LlamaTokenActionCreator llamaERC721TokenActionCreator = LlamaTokenActionCreator( + Clones.predictDeterministicAddress( + address(llamaTokenActionCreatorLogic), + keccak256( + abi.encodePacked(address(EXECUTOR), address(CORE), address(llamaTokenAdapterTimestampLogic), uint256(0)) + ), // salt + address(tokenVotingFactory) // deployer + ) + ); + LlamaTokenCaster llamaERC721TokenCaster = LlamaTokenCaster( Clones.predictDeterministicAddress( - address(llamaERC721TokenActionCreatorLogic), - keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), address(erc721VotesToken), uint256(0))), // salt + address(llamaTokenCasterLogic), + keccak256( + abi.encodePacked(address(EXECUTOR), address(CORE), address(llamaTokenAdapterTimestampLogic), uint256(0)) + ), // salt address(tokenVotingFactory) // deployer ) ); - LlamaERC721TokenCaster llamaERC721TokenCaster = LlamaERC721TokenCaster( + ILlamaTokenAdapter llamaERC721TokenAdapter = ILlamaTokenAdapter( Clones.predictDeterministicAddress( - address(llamaERC721TokenCasterLogic), - keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), address(erc721VotesToken), uint256(0))), // salt + address(llamaTokenAdapterTimestampLogic), + keccak256( + abi.encode(address(erc721VotesToken)) // salt + ), address(tokenVotingFactory) // deployer ) ); @@ -199,25 +212,25 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { address(EXECUTOR), CORE, address(erc721VotesToken), - LLAMA_TOKEN_TIMESTAMP_ADAPTER, + llamaTokenAdapterTimestampLogic, + llamaERC721TokenAdapter, 0, - false, tokenVotingActionCreatorRole, tokenVotingCasterRole, - address(llamaERC721TokenActionCreator), - address(llamaERC721TokenCaster), + llamaERC721TokenActionCreator, + llamaERC721TokenCaster, block.chainid ); CORE.executeAction(actionInfo); - assertEq(address(llamaERC721TokenActionCreator.token()), address(erc721VotesToken)); + assertEq(address(llamaERC721TokenAdapter.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(llamaERC721TokenAdapter.token()), address(erc721VotesToken)); assertEq(address(llamaERC721TokenCaster.llamaCore()), address(CORE)); assertEq(llamaERC721TokenCaster.role(), tokenVotingCasterRole); } @@ -226,21 +239,30 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { vm.assume(randomCaller != address(0)); vm.deal(randomCaller, 1 ether); - LlamaERC20TokenActionCreator llamaERC20TokenActionCreator = LlamaERC20TokenActionCreator( + LlamaTokenActionCreator llamaERC20TokenActionCreator = LlamaTokenActionCreator( Clones.predictDeterministicAddress( - address(llamaERC20TokenActionCreatorLogic), + address(llamaTokenActionCreatorLogic), keccak256(abi.encodePacked(randomCaller, address(CORE), address(erc20VotesToken), uint256(0))), // salt address(tokenVotingFactory) // deployer ) ); - LlamaERC20TokenCaster llamaERC20TokenCaster = LlamaERC20TokenCaster( + LlamaTokenCaster llamaERC20TokenCaster = LlamaTokenCaster( Clones.predictDeterministicAddress( - address(llamaERC20TokenCasterLogic), + address(llamaTokenCasterLogic), keccak256(abi.encodePacked(randomCaller, address(CORE), address(erc20VotesToken), uint256(0))), // salt address(tokenVotingFactory) // deployer ) ); + ILlamaTokenAdapter llamaERC20TokenAdapter = ILlamaTokenAdapter( + Clones.predictDeterministicAddress( + address(llamaTokenAdapterTimestampLogic), + keccak256( + abi.encode(address(erc20VotesToken)) // salt + ), + address(tokenVotingFactory) // deployer + ) + ); vm.expectEmit(); emit ActionThresholdSet(ERC20_CREATION_THRESHOLD); @@ -251,23 +273,22 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { randomCaller, CORE, address(erc20VotesToken), - LLAMA_TOKEN_TIMESTAMP_ADAPTER, + llamaTokenAdapterTimestampLogic, + llamaERC20TokenAdapter, 0, - true, tokenVotingActionCreatorRole, tokenVotingCasterRole, - address(llamaERC20TokenActionCreator), - address(llamaERC20TokenCaster), + llamaERC20TokenActionCreator, + llamaERC20TokenCaster, block.chainid ); - vm.prank(randomCaller); - tokenVotingFactory.deploy( + bytes memory adapterConfig = abi.encode(LlamaTokenAdapterTimestamp.Config(address(erc20VotesToken))); + LlamaTokenVotingFactory.LlamaTokenVotingConfig memory config = LlamaTokenVotingFactory.LlamaTokenVotingConfig( CORE, - address(erc20VotesToken), - LLAMA_TOKEN_TIMESTAMP_ADAPTER, + llamaTokenAdapterTimestampLogic, + adapterConfig, 0, - true, tokenVotingActionCreatorRole, tokenVotingCasterRole, ERC20_CREATION_THRESHOLD, @@ -275,14 +296,17 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { ERC20_VETO_QUORUM_PCT ); - assertEq(address(llamaERC20TokenActionCreator.token()), address(erc20VotesToken)); + vm.prank(randomCaller); + tokenVotingFactory.deploy(config); + + assertEq(address(llamaERC20TokenAdapter.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(llamaERC20TokenAdapter.token()), address(erc20VotesToken)); assertEq(address(llamaERC20TokenCaster.llamaCore()), address(CORE)); assertEq(llamaERC20TokenCaster.role(), tokenVotingCasterRole); } @@ -292,14 +316,12 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { // First deployment// ///////////////////// - // Set up action to call `deploy` with the ERC20 token. - bytes memory data = abi.encodeWithSelector( - LlamaTokenVotingFactory.deploy.selector, + bytes memory adapterConfig = abi.encode(LlamaTokenAdapterTimestamp.Config(address(erc20VotesToken))); + LlamaTokenVotingFactory.LlamaTokenVotingConfig memory config = LlamaTokenVotingFactory.LlamaTokenVotingConfig( CORE, - address(erc20VotesToken), - LLAMA_TOKEN_TIMESTAMP_ADAPTER, + llamaTokenAdapterTimestampLogic, + adapterConfig, 0, - true, tokenVotingActionCreatorRole, tokenVotingCasterRole, ERC20_CREATION_THRESHOLD, @@ -307,23 +329,35 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { ERC20_VETO_QUORUM_PCT ); + // Set up action to call `deploy` with the ERC20 token. + bytes memory data = abi.encodeWithSelector(LlamaTokenVotingFactory.deploy.selector, config); + ActionInfo memory actionInfo = _setPermissionCreateApproveAndQueueAction(data); // Compute addresses of ERC20 Token Voting Module - LlamaERC20TokenActionCreator llamaERC20TokenActionCreator = LlamaERC20TokenActionCreator( + LlamaTokenActionCreator llamaERC20TokenActionCreator = LlamaTokenActionCreator( Clones.predictDeterministicAddress( - address(llamaERC20TokenActionCreatorLogic), + address(llamaTokenActionCreatorLogic), keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), address(erc20VotesToken), uint256(0))), // salt address(tokenVotingFactory) // deployer ) ); - LlamaERC20TokenCaster llamaERC20TokenCaster = LlamaERC20TokenCaster( + LlamaTokenCaster llamaERC20TokenCaster = LlamaTokenCaster( Clones.predictDeterministicAddress( - address(llamaERC20TokenCasterLogic), + address(llamaTokenCasterLogic), keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), address(erc20VotesToken), uint256(0))), // salt address(tokenVotingFactory) // deployer ) ); + ILlamaTokenAdapter llamaERC20TokenAdapter = ILlamaTokenAdapter( + Clones.predictDeterministicAddress( + address(llamaTokenAdapterTimestampLogic), + keccak256( + abi.encode(address(erc20VotesToken)) // salt + ), + address(tokenVotingFactory) // deployer + ) + ); // Execute call to `deploy`. vm.expectEmit(); @@ -331,13 +365,13 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { address(EXECUTOR), CORE, address(erc20VotesToken), - LLAMA_TOKEN_TIMESTAMP_ADAPTER, + llamaTokenAdapterTimestampLogic, + llamaERC20TokenAdapter, 0, - true, tokenVotingActionCreatorRole, tokenVotingCasterRole, - address(llamaERC20TokenActionCreator), - address(llamaERC20TokenCaster), + llamaERC20TokenActionCreator, + llamaERC20TokenCaster, block.chainid ); CORE.executeAction(actionInfo); @@ -346,14 +380,12 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { // Second deployment// ////////////////////// - // Set up action to call `deploy` with the ERC20 token. - data = abi.encodeWithSelector( - LlamaTokenVotingFactory.deploy.selector, + bytes memory adapterConfig2 = abi.encode(LlamaTokenAdapterTimestamp.Config(address(erc20VotesToken))); + LlamaTokenVotingFactory.LlamaTokenVotingConfig memory config2 = LlamaTokenVotingFactory.LlamaTokenVotingConfig( CORE, - address(erc20VotesToken), - LLAMA_TOKEN_TIMESTAMP_ADAPTER, - 1, - true, + llamaTokenAdapterTimestampLogic, + adapterConfig2, + 0, tokenVotingActionCreatorRole, tokenVotingCasterRole, ERC20_CREATION_THRESHOLD, @@ -361,23 +393,35 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { ERC20_VETO_QUORUM_PCT ); + // Set up action to call `deploy` with the ERC20 token. + data = abi.encodeWithSelector(LlamaTokenVotingFactory.deploy.selector, config2); + actionInfo = _setPermissionCreateApproveAndQueueAction(data); // Compute addresses of ERC20 Token Voting Module - llamaERC20TokenActionCreator = LlamaERC20TokenActionCreator( + llamaERC20TokenActionCreator = LlamaTokenActionCreator( Clones.predictDeterministicAddress( - address(llamaERC20TokenActionCreatorLogic), + address(llamaTokenActionCreatorLogic), keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), address(erc20VotesToken), uint256(1))), // salt address(tokenVotingFactory) // deployer ) ); - llamaERC20TokenCaster = LlamaERC20TokenCaster( + llamaERC20TokenCaster = LlamaTokenCaster( Clones.predictDeterministicAddress( - address(llamaERC20TokenCasterLogic), + address(llamaTokenCasterLogic), keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), address(erc20VotesToken), uint256(1))), // salt address(tokenVotingFactory) // deployer ) ); + ILlamaTokenAdapter llamaERC20TokenAdapter2 = ILlamaTokenAdapter( + Clones.predictDeterministicAddress( + address(llamaTokenAdapterTimestampLogic), + keccak256( + abi.encode(address(erc20VotesToken)) // salt + ), + address(tokenVotingFactory) // deployer + ) + ); // Execute call to `deploy`. vm.expectEmit(); @@ -385,13 +429,13 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { address(EXECUTOR), CORE, address(erc20VotesToken), - LLAMA_TOKEN_TIMESTAMP_ADAPTER, + llamaTokenAdapterTimestampLogic, + llamaERC20TokenAdapter2, 1, - true, tokenVotingActionCreatorRole, tokenVotingCasterRole, - address(llamaERC20TokenActionCreator), - address(llamaERC20TokenCaster), + llamaERC20TokenActionCreator, + llamaERC20TokenCaster, block.chainid ); CORE.executeAction(actionInfo); From dc89f14b3d60267f16d0f2d63bb023921aa31a85 Mon Sep 17 00:00:00 2001 From: Austin Green Date: Fri, 15 Dec 2023 13:45:33 -0500 Subject: [PATCH 11/23] updating tests --- src/token-voting/LlamaTokenVotingFactory.sol | 2 +- test/token-voting/LlamaERC721TokenCaster.t.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/token-voting/LlamaTokenVotingFactory.sol b/src/token-voting/LlamaTokenVotingFactory.sol index 3597c01..1c30040 100644 --- a/src/token-voting/LlamaTokenVotingFactory.sol +++ b/src/token-voting/LlamaTokenVotingFactory.sol @@ -89,7 +89,7 @@ contract LlamaTokenVotingFactory { // Check to see if token adapter was correctly initialized if (address(tokenAdapter.token()) == address(0)) revert InvalidTokenAdapterConfig(); - if (tokenAdapter.timestampToTimepoint(block.timestamp) >= 0) revert InvalidTokenAdapterConfig(); + if (tokenAdapter.timestampToTimepoint(block.timestamp) == 0) revert InvalidTokenAdapterConfig(); // Reverts if clock is inconsistent tokenAdapter.checkIfInconsistentClock(); diff --git a/test/token-voting/LlamaERC721TokenCaster.t.sol b/test/token-voting/LlamaERC721TokenCaster.t.sol index 9acb03c..b4b3c7a 100644 --- a/test/token-voting/LlamaERC721TokenCaster.t.sol +++ b/test/token-voting/LlamaERC721TokenCaster.t.sol @@ -13,8 +13,8 @@ import {Action, ActionInfo} from "src/lib/Structs.sol"; import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; import {ILlamaRelativeStrategyBase} from "src/interfaces/ILlamaRelativeStrategyBase.sol"; import {ILlamaStrategy} from "src/interfaces/ILlamaStrategy.sol"; -import {LlamaTokenAdapterTimestamp} from "src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol"; import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; +import {LlamaTokenAdapterTimestamp} from "src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol"; import {LlamaTokenCaster} from "src/token-voting/LlamaTokenCaster.sol"; contract LlamaERC721TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUtils { From 12c8d2e8797d4536c6254ea65a353d870a161c90 Mon Sep 17 00:00:00 2001 From: Austin Green Date: Fri, 15 Dec 2023 13:49:57 -0500 Subject: [PATCH 12/23] followups --- README.md | 4 +++- script/DeployLlamaTokenVotingFactory.s.sol | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 15af30d..9a679eb 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,9 @@ repository. ## Modules -- **Token Voting:** smart contract policies that allow `ERC20Votes` or `ERC721Votes` tokenholders to create actions enforced by delegated token thresholds or collectively approve or disapprove an action through token voting. +Llama modules are extensions to Llama instances that can be adopted by using a Llama action to configure and deploy. + +- **Token Voting:** smart contract policies that allow voting token holders to create actions enforced by delegated token thresholds or collectively approve or disapprove an action through token voting. ## Prerequisites diff --git a/script/DeployLlamaTokenVotingFactory.s.sol b/script/DeployLlamaTokenVotingFactory.s.sol index e1de3ac..065a15f 100644 --- a/script/DeployLlamaTokenVotingFactory.s.sol +++ b/script/DeployLlamaTokenVotingFactory.s.sol @@ -33,14 +33,14 @@ contract DeployLlamaTokenVotingFactory is Script { llamaTokenCasterLogic = new LlamaTokenCaster(); DeployUtils.print(string.concat(" LlamaTokenCasterLogic: ", vm.toString(address(llamaTokenCasterLogic)))); + vm.broadcast(); + tokenVotingFactory = new LlamaTokenVotingFactory(llamaTokenActionCreatorLogic, llamaTokenCasterLogic); + DeployUtils.print(string.concat(" LlamaTokenVotingFactory: ", vm.toString(address(tokenVotingFactory)))); + vm.broadcast(); llamaTokenAdapterTimestampLogic = new LlamaTokenAdapterTimestamp(); DeployUtils.print( string.concat(" LlamaTokenAdapterTimestamp: ", vm.toString(address(llamaTokenAdapterTimestampLogic))) ); - - vm.broadcast(); - tokenVotingFactory = new LlamaTokenVotingFactory(llamaTokenActionCreatorLogic, llamaTokenCasterLogic); - DeployUtils.print(string.concat(" LlamaTokenVotingFactory: ", vm.toString(address(tokenVotingFactory)))); } } From 78ac847748017272d4397b020c035338e8461936 Mon Sep 17 00:00:00 2001 From: Austin Green Date: Fri, 15 Dec 2023 13:58:25 -0500 Subject: [PATCH 13/23] followups --- script/DeployLlamaTokenVotingFactory.s.sol | 8 ++++---- src/token-voting/LlamaTokenActionCreator.sol | 12 +++++------- src/token-voting/LlamaTokenVotingFactory.sol | 2 +- src/token-voting/interfaces/ILlamaTokenAdapter.sol | 4 +--- ...stamp.sol => LlamaTokenAdapterVotesTimestamp.sol} | 8 ++------ test/token-voting/LlamaERC20TokenCaster.t.sol | 4 ++-- test/token-voting/LlamaERC721TokenCaster.t.sol | 4 ++-- test/token-voting/LlamaTokenVotingFactory.t.sol | 12 ++++++------ test/token-voting/LlamaTokenVotingTestSetup.sol | 6 +++--- 9 files changed, 26 insertions(+), 34 deletions(-) rename src/token-voting/token-adapters/{LlamaTokenAdapterTimestamp.sol => LlamaTokenAdapterVotesTimestamp.sol} (91%) diff --git a/script/DeployLlamaTokenVotingFactory.s.sol b/script/DeployLlamaTokenVotingFactory.s.sol index 065a15f..12180a3 100644 --- a/script/DeployLlamaTokenVotingFactory.s.sol +++ b/script/DeployLlamaTokenVotingFactory.s.sol @@ -7,13 +7,13 @@ import {DeployUtils} from "script/DeployUtils.sol"; import {LlamaTokenActionCreator} from "src/token-voting/LlamaTokenActionCreator.sol"; import {LlamaTokenCaster} from "src/token-voting/LlamaTokenCaster.sol"; import {LlamaTokenVotingFactory} from "src/token-voting/LlamaTokenVotingFactory.sol"; -import {LlamaTokenAdapterTimestamp} from "src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol"; +import {LlamaTokenAdapterVotesTimestamp} from "src/token-voting/token-adapters/LlamaTokenAdapterVotesTimestamp.sol"; contract DeployLlamaTokenVotingFactory is Script { // Logic contracts. LlamaTokenActionCreator llamaTokenActionCreatorLogic; LlamaTokenCaster llamaTokenCasterLogic; - LlamaTokenAdapterTimestamp llamaTokenAdapterTimestampLogic; + LlamaTokenAdapterVotesTimestamp llamaTokenAdapterTimestampLogic; // Factory contracts. LlamaTokenVotingFactory tokenVotingFactory; @@ -38,9 +38,9 @@ contract DeployLlamaTokenVotingFactory is Script { DeployUtils.print(string.concat(" LlamaTokenVotingFactory: ", vm.toString(address(tokenVotingFactory)))); vm.broadcast(); - llamaTokenAdapterTimestampLogic = new LlamaTokenAdapterTimestamp(); + llamaTokenAdapterTimestampLogic = new LlamaTokenAdapterVotesTimestamp(); DeployUtils.print( - string.concat(" LlamaTokenAdapterTimestamp: ", vm.toString(address(llamaTokenAdapterTimestampLogic))) + string.concat(" LlamaTokenAdapterVotesTimestamp: ", vm.toString(address(llamaTokenAdapterTimestampLogic))) ); } } diff --git a/src/token-voting/LlamaTokenActionCreator.sol b/src/token-voting/LlamaTokenActionCreator.sol index 5e1911e..d3a27ab 100644 --- a/src/token-voting/LlamaTokenActionCreator.sol +++ b/src/token-voting/LlamaTokenActionCreator.sol @@ -135,10 +135,6 @@ contract LlamaTokenActionCreator is Initializable { tokenAdapter = _tokenAdapter; role = _role; _setActionThreshold(_creationThreshold); - - uint256 totalSupply = tokenAdapter.getPastTotalSupply(tokenAdapter.clock() - 1); - if (totalSupply == 0) revert InvalidTokenAddress(); - if (_creationThreshold > totalSupply) revert InvalidCreationThreshold(); } // =========================================== @@ -225,9 +221,6 @@ contract LlamaTokenActionCreator is Initializable { /// @dev This must be in the same decimals as the token. function setActionThreshold(uint256 _creationThreshold) external { if (msg.sender != address(llamaCore.executor())) revert OnlyLlamaExecutor(); - if (_creationThreshold > tokenAdapter.getPastTotalSupply(tokenAdapter.clock() - 1)) { - revert InvalidCreationThreshold(); - } _setActionThreshold(_creationThreshold); } @@ -274,7 +267,12 @@ contract LlamaTokenActionCreator is Initializable { /// @dev Sets the default number of tokens required to create an action. function _setActionThreshold(uint256 _creationThreshold) internal { + uint256 totalSupply = tokenAdapter.getPastTotalSupply(tokenAdapter.clock() - 1); + if (totalSupply == 0) revert InvalidTokenAddress(); + if (_creationThreshold > totalSupply) revert InvalidCreationThreshold(); + creationThreshold = _creationThreshold; + emit ActionThresholdSet(_creationThreshold); } diff --git a/src/token-voting/LlamaTokenVotingFactory.sol b/src/token-voting/LlamaTokenVotingFactory.sol index 1c30040..4a13441 100644 --- a/src/token-voting/LlamaTokenVotingFactory.sol +++ b/src/token-voting/LlamaTokenVotingFactory.sol @@ -79,7 +79,7 @@ contract LlamaTokenVotingFactory { external returns (LlamaTokenActionCreator actionCreator, LlamaTokenCaster caster) { - // Initialize token adapter based on provided logic address and config + // Deploy and initialize token adapter based on provided logic address and config ILlamaTokenAdapter tokenAdapter = ILlamaTokenAdapter( Clones.cloneDeterministic( address(tokenVotingConfig.tokenAdapterLogic), keccak256(tokenVotingConfig.adapterConfig) diff --git a/src/token-voting/interfaces/ILlamaTokenAdapter.sol b/src/token-voting/interfaces/ILlamaTokenAdapter.sol index ffa4060..41ca31e 100644 --- a/src/token-voting/interfaces/ILlamaTokenAdapter.sol +++ b/src/token-voting/interfaces/ILlamaTokenAdapter.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.23; -import {IVotes} from "@openzeppelin/governance/utils/IVotes.sol"; - /// @title ILlamaTokenAdapter /// @author Llama (devsdosomething@llama.xyz) /// @notice This contract provides an interface for voting token adapters. @@ -16,7 +14,7 @@ interface ILlamaTokenAdapter { /// (like the zero address) will revert. function initialize(bytes memory config) external returns (bool); - /// @notice Returns the token voting module's `IVotes` voting token. + /// @notice Returns the token voting module's voting token address. /// @return token The voting token. function token() external view returns (address token); diff --git a/src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol b/src/token-voting/token-adapters/LlamaTokenAdapterVotesTimestamp.sol similarity index 91% rename from src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol rename to src/token-voting/token-adapters/LlamaTokenAdapterVotesTimestamp.sol index 710bdf6..bdc3c9d 100644 --- a/src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol +++ b/src/token-voting/token-adapters/LlamaTokenAdapterVotesTimestamp.sol @@ -8,7 +8,7 @@ import {IERC6372} from "@openzeppelin/interfaces/IERC6372.sol"; import {LlamaUtils} from "src/lib/LlamaUtils.sol"; import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; -contract LlamaTokenAdapterTimestamp is ILlamaTokenAdapter, Initializable { +contract LlamaTokenAdapterVotesTimestamp is ILlamaTokenAdapter, Initializable { // ========================= // ======== Structs ======== // ========================= @@ -95,10 +95,6 @@ contract LlamaTokenAdapterTimestamp is ILlamaTokenAdapter, Initializable { } function _hasClockChanged() internal view returns (bool) { - if (keccak256(abi.encodePacked(CLOCK_MODE)) == keccak256(abi.encodePacked("mode=timestamp"))) { - return clock() != LlamaUtils.toUint48(block.timestamp); - } else { - return clock() != LlamaUtils.toUint48(block.number); - } + return clock() != LlamaUtils.toUint48(block.timestamp); } } diff --git a/test/token-voting/LlamaERC20TokenCaster.t.sol b/test/token-voting/LlamaERC20TokenCaster.t.sol index bfa5189..e58f497 100644 --- a/test/token-voting/LlamaERC20TokenCaster.t.sol +++ b/test/token-voting/LlamaERC20TokenCaster.t.sol @@ -13,7 +13,7 @@ import {Action, ActionInfo} from "src/lib/Structs.sol"; import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; import {ILlamaRelativeStrategyBase} from "src/interfaces/ILlamaRelativeStrategyBase.sol"; import {ILlamaStrategy} from "src/interfaces/ILlamaStrategy.sol"; -import {LlamaTokenAdapterTimestamp} from "src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol"; +import {LlamaTokenAdapterVotesTimestamp} from "src/token-voting/token-adapters/LlamaTokenAdapterVotesTimestamp.sol"; import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; import {LlamaTokenCaster} from "src/token-voting/LlamaTokenCaster.sol"; @@ -91,7 +91,7 @@ contract LlamaERC20TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUti } function createTimestampTokenAdapter(address token) public returns (ILlamaTokenAdapter tokenAdapter) { - bytes memory adapterConfig = abi.encode(LlamaTokenAdapterTimestamp.Config(address(token))); + bytes memory adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(token))); tokenAdapter = ILlamaTokenAdapter(Clones.cloneDeterministic(address(llamaTokenAdapterTimestampLogic), keccak256(adapterConfig))); diff --git a/test/token-voting/LlamaERC721TokenCaster.t.sol b/test/token-voting/LlamaERC721TokenCaster.t.sol index b4b3c7a..c6c122b 100644 --- a/test/token-voting/LlamaERC721TokenCaster.t.sol +++ b/test/token-voting/LlamaERC721TokenCaster.t.sol @@ -14,7 +14,7 @@ import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; import {ILlamaRelativeStrategyBase} from "src/interfaces/ILlamaRelativeStrategyBase.sol"; import {ILlamaStrategy} from "src/interfaces/ILlamaStrategy.sol"; import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; -import {LlamaTokenAdapterTimestamp} from "src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol"; +import {LlamaTokenAdapterVotesTimestamp} from "src/token-voting/token-adapters/LlamaTokenAdapterVotesTimestamp.sol"; import {LlamaTokenCaster} from "src/token-voting/LlamaTokenCaster.sol"; contract LlamaERC721TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUtils { @@ -91,7 +91,7 @@ contract LlamaERC721TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUt } function createTimestampTokenAdapter(address token) public returns (ILlamaTokenAdapter tokenAdapter) { - bytes memory adapterConfig = abi.encode(LlamaTokenAdapterTimestamp.Config(address(token))); + bytes memory adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(token))); tokenAdapter = ILlamaTokenAdapter(Clones.cloneDeterministic(address(llamaTokenAdapterTimestampLogic), keccak256(adapterConfig))); diff --git a/test/token-voting/LlamaTokenVotingFactory.t.sol b/test/token-voting/LlamaTokenVotingFactory.t.sol index 6e58da4..7c01f0d 100644 --- a/test/token-voting/LlamaTokenVotingFactory.t.sol +++ b/test/token-voting/LlamaTokenVotingFactory.t.sol @@ -11,7 +11,7 @@ import {ActionInfo} from "src/lib/Structs.sol"; import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; import {ILlamaPolicy} from "src/interfaces/ILlamaPolicy.sol"; import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; -import {LlamaTokenAdapterTimestamp} from "src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol"; +import {LlamaTokenAdapterVotesTimestamp} from "src/token-voting/token-adapters/LlamaTokenAdapterVotesTimestamp.sol"; import {LlamaTokenActionCreator} from "src/token-voting/LlamaTokenActionCreator.sol"; import {LlamaTokenCaster} from "src/token-voting/LlamaTokenCaster.sol"; import {LlamaTokenVotingFactory} from "src/token-voting/LlamaTokenVotingFactory.sol"; @@ -80,7 +80,7 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { } function test_CanDeployERC20TokenVotingModule() public { - bytes memory adapterConfig = abi.encode(LlamaTokenAdapterTimestamp.Config(address(erc20VotesToken))); + bytes memory adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(erc20VotesToken))); LlamaTokenVotingFactory.LlamaTokenVotingConfig memory config = LlamaTokenVotingFactory.LlamaTokenVotingConfig( CORE, llamaTokenAdapterTimestampLogic, @@ -156,7 +156,7 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { } function test_CanDeployERC721TokenVotingModule() public { - bytes memory adapterConfig = abi.encode(LlamaTokenAdapterTimestamp.Config(address(erc721VotesToken))); + bytes memory adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(erc721VotesToken))); LlamaTokenVotingFactory.LlamaTokenVotingConfig memory config = LlamaTokenVotingFactory.LlamaTokenVotingConfig( CORE, llamaTokenAdapterTimestampLogic, @@ -283,7 +283,7 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { block.chainid ); - bytes memory adapterConfig = abi.encode(LlamaTokenAdapterTimestamp.Config(address(erc20VotesToken))); + bytes memory adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(erc20VotesToken))); LlamaTokenVotingFactory.LlamaTokenVotingConfig memory config = LlamaTokenVotingFactory.LlamaTokenVotingConfig( CORE, llamaTokenAdapterTimestampLogic, @@ -316,7 +316,7 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { // First deployment// ///////////////////// - bytes memory adapterConfig = abi.encode(LlamaTokenAdapterTimestamp.Config(address(erc20VotesToken))); + bytes memory adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(erc20VotesToken))); LlamaTokenVotingFactory.LlamaTokenVotingConfig memory config = LlamaTokenVotingFactory.LlamaTokenVotingConfig( CORE, llamaTokenAdapterTimestampLogic, @@ -380,7 +380,7 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { // Second deployment// ////////////////////// - bytes memory adapterConfig2 = abi.encode(LlamaTokenAdapterTimestamp.Config(address(erc20VotesToken))); + bytes memory adapterConfig2 = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(erc20VotesToken))); LlamaTokenVotingFactory.LlamaTokenVotingConfig memory config2 = LlamaTokenVotingFactory.LlamaTokenVotingConfig( CORE, llamaTokenAdapterTimestampLogic, diff --git a/test/token-voting/LlamaTokenVotingTestSetup.sol b/test/token-voting/LlamaTokenVotingTestSetup.sol index 3d67b9a..0af1eeb 100644 --- a/test/token-voting/LlamaTokenVotingTestSetup.sol +++ b/test/token-voting/LlamaTokenVotingTestSetup.sol @@ -15,7 +15,7 @@ import {ILlamaPolicy} from "src/interfaces/ILlamaPolicy.sol"; import {ILlamaRelativeStrategyBase} from "src/interfaces/ILlamaRelativeStrategyBase.sol"; import {ILlamaStrategy} from "src/interfaces/ILlamaStrategy.sol"; import {RoleDescription} from "src/lib/UDVTs.sol"; -import {LlamaTokenAdapterTimestamp} from "src/token-voting/token-adapters/LlamaTokenAdapterTimestamp.sol"; +import {LlamaTokenAdapterVotesTimestamp} from "src/token-voting/token-adapters/LlamaTokenAdapterVotesTimestamp.sol"; import {LlamaTokenVotingFactory} from "src/token-voting/LlamaTokenVotingFactory.sol"; import {LlamaTokenActionCreator} from "src/token-voting/LlamaTokenActionCreator.sol"; import {LlamaTokenCaster} from "src/token-voting/LlamaTokenCaster.sol"; @@ -92,7 +92,7 @@ contract LlamaTokenVotingTestSetup is LlamaPeripheryTestSetup, DeployLlamaTokenV // ========================= function _deployERC20TokenVotingModuleAndSetRole() internal returns (LlamaTokenActionCreator, LlamaTokenCaster) { - bytes memory adapterConfig = abi.encode(LlamaTokenAdapterTimestamp.Config(address(erc20VotesToken))); + bytes memory adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(erc20VotesToken))); LlamaTokenVotingFactory.LlamaTokenVotingConfig memory config = LlamaTokenVotingFactory.LlamaTokenVotingConfig( CORE, llamaTokenAdapterTimestampLogic, @@ -122,7 +122,7 @@ contract LlamaTokenVotingTestSetup is LlamaPeripheryTestSetup, DeployLlamaTokenV } function _deployERC721TokenVotingModuleAndSetRole() internal returns (LlamaTokenActionCreator, LlamaTokenCaster) { - bytes memory adapterConfig = abi.encode(LlamaTokenAdapterTimestamp.Config(address(erc721VotesToken))); + bytes memory adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(erc721VotesToken))); LlamaTokenVotingFactory.LlamaTokenVotingConfig memory config = LlamaTokenVotingFactory.LlamaTokenVotingConfig( CORE, llamaTokenAdapterTimestampLogic, From e21397ead472bd65a4ab0dd386dbcbc5e12f7daa Mon Sep 17 00:00:00 2001 From: Austin Green Date: Fri, 15 Dec 2023 14:00:42 -0500 Subject: [PATCH 14/23] natspec --- .../token-adapters/LlamaTokenAdapterVotesTimestamp.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/token-voting/token-adapters/LlamaTokenAdapterVotesTimestamp.sol b/src/token-voting/token-adapters/LlamaTokenAdapterVotesTimestamp.sol index bdc3c9d..738ad69 100644 --- a/src/token-voting/token-adapters/LlamaTokenAdapterVotesTimestamp.sol +++ b/src/token-voting/token-adapters/LlamaTokenAdapterVotesTimestamp.sol @@ -86,6 +86,7 @@ contract LlamaTokenAdapterVotesTimestamp is ILlamaTokenAdapter, Initializable { return IVotes(token).getPastTotalSupply(timepoint); } + /// @dev Check to see if the token's CLOCK_MODE function is returning a different CLOCK_MODE. function _hasClockModeChanged() internal view returns (bool) { try IERC6372(token).CLOCK_MODE() returns (string memory mode) { return keccak256(abi.encodePacked(mode)) != keccak256(abi.encodePacked(CLOCK_MODE)); @@ -94,6 +95,7 @@ contract LlamaTokenAdapterVotesTimestamp is ILlamaTokenAdapter, Initializable { } } + /// @dev Check to see if the token's clock function is no longer returning the timestamp function _hasClockChanged() internal view returns (bool) { return clock() != LlamaUtils.toUint48(block.timestamp); } From ecde9c624a0d3ba82946d71afbb2060b5a795fec Mon Sep 17 00:00:00 2001 From: Austin Green Date: Fri, 15 Dec 2023 14:03:38 -0500 Subject: [PATCH 15/23] remove check --- src/token-voting/LlamaTokenCaster.sol | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/token-voting/LlamaTokenCaster.sol b/src/token-voting/LlamaTokenCaster.sol index c42448f..33ad3e3 100644 --- a/src/token-voting/LlamaTokenCaster.sol +++ b/src/token-voting/LlamaTokenCaster.sol @@ -213,9 +213,6 @@ contract LlamaTokenCaster is Initializable { tokenAdapter = _tokenAdapter; role = _role; _setQuorumPct(_voteQuorumPct, _vetoQuorumPct); - - uint256 totalSupply = tokenAdapter.getPastTotalSupply(tokenAdapter.clock() - 1); - if (totalSupply == 0) revert InvalidTokenAddress(); } // =========================================== From f1053cc4ece44e5df6c5354c9d3db4ef4babf481 Mon Sep 17 00:00:00 2001 From: Austin Green Date: Fri, 15 Dec 2023 14:28:27 -0500 Subject: [PATCH 16/23] fix tests --- src/token-voting/LlamaTokenVotingFactory.sol | 3 +- .../LlamaTokenVotingFactory.t.sol | 62 +++++++++++++------ 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/src/token-voting/LlamaTokenVotingFactory.sol b/src/token-voting/LlamaTokenVotingFactory.sol index 4a13441..96bc5dc 100644 --- a/src/token-voting/LlamaTokenVotingFactory.sol +++ b/src/token-voting/LlamaTokenVotingFactory.sol @@ -82,7 +82,8 @@ contract LlamaTokenVotingFactory { // Deploy and initialize token adapter based on provided logic address and config ILlamaTokenAdapter tokenAdapter = ILlamaTokenAdapter( Clones.cloneDeterministic( - address(tokenVotingConfig.tokenAdapterLogic), keccak256(tokenVotingConfig.adapterConfig) + address(tokenVotingConfig.tokenAdapterLogic), + keccak256(abi.encode(tokenVotingConfig.adapterConfig, tokenVotingConfig.nonce)) ) ); tokenAdapter.initialize(tokenVotingConfig.adapterConfig); diff --git a/test/token-voting/LlamaTokenVotingFactory.t.sol b/test/token-voting/LlamaTokenVotingFactory.t.sol index 7c01f0d..1b79f8b 100644 --- a/test/token-voting/LlamaTokenVotingFactory.t.sol +++ b/test/token-voting/LlamaTokenVotingFactory.t.sol @@ -101,14 +101,18 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { LlamaTokenActionCreator llamaERC20TokenActionCreator = LlamaTokenActionCreator( Clones.predictDeterministicAddress( address(llamaTokenActionCreatorLogic), - keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), address(erc20VotesToken), uint256(0))), // salt + keccak256( + abi.encodePacked(address(EXECUTOR), address(CORE), address(llamaTokenAdapterTimestampLogic), uint256(0)) + ), // salt address(tokenVotingFactory) // deployer ) ); LlamaTokenCaster llamaERC20TokenCaster = LlamaTokenCaster( Clones.predictDeterministicAddress( address(llamaTokenCasterLogic), - keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), address(erc20VotesToken), uint256(0))), // salt + keccak256( + abi.encodePacked(address(EXECUTOR), address(CORE), address(llamaTokenAdapterTimestampLogic), uint256(0)) + ), // salt address(tokenVotingFactory) // deployer ) ); @@ -116,7 +120,10 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { Clones.predictDeterministicAddress( address(llamaTokenAdapterTimestampLogic), keccak256( - abi.encode(address(erc20VotesToken)) // salt + abi.encode( + abi.encode(address(erc20VotesToken)), // salt + 0 + ) ), address(tokenVotingFactory) // deployer ) @@ -242,7 +249,7 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { LlamaTokenActionCreator llamaERC20TokenActionCreator = LlamaTokenActionCreator( Clones.predictDeterministicAddress( address(llamaTokenActionCreatorLogic), - keccak256(abi.encodePacked(randomCaller, address(CORE), address(erc20VotesToken), uint256(0))), // salt + keccak256(abi.encodePacked(randomCaller, address(CORE), address(llamaTokenAdapterTimestampLogic), uint256(0))), // salt address(tokenVotingFactory) // deployer ) ); @@ -250,7 +257,7 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { LlamaTokenCaster llamaERC20TokenCaster = LlamaTokenCaster( Clones.predictDeterministicAddress( address(llamaTokenCasterLogic), - keccak256(abi.encodePacked(randomCaller, address(CORE), address(erc20VotesToken), uint256(0))), // salt + keccak256(abi.encodePacked(randomCaller, address(CORE), address(llamaTokenAdapterTimestampLogic), uint256(0))), // salt address(tokenVotingFactory) // deployer ) ); @@ -258,7 +265,10 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { Clones.predictDeterministicAddress( address(llamaTokenAdapterTimestampLogic), keccak256( - abi.encode(address(erc20VotesToken)) // salt + abi.encode( + abi.encode(address(erc20VotesToken)), // salt + 0 + ) ), address(tokenVotingFactory) // deployer ) @@ -338,14 +348,18 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { LlamaTokenActionCreator llamaERC20TokenActionCreator = LlamaTokenActionCreator( Clones.predictDeterministicAddress( address(llamaTokenActionCreatorLogic), - keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), address(erc20VotesToken), uint256(0))), // salt + keccak256( + abi.encodePacked(address(EXECUTOR), address(CORE), address(llamaTokenAdapterTimestampLogic), uint256(0)) + ), // salt address(tokenVotingFactory) // deployer ) ); LlamaTokenCaster llamaERC20TokenCaster = LlamaTokenCaster( Clones.predictDeterministicAddress( address(llamaTokenCasterLogic), - keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), address(erc20VotesToken), uint256(0))), // salt + keccak256( + abi.encodePacked(address(EXECUTOR), address(CORE), address(llamaTokenAdapterTimestampLogic), uint256(0)) + ), // salt address(tokenVotingFactory) // deployer ) ); @@ -353,7 +367,10 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { Clones.predictDeterministicAddress( address(llamaTokenAdapterTimestampLogic), keccak256( - abi.encode(address(erc20VotesToken)) // salt + abi.encode( + abi.encode(address(erc20VotesToken)), // salt + 0 + ) ), address(tokenVotingFactory) // deployer ) @@ -380,12 +397,12 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { // Second deployment// ////////////////////// - bytes memory adapterConfig2 = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(erc20VotesToken))); - LlamaTokenVotingFactory.LlamaTokenVotingConfig memory config2 = LlamaTokenVotingFactory.LlamaTokenVotingConfig( + adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(erc20VotesToken))); + config = LlamaTokenVotingFactory.LlamaTokenVotingConfig( CORE, llamaTokenAdapterTimestampLogic, - adapterConfig2, - 0, + adapterConfig, + 1, tokenVotingActionCreatorRole, tokenVotingCasterRole, ERC20_CREATION_THRESHOLD, @@ -394,7 +411,7 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { ); // Set up action to call `deploy` with the ERC20 token. - data = abi.encodeWithSelector(LlamaTokenVotingFactory.deploy.selector, config2); + data = abi.encodeWithSelector(LlamaTokenVotingFactory.deploy.selector, config); actionInfo = _setPermissionCreateApproveAndQueueAction(data); @@ -402,22 +419,29 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { llamaERC20TokenActionCreator = LlamaTokenActionCreator( Clones.predictDeterministicAddress( address(llamaTokenActionCreatorLogic), - keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), address(erc20VotesToken), uint256(1))), // salt + keccak256( + abi.encodePacked(address(EXECUTOR), address(CORE), address(llamaTokenAdapterTimestampLogic), uint256(1)) + ), // salt address(tokenVotingFactory) // deployer ) ); llamaERC20TokenCaster = LlamaTokenCaster( Clones.predictDeterministicAddress( address(llamaTokenCasterLogic), - keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), address(erc20VotesToken), uint256(1))), // salt + keccak256( + abi.encodePacked(address(EXECUTOR), address(CORE), address(llamaTokenAdapterTimestampLogic), uint256(1)) + ), // salt address(tokenVotingFactory) // deployer ) ); - ILlamaTokenAdapter llamaERC20TokenAdapter2 = ILlamaTokenAdapter( + llamaERC20TokenAdapter = ILlamaTokenAdapter( Clones.predictDeterministicAddress( address(llamaTokenAdapterTimestampLogic), keccak256( - abi.encode(address(erc20VotesToken)) // salt + abi.encode( + abi.encode(address(erc20VotesToken)), // salt + 1 + ) ), address(tokenVotingFactory) // deployer ) @@ -430,7 +454,7 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { CORE, address(erc20VotesToken), llamaTokenAdapterTimestampLogic, - llamaERC20TokenAdapter2, + llamaERC20TokenAdapter, 1, tokenVotingActionCreatorRole, tokenVotingCasterRole, From efa40f8bf675dac9840335ad1d6f9fab66658ee3 Mon Sep 17 00:00:00 2001 From: Austin Green Date: Fri, 15 Dec 2023 14:29:57 -0500 Subject: [PATCH 17/23] fix 721 tests --- test/token-voting/LlamaTokenVotingFactory.t.sol | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/token-voting/LlamaTokenVotingFactory.t.sol b/test/token-voting/LlamaTokenVotingFactory.t.sol index 1b79f8b..c56929c 100644 --- a/test/token-voting/LlamaTokenVotingFactory.t.sol +++ b/test/token-voting/LlamaTokenVotingFactory.t.sol @@ -199,11 +199,15 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { address(tokenVotingFactory) // deployer ) ); + ILlamaTokenAdapter llamaERC721TokenAdapter = ILlamaTokenAdapter( Clones.predictDeterministicAddress( address(llamaTokenAdapterTimestampLogic), keccak256( - abi.encode(address(erc721VotesToken)) // salt + abi.encode( + abi.encode(address(erc721VotesToken)), // salt + 0 + ) ), address(tokenVotingFactory) // deployer ) From 4eead82e89b4394cc2769a39d2ced1540eb19deb Mon Sep 17 00:00:00 2001 From: Austin Green Date: Fri, 15 Dec 2023 15:03:44 -0500 Subject: [PATCH 18/23] salt refactor --- src/token-voting/LlamaTokenVotingFactory.sol | 40 ++------- test/token-voting/LlamaERC20TokenCaster.t.sol | 15 ++-- .../token-voting/LlamaERC721TokenCaster.t.sol | 15 ++-- .../LlamaTokenVotingFactory.t.sol | 84 ++++++------------- 4 files changed, 51 insertions(+), 103 deletions(-) diff --git a/src/token-voting/LlamaTokenVotingFactory.sol b/src/token-voting/LlamaTokenVotingFactory.sol index 96bc5dc..8a30b42 100644 --- a/src/token-voting/LlamaTokenVotingFactory.sol +++ b/src/token-voting/LlamaTokenVotingFactory.sol @@ -79,13 +79,15 @@ contract LlamaTokenVotingFactory { external returns (LlamaTokenActionCreator actionCreator, LlamaTokenCaster caster) { - // Deploy and initialize token adapter based on provided logic address and config - ILlamaTokenAdapter tokenAdapter = ILlamaTokenAdapter( - Clones.cloneDeterministic( - address(tokenVotingConfig.tokenAdapterLogic), - keccak256(abi.encode(tokenVotingConfig.adapterConfig, tokenVotingConfig.nonce)) + bytes32 salt = keccak256( + abi.encode( + msg.sender, address(tokenVotingConfig.llamaCore), tokenVotingConfig.adapterConfig, tokenVotingConfig.nonce ) ); + + // Deploy and initialize token adapter based on provided logic address and config + ILlamaTokenAdapter tokenAdapter = + ILlamaTokenAdapter(Clones.cloneDeterministic(address(tokenVotingConfig.tokenAdapterLogic), salt)); tokenAdapter.initialize(tokenVotingConfig.adapterConfig); // Check to see if token adapter was correctly initialized @@ -96,19 +98,7 @@ contract LlamaTokenVotingFactory { tokenAdapter.checkIfInconsistentClock(); // Deploy and initialize `LlamaTokenActionCreator` contract - actionCreator = LlamaTokenActionCreator( - Clones.cloneDeterministic( - address(LLAMA_TOKEN_ACTION_CREATOR_LOGIC), - keccak256( - abi.encodePacked( - msg.sender, - address(tokenVotingConfig.llamaCore), - address(tokenVotingConfig.tokenAdapterLogic), - tokenVotingConfig.nonce - ) - ) - ) - ); + actionCreator = LlamaTokenActionCreator(Clones.cloneDeterministic(address(LLAMA_TOKEN_ACTION_CREATOR_LOGIC), salt)); actionCreator.initialize( tokenVotingConfig.llamaCore, @@ -118,19 +108,7 @@ contract LlamaTokenVotingFactory { ); // Deploy and initialize `LlamaTokenCaster` contract - caster = LlamaTokenCaster( - Clones.cloneDeterministic( - address(LLAMA_TOKEN_CASTER_LOGIC), - keccak256( - abi.encodePacked( - msg.sender, - address(tokenVotingConfig.llamaCore), - address(tokenVotingConfig.tokenAdapterLogic), - tokenVotingConfig.nonce - ) - ) - ) - ); + caster = LlamaTokenCaster(Clones.cloneDeterministic(address(LLAMA_TOKEN_CASTER_LOGIC), salt)); caster.initialize( tokenVotingConfig.llamaCore, diff --git a/test/token-voting/LlamaERC20TokenCaster.t.sol b/test/token-voting/LlamaERC20TokenCaster.t.sol index 109011b..bd4b744 100644 --- a/test/token-voting/LlamaERC20TokenCaster.t.sol +++ b/test/token-voting/LlamaERC20TokenCaster.t.sol @@ -92,11 +92,12 @@ contract LlamaERC20TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUti llamaERC20TokenCaster.castVeto(actionInfo, uint8(VoteType.For), ""); } - function createTimestampTokenAdapter(address token) public returns (ILlamaTokenAdapter tokenAdapter) { + function createTimestampTokenAdapter(address token, uint256 nonce) public returns (ILlamaTokenAdapter tokenAdapter) { bytes memory adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(token))); - tokenAdapter = - ILlamaTokenAdapter(Clones.cloneDeterministic(address(llamaTokenAdapterTimestampLogic), keccak256(adapterConfig))); + bytes32 salt = keccak256(abi.encode(msg.sender, address(CORE), adapterConfig, nonce)); + + tokenAdapter = ILlamaTokenAdapter(Clones.cloneDeterministic(address(llamaTokenAdapterTimestampLogic), salt)); tokenAdapter.initialize(adapterConfig); } } @@ -125,7 +126,7 @@ contract CastVote is LlamaERC20TokenCasterTest { address(llamaTokenCasterLogic), keccak256(abi.encodePacked(address(erc20VotesToken), msg.sender)) ) ); - ILlamaTokenAdapter tokenAdapter = createTimestampTokenAdapter(address(erc20VotesToken)); + ILlamaTokenAdapter tokenAdapter = createTimestampTokenAdapter(address(erc20VotesToken), 0); casterWithWrongRole.initialize(CORE, tokenAdapter, madeUpRole, ERC20_VOTE_QUORUM_PCT, ERC20_VETO_QUORUM_PCT); vm.expectRevert(abi.encodeWithSelector(ILlamaRelativeStrategyBase.InvalidRole.selector, tokenVotingCasterRole)); @@ -339,7 +340,7 @@ contract CastVeto is LlamaERC20TokenCasterTest { address(llamaTokenCasterLogic), keccak256(abi.encodePacked(address(erc20VotesToken), msg.sender)) ) ); - ILlamaTokenAdapter tokenAdapter = createTimestampTokenAdapter(address(erc20VotesToken)); + ILlamaTokenAdapter tokenAdapter = createTimestampTokenAdapter(address(erc20VotesToken), 0); casterWithWrongRole.initialize(CORE, tokenAdapter, madeUpRole, ERC20_VOTE_QUORUM_PCT, ERC20_VETO_QUORUM_PCT); vm.expectRevert(abi.encodeWithSelector(ILlamaRelativeStrategyBase.InvalidRole.selector, tokenVotingCasterRole)); @@ -600,7 +601,7 @@ contract SubmitApprovals is LlamaERC20TokenCasterTest { address(llamaTokenCasterLogic), keccak256(abi.encodePacked(address(erc20VotesToken), msg.sender)) ) ); - ILlamaTokenAdapter tokenAdapter = createTimestampTokenAdapter(address(erc20VotesToken)); + ILlamaTokenAdapter tokenAdapter = createTimestampTokenAdapter(address(erc20VotesToken), 0); casterWithWrongRole.initialize(CORE, tokenAdapter, madeUpRole, ERC20_VOTE_QUORUM_PCT, ERC20_VETO_QUORUM_PCT); vm.expectRevert(abi.encodeWithSelector(ILlamaRelativeStrategyBase.InvalidRole.selector, tokenVotingCasterRole)); casterWithWrongRole.submitApproval(actionInfo); @@ -684,7 +685,7 @@ contract SubmitDisapprovals is LlamaERC20TokenCasterTest { address(llamaTokenCasterLogic), keccak256(abi.encodePacked(address(erc20VotesToken), msg.sender)) ) ); - ILlamaTokenAdapter tokenAdapter = createTimestampTokenAdapter(address(erc20VotesToken)); + ILlamaTokenAdapter tokenAdapter = createTimestampTokenAdapter(address(erc20VotesToken), 0); casterWithWrongRole.initialize(CORE, tokenAdapter, madeUpRole, ERC20_VOTE_QUORUM_PCT, ERC20_VETO_QUORUM_PCT); vm.expectRevert(abi.encodeWithSelector(ILlamaRelativeStrategyBase.InvalidRole.selector, tokenVotingCasterRole)); casterWithWrongRole.submitDisapproval(actionInfo); diff --git a/test/token-voting/LlamaERC721TokenCaster.t.sol b/test/token-voting/LlamaERC721TokenCaster.t.sol index acd60d0..7346f6e 100644 --- a/test/token-voting/LlamaERC721TokenCaster.t.sol +++ b/test/token-voting/LlamaERC721TokenCaster.t.sol @@ -92,11 +92,12 @@ contract LlamaERC721TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUt llamaERC721TokenCaster.castVeto(actionInfo, uint8(VoteType.For), ""); } - function createTimestampTokenAdapter(address token) public returns (ILlamaTokenAdapter tokenAdapter) { + function createTimestampTokenAdapter(address token, uint256 nonce) public returns (ILlamaTokenAdapter tokenAdapter) { bytes memory adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(token))); - tokenAdapter = - ILlamaTokenAdapter(Clones.cloneDeterministic(address(llamaTokenAdapterTimestampLogic), keccak256(adapterConfig))); + bytes32 salt = keccak256(abi.encode(msg.sender, address(CORE), adapterConfig, nonce)); + + tokenAdapter = ILlamaTokenAdapter(Clones.cloneDeterministic(address(llamaTokenAdapterTimestampLogic), salt)); tokenAdapter.initialize(adapterConfig); } } @@ -120,7 +121,7 @@ contract CastVote is LlamaERC721TokenCasterTest { } function test_RevertsIf_ApprovalNotEnabled() public { - ILlamaTokenAdapter tokenAdapter = createTimestampTokenAdapter(address(erc721VotesToken)); + ILlamaTokenAdapter tokenAdapter = createTimestampTokenAdapter(address(erc721VotesToken), 0); LlamaTokenCaster casterWithWrongRole = LlamaTokenCaster( Clones.cloneDeterministic( @@ -336,7 +337,7 @@ contract CastVeto is LlamaERC721TokenCasterTest { } function test_RevertsIf_DisapprovalNotEnabled() public { - ILlamaTokenAdapter tokenAdapter = createTimestampTokenAdapter(address(erc721VotesToken)); + ILlamaTokenAdapter tokenAdapter = createTimestampTokenAdapter(address(erc721VotesToken), 0); LlamaTokenCaster casterWithWrongRole = LlamaTokenCaster( Clones.cloneDeterministic( @@ -596,7 +597,7 @@ contract SubmitApprovals is LlamaERC721TokenCasterTest { } function test_RevertsIf_ApprovalNotEnabled() public { - ILlamaTokenAdapter tokenAdapter = createTimestampTokenAdapter(address(erc721VotesToken)); + ILlamaTokenAdapter tokenAdapter = createTimestampTokenAdapter(address(erc721VotesToken), 0); LlamaTokenCaster casterWithWrongRole = LlamaTokenCaster( Clones.cloneDeterministic( @@ -686,7 +687,7 @@ contract SubmitDisapprovals is LlamaERC721TokenCasterTest { address(llamaTokenCasterLogic), keccak256(abi.encodePacked(address(erc721VotesToken), msg.sender)) ) ); - ILlamaTokenAdapter tokenAdapter = createTimestampTokenAdapter(address(erc721VotesToken)); + ILlamaTokenAdapter tokenAdapter = createTimestampTokenAdapter(address(erc721VotesToken), 0); casterWithWrongRole.initialize(CORE, tokenAdapter, madeUpRole, ERC721_VOTE_QUORUM_PCT, ERC721_VETO_QUORUM_PCT); vm.expectRevert(abi.encodeWithSelector(ILlamaRelativeStrategyBase.InvalidRole.selector, tokenVotingCasterRole)); casterWithWrongRole.submitDisapproval(actionInfo); diff --git a/test/token-voting/LlamaTokenVotingFactory.t.sol b/test/token-voting/LlamaTokenVotingFactory.t.sol index c56929c..df7a9f3 100644 --- a/test/token-voting/LlamaTokenVotingFactory.t.sol +++ b/test/token-voting/LlamaTokenVotingFactory.t.sol @@ -97,34 +97,27 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { bytes memory data = abi.encodeWithSelector(LlamaTokenVotingFactory.deploy.selector, config); ActionInfo memory actionInfo = _setPermissionCreateApproveAndQueueAction(data); + bytes32 salt = keccak256(abi.encode(address(EXECUTOR), address(CORE), adapterConfig, uint256(0))); + // Compute addresses of ERC20 Token Voting Module LlamaTokenActionCreator llamaERC20TokenActionCreator = LlamaTokenActionCreator( Clones.predictDeterministicAddress( address(llamaTokenActionCreatorLogic), - keccak256( - abi.encodePacked(address(EXECUTOR), address(CORE), address(llamaTokenAdapterTimestampLogic), uint256(0)) - ), // salt + salt, address(tokenVotingFactory) // deployer ) ); LlamaTokenCaster llamaERC20TokenCaster = LlamaTokenCaster( Clones.predictDeterministicAddress( address(llamaTokenCasterLogic), - keccak256( - abi.encodePacked(address(EXECUTOR), address(CORE), address(llamaTokenAdapterTimestampLogic), uint256(0)) - ), // salt + salt, address(tokenVotingFactory) // deployer ) ); ILlamaTokenAdapter llamaERC20TokenAdapter = ILlamaTokenAdapter( Clones.predictDeterministicAddress( address(llamaTokenAdapterTimestampLogic), - keccak256( - abi.encode( - abi.encode(address(erc20VotesToken)), // salt - 0 - ) - ), + salt, address(tokenVotingFactory) // deployer ) ); @@ -180,35 +173,27 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { bytes memory data = abi.encodeWithSelector(LlamaTokenVotingFactory.deploy.selector, config); ActionInfo memory actionInfo = _setPermissionCreateApproveAndQueueAction(data); + bytes32 salt = keccak256(abi.encode(address(EXECUTOR), address(CORE), adapterConfig, uint256(0))); + // Compute addresses of ERC721 Token Voting Module LlamaTokenActionCreator llamaERC721TokenActionCreator = LlamaTokenActionCreator( Clones.predictDeterministicAddress( address(llamaTokenActionCreatorLogic), - keccak256( - abi.encodePacked(address(EXECUTOR), address(CORE), address(llamaTokenAdapterTimestampLogic), uint256(0)) - ), // salt + salt, address(tokenVotingFactory) // deployer ) ); LlamaTokenCaster llamaERC721TokenCaster = LlamaTokenCaster( Clones.predictDeterministicAddress( address(llamaTokenCasterLogic), - keccak256( - abi.encodePacked(address(EXECUTOR), address(CORE), address(llamaTokenAdapterTimestampLogic), uint256(0)) - ), // salt + salt, address(tokenVotingFactory) // deployer ) ); - ILlamaTokenAdapter llamaERC721TokenAdapter = ILlamaTokenAdapter( Clones.predictDeterministicAddress( address(llamaTokenAdapterTimestampLogic), - keccak256( - abi.encode( - abi.encode(address(erc721VotesToken)), // salt - 0 - ) - ), + salt, address(tokenVotingFactory) // deployer ) ); @@ -250,10 +235,13 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { vm.assume(randomCaller != address(0)); vm.deal(randomCaller, 1 ether); + bytes memory adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(erc20VotesToken))); + bytes32 salt = keccak256(abi.encode(randomCaller, address(CORE), adapterConfig, uint256(0))); + LlamaTokenActionCreator llamaERC20TokenActionCreator = LlamaTokenActionCreator( Clones.predictDeterministicAddress( address(llamaTokenActionCreatorLogic), - keccak256(abi.encodePacked(randomCaller, address(CORE), address(llamaTokenAdapterTimestampLogic), uint256(0))), // salt + salt, address(tokenVotingFactory) // deployer ) ); @@ -261,19 +249,14 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { LlamaTokenCaster llamaERC20TokenCaster = LlamaTokenCaster( Clones.predictDeterministicAddress( address(llamaTokenCasterLogic), - keccak256(abi.encodePacked(randomCaller, address(CORE), address(llamaTokenAdapterTimestampLogic), uint256(0))), // salt + salt, address(tokenVotingFactory) // deployer ) ); ILlamaTokenAdapter llamaERC20TokenAdapter = ILlamaTokenAdapter( Clones.predictDeterministicAddress( address(llamaTokenAdapterTimestampLogic), - keccak256( - abi.encode( - abi.encode(address(erc20VotesToken)), // salt - 0 - ) - ), + salt, address(tokenVotingFactory) // deployer ) ); @@ -297,7 +280,6 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { block.chainid ); - bytes memory adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(erc20VotesToken))); LlamaTokenVotingFactory.LlamaTokenVotingConfig memory config = LlamaTokenVotingFactory.LlamaTokenVotingConfig( CORE, llamaTokenAdapterTimestampLogic, @@ -348,34 +330,27 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { ActionInfo memory actionInfo = _setPermissionCreateApproveAndQueueAction(data); + bytes32 salt = keccak256(abi.encode(address(EXECUTOR), address(CORE), adapterConfig, uint256(0))); + // Compute addresses of ERC20 Token Voting Module LlamaTokenActionCreator llamaERC20TokenActionCreator = LlamaTokenActionCreator( Clones.predictDeterministicAddress( address(llamaTokenActionCreatorLogic), - keccak256( - abi.encodePacked(address(EXECUTOR), address(CORE), address(llamaTokenAdapterTimestampLogic), uint256(0)) - ), // salt + salt, address(tokenVotingFactory) // deployer ) ); LlamaTokenCaster llamaERC20TokenCaster = LlamaTokenCaster( Clones.predictDeterministicAddress( address(llamaTokenCasterLogic), - keccak256( - abi.encodePacked(address(EXECUTOR), address(CORE), address(llamaTokenAdapterTimestampLogic), uint256(0)) - ), // salt + salt, address(tokenVotingFactory) // deployer ) ); ILlamaTokenAdapter llamaERC20TokenAdapter = ILlamaTokenAdapter( Clones.predictDeterministicAddress( address(llamaTokenAdapterTimestampLogic), - keccak256( - abi.encode( - abi.encode(address(erc20VotesToken)), // salt - 0 - ) - ), + salt, address(tokenVotingFactory) // deployer ) ); @@ -419,34 +394,27 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { actionInfo = _setPermissionCreateApproveAndQueueAction(data); + salt = keccak256(abi.encode(address(EXECUTOR), address(CORE), adapterConfig, uint256(1))); + // Compute addresses of ERC20 Token Voting Module llamaERC20TokenActionCreator = LlamaTokenActionCreator( Clones.predictDeterministicAddress( address(llamaTokenActionCreatorLogic), - keccak256( - abi.encodePacked(address(EXECUTOR), address(CORE), address(llamaTokenAdapterTimestampLogic), uint256(1)) - ), // salt + salt, // salt address(tokenVotingFactory) // deployer ) ); llamaERC20TokenCaster = LlamaTokenCaster( Clones.predictDeterministicAddress( address(llamaTokenCasterLogic), - keccak256( - abi.encodePacked(address(EXECUTOR), address(CORE), address(llamaTokenAdapterTimestampLogic), uint256(1)) - ), // salt + salt, // salt address(tokenVotingFactory) // deployer ) ); llamaERC20TokenAdapter = ILlamaTokenAdapter( Clones.predictDeterministicAddress( address(llamaTokenAdapterTimestampLogic), - keccak256( - abi.encode( - abi.encode(address(erc20VotesToken)), // salt - 1 - ) - ), + salt, address(tokenVotingFactory) // deployer ) ); From 63fa191312a584f2a24efbb5e8b7a88eab377499 Mon Sep 17 00:00:00 2001 From: Austin Green Date: Fri, 15 Dec 2023 13:08:24 -0700 Subject: [PATCH 19/23] followups --- src/token-voting/LlamaTokenActionCreator.sol | 6 +++--- src/token-voting/LlamaTokenCaster.sol | 2 +- src/token-voting/LlamaTokenVotingFactory.sol | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/token-voting/LlamaTokenActionCreator.sol b/src/token-voting/LlamaTokenActionCreator.sol index a231d32..f96b4ff 100644 --- a/src/token-voting/LlamaTokenActionCreator.sol +++ b/src/token-voting/LlamaTokenActionCreator.sol @@ -34,8 +34,8 @@ contract LlamaTokenActionCreator is Initializable { /// @dev The recovered signer does not match the expected token holder. error InvalidSignature(); - /// @dev Thrown when an invalid `token` address is passed to the constructor. - error InvalidTokenAddress(); + /// @dev Thrown when a `token` with an invalid totaly supply is passed to the constructor. + error InvalidTotalSupply(); /// @dev Thrown when an invalid `creationThreshold` is passed to the constructor. error InvalidCreationThreshold(); @@ -268,7 +268,7 @@ contract LlamaTokenActionCreator is Initializable { /// @dev Sets the default number of tokens required to create an action. function _setActionThreshold(uint256 _creationThreshold) internal { uint256 totalSupply = tokenAdapter.getPastTotalSupply(tokenAdapter.clock() - 1); - if (totalSupply == 0) revert InvalidTokenAddress(); + if (totalSupply == 0) revert InvalidTotalSupply(); if (_creationThreshold > totalSupply) revert InvalidCreationThreshold(); creationThreshold = _creationThreshold; diff --git a/src/token-voting/LlamaTokenCaster.sol b/src/token-voting/LlamaTokenCaster.sol index df35267..5b2d784 100644 --- a/src/token-voting/LlamaTokenCaster.sol +++ b/src/token-voting/LlamaTokenCaster.sol @@ -97,7 +97,7 @@ contract LlamaTokenCaster is Initializable { error InvalidSignature(); /// @dev Thrown when an invalid `token` address is passed to the constructor. - error InvalidTokenAddress(); + error InvalidTotalSupply(); /// @dev Thrown when an invalid `support` value is used when casting. error InvalidSupport(uint8 support); diff --git a/src/token-voting/LlamaTokenVotingFactory.sol b/src/token-voting/LlamaTokenVotingFactory.sol index 8a30b42..c6111f4 100644 --- a/src/token-voting/LlamaTokenVotingFactory.sol +++ b/src/token-voting/LlamaTokenVotingFactory.sol @@ -80,7 +80,7 @@ contract LlamaTokenVotingFactory { returns (LlamaTokenActionCreator actionCreator, LlamaTokenCaster caster) { bytes32 salt = keccak256( - abi.encode( + abi.encodePacked( msg.sender, address(tokenVotingConfig.llamaCore), tokenVotingConfig.adapterConfig, tokenVotingConfig.nonce ) ); @@ -93,6 +93,7 @@ contract LlamaTokenVotingFactory { // Check to see if token adapter was correctly initialized if (address(tokenAdapter.token()) == address(0)) revert InvalidTokenAdapterConfig(); if (tokenAdapter.timestampToTimepoint(block.timestamp) == 0) revert InvalidTokenAdapterConfig(); + if (tokenAdapter.clock() == 0) revert InvalidTokenAdapterConfig(); // Reverts if clock is inconsistent tokenAdapter.checkIfInconsistentClock(); From d346cd16408d6d632e0a533b903e751056dd1ef4 Mon Sep 17 00:00:00 2001 From: Austin Green Date: Fri, 15 Dec 2023 13:12:31 -0700 Subject: [PATCH 20/23] follow ups --- src/token-voting/LlamaTokenActionCreator.sol | 3 --- src/token-voting/LlamaTokenCaster.sol | 3 --- 2 files changed, 6 deletions(-) diff --git a/src/token-voting/LlamaTokenActionCreator.sol b/src/token-voting/LlamaTokenActionCreator.sol index f96b4ff..19c0a19 100644 --- a/src/token-voting/LlamaTokenActionCreator.sol +++ b/src/token-voting/LlamaTokenActionCreator.sol @@ -34,9 +34,6 @@ contract LlamaTokenActionCreator is Initializable { /// @dev The recovered signer does not match the expected token holder. error InvalidSignature(); - /// @dev Thrown when a `token` with an invalid totaly supply is passed to the constructor. - error InvalidTotalSupply(); - /// @dev Thrown when an invalid `creationThreshold` is passed to the constructor. error InvalidCreationThreshold(); diff --git a/src/token-voting/LlamaTokenCaster.sol b/src/token-voting/LlamaTokenCaster.sol index 5b2d784..4da6b06 100644 --- a/src/token-voting/LlamaTokenCaster.sol +++ b/src/token-voting/LlamaTokenCaster.sol @@ -96,9 +96,6 @@ contract LlamaTokenCaster is Initializable { /// @dev The recovered signer does not match the expected tokenholder. error InvalidSignature(); - /// @dev Thrown when an invalid `token` address is passed to the constructor. - error InvalidTotalSupply(); - /// @dev Thrown when an invalid `support` value is used when casting. error InvalidSupport(uint8 support); From 4be33b05b9d066d9e5730e44c7795e1688d0947a Mon Sep 17 00:00:00 2001 From: Austin Green Date: Fri, 15 Dec 2023 13:15:27 -0700 Subject: [PATCH 21/23] encodePacked --- test/token-voting/LlamaERC20TokenCaster.t.sol | 2 +- test/token-voting/LlamaERC721TokenCaster.t.sol | 2 +- test/token-voting/LlamaTokenVotingFactory.t.sol | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/token-voting/LlamaERC20TokenCaster.t.sol b/test/token-voting/LlamaERC20TokenCaster.t.sol index bd4b744..abe8caf 100644 --- a/test/token-voting/LlamaERC20TokenCaster.t.sol +++ b/test/token-voting/LlamaERC20TokenCaster.t.sol @@ -95,7 +95,7 @@ contract LlamaERC20TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUti function createTimestampTokenAdapter(address token, uint256 nonce) public returns (ILlamaTokenAdapter tokenAdapter) { bytes memory adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(token))); - bytes32 salt = keccak256(abi.encode(msg.sender, address(CORE), adapterConfig, nonce)); + bytes32 salt = keccak256(abi.encodePacked(msg.sender, address(CORE), adapterConfig, nonce)); tokenAdapter = ILlamaTokenAdapter(Clones.cloneDeterministic(address(llamaTokenAdapterTimestampLogic), salt)); tokenAdapter.initialize(adapterConfig); diff --git a/test/token-voting/LlamaERC721TokenCaster.t.sol b/test/token-voting/LlamaERC721TokenCaster.t.sol index 7346f6e..1927502 100644 --- a/test/token-voting/LlamaERC721TokenCaster.t.sol +++ b/test/token-voting/LlamaERC721TokenCaster.t.sol @@ -95,7 +95,7 @@ contract LlamaERC721TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUt function createTimestampTokenAdapter(address token, uint256 nonce) public returns (ILlamaTokenAdapter tokenAdapter) { bytes memory adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(token))); - bytes32 salt = keccak256(abi.encode(msg.sender, address(CORE), adapterConfig, nonce)); + bytes32 salt = keccak256(abi.encodePacked(msg.sender, address(CORE), adapterConfig, nonce)); tokenAdapter = ILlamaTokenAdapter(Clones.cloneDeterministic(address(llamaTokenAdapterTimestampLogic), salt)); tokenAdapter.initialize(adapterConfig); diff --git a/test/token-voting/LlamaTokenVotingFactory.t.sol b/test/token-voting/LlamaTokenVotingFactory.t.sol index df7a9f3..053bc55 100644 --- a/test/token-voting/LlamaTokenVotingFactory.t.sol +++ b/test/token-voting/LlamaTokenVotingFactory.t.sol @@ -97,7 +97,7 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { bytes memory data = abi.encodeWithSelector(LlamaTokenVotingFactory.deploy.selector, config); ActionInfo memory actionInfo = _setPermissionCreateApproveAndQueueAction(data); - bytes32 salt = keccak256(abi.encode(address(EXECUTOR), address(CORE), adapterConfig, uint256(0))); + bytes32 salt = keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), adapterConfig, uint256(0))); // Compute addresses of ERC20 Token Voting Module LlamaTokenActionCreator llamaERC20TokenActionCreator = LlamaTokenActionCreator( @@ -173,7 +173,7 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { bytes memory data = abi.encodeWithSelector(LlamaTokenVotingFactory.deploy.selector, config); ActionInfo memory actionInfo = _setPermissionCreateApproveAndQueueAction(data); - bytes32 salt = keccak256(abi.encode(address(EXECUTOR), address(CORE), adapterConfig, uint256(0))); + bytes32 salt = keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), adapterConfig, uint256(0))); // Compute addresses of ERC721 Token Voting Module LlamaTokenActionCreator llamaERC721TokenActionCreator = LlamaTokenActionCreator( @@ -236,7 +236,7 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { vm.deal(randomCaller, 1 ether); bytes memory adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(erc20VotesToken))); - bytes32 salt = keccak256(abi.encode(randomCaller, address(CORE), adapterConfig, uint256(0))); + bytes32 salt = keccak256(abi.encodePacked(randomCaller, address(CORE), adapterConfig, uint256(0))); LlamaTokenActionCreator llamaERC20TokenActionCreator = LlamaTokenActionCreator( Clones.predictDeterministicAddress( @@ -330,7 +330,7 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { ActionInfo memory actionInfo = _setPermissionCreateApproveAndQueueAction(data); - bytes32 salt = keccak256(abi.encode(address(EXECUTOR), address(CORE), adapterConfig, uint256(0))); + bytes32 salt = keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), adapterConfig, uint256(0))); // Compute addresses of ERC20 Token Voting Module LlamaTokenActionCreator llamaERC20TokenActionCreator = LlamaTokenActionCreator( From 3f158f7c40396f131630d5e8432f8955e8c27f08 Mon Sep 17 00:00:00 2001 From: Austin Green Date: Fri, 15 Dec 2023 13:18:46 -0700 Subject: [PATCH 22/23] add error --- src/token-voting/LlamaTokenActionCreator.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/token-voting/LlamaTokenActionCreator.sol b/src/token-voting/LlamaTokenActionCreator.sol index 19c0a19..f96b4ff 100644 --- a/src/token-voting/LlamaTokenActionCreator.sol +++ b/src/token-voting/LlamaTokenActionCreator.sol @@ -34,6 +34,9 @@ contract LlamaTokenActionCreator is Initializable { /// @dev The recovered signer does not match the expected token holder. error InvalidSignature(); + /// @dev Thrown when a `token` with an invalid totaly supply is passed to the constructor. + error InvalidTotalSupply(); + /// @dev Thrown when an invalid `creationThreshold` is passed to the constructor. error InvalidCreationThreshold(); From a621c0abd838ab027f3edb59f2c5a68266a468e9 Mon Sep 17 00:00:00 2001 From: Austin Green Date: Fri, 15 Dec 2023 13:29:13 -0700 Subject: [PATCH 23/23] encodePacked --- test/token-voting/LlamaTokenVotingFactory.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/token-voting/LlamaTokenVotingFactory.t.sol b/test/token-voting/LlamaTokenVotingFactory.t.sol index 053bc55..f61b44d 100644 --- a/test/token-voting/LlamaTokenVotingFactory.t.sol +++ b/test/token-voting/LlamaTokenVotingFactory.t.sol @@ -394,7 +394,7 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { actionInfo = _setPermissionCreateApproveAndQueueAction(data); - salt = keccak256(abi.encode(address(EXECUTOR), address(CORE), adapterConfig, uint256(1))); + salt = keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), adapterConfig, uint256(1))); // Compute addresses of ERC20 Token Voting Module llamaERC20TokenActionCreator = LlamaTokenActionCreator(