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 bb1b645..12180a3 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 {LlamaTokenAdapterVotesTimestamp} from "src/token-voting/token-adapters/LlamaTokenAdapterVotesTimestamp.sol"; contract DeployLlamaTokenVotingFactory is Script { // Logic contracts. - LlamaERC20TokenActionCreator llamaERC20TokenActionCreatorLogic; - LlamaERC20TokenCaster llamaERC20TokenCasterLogic; - LlamaERC721TokenActionCreator llamaERC721TokenActionCreatorLogic; - LlamaERC721TokenCaster llamaERC721TokenCasterLogic; + LlamaTokenActionCreator llamaTokenActionCreatorLogic; + LlamaTokenCaster llamaTokenCasterLogic; + LlamaTokenAdapterVotesTimestamp llamaTokenAdapterTimestampLogic; // Factory contracts. LlamaTokenVotingFactory tokenVotingFactory; @@ -26,36 +24,23 @@ 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))) - ); + tokenVotingFactory = new LlamaTokenVotingFactory(llamaTokenActionCreatorLogic, llamaTokenCasterLogic); + DeployUtils.print(string.concat(" LlamaTokenVotingFactory: ", vm.toString(address(tokenVotingFactory)))); vm.broadcast(); - llamaERC721TokenCasterLogic = new LlamaERC721TokenCaster(); + llamaTokenAdapterTimestampLogic = new LlamaTokenAdapterVotesTimestamp(); DeployUtils.print( - string.concat(" LlamaERC721TokenCasterLogic: ", vm.toString(address(llamaERC721TokenCasterLogic))) - ); - - vm.broadcast(); - tokenVotingFactory = new LlamaTokenVotingFactory( - llamaERC20TokenActionCreatorLogic, - llamaERC20TokenCasterLogic, - llamaERC721TokenActionCreatorLogic, - llamaERC721TokenCasterLogic + string.concat(" LlamaTokenAdapterVotesTimestamp: ", vm.toString(address(llamaTokenAdapterTimestampLogic))) ); - DeployUtils.print(string.concat(" LlamaTokenVotingFactory: ", vm.toString(address(tokenVotingFactory)))); - - // Deploy the timestamp managers here when we develop them. } } diff --git a/src/token-voting/ILlamaTokenClockAdapter.sol b/src/token-voting/ILlamaTokenClockAdapter.sol deleted file mode 100644 index 0d6fef0..0000000 --- a/src/token-voting/ILlamaTokenClockAdapter.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -/// @title ILlamaTokenClockAdapter -/// @author Llama (devsdosomething@llama.xyz) -/// @notice This contract provides an interface for clock adapters. Clock adapters enable voting tokens that don't use -/// timestamp-based checkpointing to work with the Llama token voting module. -interface ILlamaTokenClockAdapter { - /// @notice Returns the current timepoint according to the token's clock. - /// @return timepoint the current timepoint - function clock() external view returns (uint48 timepoint); - - /// @notice Machine-readable description of the clock as specified in ERC-6372. - function CLOCK_MODE() external view returns (string memory); - - /// @notice Converts a timestamp to timepoint units. - /// @param timestamp The timestamp to convert. - /// @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); -} diff --git a/src/token-voting/LlamaERC20TokenActionCreator.sol b/src/token-voting/LlamaERC20TokenActionCreator.sol deleted file mode 100644 index bc40128..0000000 --- a/src/token-voting/LlamaERC20TokenActionCreator.sol +++ /dev/null @@ -1,61 +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 {ILlamaTokenClockAdapter} from "src/token-voting/ILlamaTokenClockAdapter.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 { - /// @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() { - _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 _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, - ILlamaTokenClockAdapter _clockAdapter, - uint8 _role, - uint256 _creationThreshold - ) external initializer { - __initializeLlamaTokenActionCreatorMinimalProxy(_llamaCore, _clockAdapter, _role, _creationThreshold); - token = _token; - uint256 totalSupply = token.getPastTotalSupply(_currentTimepointMinusOne()); - 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 deleted file mode 100644 index 5017f36..0000000 --- a/src/token-voting/LlamaERC20TokenCaster.sol +++ /dev/null @@ -1,60 +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 {ILlamaTokenClockAdapter} from "src/token-voting/ILlamaTokenClockAdapter.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 { - /// @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() { - _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 _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, - ILlamaTokenClockAdapter _clockAdapter, - uint8 _role, - uint16 _voteQuorumPct, - uint16 _vetoQuorumPct - ) external initializer { - __initializeLlamaTokenCasterMinimalProxy(_llamaCore, _clockAdapter, _role, _voteQuorumPct, _vetoQuorumPct); - token = _token; - uint256 totalSupply = token.getPastTotalSupply(_currentTimepointMinusOne()); - 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 deleted file mode 100644 index 07526bf..0000000 --- a/src/token-voting/LlamaERC721TokenActionCreator.sol +++ /dev/null @@ -1,63 +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 {ILlamaTokenClockAdapter} from "src/token-voting/ILlamaTokenClockAdapter.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 { - /// @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() { - _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 _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, - ILlamaTokenClockAdapter _clockAdapter, - uint8 _role, - uint256 _creationThreshold - ) external initializer { - __initializeLlamaTokenActionCreatorMinimalProxy(_llamaCore, _clockAdapter, _role, _creationThreshold); - token = _token; - if (!token.supportsInterface(type(IERC721).interfaceId)) revert InvalidTokenAddress(); - uint256 totalSupply = token.getPastTotalSupply(_currentTimepointMinusOne()); - 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 deleted file mode 100644 index 52f52d0..0000000 --- a/src/token-voting/LlamaERC721TokenCaster.sol +++ /dev/null @@ -1,62 +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 {ILlamaTokenClockAdapter} from "src/token-voting/ILlamaTokenClockAdapter.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 { - /// @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() { - _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 _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, - ILlamaTokenClockAdapter _clockAdapter, - uint8 _role, - uint16 _voteQuorumPct, - uint16 _vetoQuorumPct - ) external initializer { - __initializeLlamaTokenCasterMinimalProxy(_llamaCore, _clockAdapter, _role, _voteQuorumPct, _vetoQuorumPct); - token = _token; - if (!token.supportsInterface(type(IERC721).interfaceId)) revert InvalidTokenAddress(); - uint256 totalSupply = token.getPastTotalSupply(_currentTimepointMinusOne()); - 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 8ce6d9d..f96b4ff 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"; @@ -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 ======== // ======================== @@ -34,8 +34,8 @@ abstract 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(); @@ -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; @@ -110,23 +110,29 @@ 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, - ILlamaTokenClockAdapter _clockAdapter, - 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); llamaCore = _llamaCore; - clockAdapter = _clockAdapter; + tokenAdapter = _tokenAdapter; role = _role; _setActionThreshold(_creationThreshold); } @@ -215,7 +221,6 @@ 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(); _setActionThreshold(_creationThreshold); } @@ -242,10 +247,10 @@ 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 + // Reverts if clock or CLOCK_MODE() has changed + tokenAdapter.checkIfInconsistentClock(); - 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); @@ -262,40 +267,15 @@ abstract contract LlamaTokenActionCreator is Initializable { /// @dev Sets the default number of tokens required to create an action. function _setActionThreshold(uint256 _creationThreshold) internal { - creationThreshold = _creationThreshold; - emit ActionThresholdSet(_creationThreshold); - } + uint256 totalSupply = tokenAdapter.getPastTotalSupply(tokenAdapter.clock() - 1); + if (totalSupply == 0) revert InvalidTotalSupply(); + if (_creationThreshold > totalSupply) revert InvalidCreationThreshold(); - ///@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; - } + creationThreshold = _creationThreshold; - // 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")); + emit ActionThresholdSet(_creationThreshold); } - /// @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/LlamaTokenCaster.sol b/src/token-voting/LlamaTokenCaster.sol index ba613fb..4da6b06 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 {PeriodPctCheckpoints} from "src/lib/PeriodPctCheckpoints.sol"; @@ -20,7 +20,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 PeriodPctCheckpoints for PeriodPctCheckpoints.History; using QuorumCheckpoints for QuorumCheckpoints.History; // ========================= @@ -96,9 +96,6 @@ abstract 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 InvalidTokenAddress(); - /// @dev Thrown when an invalid `support` value is used when casting. error InvalidSupport(uint8 support); @@ -176,7 +173,7 @@ abstract contract LlamaTokenCaster is Initializable { PeriodPctCheckpoints.History internal periodPctsCheckpoint; /// @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. @@ -194,23 +191,32 @@ 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, - ILlamaTokenClockAdapter _clockAdapter, + 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); llamaCore = _llamaCore; - clockAdapter = _clockAdapter; + tokenAdapter = _tokenAdapter; role = _role; _setQuorumPct(_voteQuorumPct, _vetoQuorumPct); _setPeriodPcts(2500, 5000, 2500); // default to 25% delay, 50% casting, 25% submission periods @@ -323,9 +329,10 @@ abstract contract LlamaTokenCaster is Initializable { if (block.timestamp > action.creationTime + approvalPeriod) revert SubmissionPeriodOver(); - _isClockModeSupported(); // reverts if clock mode is not supported + // Reverts if clock or CLOCK_MODE() has changed + tokenAdapter.checkIfInconsistentClock(); - 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; @@ -356,9 +363,10 @@ abstract contract LlamaTokenCaster is Initializable { } if (block.timestamp >= action.minExecutionTime) revert SubmissionPeriodOver(); - _isClockModeSupported(); // reverts if clock mode is not supported + // Reverts if clock or CLOCK_MODE() has changed + tokenAdapter.checkIfInconsistentClock(); - 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; @@ -473,7 +481,9 @@ abstract contract LlamaTokenCaster is Initializable { revert CastingPeriodOver(); } - uint96 weight = LlamaUtils.toUint96(_getPastVotes(caster, _timestampToTimepoint(delayPeriodTimestamp) - 1)); + uint96 weight = LlamaUtils.toUint96( + tokenAdapter.getPastVotes(caster, tokenAdapter.timestampToTimepoint(delayPeriodTimestamp) - 1) + ); _preCastAssertions(support); if (support == uint8(VoteType.Against)) casts[actionInfo.id].votesAgainst += weight; @@ -504,7 +514,9 @@ abstract contract LlamaTokenCaster is Initializable { revert CastingPeriodOver(); } - uint96 weight = LlamaUtils.toUint96(_getPastVotes(caster, _timestampToTimepoint(delayPeriodTimestamp) - 1)); + uint96 weight = LlamaUtils.toUint96( + tokenAdapter.getPastVotes(caster, tokenAdapter.timestampToTimepoint(delayPeriodTimestamp) - 1) + ); _preCastAssertions(support); if (support == uint8(VoteType.Against)) casts[actionInfo.id].vetoesAgainst += weight; @@ -519,7 +531,8 @@ abstract contract LlamaTokenCaster is Initializable { function _preCastAssertions(uint8 support) internal view { if (support > uint8(VoteType.Abstain)) revert InvalidSupport(support); - _isClockModeSupported(); // reverts if clock mode is not supported + // Reverts if clock or CLOCK_MODE() has changed + tokenAdapter.checkIfInconsistentClock(); } /// @dev Sets the voting quorum and vetoing quorum. @@ -538,45 +551,9 @@ abstract contract LlamaTokenCaster is Initializable { periodPctsCheckpoint.push(_delayPeriodPct, _castingPeriodPct, _submissionPeriodPct); emit PeriodsPctSet(_delayPeriodPct, _castingPeriodPct, _submissionPeriodPct); } - - /// @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) { nonce = nonces[tokenholder][selector]; nonces[tokenholder][selector] = LlamaUtils.uncheckedIncrement(nonce); diff --git a/src/token-voting/LlamaTokenVotingFactory.sol b/src/token-voting/LlamaTokenVotingFactory.sol index 1973642..c6111f4 100644 --- a/src/token-voting/LlamaTokenVotingFactory.sol +++ b/src/token-voting/LlamaTokenVotingFactory.sol @@ -2,196 +2,135 @@ 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 {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; -import {ILlamaTokenClockAdapter} from "src/token-voting/ILlamaTokenClockAdapter.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 {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.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. + 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, - ILlamaTokenClockAdapter clockAdapter, + ILlamaTokenAdapter tokenAdapterLogic, + 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, - ILlamaTokenClockAdapter clockAdapter, - 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, clockAdapter, nonce, actionCreatorRole, creationThreshold - ) - ); - caster = address( - _deployLlamaERC20TokenCaster( - ERC20Votes(token), llamaCore, clockAdapter, nonce, casterRole, voteQuorumPct, vetoQuorumPct - ) - ); - } else { - actionCreator = address( - _deployLlamaERC721TokenActionCreator( - ERC721Votes(token), llamaCore, clockAdapter, nonce, actionCreatorRole, creationThreshold - ) - ); - caster = address( - _deployLlamaERC721TokenCaster( - ERC721Votes(token), llamaCore, clockAdapter, nonce, casterRole, voteQuorumPct, vetoQuorumPct - ) - ); - } - - emit LlamaTokenVotingInstanceCreated( - msg.sender, - llamaCore, - token, - clockAdapter, - 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) + { + bytes32 salt = keccak256( + abi.encodePacked( + msg.sender, address(tokenVotingConfig.llamaCore), tokenVotingConfig.adapterConfig, tokenVotingConfig.nonce + ) ); - } - // ==================================== - // ======== Internal Functions ======== - // ==================================== + // 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); - /// @dev Deploys and initiliazes a new `LlamaERC20TokenActionCreator` clone. - function _deployLlamaERC20TokenActionCreator( - ERC20Votes token, - ILlamaCore llamaCore, - ILlamaTokenClockAdapter clockAdapter, - uint256 nonce, - uint8 role, - uint256 creationThreshold - ) internal returns (LlamaERC20TokenActionCreator actionCreator) { - actionCreator = LlamaERC20TokenActionCreator( - Clones.cloneDeterministic( - address(ERC20_TOKEN_ACTION_CREATOR_LOGIC), - keccak256(abi.encodePacked(msg.sender, address(llamaCore), address(token), nonce)) - ) - ); - actionCreator.initialize(token, llamaCore, clockAdapter, role, creationThreshold); - } + // 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(); - /// @dev Deploys and initiliazes a new `LlamaERC721TokenActionCreator` clone. - function _deployLlamaERC721TokenActionCreator( - ERC721Votes token, - ILlamaCore llamaCore, - ILlamaTokenClockAdapter clockAdapter, - 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)) - ) + // Reverts if clock is inconsistent + tokenAdapter.checkIfInconsistentClock(); + + // Deploy and initialize `LlamaTokenActionCreator` contract + actionCreator = LlamaTokenActionCreator(Clones.cloneDeterministic(address(LLAMA_TOKEN_ACTION_CREATOR_LOGIC), salt)); + + actionCreator.initialize( + tokenVotingConfig.llamaCore, + tokenAdapter, + tokenVotingConfig.actionCreatorRole, + tokenVotingConfig.creationThreshold ); - actionCreator.initialize(token, llamaCore, clockAdapter, role, creationThreshold); - } - /// @dev Deploys and initiliazes a new `LlamaERC20TokenCaster` clone. - function _deployLlamaERC20TokenCaster( - ERC20Votes token, - ILlamaCore llamaCore, - ILlamaTokenClockAdapter clockAdapter, - uint256 nonce, - uint8 role, - uint16 voteQuorumPct, - uint16 vetoQuorumPct - ) internal returns (LlamaERC20TokenCaster caster) { - caster = LlamaERC20TokenCaster( - Clones.cloneDeterministic( - address(ERC20_TOKEN_CASTER_LOGIC), - keccak256(abi.encodePacked(msg.sender, address(llamaCore), address(token), nonce)) - ) + // Deploy and initialize `LlamaTokenCaster` contract + caster = LlamaTokenCaster(Clones.cloneDeterministic(address(LLAMA_TOKEN_CASTER_LOGIC), salt)); + + caster.initialize( + tokenVotingConfig.llamaCore, + tokenAdapter, + tokenVotingConfig.casterRole, + tokenVotingConfig.voteQuorumPct, + tokenVotingConfig.vetoQuorumPct ); - caster.initialize(token, llamaCore, clockAdapter, role, voteQuorumPct, vetoQuorumPct); - } - /// @dev Deploys and initiliazes a new `LlamaERC721TokenCaster` clone. - function _deployLlamaERC721TokenCaster( - ERC721Votes token, - ILlamaCore llamaCore, - ILlamaTokenClockAdapter clockAdapter, - 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)) - ) + emit LlamaTokenVotingInstanceCreated( + msg.sender, + tokenVotingConfig.llamaCore, + tokenAdapter.token(), + tokenVotingConfig.tokenAdapterLogic, + tokenAdapter, + tokenVotingConfig.nonce, + tokenVotingConfig.actionCreatorRole, + tokenVotingConfig.casterRole, + actionCreator, + caster, + block.chainid ); - caster.initialize(token, llamaCore, clockAdapter, role, voteQuorumPct, vetoQuorumPct); } } diff --git a/src/token-voting/interfaces/ILlamaTokenAdapter.sol b/src/token-voting/interfaces/ILlamaTokenAdapter.sol new file mode 100644 index 0000000..41ca31e --- /dev/null +++ b/src/token-voting/interfaces/ILlamaTokenAdapter.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +/// @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 voting token address. + /// @return token The voting token. + function token() external view returns (address token); + + /// @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. + /// @param timestamp The timestamp to convert. + /// @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); +} diff --git a/src/token-voting/token-adapters/LlamaTokenAdapterVotesTimestamp.sol b/src/token-voting/token-adapters/LlamaTokenAdapterVotesTimestamp.sol new file mode 100644 index 0000000..738ad69 --- /dev/null +++ b/src/token-voting/token-adapters/LlamaTokenAdapterVotesTimestamp.sol @@ -0,0 +1,102 @@ +// 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 LlamaTokenAdapterVotesTimestamp is ILlamaTokenAdapter, Initializable { + // ========================= + // ======== Structs ======== + // ========================= + + /// @dev Llama token adapter initialization configuration. + struct Config { + address token; // The address of the voting token. + } + + // ======================== + // ======== 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() { + _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"; + + return true; + } + + /// @inheritdoc ILlamaTokenAdapter + function clock() public view returns (uint48 timepoint) { + try IERC6372(address(token)).clock() returns (uint48 tokenTimepoint) { + timepoint = tokenTimepoint; + } catch { + timepoint = LlamaUtils.toUint48(block.timestamp); + } + } + + /// @inheritdoc ILlamaTokenAdapter + function checkIfInconsistentClock() external view { + bool hasClockChanged = _hasClockChanged(); + bool hasClockModeChanged = _hasClockModeChanged(); + + 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 IVotes(token).getPastVotes(account, timepoint); + } + + /// @inheritdoc ILlamaTokenAdapter + function getPastTotalSupply(uint48 timepoint) external view returns (uint256) { + 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)); + } catch { + return false; + } + } + + /// @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); + } +} 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/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 b341798..abe8caf 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 {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"; 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(), @@ -90,6 +91,15 @@ contract LlamaERC20TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUti vm.prank(tokenHolder3); llamaERC20TokenCaster.castVeto(actionInfo, uint8(VoteType.For), ""); } + + function createTimestampTokenAdapter(address token, uint256 nonce) public returns (ILlamaTokenAdapter tokenAdapter) { + bytes memory adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(token))); + + bytes32 salt = keccak256(abi.encodePacked(msg.sender, address(CORE), adapterConfig, nonce)); + + tokenAdapter = ILlamaTokenAdapter(Clones.cloneDeterministic(address(llamaTokenAdapterTimestampLogic), salt)); + tokenAdapter.initialize(adapterConfig); + } } contract CastVote is LlamaERC20TokenCasterTest { @@ -111,14 +121,13 @@ 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)) + 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), 0); + 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), ""); @@ -326,14 +335,13 @@ 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)) + 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), 0); + 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), ""); @@ -588,14 +596,13 @@ 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)) + 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), 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); } @@ -673,14 +680,13 @@ contract SubmitDisapprovals is LlamaERC20TokenCasterTest { function test_RevertsIf_DisapprovalNotEnabled() public { vm.warp(block.timestamp + (1 days * THREE_QUARTERS_IN_BPS) / ONE_HUNDRED_IN_BPS); - LlamaERC20TokenCaster casterWithWrongRole = LlamaERC20TokenCaster( + 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), 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/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/LlamaERC721TokenCaster.t.sol b/test/token-voting/LlamaERC721TokenCaster.t.sol index cd41cdb..1927502 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 {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; +import {LlamaTokenAdapterVotesTimestamp} from "src/token-voting/token-adapters/LlamaTokenAdapterVotesTimestamp.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(), @@ -90,6 +91,15 @@ contract LlamaERC721TokenCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigUt vm.prank(tokenHolder3); llamaERC721TokenCaster.castVeto(actionInfo, uint8(VoteType.For), ""); } + + function createTimestampTokenAdapter(address token, uint256 nonce) public returns (ILlamaTokenAdapter tokenAdapter) { + bytes memory adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(token))); + + bytes32 salt = keccak256(abi.encodePacked(msg.sender, address(CORE), adapterConfig, nonce)); + + tokenAdapter = ILlamaTokenAdapter(Clones.cloneDeterministic(address(llamaTokenAdapterTimestampLogic), salt)); + tokenAdapter.initialize(adapterConfig); + } } contract CastVote is LlamaERC721TokenCasterTest { @@ -111,14 +121,15 @@ contract CastVote is LlamaERC721TokenCasterTest { } function test_RevertsIf_ApprovalNotEnabled() public { - LlamaERC721TokenCaster casterWithWrongRole = LlamaERC721TokenCaster( + ILlamaTokenAdapter tokenAdapter = createTimestampTokenAdapter(address(erc721VotesToken), 0); + + 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), ""); @@ -326,14 +337,14 @@ contract CastVeto is LlamaERC721TokenCasterTest { } function test_RevertsIf_DisapprovalNotEnabled() public { - LlamaERC721TokenCaster casterWithWrongRole = LlamaERC721TokenCaster( + ILlamaTokenAdapter tokenAdapter = createTimestampTokenAdapter(address(erc721VotesToken), 0); + + 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), ""); @@ -586,14 +597,14 @@ contract SubmitApprovals is LlamaERC721TokenCasterTest { } function test_RevertsIf_ApprovalNotEnabled() public { - LlamaERC721TokenCaster casterWithWrongRole = LlamaERC721TokenCaster( + ILlamaTokenAdapter tokenAdapter = createTimestampTokenAdapter(address(erc721VotesToken), 0); + + 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); } @@ -671,14 +682,13 @@ contract SubmitDisapprovals is LlamaERC721TokenCasterTest { function test_RevertsIf_DisapprovalNotEnabled() public { vm.warp(block.timestamp + (1 days * THREE_QUARTERS_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), 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 5c97cdc..f61b44d 100644 --- a/test/token-voting/LlamaTokenVotingFactory.t.sol +++ b/test/token-voting/LlamaTokenVotingFactory.t.sol @@ -10,11 +10,10 @@ 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 {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 {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.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"; contract LlamaTokenVotingFactoryTest is LlamaTokenVotingTestSetup { @@ -22,13 +21,13 @@ contract LlamaTokenVotingFactoryTest is LlamaTokenVotingTestSetup { address indexed deployer, ILlamaCore indexed llamaCore, address indexed token, - ILlamaTokenClockAdapter clockAdapter, + 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,34 +80,44 @@ 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(LlamaTokenAdapterVotesTimestamp.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); + bytes32 salt = keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), adapterConfig, uint256(0))); + // Compute addresses of ERC20 Token Voting Module - LlamaERC20TokenActionCreator llamaERC20TokenActionCreator = LlamaERC20TokenActionCreator( + LlamaTokenActionCreator llamaERC20TokenActionCreator = LlamaTokenActionCreator( Clones.predictDeterministicAddress( - address(llamaERC20TokenActionCreatorLogic), - keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), address(erc20VotesToken), uint256(0))), // salt + address(llamaTokenActionCreatorLogic), + salt, address(tokenVotingFactory) // deployer ) ); - LlamaERC20TokenCaster llamaERC20TokenCaster = LlamaERC20TokenCaster( + LlamaTokenCaster llamaERC20TokenCaster = LlamaTokenCaster( Clones.predictDeterministicAddress( - address(llamaERC20TokenCasterLogic), - keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), address(erc20VotesToken), uint256(0))), // salt + address(llamaTokenCasterLogic), + salt, + address(tokenVotingFactory) // deployer + ) + ); + ILlamaTokenAdapter llamaERC20TokenAdapter = ILlamaTokenAdapter( + Clones.predictDeterministicAddress( + address(llamaTokenAdapterTimestampLogic), + salt, address(tokenVotingFactory) // deployer ) ); @@ -133,58 +132,68 @@ 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(LlamaTokenAdapterVotesTimestamp.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); + bytes32 salt = keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), adapterConfig, uint256(0))); + // Compute addresses of ERC721 Token Voting Module - LlamaERC721TokenActionCreator llamaERC721TokenActionCreator = LlamaERC721TokenActionCreator( + LlamaTokenActionCreator llamaERC721TokenActionCreator = LlamaTokenActionCreator( Clones.predictDeterministicAddress( - address(llamaERC721TokenActionCreatorLogic), - keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), address(erc721VotesToken), uint256(0))), // salt + address(llamaTokenActionCreatorLogic), + salt, address(tokenVotingFactory) // deployer ) ); - LlamaERC721TokenCaster llamaERC721TokenCaster = LlamaERC721TokenCaster( + LlamaTokenCaster llamaERC721TokenCaster = LlamaTokenCaster( Clones.predictDeterministicAddress( - address(llamaERC721TokenCasterLogic), - keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), address(erc721VotesToken), uint256(0))), // salt + address(llamaTokenCasterLogic), + salt, + address(tokenVotingFactory) // deployer + ) + ); + ILlamaTokenAdapter llamaERC721TokenAdapter = ILlamaTokenAdapter( + Clones.predictDeterministicAddress( + address(llamaTokenAdapterTimestampLogic), + salt, address(tokenVotingFactory) // deployer ) ); @@ -199,25 +208,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,18 +235,28 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { vm.assume(randomCaller != address(0)); vm.deal(randomCaller, 1 ether); - LlamaERC20TokenActionCreator llamaERC20TokenActionCreator = LlamaERC20TokenActionCreator( + bytes memory adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(erc20VotesToken))); + bytes32 salt = keccak256(abi.encodePacked(randomCaller, address(CORE), adapterConfig, uint256(0))); + + LlamaTokenActionCreator llamaERC20TokenActionCreator = LlamaTokenActionCreator( Clones.predictDeterministicAddress( - address(llamaERC20TokenActionCreatorLogic), - keccak256(abi.encodePacked(randomCaller, address(CORE), address(erc20VotesToken), uint256(0))), // salt + address(llamaTokenActionCreatorLogic), + salt, address(tokenVotingFactory) // deployer ) ); - LlamaERC20TokenCaster llamaERC20TokenCaster = LlamaERC20TokenCaster( + LlamaTokenCaster llamaERC20TokenCaster = LlamaTokenCaster( + Clones.predictDeterministicAddress( + address(llamaTokenCasterLogic), + salt, + address(tokenVotingFactory) // deployer + ) + ); + ILlamaTokenAdapter llamaERC20TokenAdapter = ILlamaTokenAdapter( Clones.predictDeterministicAddress( - address(llamaERC20TokenCasterLogic), - keccak256(abi.encodePacked(randomCaller, address(CORE), address(erc20VotesToken), uint256(0))), // salt + address(llamaTokenAdapterTimestampLogic), + salt, address(tokenVotingFactory) // deployer ) ); @@ -251,23 +270,21 @@ 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( + LlamaTokenVotingFactory.LlamaTokenVotingConfig memory config = LlamaTokenVotingFactory.LlamaTokenVotingConfig( CORE, - address(erc20VotesToken), - LLAMA_TOKEN_TIMESTAMP_ADAPTER, + llamaTokenAdapterTimestampLogic, + adapterConfig, 0, - true, tokenVotingActionCreatorRole, tokenVotingCasterRole, ERC20_CREATION_THRESHOLD, @@ -275,14 +292,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 +312,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(LlamaTokenAdapterVotesTimestamp.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,20 +325,32 @@ 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); + bytes32 salt = keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), adapterConfig, uint256(0))); + // Compute addresses of ERC20 Token Voting Module - LlamaERC20TokenActionCreator llamaERC20TokenActionCreator = LlamaERC20TokenActionCreator( + LlamaTokenActionCreator llamaERC20TokenActionCreator = LlamaTokenActionCreator( Clones.predictDeterministicAddress( - address(llamaERC20TokenActionCreatorLogic), - keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), address(erc20VotesToken), uint256(0))), // salt + address(llamaTokenActionCreatorLogic), + salt, address(tokenVotingFactory) // deployer ) ); - LlamaERC20TokenCaster llamaERC20TokenCaster = LlamaERC20TokenCaster( + LlamaTokenCaster llamaERC20TokenCaster = LlamaTokenCaster( Clones.predictDeterministicAddress( - address(llamaERC20TokenCasterLogic), - keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), address(erc20VotesToken), uint256(0))), // salt + address(llamaTokenCasterLogic), + salt, + address(tokenVotingFactory) // deployer + ) + ); + ILlamaTokenAdapter llamaERC20TokenAdapter = ILlamaTokenAdapter( + Clones.predictDeterministicAddress( + address(llamaTokenAdapterTimestampLogic), + salt, address(tokenVotingFactory) // deployer ) ); @@ -331,13 +361,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 +376,12 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { // Second deployment// ////////////////////// - // Set up action to call `deploy` with the ERC20 token. - data = abi.encodeWithSelector( - LlamaTokenVotingFactory.deploy.selector, + adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(erc20VotesToken))); + config = LlamaTokenVotingFactory.LlamaTokenVotingConfig( CORE, - address(erc20VotesToken), - LLAMA_TOKEN_TIMESTAMP_ADAPTER, + llamaTokenAdapterTimestampLogic, + adapterConfig, 1, - true, tokenVotingActionCreatorRole, tokenVotingCasterRole, ERC20_CREATION_THRESHOLD, @@ -361,20 +389,32 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { ERC20_VETO_QUORUM_PCT ); + // Set up action to call `deploy` with the ERC20 token. + data = abi.encodeWithSelector(LlamaTokenVotingFactory.deploy.selector, config); + actionInfo = _setPermissionCreateApproveAndQueueAction(data); + salt = keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), adapterConfig, uint256(1))); + // Compute addresses of ERC20 Token Voting Module - llamaERC20TokenActionCreator = LlamaERC20TokenActionCreator( + llamaERC20TokenActionCreator = LlamaTokenActionCreator( + Clones.predictDeterministicAddress( + address(llamaTokenActionCreatorLogic), + salt, // salt + address(tokenVotingFactory) // deployer + ) + ); + llamaERC20TokenCaster = LlamaTokenCaster( Clones.predictDeterministicAddress( - address(llamaERC20TokenActionCreatorLogic), - keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), address(erc20VotesToken), uint256(1))), // salt + address(llamaTokenCasterLogic), + salt, // salt address(tokenVotingFactory) // deployer ) ); - llamaERC20TokenCaster = LlamaERC20TokenCaster( + llamaERC20TokenAdapter = ILlamaTokenAdapter( Clones.predictDeterministicAddress( - address(llamaERC20TokenCasterLogic), - keccak256(abi.encodePacked(address(EXECUTOR), address(CORE), address(erc20VotesToken), uint256(1))), // salt + address(llamaTokenAdapterTimestampLogic), + salt, address(tokenVotingFactory) // deployer ) ); @@ -385,13 +425,13 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { address(EXECUTOR), CORE, address(erc20VotesToken), - LLAMA_TOKEN_TIMESTAMP_ADAPTER, + llamaTokenAdapterTimestampLogic, + llamaERC20TokenAdapter, 1, - true, tokenVotingActionCreatorRole, tokenVotingCasterRole, - address(llamaERC20TokenActionCreator), - address(llamaERC20TokenCaster), + llamaERC20TokenActionCreator, + llamaERC20TokenCaster, block.chainid ); CORE.executeAction(actionInfo); diff --git a/test/token-voting/LlamaTokenVotingTestSetup.sol b/test/token-voting/LlamaTokenVotingTestSetup.sol index 01fe6eb..0afb7f5 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 {ILlamaTokenClockAdapter} from "src/token-voting/ILlamaTokenClockAdapter.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 {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"; contract LlamaTokenVotingTestSetup is LlamaPeripheryTestSetup, DeployLlamaTokenVotingFactory { // Percentages @@ -38,10 +37,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 - // adapter. - ILlamaTokenClockAdapter constant LLAMA_TOKEN_TIMESTAMP_ADAPTER = ILlamaTokenClockAdapter(address(0)); - // Votes Tokens MockERC20Votes public erc20VotesToken; MockERC721Votes public erc721VotesToken; @@ -97,61 +92,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(LlamaTokenAdapterVotesTimestamp.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(LlamaTokenAdapterVotesTimestamp.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 {