From 212d04817b09316ef9e95286e81ad4eb0b9aab8f Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Wed, 6 Dec 2023 16:06:51 -0500 Subject: [PATCH 01/25] Initializable --- src/token-voting/TokenholderActionCreator.sol | 4 +++- src/token-voting/TokenholderCaster.sol | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/token-voting/TokenholderActionCreator.sol b/src/token-voting/TokenholderActionCreator.sol index 0ca6571..983172e 100644 --- a/src/token-voting/TokenholderActionCreator.sol +++ b/src/token-voting/TokenholderActionCreator.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.23; +import {Initializable} from "@openzeppelin/proxy/utils/Initializable.sol"; + import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; import {ILlamaStrategy} from "src/interfaces/ILlamaStrategy.sol"; import {Action, ActionInfo} from "src/lib/Structs.sol"; @@ -14,7 +16,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 TokenholderActionCreator { +abstract contract TokenholderActionCreator is Initializable { /// @notice The core contract for this Llama instance. ILlamaCore public immutable LLAMA_CORE; diff --git a/src/token-voting/TokenholderCaster.sol b/src/token-voting/TokenholderCaster.sol index 0009243..bf582a7 100644 --- a/src/token-voting/TokenholderCaster.sol +++ b/src/token-voting/TokenholderCaster.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.23; +import {Initializable} from "@openzeppelin/proxy/utils/Initializable.sol"; + import {FixedPointMathLib} from "@solmate/utils/FixedPointMathLib.sol"; import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; import {ActionState} from "src/lib/Enums.sol"; @@ -16,7 +18,7 @@ import {ILlamaRelativeStrategyBase} from "src/interfaces/ILlamaRelativeStrategyB /// 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 TokenholderCaster { +abstract contract TokenholderCaster is Initializable { // ========================= // ======== Structs ======== // ========================= From c717781ff0055d6fb9448c95d047b872858c1ec9 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Wed, 6 Dec 2023 16:44:51 -0500 Subject: [PATCH 02/25] refactor --- src/token-voting/TokenholderActionCreator.sol | 21 +++--- src/token-voting/TokenholderCaster.sol | 72 ++++++++++--------- 2 files changed, 50 insertions(+), 43 deletions(-) diff --git a/src/token-voting/TokenholderActionCreator.sol b/src/token-voting/TokenholderActionCreator.sol index 983172e..4d273c2 100644 --- a/src/token-voting/TokenholderActionCreator.sol +++ b/src/token-voting/TokenholderActionCreator.sol @@ -18,7 +18,7 @@ import {LlamaUtils} from "src/lib/LlamaUtils.sol"; /// is not allowed by the policy will result in a revert. abstract contract TokenholderActionCreator is Initializable { /// @notice The core contract for this Llama instance. - ILlamaCore public immutable LLAMA_CORE; + ILlamaCore public llamaCore; /// @dev EIP-712 actionInfo typehash. bytes32 internal constant ACTION_INFO_TYPEHASH = keccak256( @@ -96,14 +96,15 @@ abstract contract TokenholderActionCreator is Initializable { /// @dev Thrown when an address other than the `LlamaExecutor` tries to call a function. error OnlyLlamaExecutor(); - /// @param llamaCore The `LlamaCore` contract for this Llama instance. + /// @dev This will be called by the `initialize` of the inheriting contract. + /// @param _llamaCore The `LlamaCore` contract for this Llama instance. /// @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. - constructor(ILlamaCore llamaCore, uint256 _creationThreshold) { - if (llamaCore.actionsCount() < 0) revert InvalidLlamaCoreAddress(); + function __initializeTokenholderActionCreatorMinimalProxy(ILlamaCore _llamaCore, uint256 _creationThreshold) internal { + if (_llamaCore.actionsCount() < 0) revert InvalidLlamaCoreAddress(); - LLAMA_CORE = llamaCore; + llamaCore = _llamaCore; _setActionThreshold(_creationThreshold); } @@ -186,7 +187,7 @@ abstract contract TokenholderActionCreator is Initializable { /// @param _creationThreshold The number of tokens required to create an action. /// @dev This must be in the same decimals as the token. function setActionThreshold(uint256 _creationThreshold) external { - if (msg.sender != address(LLAMA_CORE.executor())) revert OnlyLlamaExecutor(); + if (msg.sender != address(llamaCore.executor())) revert OnlyLlamaExecutor(); if (_creationThreshold > _getPastTotalSupply(block.timestamp - 1)) revert InvalidCreationThreshold(); _setActionThreshold(_creationThreshold); } @@ -217,7 +218,7 @@ abstract contract TokenholderActionCreator is Initializable { uint256 balance = _getPastVotes(tokenHolder, block.timestamp - 1); if (balance < creationThreshold) revert InsufficientBalance(balance); - actionId = LLAMA_CORE.createAction(role, strategy, target, value, data, description); + actionId = llamaCore.createAction(role, strategy, target, value, data, description); actionCreators[actionId] = tokenHolder; emit ActionCreated(actionId, tokenHolder, role, strategy, target, value, data, description); } @@ -229,7 +230,7 @@ abstract contract TokenholderActionCreator is Initializable { function _cancelAction(address creator, ActionInfo calldata actionInfo) internal { if (creator != actionCreators[actionInfo.id]) revert OnlyActionCreator(); - LLAMA_CORE.cancelAction(actionInfo); + llamaCore.cancelAction(actionInfo); emit ActionCanceled(actionInfo.id, creator); } @@ -250,7 +251,7 @@ abstract contract TokenholderActionCreator is Initializable { function _getDomainHash() internal view returns (bytes32) { return keccak256( abi.encode( - EIP712_DOMAIN_TYPEHASH, keccak256(bytes(LLAMA_CORE.name())), keccak256(bytes("1")), block.chainid, address(this) + EIP712_DOMAIN_TYPEHASH, keccak256(bytes(llamaCore.name())), keccak256(bytes("1")), block.chainid, address(this) ) ); } @@ -299,8 +300,8 @@ abstract contract TokenholderActionCreator is Initializable { return keccak256(abi.encodePacked("\x19\x01", _getDomainHash(), cancelActionHash)); } - /// @dev Returns the hash of `actionInfo`. + /// @dev Returns the hash of `actionInfo`. function _getActionInfoHash(ActionInfo calldata actionInfo) internal pure returns (bytes32) { return keccak256( abi.encode( diff --git a/src/token-voting/TokenholderCaster.sol b/src/token-voting/TokenholderCaster.sol index bf582a7..922f1bc 100644 --- a/src/token-voting/TokenholderCaster.sol +++ b/src/token-voting/TokenholderCaster.sol @@ -144,17 +144,17 @@ abstract contract TokenholderCaster is Initializable { uint256 internal constant TWO_THIRDS_IN_BPS = 6667; /// @notice The core contract for this Llama instance. - ILlamaCore public immutable LLAMA_CORE; + ILlamaCore public llamaCore; /// @notice The minimum % of approvals required to submit approvals to `LlamaCore`. - uint256 public immutable MIN_APPROVAL_PCT; + uint256 public minApprovalPct; /// @notice The minimum % of disapprovals required to submit disapprovals to `LlamaCore`. - uint256 public immutable MIN_DISAPPROVAL_PCT; + uint256 public minDisapprovalPct; /// @notice The role used by this contract to cast approvals and disapprovals. /// @dev This role is expected to have the ability to force approve and disapprove actions. - uint8 public immutable ROLE; + uint8 public role; /// @dev EIP-712 base typehash. bytes32 internal constant EIP712_DOMAIN_TYPEHASH = @@ -183,22 +183,28 @@ abstract contract TokenholderCaster is Initializable { /// `cancelAction`, `castApproval` and `castDisapproval`) signed by the tokenholders. mapping(address tokenholders => mapping(bytes4 selector => uint256 currentNonce)) public nonces; - /// @param llamaCore The `LlamaCore` contract for this Llama instance. - /// @param role The role used by this contract to cast approvals and disapprovals. - /// @param minApprovalPct The minimum % of approvals required to submit approvals to `LlamaCore`. - /// @param minDisapprovalPct The minimum % of disapprovals required to submit disapprovals to `LlamaCore`. - constructor(ILlamaCore llamaCore, uint8 role, uint256 minApprovalPct, uint256 minDisapprovalPct) { - if (llamaCore.actionsCount() < 0) revert InvalidLlamaCoreAddress(); - if (role > llamaCore.policy().numRoles()) revert RoleNotInitialized(role); - if (minApprovalPct > ONE_HUNDRED_IN_BPS || minApprovalPct <= 0) revert InvalidMinApprovalPct(minApprovalPct); - if (minDisapprovalPct > ONE_HUNDRED_IN_BPS || minDisapprovalPct <= 0) { - revert InvalidMinDisapprovalPct(minDisapprovalPct); + /// @dev This will be called by the `initialize` of the inheriting contract. + /// @param _llamaCore The `LlamaCore` contract for this Llama instance. + /// @param _role The role used by this contract to cast approvals and disapprovals. + /// @param _minApprovalPct The minimum % of approvals required to submit approvals to `LlamaCore`. + /// @param _minDisapprovalPct The minimum % of disapprovals required to submit disapprovals to `LlamaCore`. + function __initializeTokenholderActionCreatorMinimalProxy( + ILlamaCore _llamaCore, + uint8 _role, + uint256 _minApprovalPct, + uint256 _minDisapprovalPct + ) internal { + if (_llamaCore.actionsCount() < 0) revert InvalidLlamaCoreAddress(); + if (_role > _llamaCore.policy().numRoles()) revert RoleNotInitialized(_role); + if (_minApprovalPct > ONE_HUNDRED_IN_BPS || _minApprovalPct <= 0) revert InvalidMinApprovalPct(_minApprovalPct); + if (_minDisapprovalPct > ONE_HUNDRED_IN_BPS || _minDisapprovalPct <= 0) { + revert InvalidMinDisapprovalPct(_minDisapprovalPct); } - LLAMA_CORE = llamaCore; - ROLE = role; - MIN_APPROVAL_PCT = minApprovalPct; - MIN_DISAPPROVAL_PCT = minDisapprovalPct; + llamaCore = _llamaCore; + role = _role; + minApprovalPct = _minApprovalPct; + minDisapprovalPct = _minDisapprovalPct; } /// @notice How tokenholders add their support of the approval of an action with a reason. @@ -259,7 +265,7 @@ abstract contract TokenholderCaster is Initializable { /// @param actionInfo Data required to create an action. /// @dev this function can be called by anyone function submitApprovals(ActionInfo calldata actionInfo) external { - Action memory action = LLAMA_CORE.getAction(actionInfo.id); + Action memory action = llamaCore.getAction(actionInfo.id); if (casts[actionInfo.id].approvalSubmitted) revert AlreadySubmittedApproval(); // check to make sure the casting period has ended @@ -280,12 +286,12 @@ abstract contract TokenholderCaster is Initializable { uint96 approvalsFor = casts[actionInfo.id].approvalsFor; uint96 approvalsAgainst = casts[actionInfo.id].approvalsAgainst; uint96 approvalsAbstain = casts[actionInfo.id].approvalsAbstain; - uint256 threshold = FixedPointMathLib.mulDivUp(totalSupply, MIN_APPROVAL_PCT, ONE_HUNDRED_IN_BPS); + uint256 threshold = FixedPointMathLib.mulDivUp(totalSupply, minApprovalPct, ONE_HUNDRED_IN_BPS); if (approvalsFor < threshold) revert InsufficientApprovals(approvalsFor, threshold); if (approvalsFor <= approvalsAgainst) revert ForDoesNotSurpassAgainst(approvalsFor, approvalsAgainst); casts[actionInfo.id].approvalSubmitted = true; - LLAMA_CORE.castApproval(ROLE, actionInfo, ""); + llamaCore.castApproval(role, actionInfo, ""); emit ApprovalsSubmitted(actionInfo.id, approvalsFor, approvalsAgainst, approvalsAbstain); } @@ -293,9 +299,9 @@ abstract contract TokenholderCaster is Initializable { /// @param actionInfo Data required to create an action. /// @dev this function can be called by anyone function submitDisapprovals(ActionInfo calldata actionInfo) external { - Action memory action = LLAMA_CORE.getAction(actionInfo.id); + Action memory action = llamaCore.getAction(actionInfo.id); - actionInfo.strategy.checkIfDisapprovalEnabled(actionInfo, msg.sender, ROLE); // Reverts if not allowed. + actionInfo.strategy.checkIfDisapprovalEnabled(actionInfo, msg.sender, role); // Reverts if not allowed. if (casts[actionInfo.id].disapprovalSubmitted) revert AlreadySubmittedDisapproval(); uint256 queuingPeriod = ILlamaRelativeStrategyBase(address(actionInfo.strategy)).queuingPeriod(); @@ -314,22 +320,22 @@ abstract contract TokenholderCaster is Initializable { uint96 disapprovalsFor = casts[actionInfo.id].disapprovalsFor; uint96 disapprovalsAgainst = casts[actionInfo.id].disapprovalsAgainst; uint96 disapprovalsAbstain = casts[actionInfo.id].disapprovalsAbstain; - uint256 threshold = FixedPointMathLib.mulDivUp(totalSupply, MIN_DISAPPROVAL_PCT, ONE_HUNDRED_IN_BPS); + uint256 threshold = FixedPointMathLib.mulDivUp(totalSupply, minDisapprovalPct, ONE_HUNDRED_IN_BPS); if (disapprovalsFor < threshold) revert InsufficientApprovals(disapprovalsFor, threshold); if (disapprovalsFor <= disapprovalsAgainst) revert ForDoesNotSurpassAgainst(disapprovalsFor, disapprovalsAgainst); casts[actionInfo.id].disapprovalSubmitted = true; - LLAMA_CORE.castDisapproval(ROLE, actionInfo, ""); + llamaCore.castDisapproval(role, actionInfo, ""); emit DisapprovalsSubmitted(actionInfo.id, disapprovalsFor, disapprovalsAgainst, disapprovalsAbstain); } function _castApproval(address caster, ActionInfo calldata actionInfo, uint8 support, string calldata reason) internal { - Action memory action = LLAMA_CORE.getAction(actionInfo.id); + Action memory action = llamaCore.getAction(actionInfo.id); - actionInfo.strategy.checkIfApprovalEnabled(actionInfo, caster, ROLE); // Reverts if not allowed. - if (LLAMA_CORE.getActionState(actionInfo) != uint8(ActionState.Active)) revert ActionNotActive(); + actionInfo.strategy.checkIfApprovalEnabled(actionInfo, caster, role); // Reverts if not allowed. + if (llamaCore.getActionState(actionInfo) != uint8(ActionState.Active)) revert ActionNotActive(); if (casts[actionInfo.id].castApproval[caster]) revert AlreadyCastApproval(); if ( block.timestamp @@ -345,15 +351,15 @@ abstract contract TokenholderCaster is Initializable { else if (support == 1) casts[actionInfo.id].approvalsFor += LlamaUtils.toUint96(balance); else if (support == 2) casts[actionInfo.id].approvalsAbstain += LlamaUtils.toUint96(balance); casts[actionInfo.id].castApproval[caster] = true; - emit ApprovalCast(actionInfo.id, caster, ROLE, support, balance, reason); + emit ApprovalCast(actionInfo.id, caster, role, support, balance, reason); } function _castDisapproval(address caster, ActionInfo calldata actionInfo, uint8 support, string calldata reason) internal { - Action memory action = LLAMA_CORE.getAction(actionInfo.id); + Action memory action = llamaCore.getAction(actionInfo.id); - actionInfo.strategy.checkIfDisapprovalEnabled(actionInfo, caster, ROLE); // Reverts if not allowed. + actionInfo.strategy.checkIfDisapprovalEnabled(actionInfo, caster, role); // Reverts if not allowed. if (!actionInfo.strategy.isActionApproved(actionInfo)) revert ActionNotApproved(); if (actionInfo.strategy.isActionExpired(actionInfo)) revert ActionExpired(); if (casts[actionInfo.id].castDisapproval[caster]) revert AlreadyCastDisapproval(); @@ -371,7 +377,7 @@ abstract contract TokenholderCaster is Initializable { else if (support == 1) casts[actionInfo.id].disapprovalsFor += LlamaUtils.toUint96(balance); else if (support == 2) casts[actionInfo.id].disapprovalsAbstain += LlamaUtils.toUint96(balance); casts[actionInfo.id].castDisapproval[caster] = true; - emit DisapprovalCast(actionInfo.id, caster, ROLE, support, balance, reason); + emit DisapprovalCast(actionInfo.id, caster, role, support, balance, reason); } function _preCastAssertions(uint256 balance, uint8 support) internal view { @@ -411,7 +417,7 @@ abstract contract TokenholderCaster is Initializable { function _getDomainHash() internal view returns (bytes32) { return keccak256( abi.encode( - EIP712_DOMAIN_TYPEHASH, keccak256(bytes(LLAMA_CORE.name())), keccak256(bytes("1")), block.chainid, address(this) + EIP712_DOMAIN_TYPEHASH, keccak256(bytes(llamaCore.name())), keccak256(bytes("1")), block.chainid, address(this) ) ); } From f7012d2cbe7eca834cbce9ebf9a26ed901198b28 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Wed, 6 Dec 2023 16:46:04 -0500 Subject: [PATCH 03/25] fix --- src/token-voting/TokenholderCaster.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/token-voting/TokenholderCaster.sol b/src/token-voting/TokenholderCaster.sol index 922f1bc..bae5a6e 100644 --- a/src/token-voting/TokenholderCaster.sol +++ b/src/token-voting/TokenholderCaster.sol @@ -188,7 +188,7 @@ abstract contract TokenholderCaster is Initializable { /// @param _role The role used by this contract to cast approvals and disapprovals. /// @param _minApprovalPct The minimum % of approvals required to submit approvals to `LlamaCore`. /// @param _minDisapprovalPct The minimum % of disapprovals required to submit disapprovals to `LlamaCore`. - function __initializeTokenholderActionCreatorMinimalProxy( + function __initializeTokenholderCasterMinimalProxy( ILlamaCore _llamaCore, uint8 _role, uint256 _minApprovalPct, From c6bf3cec730300a9b36d4296970cd52d1dcfa581 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Wed, 6 Dec 2023 17:08:05 -0500 Subject: [PATCH 04/25] refactor --- .../ERC20TokenholderActionCreator.sol | 31 +++++++++++----- src/token-voting/ERC20TokenholderCaster.sol | 37 ++++++++++++++----- .../ERC721TokenholderActionCreator.sol | 33 ++++++++++++----- src/token-voting/ERC721TokenholderCaster.sol | 37 ++++++++++++++----- 4 files changed, 101 insertions(+), 37 deletions(-) diff --git a/src/token-voting/ERC20TokenholderActionCreator.sol b/src/token-voting/ERC20TokenholderActionCreator.sol index 4e48d6f..1ff0962 100644 --- a/src/token-voting/ERC20TokenholderActionCreator.sol +++ b/src/token-voting/ERC20TokenholderActionCreator.sol @@ -10,26 +10,39 @@ import {ERC20Votes} from "@openzeppelin/token/ERC20/extensions/ERC20Votes.sol"; /// @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 ERC20TokenholderActionCreator is TokenholderActionCreator { - ERC20Votes public immutable TOKEN; + ERC20Votes public token; - constructor(ERC20Votes token, ILlamaCore llamaCore, uint256 _creationThreshold) - TokenholderActionCreator(llamaCore, _creationThreshold) - { - TOKEN = token; - uint256 totalSupply = TOKEN.totalSupply(); + /// @dev This contract is deployed as a minimal proxy from the factory's `deployTokenVotingModule` function. The + /// `_disableInitializers` locks the implementation (logic) contract, preventing any future initialization of it. + constructor() { + _disableInitializers(); + } + + /// @notice Initializes a new `ERC20TokenholderActionCreator` clone. + /// @dev This function is called by the `deployTokenVotingModule` 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 _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, uint256 _creationThreshold) external initializer { + __initializeTokenholderActionCreatorMinimalProxy(_llamaCore, _creationThreshold); + token = _token; + uint256 totalSupply = token.totalSupply(); if (totalSupply == 0) revert InvalidTokenAddress(); if (_creationThreshold > totalSupply) revert InvalidCreationThreshold(); } function _getPastVotes(address account, uint256 timestamp) internal view virtual override returns (uint256) { - return TOKEN.getPastVotes(account, timestamp); + return token.getPastVotes(account, timestamp); } function _getPastTotalSupply(uint256 timestamp) internal view virtual override returns (uint256) { - return TOKEN.getPastTotalSupply(timestamp); + return token.getPastTotalSupply(timestamp); } function _getClockMode() internal view virtual override returns (string memory) { - return TOKEN.CLOCK_MODE(); + return token.CLOCK_MODE(); } } diff --git a/src/token-voting/ERC20TokenholderCaster.sol b/src/token-voting/ERC20TokenholderCaster.sol index f77e3a4..1ae50af 100644 --- a/src/token-voting/ERC20TokenholderCaster.sol +++ b/src/token-voting/ERC20TokenholderCaster.sol @@ -10,25 +10,44 @@ import {ERC20Votes} from "@openzeppelin/token/ERC20/extensions/ERC20Votes.sol"; /// @notice This contract lets holders of a given governance ERC20Votes token cast approvals and disapprovals /// on created actions. contract ERC20TokenholderCaster is TokenholderCaster { - ERC20Votes public immutable TOKEN; + ERC20Votes public token; - constructor(ERC20Votes token, ILlamaCore llamaCore, uint8 role, uint256 minApprovalPct, uint256 minDisapprovalPct) - TokenholderCaster(llamaCore, role, minApprovalPct, minDisapprovalPct) - { - TOKEN = token; - uint256 totalSupply = TOKEN.totalSupply(); + /// @dev This contract is deployed as a minimal proxy from the factory's `deployTokenVotingModule` function. The + /// `_disableInitializers` locks the implementation (logic) contract, preventing any future initialization of it. + constructor() { + _disableInitializers(); + } + + /// @notice Initializes a new `ERC20TokenholderCaster` clone. + /// @dev This function is called by the `deployTokenVotingModule` 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 _minApprovalPct The minimum % of approvals required to submit approvals to `LlamaCore`. + /// @param _minDisapprovalPct The minimum % of disapprovals required to submit disapprovals to `LlamaCore`. + function initialize( + ERC20Votes _token, + ILlamaCore _llamaCore, + uint8 _role, + uint256 _minApprovalPct, + uint256 _minDisapprovalPct + ) external initializer { + __initializeTokenholderCasterMinimalProxy(_llamaCore, _role, _minApprovalPct, _minDisapprovalPct); + token = _token; + uint256 totalSupply = token.totalSupply(); if (totalSupply == 0) revert InvalidTokenAddress(); } function _getPastVotes(address account, uint256 timestamp) internal view virtual override returns (uint256) { - return TOKEN.getPastVotes(account, timestamp); + return token.getPastVotes(account, timestamp); } function _getPastTotalSupply(uint256 timestamp) internal view virtual override returns (uint256) { - return TOKEN.getPastTotalSupply(timestamp); + return token.getPastTotalSupply(timestamp); } function _getClockMode() internal view virtual override returns (string memory) { - return TOKEN.CLOCK_MODE(); + return token.CLOCK_MODE(); } } diff --git a/src/token-voting/ERC721TokenholderActionCreator.sol b/src/token-voting/ERC721TokenholderActionCreator.sol index 4287696..1687d24 100644 --- a/src/token-voting/ERC721TokenholderActionCreator.sol +++ b/src/token-voting/ERC721TokenholderActionCreator.sol @@ -11,27 +11,40 @@ import {IERC721} from "@openzeppelin/token/ERC721/IERC721.sol"; /// @notice This contract lets holders of a given governance ERC721Votes token create actions on the llama instance if /// they hold enough tokens. contract ERC721TokenholderActionCreator is TokenholderActionCreator { - ERC721Votes public immutable TOKEN; + ERC721Votes public token; - constructor(ERC721Votes token, ILlamaCore llamaCore, uint256 _creationThreshold) - TokenholderActionCreator(llamaCore, _creationThreshold) - { - TOKEN = token; - if (!TOKEN.supportsInterface(type(IERC721).interfaceId)) revert InvalidTokenAddress(); - uint256 totalSupply = TOKEN.getPastTotalSupply(block.timestamp - 1); + /// @dev This contract is deployed as a minimal proxy from the factory's `deployTokenVotingModule` function. The + /// `_disableInitializers` locks the implementation (logic) contract, preventing any future initialization of it. + constructor() { + _disableInitializers(); + } + + /// @notice Initializes a new `ERC721TokenholderActionCreator` clone. + /// @dev This function is called by the `deployTokenVotingModule` 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 _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, uint256 _creationThreshold) external initializer { + __initializeTokenholderActionCreatorMinimalProxy(_llamaCore, _creationThreshold); + token = _token; + if (!token.supportsInterface(type(IERC721).interfaceId)) revert InvalidTokenAddress(); + uint256 totalSupply = token.getPastTotalSupply(block.timestamp - 1); if (totalSupply == 0) revert InvalidTokenAddress(); if (_creationThreshold > totalSupply) revert InvalidCreationThreshold(); } function _getPastVotes(address account, uint256 timestamp) internal view virtual override returns (uint256) { - return TOKEN.getPastVotes(account, timestamp); + return token.getPastVotes(account, timestamp); } function _getPastTotalSupply(uint256 timestamp) internal view virtual override returns (uint256) { - return TOKEN.getPastTotalSupply(timestamp); + return token.getPastTotalSupply(timestamp); } function _getClockMode() internal view virtual override returns (string memory) { - return TOKEN.CLOCK_MODE(); + return token.CLOCK_MODE(); } } diff --git a/src/token-voting/ERC721TokenholderCaster.sol b/src/token-voting/ERC721TokenholderCaster.sol index ada49ea..fa9f2c3 100644 --- a/src/token-voting/ERC721TokenholderCaster.sol +++ b/src/token-voting/ERC721TokenholderCaster.sol @@ -11,24 +11,43 @@ import {IERC721} from "@openzeppelin/token/ERC721/IERC721.sol"; /// @notice This contract lets holders of a given governance ERC721Votes token cast approvals and disapprovals /// on created actions. contract ERC721TokenholderCaster is TokenholderCaster { - ERC721Votes public immutable TOKEN; + ERC721Votes public token; - constructor(ERC721Votes token, ILlamaCore llamaCore, uint8 role, uint256 minApprovalPct, uint256 minDisapprovalPct) - TokenholderCaster(llamaCore, role, minApprovalPct, minDisapprovalPct) - { - TOKEN = token; - if (!TOKEN.supportsInterface(type(IERC721).interfaceId)) revert InvalidTokenAddress(); + /// @dev This contract is deployed as a minimal proxy from the factory's `deployTokenVotingModule` function. The + /// `_disableInitializers` locks the implementation (logic) contract, preventing any future initialization of it. + constructor() { + _disableInitializers(); + } + + /// @notice Initializes a new `ERC721TokenholderCaster` clone. + /// @dev This function is called by the `deployTokenVotingModule` 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 _minApprovalPct The minimum % of approvals required to submit approvals to `LlamaCore`. + /// @param _minDisapprovalPct The minimum % of disapprovals required to submit disapprovals to `LlamaCore`. + function initialize( + ERC721Votes _token, + ILlamaCore _llamaCore, + uint8 _role, + uint256 _minApprovalPct, + uint256 _minDisapprovalPct + ) external initializer { + __initializeTokenholderCasterMinimalProxy(_llamaCore, _role, _minApprovalPct, _minDisapprovalPct); + token = _token; + if (!token.supportsInterface(type(IERC721).interfaceId)) revert InvalidTokenAddress(); } function _getPastVotes(address account, uint256 timestamp) internal view virtual override returns (uint256) { - return TOKEN.getPastVotes(account, timestamp); + return token.getPastVotes(account, timestamp); } function _getPastTotalSupply(uint256 timestamp) internal view virtual override returns (uint256) { - return TOKEN.getPastTotalSupply(timestamp); + return token.getPastTotalSupply(timestamp); } function _getClockMode() internal view virtual override returns (string memory) { - return TOKEN.CLOCK_MODE(); + return token.CLOCK_MODE(); } } From 49560efd53f58997c3aecaef1873cfb2b0330554 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Wed, 6 Dec 2023 17:16:17 -0500 Subject: [PATCH 05/25] script --- script/DeployLlamaTokenVotingFactory.s.sol | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/script/DeployLlamaTokenVotingFactory.s.sol b/script/DeployLlamaTokenVotingFactory.s.sol index ecbb33d..9ec2c8c 100644 --- a/script/DeployLlamaTokenVotingFactory.s.sol +++ b/script/DeployLlamaTokenVotingFactory.s.sol @@ -4,14 +4,51 @@ pragma solidity 0.8.23; import {Script} from "forge-std/Script.sol"; import {DeployUtils} from "script/DeployUtils.sol"; +import {ERC20TokenholderActionCreator} from "src/token-voting/ERC20TokenholderActionCreator.sol"; +import {ERC20TokenholderCaster} from "src/token-voting/ERC20TokenholderCaster.sol"; +import {ERC721TokenholderActionCreator} from "src/token-voting/ERC721TokenholderActionCreator.sol"; +import {ERC721TokenholderCaster} from "src/token-voting/ERC721TokenholderCaster.sol"; import {LlamaTokenVotingFactory} from "src/token-voting/LlamaTokenVotingFactory.sol"; contract DeployLlamaFactory is Script { + // Logic contracts. + ERC20TokenholderActionCreator erc20TokenholderActionCreatorLogic; + ERC20TokenholderCaster erc20TokenholderCasterLogic; + ERC721TokenholderActionCreator erc721TokenholderActionCreatorLogic; + ERC721TokenholderCaster erc721TokenholderCasterLogic; + + // Factory contracts. LlamaTokenVotingFactory tokenVotingFactory; function run() public { DeployUtils.print(string.concat("Deploying Llama token voting factory to chain:", vm.toString(block.chainid))); + vm.broadcast(); + erc20TokenholderActionCreatorLogic = new ERC20TokenholderActionCreator(); + DeployUtils.print( + string.concat(" ERC20TokenholderActionCreatorLogic: ", vm.toString(address(erc20TokenholderActionCreatorLogic))) + ); + + vm.broadcast(); + erc20TokenholderCasterLogic = new ERC20TokenholderCaster(); + DeployUtils.print( + string.concat(" ERC20TokenholderCasterLogic: ", vm.toString(address(erc20TokenholderCasterLogic))) + ); + + vm.broadcast(); + erc721TokenholderActionCreatorLogic = new ERC721TokenholderActionCreator(); + DeployUtils.print( + string.concat( + " ERC721TokenholderActionCreatorLogic: ", vm.toString(address(erc721TokenholderActionCreatorLogic)) + ) + ); + + vm.broadcast(); + erc721TokenholderCasterLogic = new ERC721TokenholderCaster(); + DeployUtils.print( + string.concat(" ERC721TokenholderCasterLogic: ", vm.toString(address(erc721TokenholderCasterLogic))) + ); + vm.broadcast(); tokenVotingFactory = new LlamaTokenVotingFactory(); DeployUtils.print(string.concat(" LlamaTokenVotingFactory: ", vm.toString(address(tokenVotingFactory)))); From ae21b2e2b7670f265898ca8b32e6f52ce245fae8 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Wed, 6 Dec 2023 19:49:31 -0500 Subject: [PATCH 06/25] factor --- script/DeployLlamaTokenVotingFactory.s.sol | 7 ++- src/token-voting/LlamaTokenVotingFactory.sol | 55 ++++++++++++++++++-- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/script/DeployLlamaTokenVotingFactory.s.sol b/script/DeployLlamaTokenVotingFactory.s.sol index 9ec2c8c..089ead4 100644 --- a/script/DeployLlamaTokenVotingFactory.s.sol +++ b/script/DeployLlamaTokenVotingFactory.s.sol @@ -50,7 +50,12 @@ contract DeployLlamaFactory is Script { ); vm.broadcast(); - tokenVotingFactory = new LlamaTokenVotingFactory(); + tokenVotingFactory = new LlamaTokenVotingFactory( + erc20TokenholderActionCreatorLogic, + erc20TokenholderCasterLogic, + erc721TokenholderActionCreatorLogic, + erc721TokenholderCasterLogic + ); DeployUtils.print(string.concat(" LlamaTokenVotingFactory: ", vm.toString(address(tokenVotingFactory)))); } } diff --git a/src/token-voting/LlamaTokenVotingFactory.sol b/src/token-voting/LlamaTokenVotingFactory.sol index bb3e42e..7c30247 100644 --- a/src/token-voting/LlamaTokenVotingFactory.sol +++ b/src/token-voting/LlamaTokenVotingFactory.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.23; +import {Clones} from "@openzeppelin/proxy/Clones.sol"; + import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; import {ILlamaExecutor} from "src/interfaces/ILlamaExecutor.sol"; import {ERC20TokenholderActionCreator} from "src/token-voting/ERC20TokenholderActionCreator.sol"; @@ -25,6 +27,31 @@ contract LlamaTokenVotingFactory { address caster, address indexed token, uint256 minApprovalPct, uint256 minDisapprovalPct ); + /// @notice The ERC20 Tokenholder Action Creator (logic) contract. + ERC20TokenholderActionCreator public immutable ERC20_TOKENHOLDER_ACTION_CREATOR_LOGIC; + + /// @notice The ERC20 Tokenholder Caster (logic) contract. + ERC20TokenholderCaster public immutable ERC20_TOKENHOLDER_CASTER_LOGIC; + + /// @notice The ERC721 Tokenholder Action Creator (logic) contract. + ERC721TokenholderActionCreator public immutable ERC721_TOKENHOLDER_ACTION_CREATOR_LOGIC; + + /// @notice The ERC721 Tokenholder Caster (logic) contract. + ERC721TokenholderCaster public immutable ERC721_TOKENHOLDER_CASTER_LOGIC; + + /// @dev Set the logic contracts used to deploy Token Voting modules. + constructor( + ERC20TokenholderActionCreator erc20TokenholderActionCreatorLogic, + ERC20TokenholderCaster erc20TokenholderCasterLogic, + ERC721TokenholderActionCreator erc721TokenholderActionCreatorLogic, + ERC721TokenholderCaster erc721TokenholderCasterLogic + ) { + ERC20_TOKENHOLDER_ACTION_CREATOR_LOGIC = erc20TokenholderActionCreatorLogic; + ERC20_TOKENHOLDER_CASTER_LOGIC = erc20TokenholderCasterLogic; + ERC721_TOKENHOLDER_ACTION_CREATOR_LOGIC = erc721TokenholderActionCreatorLogic; + ERC721_TOKENHOLDER_CASTER_LOGIC = erc721TokenholderCasterLogic; + } + ///@notice Deploys a token voting module in a single function so it can be deployed in a single llama action. ///@dev This method CAN NOT be used in tandem with `delegateCallDeployTokenVotingModuleWithRoles`. You must use one or /// the other due to the delegateCallDeployTokenVotingModuleWithRoles method requring the contract to be authorized as @@ -62,7 +89,12 @@ contract LlamaTokenVotingFactory { internal returns (ERC20TokenholderActionCreator actionCreator) { - actionCreator = new ERC20TokenholderActionCreator(token, llamaCore, creationThreshold); + actionCreator = ERC20TokenholderActionCreator( + Clones.cloneDeterministic( + address(ERC20_TOKENHOLDER_ACTION_CREATOR_LOGIC), keccak256(abi.encodePacked(address(token), msg.sender)) + ) + ); + actionCreator.initialize(token, llamaCore, creationThreshold); emit ERC20TokenholderActionCreatorCreated(address(actionCreator), address(token)); } @@ -70,7 +102,12 @@ contract LlamaTokenVotingFactory { internal returns (ERC721TokenholderActionCreator actionCreator) { - actionCreator = new ERC721TokenholderActionCreator(token, llamaCore, creationThreshold); + actionCreator = ERC721TokenholderActionCreator( + Clones.cloneDeterministic( + address(ERC721_TOKENHOLDER_ACTION_CREATOR_LOGIC), keccak256(abi.encodePacked(address(token), msg.sender)) + ) + ); + actionCreator.initialize(token, llamaCore, creationThreshold); emit ERC721TokenholderActionCreatorCreated(address(actionCreator), address(token)); } @@ -81,7 +118,12 @@ contract LlamaTokenVotingFactory { uint256 minApprovalPct, uint256 minDisapprovalPct ) internal returns (ERC20TokenholderCaster caster) { - caster = new ERC20TokenholderCaster(token, llamaCore, role, minApprovalPct, minDisapprovalPct); + caster = ERC20TokenholderCaster( + Clones.cloneDeterministic( + address(ERC20_TOKENHOLDER_CASTER_LOGIC), keccak256(abi.encodePacked(address(token), msg.sender)) + ) + ); + caster.initialize(token, llamaCore, role, minApprovalPct, minDisapprovalPct); emit ERC20TokenholderCasterCreated(address(caster), address(token), minApprovalPct, minDisapprovalPct); } @@ -92,7 +134,12 @@ contract LlamaTokenVotingFactory { uint256 minApprovalPct, uint256 minDisapprovalPct ) internal returns (ERC721TokenholderCaster caster) { - caster = new ERC721TokenholderCaster(token, llamaCore, role, minApprovalPct, minDisapprovalPct); + caster = ERC721TokenholderCaster( + Clones.cloneDeterministic( + address(ERC721_TOKENHOLDER_CASTER_LOGIC), keccak256(abi.encodePacked(address(token), msg.sender)) + ) + ); + caster.initialize(token, llamaCore, role, minApprovalPct, minDisapprovalPct); emit ERC721TokenholderCasterCreated(address(caster), address(token), minApprovalPct, minDisapprovalPct); } } From 890ad6c1461ebd306737860fad205eb5f9867192 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Wed, 6 Dec 2023 19:53:53 -0500 Subject: [PATCH 07/25] support --- src/token-voting/TokenholderCaster.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/token-voting/TokenholderCaster.sol b/src/token-voting/TokenholderCaster.sol index bae5a6e..c342609 100644 --- a/src/token-voting/TokenholderCaster.sol +++ b/src/token-voting/TokenholderCaster.sol @@ -448,7 +448,7 @@ abstract contract TokenholderCaster is Initializable { /// recover the signer. function _getCastDisapprovalTypedDataHash( address tokenholder, - uint8 role, + uint8 support, ActionInfo calldata actionInfo, string calldata reason ) internal returns (bytes32) { @@ -456,7 +456,7 @@ abstract contract TokenholderCaster is Initializable { abi.encode( CAST_DISAPPROVAL_BY_SIG_TYPEHASH, tokenholder, - role, + support, _getActionInfoHash(actionInfo), keccak256(bytes(reason)), _useNonce(tokenholder, msg.sig) From 2e2a4caf5e4046bf7ccc69ab4a66a35da1907a13 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Thu, 7 Dec 2023 10:58:02 -0500 Subject: [PATCH 08/25] print --- script/DeployLlamaTokenVotingFactory.s.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/script/DeployLlamaTokenVotingFactory.s.sol b/script/DeployLlamaTokenVotingFactory.s.sol index 089ead4..99af3d4 100644 --- a/script/DeployLlamaTokenVotingFactory.s.sol +++ b/script/DeployLlamaTokenVotingFactory.s.sol @@ -21,7 +21,9 @@ contract DeployLlamaFactory is Script { LlamaTokenVotingFactory tokenVotingFactory; function run() public { - DeployUtils.print(string.concat("Deploying Llama token voting factory to chain:", vm.toString(block.chainid))); + DeployUtils.print( + string.concat("Deploying Llama token voting factory and logic contracts to chain:", vm.toString(block.chainid)) + ); vm.broadcast(); erc20TokenholderActionCreatorLogic = new ERC20TokenholderActionCreator(); From 499dc3bebfdda3d2daa66a69b073395b1808f92f Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Thu, 7 Dec 2023 16:59:59 -0500 Subject: [PATCH 09/25] LlamaTokenVotingTestSetup --- justfile | 6 +++--- script/DeployLlamaTokenVotingFactory.s.sol | 2 +- ...tSetup.sol => LlamaPeripheryTestSetup.sol} | 3 +-- .../token-voting/ERC20TokenholderCaster.t.sol | 4 ++-- .../ERC721TokenholderActionCreator.t.sol | 4 ++-- .../ERC721TokenholderCaster.t.sol | 4 ++-- .../LlamaTokenVotingFactory.t.sol | 5 ++--- .../LlamaTokenVotingTestSetup.sol | 19 +++++++++++++++++++ 8 files changed, 32 insertions(+), 15 deletions(-) rename test/{PeripheryTestSetup.sol => LlamaPeripheryTestSetup.sol} (95%) create mode 100644 test/token-voting/LlamaTokenVotingTestSetup.sol diff --git a/justfile b/justfile index 774e17f..cd8c8a5 100644 --- a/justfile +++ b/justfile @@ -18,8 +18,8 @@ run-script script_name flags='' sig='' args='': -vvvv {{flags}} mv _test test -dry-run-deploy: (run-script 'DeployLlamaFactory') +dry-run-deploy: (run-script 'DeployLlamaTokenVotingFactory') -deploy: (run-script 'DeployLlamaFactory' '--broadcast --verify --build-info --build-info-path build_info') +deploy: (run-script 'DeployLlamaTokenVotingFactory' '--broadcast --verify --build-info --build-info-path build_info') -verify: (run-script 'DeployLlamaFactory' '--verify --resume') +verify: (run-script 'DeployLlamaTokenVotingFactory' '--verify --resume') diff --git a/script/DeployLlamaTokenVotingFactory.s.sol b/script/DeployLlamaTokenVotingFactory.s.sol index 99af3d4..bd28a24 100644 --- a/script/DeployLlamaTokenVotingFactory.s.sol +++ b/script/DeployLlamaTokenVotingFactory.s.sol @@ -10,7 +10,7 @@ import {ERC721TokenholderActionCreator} from "src/token-voting/ERC721Tokenholder import {ERC721TokenholderCaster} from "src/token-voting/ERC721TokenholderCaster.sol"; import {LlamaTokenVotingFactory} from "src/token-voting/LlamaTokenVotingFactory.sol"; -contract DeployLlamaFactory is Script { +contract DeployLlamaTokenVotingFactory is Script { // Logic contracts. ERC20TokenholderActionCreator erc20TokenholderActionCreatorLogic; ERC20TokenholderCaster erc20TokenholderCasterLogic; diff --git a/test/PeripheryTestSetup.sol b/test/LlamaPeripheryTestSetup.sol similarity index 95% rename from test/PeripheryTestSetup.sol rename to test/LlamaPeripheryTestSetup.sol index fa6bdf1..83d086b 100644 --- a/test/PeripheryTestSetup.sol +++ b/test/LlamaPeripheryTestSetup.sol @@ -6,7 +6,6 @@ import {Test, console2} from "forge-std/Test.sol"; import {Vm} from "forge-std/Vm.sol"; import {ILlamaAccount} from "src/interfaces/ILlamaAccount.sol"; -import {ILlamaPolicyMetadata} from "src/interfaces/ILlamaPolicyMetadata.sol"; import {ILlamaStrategy} from "src/interfaces/ILlamaStrategy.sol"; import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; import {ILlamaExecutor} from "src/interfaces/ILlamaExecutor.sol"; @@ -14,7 +13,7 @@ import {ILlamaLens} from "src/interfaces/ILlamaLens.sol"; import {ILlamaPolicy} from "src/interfaces/ILlamaPolicy.sol"; import {ActionInfo, PermissionData, RoleHolderData} from "src/lib/Structs.sol"; -contract PeripheryTestSetup is Test { +contract LlamaPeripheryTestSetup is Test { string MAINNET_RPC_URL = vm.envString("MAINNET_RPC_URL"); // can't use constant here // Llama's Llama instance. diff --git a/test/token-voting/ERC20TokenholderCaster.t.sol b/test/token-voting/ERC20TokenholderCaster.t.sol index 58dbccf..4a8eef8 100644 --- a/test/token-voting/ERC20TokenholderCaster.t.sol +++ b/test/token-voting/ERC20TokenholderCaster.t.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.23; import {Test, console2} from "forge-std/Test.sol"; import {MockERC20Votes} from "test/mock/MockERC20Votes.sol"; +import {LlamaTokenVotingTestSetup} from "test/token-voting/LlamaTokenVotingTestSetup.sol"; import {Action, ActionInfo, PermissionData} from "src/lib/Structs.sol"; import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; @@ -14,9 +15,8 @@ import {RoleDescription} from "src/lib/UDVTs.sol"; import {ERC20Votes} from "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Votes.sol"; import {ERC20TokenholderCaster} from "src/token-voting/ERC20TokenholderCaster.sol"; import {TokenholderCaster} from "src/token-voting/TokenholderCaster.sol"; -import {PeripheryTestSetup} from "test/PeripheryTestSetup.sol"; -contract ERC20TokenholderCasterTest is PeripheryTestSetup { +contract ERC20TokenholderCasterTest is LlamaTokenVotingTestSetup { uint256 constant DEFAULT_APPROVAL_THRESHOLD = 1000; uint16 constant ONE_HUNDRED_IN_BPS = 10_000; uint256 constant ONE_THIRD_IN_BPS = 3333; diff --git a/test/token-voting/ERC721TokenholderActionCreator.t.sol b/test/token-voting/ERC721TokenholderActionCreator.t.sol index 0be6113..e9601b2 100644 --- a/test/token-voting/ERC721TokenholderActionCreator.t.sol +++ b/test/token-voting/ERC721TokenholderActionCreator.t.sol @@ -5,7 +5,7 @@ import {Test, console2} from "forge-std/Test.sol"; import {ERC721Votes} from "lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Votes.sol"; import {MockERC721Votes} from "test/mock/MockERC721Votes.sol"; -import {PeripheryTestSetup} from "test/PeripheryTestSetup.sol"; +import {LlamaTokenVotingTestSetup} from "test/token-voting/LlamaTokenVotingTestSetup.sol"; import {Action, ActionInfo} from "src/lib/Structs.sol"; import {RoleDescription} from "src/lib/UDVTs.sol"; @@ -15,7 +15,7 @@ import {ILlamaStrategy} from "src/interfaces/ILlamaStrategy.sol"; import {ERC721TokenholderActionCreator} from "src/token-voting/ERC721TokenholderActionCreator.sol"; import {TokenholderActionCreator} from "src/token-voting/TokenholderActionCreator.sol"; -contract ERC721TokenholderActionCreatorTest is PeripheryTestSetup { +contract ERC721TokenholderActionCreatorTest is LlamaTokenVotingTestSetup { event ActionCreated( uint256 id, address indexed creator, diff --git a/test/token-voting/ERC721TokenholderCaster.t.sol b/test/token-voting/ERC721TokenholderCaster.t.sol index d345d4a..029ee6f 100644 --- a/test/token-voting/ERC721TokenholderCaster.t.sol +++ b/test/token-voting/ERC721TokenholderCaster.t.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.23; import {Test, console2} from "forge-std/Test.sol"; import {MockERC721Votes} from "test/mock/MockERC721Votes.sol"; +import {LlamaTokenVotingTestSetup} from "test/token-voting/LlamaTokenVotingTestSetup.sol"; import {Action, ActionInfo, PermissionData} from "src/lib/Structs.sol"; import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; @@ -14,9 +15,8 @@ import {RoleDescription} from "src/lib/UDVTs.sol"; import {ERC721Votes} from "lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Votes.sol"; import {ERC721TokenholderCaster} from "src/token-voting/ERC721TokenholderCaster.sol"; import {TokenholderCaster} from "src/token-voting/TokenholderCaster.sol"; -import {PeripheryTestSetup} from "test/PeripheryTestSetup.sol"; -contract ERC721TokenholderCasterTest is PeripheryTestSetup { +contract ERC721TokenholderCasterTest is LlamaTokenVotingTestSetup { uint256 constant DEFAULT_APPROVAL_THRESHOLD = 2; uint16 constant ONE_HUNDRED_IN_BPS = 10_000; uint256 constant ONE_THIRD_IN_BPS = 3333; diff --git a/test/token-voting/LlamaTokenVotingFactory.t.sol b/test/token-voting/LlamaTokenVotingFactory.t.sol index 73b9354..826ef85 100644 --- a/test/token-voting/LlamaTokenVotingFactory.t.sol +++ b/test/token-voting/LlamaTokenVotingFactory.t.sol @@ -5,7 +5,7 @@ import {Test, console2} from "forge-std/Test.sol"; import {MockERC20Votes} from "test/mock/MockERC20Votes.sol"; import {MockERC721Votes} from "test/mock/MockERC721Votes.sol"; -import {PeripheryTestSetup} from "test/PeripheryTestSetup.sol"; +import {LlamaTokenVotingTestSetup} from "test/token-voting/LlamaTokenVotingTestSetup.sol"; import {Action, ActionInfo} from "src/lib/Structs.sol"; import {RoleDescription} from "src/lib/UDVTs.sol"; @@ -14,7 +14,7 @@ import {ERC721Votes} from "lib/openzeppelin-contracts/contracts/token/ERC721/ext import {ILlamaPolicy} from "src/interfaces/ILlamaPolicy.sol"; import {LlamaTokenVotingFactory} from "src/token-voting/LlamaTokenVotingFactory.sol"; -contract LlamaTokenVotingFactoryTest is PeripheryTestSetup { +contract LlamaTokenVotingFactoryTest is LlamaTokenVotingTestSetup { event ERC20TokenholderActionCreatorCreated(address actionCreator, address indexed token); event ERC721TokenholderActionCreatorCreated(address actionCreator, address indexed token); event ERC20TokenholderCasterCreated( @@ -118,4 +118,3 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { CORE.executeAction(actionInfo); } } - diff --git a/test/token-voting/LlamaTokenVotingTestSetup.sol b/test/token-voting/LlamaTokenVotingTestSetup.sol new file mode 100644 index 0000000..c95bdad --- /dev/null +++ b/test/token-voting/LlamaTokenVotingTestSetup.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {Test, console2} from "forge-std/Test.sol"; + +import {Vm} from "forge-std/Vm.sol"; + +import {LlamaPeripheryTestSetup} from "test/LlamaPeripheryTestSetup.sol"; + +import {DeployLlamaTokenVotingFactory} from "script/DeployLlamaTokenVotingFactory.s.sol"; + +contract LlamaTokenVotingTestSetup is LlamaPeripheryTestSetup, DeployLlamaTokenVotingFactory { + function setUp() public virtual override { + LlamaPeripheryTestSetup.setUp(); + + // Deploy the Llama Token Voting factory and logic contracts + DeployLlamaTokenVotingFactory.run(); + } +} From 2503407fa011867e087a9f0210fbfd261cfcc2a3 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Thu, 7 Dec 2023 17:17:46 -0500 Subject: [PATCH 10/25] LlamaTokenVotingTestSetup.setUp() --- test/token-voting/ERC20TokenholderActionCreator.t.sol | 6 +++--- test/token-voting/ERC20TokenholderCaster.t.sol | 2 +- test/token-voting/ERC721TokenholderActionCreator.t.sol | 2 +- test/token-voting/ERC721TokenholderCaster.t.sol | 2 +- test/token-voting/LlamaTokenVotingFactory.t.sol | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/token-voting/ERC20TokenholderActionCreator.t.sol b/test/token-voting/ERC20TokenholderActionCreator.t.sol index 4191ddc..989c001 100644 --- a/test/token-voting/ERC20TokenholderActionCreator.t.sol +++ b/test/token-voting/ERC20TokenholderActionCreator.t.sol @@ -5,7 +5,7 @@ import {Test, console2} from "forge-std/Test.sol"; import {ERC20Votes} from "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Votes.sol"; import {MockERC20Votes} from "test/mock/MockERC20Votes.sol"; -import {PeripheryTestSetup} from "test/PeripheryTestSetup.sol"; +import {LlamaTokenVotingTestSetup} from "test/token-voting/LlamaTokenVotingTestSetup.sol"; import {Action, ActionInfo} from "src/lib/Structs.sol"; import {RoleDescription} from "src/lib/UDVTs.sol"; @@ -15,7 +15,7 @@ import {ILlamaStrategy} from "src/interfaces/ILlamaStrategy.sol"; import {ERC20TokenholderActionCreator} from "src/token-voting/ERC20TokenholderActionCreator.sol"; import {TokenholderActionCreator} from "src/token-voting/TokenholderActionCreator.sol"; -contract ERC20TokenholderActionCreatorTest is PeripheryTestSetup { +contract ERC20TokenholderActionCreatorTest is LlamaTokenVotingTestSetup { event ActionCreated( uint256 id, address indexed creator, @@ -39,7 +39,7 @@ contract ERC20TokenholderActionCreatorTest is PeripheryTestSetup { address notATokenHolder = makeAddr("notATokenHolder"); function setUp() public virtual override { - PeripheryTestSetup.setUp(); + LlamaTokenVotingTestSetup.setUp(); vm.deal(address(this), 1 ether); vm.deal(address(msg.sender), 1 ether); diff --git a/test/token-voting/ERC20TokenholderCaster.t.sol b/test/token-voting/ERC20TokenholderCaster.t.sol index 4a8eef8..b148a89 100644 --- a/test/token-voting/ERC20TokenholderCaster.t.sol +++ b/test/token-voting/ERC20TokenholderCaster.t.sol @@ -107,7 +107,7 @@ contract ERC20TokenholderCasterTest is LlamaTokenVotingTestSetup { } function setUp() public virtual override { - PeripheryTestSetup.setUp(); + LlamaTokenVotingTestSetup.setUp(); vm.deal(address(this), 1 ether); vm.deal(address(msg.sender), 1 ether); vm.deal(address(EXECUTOR), 1 ether); diff --git a/test/token-voting/ERC721TokenholderActionCreator.t.sol b/test/token-voting/ERC721TokenholderActionCreator.t.sol index e9601b2..2f910ac 100644 --- a/test/token-voting/ERC721TokenholderActionCreator.t.sol +++ b/test/token-voting/ERC721TokenholderActionCreator.t.sol @@ -39,7 +39,7 @@ contract ERC721TokenholderActionCreatorTest is LlamaTokenVotingTestSetup { address notATokenHolder = makeAddr("notATokenHolder"); function setUp() public virtual override { - PeripheryTestSetup.setUp(); + LlamaTokenVotingTestSetup.setUp(); vm.deal(address(this), 1 ether); vm.deal(address(msg.sender), 1 ether); diff --git a/test/token-voting/ERC721TokenholderCaster.t.sol b/test/token-voting/ERC721TokenholderCaster.t.sol index 029ee6f..2a8f5b4 100644 --- a/test/token-voting/ERC721TokenholderCaster.t.sol +++ b/test/token-voting/ERC721TokenholderCaster.t.sol @@ -107,7 +107,7 @@ contract ERC721TokenholderCasterTest is LlamaTokenVotingTestSetup { } function setUp() public virtual override { - PeripheryTestSetup.setUp(); + LlamaTokenVotingTestSetup.setUp(); vm.deal(address(this), 1 ether); vm.deal(address(msg.sender), 1 ether); vm.deal(address(EXECUTOR), 1 ether); diff --git a/test/token-voting/LlamaTokenVotingFactory.t.sol b/test/token-voting/LlamaTokenVotingFactory.t.sol index 826ef85..8424bf8 100644 --- a/test/token-voting/LlamaTokenVotingFactory.t.sol +++ b/test/token-voting/LlamaTokenVotingFactory.t.sol @@ -35,7 +35,7 @@ contract LlamaTokenVotingFactoryTest is LlamaTokenVotingTestSetup { ERC721Votes public ERC721TOKEN; function setUp() public override { - PeripheryTestSetup.setUp(); + LlamaTokenVotingTestSetup.setUp(); FACTORY = new LlamaTokenVotingFactory(); MockERC20Votes mockERC20Votes = new MockERC20Votes(); From 0d956f60b1ab8f9703bdf7b03369f1b94d754743 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Thu, 7 Dec 2023 19:10:14 -0500 Subject: [PATCH 11/25] sort of working factory test --- test/LlamaPeripheryTestSetup.sol | 5 + .../LlamaTokenVotingFactory.t.sol | 115 +++++++++--------- .../LlamaTokenVotingTestSetup.sol | 43 ++++++- 3 files changed, 103 insertions(+), 60 deletions(-) diff --git a/test/LlamaPeripheryTestSetup.sol b/test/LlamaPeripheryTestSetup.sol index 83d086b..f034e5f 100644 --- a/test/LlamaPeripheryTestSetup.sol +++ b/test/LlamaPeripheryTestSetup.sol @@ -51,4 +51,9 @@ contract LlamaPeripheryTestSetup is Test { function setUp() public virtual { vm.createSelectFork(MAINNET_RPC_URL, 18_707_845); } + + function mineBlock() internal { + vm.roll(block.number + 1); + vm.warp(block.timestamp + 1); + } } diff --git a/test/token-voting/LlamaTokenVotingFactory.t.sol b/test/token-voting/LlamaTokenVotingFactory.t.sol index 8424bf8..5883cff 100644 --- a/test/token-voting/LlamaTokenVotingFactory.t.sol +++ b/test/token-voting/LlamaTokenVotingFactory.t.sol @@ -3,14 +3,11 @@ pragma solidity ^0.8.23; import {Test, console2} from "forge-std/Test.sol"; -import {MockERC20Votes} from "test/mock/MockERC20Votes.sol"; -import {MockERC721Votes} from "test/mock/MockERC721Votes.sol"; +import {Clones} from "@openzeppelin/proxy/Clones.sol"; + import {LlamaTokenVotingTestSetup} from "test/token-voting/LlamaTokenVotingTestSetup.sol"; -import {Action, ActionInfo} from "src/lib/Structs.sol"; -import {RoleDescription} from "src/lib/UDVTs.sol"; -import {ERC20Votes} from "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Votes.sol"; -import {ERC721Votes} from "lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Votes.sol"; +import {ActionInfo} from "src/lib/Structs.sol"; import {ILlamaPolicy} from "src/interfaces/ILlamaPolicy.sol"; import {LlamaTokenVotingFactory} from "src/token-voting/LlamaTokenVotingFactory.sol"; @@ -23,51 +20,24 @@ contract LlamaTokenVotingFactoryTest is LlamaTokenVotingTestSetup { event ERC721TokenholderCasterCreated( address caster, address indexed token, uint256 minApprovalPct, uint256 minDisapprovalPct ); - event RoleAssigned(address indexed policyholder, uint8 indexed role, uint64 expiration, uint96 quantity); - event RoleInitialized(uint8 indexed role, RoleDescription description); - - uint256 public constant CREATION_THRESHOLD = 100; - uint256 public constant MIN_APPROVAL_PCT = 1000; - uint256 public constant MIN_DISAPPROVAL_PCT = 1000; - - LlamaTokenVotingFactory public FACTORY; - ERC20Votes public ERC20TOKEN; - ERC721Votes public ERC721TOKEN; function setUp() public override { LlamaTokenVotingTestSetup.setUp(); + } - FACTORY = new LlamaTokenVotingFactory(); - MockERC20Votes mockERC20Votes = new MockERC20Votes(); - ERC20TOKEN = ERC20Votes(address(mockERC20Votes)); - MockERC721Votes mockERC721Votes = new MockERC721Votes(); - ERC721TOKEN = ERC721Votes(address(mockERC721Votes)); - - mockERC20Votes.mint(coreTeam1, 100); - mockERC20Votes.mint(coreTeam2, 100); - mockERC20Votes.mint(coreTeam3, 100); - mockERC20Votes.mint(coreTeam4, 100); - - mockERC721Votes.mint(coreTeam1, 0); - mockERC721Votes.mint(coreTeam2, 1); - mockERC721Votes.mint(coreTeam3, 2); - mockERC721Votes.mint(coreTeam4, 3); - - ILlamaPolicy.PermissionData memory newPermission1 = ILlamaPolicy.PermissionData( - address(FACTORY), LlamaTokenVotingFactory.deployTokenVotingModule.selector, address(STRATEGY) + function _setPermissionCreateApproveAndQueueAction(bytes memory data) internal returns (ActionInfo memory actionInfo) { + // Assign `deployTokenVotingModule` permission to the `CORE_TEAM_ROLE` role. + ILlamaPolicy.PermissionData memory deployTokenVotingPermission = ILlamaPolicy.PermissionData( + address(tokenVotingFactory), LlamaTokenVotingFactory.deployTokenVotingModule.selector, address(STRATEGY) ); - vm.startPrank(address(EXECUTOR)); - POLICY.setRolePermission(CORE_TEAM_ROLE, newPermission1, true); - vm.stopPrank(); - - vm.warp(block.timestamp + 1); - } + vm.prank(address(EXECUTOR)); + POLICY.setRolePermission(CORE_TEAM_ROLE, deployTokenVotingPermission, true); - function _createApproveAndQueueAction(bytes memory data) internal returns (ActionInfo memory actionInfo) { + // Create an action and queue it to deploy the token voting module. vm.prank(coreTeam4); - uint256 actionId = CORE.createAction(CORE_TEAM_ROLE, STRATEGY, address(FACTORY), 0, data, ""); - actionInfo = ActionInfo(actionId, coreTeam4, CORE_TEAM_ROLE, STRATEGY, address(FACTORY), 0, data); + uint256 actionId = CORE.createAction(CORE_TEAM_ROLE, STRATEGY, address(tokenVotingFactory), 0, data, ""); + actionInfo = ActionInfo(actionId, coreTeam4, CORE_TEAM_ROLE, STRATEGY, address(tokenVotingFactory), 0, data); vm.prank(coreTeam1); CORE.castApproval(CORE_TEAM_ROLE, actionInfo, ""); @@ -75,46 +45,75 @@ contract LlamaTokenVotingFactoryTest is LlamaTokenVotingTestSetup { CORE.castApproval(CORE_TEAM_ROLE, actionInfo, ""); vm.prank(coreTeam3); CORE.castApproval(CORE_TEAM_ROLE, actionInfo, ""); - - // vm.warp(block.timestamp + 1 days); - // CORE.queueAction(actionInfo); - // vm.warp(block.timestamp + 5 days); } } contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { function test_CanDeployERC20TokenVotingModule() public { + // Set up action to call `deployTokenVotingModule` with the ERC20 token. bytes memory data = abi.encodeWithSelector( LlamaTokenVotingFactory.deployTokenVotingModule.selector, - address(ERC20TOKEN), + address(erc20VotesToken), true, - CREATION_THRESHOLD, - MIN_APPROVAL_PCT, - MIN_DISAPPROVAL_PCT + ERC20_CREATION_THRESHOLD, + ERC20_MIN_APPROVAL_PCT, + ERC20_MIN_DISAPPROVAL_PCT ); + ActionInfo memory actionInfo = _setPermissionCreateApproveAndQueueAction(data); - ActionInfo memory actionInfo = _createApproveAndQueueAction(data); + // Compute addresses of ERC20 Token Voting Module + address erc20TokenholderActionCreator = Clones.predictDeterministicAddress( + address(erc20TokenholderActionCreatorLogic), + keccak256(abi.encodePacked(address(erc20VotesToken), address(EXECUTOR))), // salt + address(tokenVotingFactory) // deployer + ); + address erc20TokenholderCaster = Clones.predictDeterministicAddress( + address(erc20TokenholderCasterLogic), + keccak256(abi.encodePacked(address(erc20VotesToken), address(EXECUTOR))), // salt + address(tokenVotingFactory) // deployer + ); + // Expect events to be emitted on call to `deployTokenVotingModule`. vm.expectEmit(); - emit ERC20TokenholderActionCreatorCreated(0x104fBc016F4bb334D775a19E8A6510109AC63E00, address(ERC20TOKEN)); + emit ERC20TokenholderActionCreatorCreated(erc20TokenholderActionCreator, address(erc20VotesToken)); vm.expectEmit(); emit ERC20TokenholderCasterCreated( - 0x037eDa3aDB1198021A9b2e88C22B464fD38db3f3, address(ERC20TOKEN), MIN_APPROVAL_PCT, MIN_DISAPPROVAL_PCT + erc20TokenholderCaster, address(erc20VotesToken), ERC20_MIN_APPROVAL_PCT, ERC20_MIN_DISAPPROVAL_PCT ); CORE.executeAction(actionInfo); } function test_CanDeployERC721TokenVotingModule() public { + // Set up action to call `deployTokenVotingModule` with the ERC721 token. bytes memory data = abi.encodeWithSelector( - LlamaTokenVotingFactory.deployTokenVotingModule.selector, address(ERC721TOKEN), false, 1, 1, 1 + LlamaTokenVotingFactory.deployTokenVotingModule.selector, + address(erc721VotesToken), + false, + ERC721_CREATION_THRESHOLD, + ERC721_MIN_APPROVAL_PCT, + ERC721_MIN_DISAPPROVAL_PCT ); + ActionInfo memory actionInfo = _setPermissionCreateApproveAndQueueAction(data); - ActionInfo memory actionInfo = _createApproveAndQueueAction(data); + // Compute addresses of ERC721 Token Voting Module + address erc721TokenholderActionCreator = Clones.predictDeterministicAddress( + address(erc721TokenholderActionCreatorLogic), + keccak256(abi.encodePacked(address(erc721VotesToken), address(EXECUTOR))), // salt + address(tokenVotingFactory) // deployer + ); + address erc721TokenholderCaster = Clones.predictDeterministicAddress( + address(erc721TokenholderCasterLogic), + keccak256(abi.encodePacked(address(erc721VotesToken), address(EXECUTOR))), // salt + address(tokenVotingFactory) // deployer + ); + // Expect events to be emitted on call to `deployTokenVotingModule`. vm.expectEmit(); - emit ERC721TokenholderActionCreatorCreated(0x104fBc016F4bb334D775a19E8A6510109AC63E00, address(ERC721TOKEN)); + emit ERC721TokenholderActionCreatorCreated(erc721TokenholderActionCreator, address(erc721VotesToken)); vm.expectEmit(); - emit ERC721TokenholderCasterCreated(0x037eDa3aDB1198021A9b2e88C22B464fD38db3f3, address(ERC721TOKEN), 1, 1); + emit ERC721TokenholderCasterCreated( + erc721TokenholderCaster, address(erc721VotesToken), ERC721_MIN_APPROVAL_PCT, ERC721_MIN_DISAPPROVAL_PCT + ); CORE.executeAction(actionInfo); } } diff --git a/test/token-voting/LlamaTokenVotingTestSetup.sol b/test/token-voting/LlamaTokenVotingTestSetup.sol index c95bdad..dbb82e7 100644 --- a/test/token-voting/LlamaTokenVotingTestSetup.sol +++ b/test/token-voting/LlamaTokenVotingTestSetup.sol @@ -2,18 +2,57 @@ pragma solidity ^0.8.23; import {Test, console2} from "forge-std/Test.sol"; - import {Vm} from "forge-std/Vm.sol"; +import {ERC20Votes} from "@openzeppelin/token/ERC20/extensions/ERC20Votes.sol"; +import {ERC721Votes} from "@openzeppelin/token/ERC721/extensions/ERC721Votes.sol"; + +import {MockERC20Votes} from "test/mock/MockERC20Votes.sol"; +import {MockERC721Votes} from "test/mock/MockERC721Votes.sol"; import {LlamaPeripheryTestSetup} from "test/LlamaPeripheryTestSetup.sol"; import {DeployLlamaTokenVotingFactory} from "script/DeployLlamaTokenVotingFactory.s.sol"; contract LlamaTokenVotingTestSetup is LlamaPeripheryTestSetup, DeployLlamaTokenVotingFactory { + // ERC20 Token Voting Constants. + uint256 public constant ERC20_CREATION_THRESHOLD = 100; + uint256 public constant ERC20_MIN_APPROVAL_PCT = 1000; + uint256 public constant ERC20_MIN_DISAPPROVAL_PCT = 1000; + + // ERC721 Token Voting Constants. + uint256 public constant ERC721_CREATION_THRESHOLD = 1; + uint256 public constant ERC721_MIN_APPROVAL_PCT = 1; + uint256 public constant ERC721_MIN_DISAPPROVAL_PCT = 1; + + // Tokens + MockERC20Votes public mockERC20Votes; + ERC20Votes public erc20VotesToken; + MockERC721Votes public mockERC721Votes; + ERC721Votes public erc721VotesToken; + function setUp() public virtual override { LlamaPeripheryTestSetup.setUp(); - // Deploy the Llama Token Voting factory and logic contracts + // Deploy the Llama Token Voting factory and logic contracts. DeployLlamaTokenVotingFactory.run(); + + // Deploy the ERC20 and ERC721 tokens. + mockERC20Votes = new MockERC20Votes(); + erc20VotesToken = ERC20Votes(address(mockERC20Votes)); + mockERC721Votes = new MockERC721Votes(); + erc721VotesToken = ERC721Votes(address(mockERC721Votes)); + + // Mint tokens to core team members. + mockERC20Votes.mint(coreTeam1, 100); + mockERC20Votes.mint(coreTeam2, 100); + mockERC20Votes.mint(coreTeam3, 100); + mockERC20Votes.mint(coreTeam4, 100); + mockERC721Votes.mint(coreTeam1, 0); + mockERC721Votes.mint(coreTeam2, 1); + mockERC721Votes.mint(coreTeam3, 2); + mockERC721Votes.mint(coreTeam4, 3); + + // Mine block so that the ERC20 and ERC721 supply will be available when doing a past timestamp check. + mineBlock(); } } From b3e13e85a5235ef660719f756c6d3cdb76096f38 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Thu, 7 Dec 2023 19:24:28 -0500 Subject: [PATCH 12/25] cleanup --- .../LlamaTokenVotingTestSetup.sol | 33 ++++++++----------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/test/token-voting/LlamaTokenVotingTestSetup.sol b/test/token-voting/LlamaTokenVotingTestSetup.sol index dbb82e7..627ba4b 100644 --- a/test/token-voting/LlamaTokenVotingTestSetup.sol +++ b/test/token-voting/LlamaTokenVotingTestSetup.sol @@ -4,9 +4,6 @@ pragma solidity ^0.8.23; import {Test, console2} from "forge-std/Test.sol"; import {Vm} from "forge-std/Vm.sol"; -import {ERC20Votes} from "@openzeppelin/token/ERC20/extensions/ERC20Votes.sol"; -import {ERC721Votes} from "@openzeppelin/token/ERC721/extensions/ERC721Votes.sol"; - import {MockERC20Votes} from "test/mock/MockERC20Votes.sol"; import {MockERC721Votes} from "test/mock/MockERC721Votes.sol"; import {LlamaPeripheryTestSetup} from "test/LlamaPeripheryTestSetup.sol"; @@ -24,11 +21,9 @@ contract LlamaTokenVotingTestSetup is LlamaPeripheryTestSetup, DeployLlamaTokenV uint256 public constant ERC721_MIN_APPROVAL_PCT = 1; uint256 public constant ERC721_MIN_DISAPPROVAL_PCT = 1; - // Tokens - MockERC20Votes public mockERC20Votes; - ERC20Votes public erc20VotesToken; - MockERC721Votes public mockERC721Votes; - ERC721Votes public erc721VotesToken; + // Votes Tokens + MockERC20Votes public erc20VotesToken; + MockERC721Votes public erc721VotesToken; function setUp() public virtual override { LlamaPeripheryTestSetup.setUp(); @@ -37,20 +32,18 @@ contract LlamaTokenVotingTestSetup is LlamaPeripheryTestSetup, DeployLlamaTokenV DeployLlamaTokenVotingFactory.run(); // Deploy the ERC20 and ERC721 tokens. - mockERC20Votes = new MockERC20Votes(); - erc20VotesToken = ERC20Votes(address(mockERC20Votes)); - mockERC721Votes = new MockERC721Votes(); - erc721VotesToken = ERC721Votes(address(mockERC721Votes)); + erc20VotesToken = new MockERC20Votes(); + erc721VotesToken = new MockERC721Votes(); // Mint tokens to core team members. - mockERC20Votes.mint(coreTeam1, 100); - mockERC20Votes.mint(coreTeam2, 100); - mockERC20Votes.mint(coreTeam3, 100); - mockERC20Votes.mint(coreTeam4, 100); - mockERC721Votes.mint(coreTeam1, 0); - mockERC721Votes.mint(coreTeam2, 1); - mockERC721Votes.mint(coreTeam3, 2); - mockERC721Votes.mint(coreTeam4, 3); + erc20VotesToken.mint(coreTeam1, 100); + erc20VotesToken.mint(coreTeam2, 100); + erc20VotesToken.mint(coreTeam3, 100); + erc20VotesToken.mint(coreTeam4, 100); + erc721VotesToken.mint(coreTeam1, 0); + erc721VotesToken.mint(coreTeam2, 1); + erc721VotesToken.mint(coreTeam3, 2); + erc721VotesToken.mint(coreTeam4, 3); // Mine block so that the ERC20 and ERC721 supply will be available when doing a past timestamp check. mineBlock(); From a23f5beb538bc18d7a9733be8af601ee72c11355 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Thu, 7 Dec 2023 19:26:23 -0500 Subject: [PATCH 13/25] cleanup --- test/token-voting/LlamaTokenVotingTestSetup.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/token-voting/LlamaTokenVotingTestSetup.sol b/test/token-voting/LlamaTokenVotingTestSetup.sol index 627ba4b..551b4d2 100644 --- a/test/token-voting/LlamaTokenVotingTestSetup.sol +++ b/test/token-voting/LlamaTokenVotingTestSetup.sol @@ -45,7 +45,7 @@ contract LlamaTokenVotingTestSetup is LlamaPeripheryTestSetup, DeployLlamaTokenV erc721VotesToken.mint(coreTeam3, 2); erc721VotesToken.mint(coreTeam4, 3); - // Mine block so that the ERC20 and ERC721 supply will be available when doing a past timestamp check. + // Mine block so that the ERC20 and ERC721 supply will be available when doing a past timestamp check at initialize. mineBlock(); } } From cc5e1cf90ef1427405748fa2018f43337c65a7a1 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Thu, 7 Dec 2023 19:37:13 -0500 Subject: [PATCH 14/25] factory constructor tests --- .../LlamaTokenVotingFactory.t.sol | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/token-voting/LlamaTokenVotingFactory.t.sol b/test/token-voting/LlamaTokenVotingFactory.t.sol index 5883cff..f8ccf3e 100644 --- a/test/token-voting/LlamaTokenVotingFactory.t.sol +++ b/test/token-voting/LlamaTokenVotingFactory.t.sol @@ -48,6 +48,29 @@ contract LlamaTokenVotingFactoryTest is LlamaTokenVotingTestSetup { } } +contract Constructor is LlamaTokenVotingFactoryTest { + function test_SetsERC20TokenholderActionCreatorLogicAddress() public { + assertEq( + address(tokenVotingFactory.ERC20_TOKENHOLDER_ACTION_CREATOR_LOGIC()), address(erc20TokenholderActionCreatorLogic) + ); + } + + function test_SetsERC20TokenholderCasterLogicAddress() public { + assertEq(address(tokenVotingFactory.ERC20_TOKENHOLDER_CASTER_LOGIC()), address(erc20TokenholderCasterLogic)); + } + + function test_SetsERC721TokenholderActionCreatorLogicAddress() public { + assertEq( + address(tokenVotingFactory.ERC721_TOKENHOLDER_ACTION_CREATOR_LOGIC()), + address(erc721TokenholderActionCreatorLogic) + ); + } + + function test_SetsERC721TokenholderCasterLogicAddress() public { + assertEq(address(tokenVotingFactory.ERC721_TOKENHOLDER_CASTER_LOGIC()), address(erc721TokenholderCasterLogic)); + } +} + contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { function test_CanDeployERC20TokenVotingModule() public { // Set up action to call `deployTokenVotingModule` with the ERC20 token. From 95caa836de53609ba8191a38c4312de758b5aed5 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Thu, 7 Dec 2023 20:11:31 -0500 Subject: [PATCH 15/25] initialize asserts --- .../LlamaTokenVotingFactory.t.sol | 77 +++++++++++++------ 1 file changed, 55 insertions(+), 22 deletions(-) diff --git a/test/token-voting/LlamaTokenVotingFactory.t.sol b/test/token-voting/LlamaTokenVotingFactory.t.sol index f8ccf3e..9ffcd04 100644 --- a/test/token-voting/LlamaTokenVotingFactory.t.sol +++ b/test/token-voting/LlamaTokenVotingFactory.t.sol @@ -9,6 +9,10 @@ import {LlamaTokenVotingTestSetup} from "test/token-voting/LlamaTokenVotingTestS import {ActionInfo} from "src/lib/Structs.sol"; import {ILlamaPolicy} from "src/interfaces/ILlamaPolicy.sol"; +import {ERC20TokenholderActionCreator} from "src/token-voting/ERC20TokenholderActionCreator.sol"; +import {ERC20TokenholderCaster} from "src/token-voting/ERC20TokenholderCaster.sol"; +import {ERC721TokenholderActionCreator} from "src/token-voting/ERC721TokenholderActionCreator.sol"; +import {ERC721TokenholderCaster} from "src/token-voting/ERC721TokenholderCaster.sol"; import {LlamaTokenVotingFactory} from "src/token-voting/LlamaTokenVotingFactory.sol"; contract LlamaTokenVotingFactoryTest is LlamaTokenVotingTestSetup { @@ -20,6 +24,7 @@ contract LlamaTokenVotingFactoryTest is LlamaTokenVotingTestSetup { event ERC721TokenholderCasterCreated( address caster, address indexed token, uint256 minApprovalPct, uint256 minDisapprovalPct ); + event ActionThresholdSet(uint256 newThreshold); function setUp() public override { LlamaTokenVotingTestSetup.setUp(); @@ -85,25 +90,39 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { ActionInfo memory actionInfo = _setPermissionCreateApproveAndQueueAction(data); // Compute addresses of ERC20 Token Voting Module - address erc20TokenholderActionCreator = Clones.predictDeterministicAddress( - address(erc20TokenholderActionCreatorLogic), - keccak256(abi.encodePacked(address(erc20VotesToken), address(EXECUTOR))), // salt - address(tokenVotingFactory) // deployer + ERC20TokenholderActionCreator erc20TokenholderActionCreator = ERC20TokenholderActionCreator( + Clones.predictDeterministicAddress( + address(erc20TokenholderActionCreatorLogic), + keccak256(abi.encodePacked(address(erc20VotesToken), address(EXECUTOR))), // salt + address(tokenVotingFactory) // deployer + ) ); - address erc20TokenholderCaster = Clones.predictDeterministicAddress( - address(erc20TokenholderCasterLogic), - keccak256(abi.encodePacked(address(erc20VotesToken), address(EXECUTOR))), // salt - address(tokenVotingFactory) // deployer + ERC20TokenholderCaster erc20TokenholderCaster = ERC20TokenholderCaster( + Clones.predictDeterministicAddress( + address(erc20TokenholderCasterLogic), + keccak256(abi.encodePacked(address(erc20VotesToken), address(EXECUTOR))), // salt + address(tokenVotingFactory) // deployer + ) ); - // Expect events to be emitted on call to `deployTokenVotingModule`. + // Execute call to `deployTokenVotingModule`. vm.expectEmit(); - emit ERC20TokenholderActionCreatorCreated(erc20TokenholderActionCreator, address(erc20VotesToken)); + emit ActionThresholdSet(ERC20_CREATION_THRESHOLD); + vm.expectEmit(); + emit ERC20TokenholderActionCreatorCreated(address(erc20TokenholderActionCreator), address(erc20VotesToken)); vm.expectEmit(); emit ERC20TokenholderCasterCreated( - erc20TokenholderCaster, address(erc20VotesToken), ERC20_MIN_APPROVAL_PCT, ERC20_MIN_DISAPPROVAL_PCT + address(erc20TokenholderCaster), address(erc20VotesToken), ERC20_MIN_APPROVAL_PCT, ERC20_MIN_DISAPPROVAL_PCT ); CORE.executeAction(actionInfo); + + assertEq(address(erc20TokenholderActionCreator.token()), address(erc20VotesToken)); + assertEq(address(erc20TokenholderActionCreator.llamaCore()), address(CORE)); + assertEq(erc20TokenholderActionCreator.creationThreshold(), ERC20_CREATION_THRESHOLD); + assertEq(address(erc20TokenholderCaster.token()), address(erc20VotesToken)); + assertEq(address(erc20TokenholderCaster.llamaCore()), address(CORE)); + assertEq(erc20TokenholderCaster.minApprovalPct(), ERC20_MIN_APPROVAL_PCT); + assertEq(erc20TokenholderCaster.minDisapprovalPct(), ERC20_MIN_DISAPPROVAL_PCT); } function test_CanDeployERC721TokenVotingModule() public { @@ -119,24 +138,38 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { ActionInfo memory actionInfo = _setPermissionCreateApproveAndQueueAction(data); // Compute addresses of ERC721 Token Voting Module - address erc721TokenholderActionCreator = Clones.predictDeterministicAddress( - address(erc721TokenholderActionCreatorLogic), - keccak256(abi.encodePacked(address(erc721VotesToken), address(EXECUTOR))), // salt - address(tokenVotingFactory) // deployer + ERC721TokenholderActionCreator erc721TokenholderActionCreator = ERC721TokenholderActionCreator( + Clones.predictDeterministicAddress( + address(erc721TokenholderActionCreatorLogic), + keccak256(abi.encodePacked(address(erc721VotesToken), address(EXECUTOR))), // salt + address(tokenVotingFactory) // deployer + ) ); - address erc721TokenholderCaster = Clones.predictDeterministicAddress( - address(erc721TokenholderCasterLogic), - keccak256(abi.encodePacked(address(erc721VotesToken), address(EXECUTOR))), // salt - address(tokenVotingFactory) // deployer + ERC721TokenholderCaster erc721TokenholderCaster = ERC721TokenholderCaster( + Clones.predictDeterministicAddress( + address(erc721TokenholderCasterLogic), + keccak256(abi.encodePacked(address(erc721VotesToken), address(EXECUTOR))), // salt + address(tokenVotingFactory) // deployer + ) ); - // Expect events to be emitted on call to `deployTokenVotingModule`. + // Execute call to `deployTokenVotingModule`. vm.expectEmit(); - emit ERC721TokenholderActionCreatorCreated(erc721TokenholderActionCreator, address(erc721VotesToken)); + emit ActionThresholdSet(ERC721_CREATION_THRESHOLD); + vm.expectEmit(); + emit ERC721TokenholderActionCreatorCreated(address(erc721TokenholderActionCreator), address(erc721VotesToken)); vm.expectEmit(); emit ERC721TokenholderCasterCreated( - erc721TokenholderCaster, address(erc721VotesToken), ERC721_MIN_APPROVAL_PCT, ERC721_MIN_DISAPPROVAL_PCT + address(erc721TokenholderCaster), address(erc721VotesToken), ERC721_MIN_APPROVAL_PCT, ERC721_MIN_DISAPPROVAL_PCT ); CORE.executeAction(actionInfo); + + assertEq(address(erc721TokenholderActionCreator.token()), address(erc721VotesToken)); + assertEq(address(erc721TokenholderActionCreator.llamaCore()), address(CORE)); + assertEq(erc721TokenholderActionCreator.creationThreshold(), ERC721_CREATION_THRESHOLD); + assertEq(address(erc721TokenholderCaster.token()), address(erc721VotesToken)); + assertEq(address(erc721TokenholderCaster.llamaCore()), address(CORE)); + assertEq(erc721TokenholderCaster.minApprovalPct(), ERC721_MIN_APPROVAL_PCT); + assertEq(erc721TokenholderCaster.minDisapprovalPct(), ERC721_MIN_DISAPPROVAL_PCT); } } From 295d652e70a9c34b61a4d7a3539d2bd527ad0bb4 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Thu, 7 Dec 2023 21:36:23 -0500 Subject: [PATCH 16/25] tokenholder --- test/LlamaPeripheryTestSetup.sol | 4 ---- test/token-voting/LlamaTokenVotingTestSetup.sol | 7 +++++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/test/LlamaPeripheryTestSetup.sol b/test/LlamaPeripheryTestSetup.sol index cc72f6d..f034e5f 100644 --- a/test/LlamaPeripheryTestSetup.sol +++ b/test/LlamaPeripheryTestSetup.sol @@ -34,9 +34,6 @@ contract LlamaPeripheryTestSetup is Test { address coreTeam4 = 0x6b45E38c87bfCa15ee90AAe2AFe3CFC58cE08F75; address coreTeam5 = 0xbdfcE43E5D2C7AA8599290d940c9932B8dBC94Ca; - address tokenHolder; - uint256 tokenHolderPrivateKey; - // Function selectors used in tests. bytes4 public constant SET_ROLE_HOLDER_SELECTOR = 0x2524842c; // pause(bool) @@ -53,7 +50,6 @@ contract LlamaPeripheryTestSetup is Test { function setUp() public virtual { vm.createSelectFork(MAINNET_RPC_URL, 18_707_845); - (tokenHolder, tokenHolderPrivateKey) = makeAddrAndKey("tokenHolder"); } function mineBlock() internal { diff --git a/test/token-voting/LlamaTokenVotingTestSetup.sol b/test/token-voting/LlamaTokenVotingTestSetup.sol index 551b4d2..4f23e5d 100644 --- a/test/token-voting/LlamaTokenVotingTestSetup.sol +++ b/test/token-voting/LlamaTokenVotingTestSetup.sol @@ -25,6 +25,10 @@ contract LlamaTokenVotingTestSetup is LlamaPeripheryTestSetup, DeployLlamaTokenV MockERC20Votes public erc20VotesToken; MockERC721Votes public erc721VotesToken; + // Token holders. + address tokenHolder; + uint256 tokenHolderPrivateKey; + function setUp() public virtual override { LlamaPeripheryTestSetup.setUp(); @@ -47,5 +51,8 @@ contract LlamaTokenVotingTestSetup is LlamaPeripheryTestSetup, DeployLlamaTokenV // Mine block so that the ERC20 and ERC721 supply will be available when doing a past timestamp check at initialize. mineBlock(); + + // Setting up tokenholder addresses and private keys. + (tokenHolder, tokenHolderPrivateKey) = makeAddrAndKey("tokenHolder"); } } From ae0b6a987fa9f197d1266ebf9f933c4e89e320bb Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Thu, 7 Dec 2023 23:36:14 -0500 Subject: [PATCH 17/25] refactor --- .../LlamaTokenVotingFactory.t.sol | 44 ++++++------- .../LlamaTokenVotingTestSetup.sol | 66 ++++++++++++++----- 2 files changed, 73 insertions(+), 37 deletions(-) diff --git a/test/token-voting/LlamaTokenVotingFactory.t.sol b/test/token-voting/LlamaTokenVotingFactory.t.sol index 9ffcd04..500249b 100644 --- a/test/token-voting/LlamaTokenVotingFactory.t.sol +++ b/test/token-voting/LlamaTokenVotingFactory.t.sol @@ -29,28 +29,6 @@ contract LlamaTokenVotingFactoryTest is LlamaTokenVotingTestSetup { function setUp() public override { LlamaTokenVotingTestSetup.setUp(); } - - function _setPermissionCreateApproveAndQueueAction(bytes memory data) internal returns (ActionInfo memory actionInfo) { - // Assign `deployTokenVotingModule` permission to the `CORE_TEAM_ROLE` role. - ILlamaPolicy.PermissionData memory deployTokenVotingPermission = ILlamaPolicy.PermissionData( - address(tokenVotingFactory), LlamaTokenVotingFactory.deployTokenVotingModule.selector, address(STRATEGY) - ); - - vm.prank(address(EXECUTOR)); - POLICY.setRolePermission(CORE_TEAM_ROLE, deployTokenVotingPermission, true); - - // Create an action and queue it to deploy the token voting module. - vm.prank(coreTeam4); - uint256 actionId = CORE.createAction(CORE_TEAM_ROLE, STRATEGY, address(tokenVotingFactory), 0, data, ""); - actionInfo = ActionInfo(actionId, coreTeam4, CORE_TEAM_ROLE, STRATEGY, address(tokenVotingFactory), 0, data); - - vm.prank(coreTeam1); - CORE.castApproval(CORE_TEAM_ROLE, actionInfo, ""); - vm.prank(coreTeam2); - CORE.castApproval(CORE_TEAM_ROLE, actionInfo, ""); - vm.prank(coreTeam3); - CORE.castApproval(CORE_TEAM_ROLE, actionInfo, ""); - } } contract Constructor is LlamaTokenVotingFactoryTest { @@ -77,6 +55,28 @@ contract Constructor is LlamaTokenVotingFactoryTest { } contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { + function _setPermissionCreateApproveAndQueueAction(bytes memory data) internal returns (ActionInfo memory actionInfo) { + // Assign `deployTokenVotingModule` permission to the `CORE_TEAM_ROLE` role. + ILlamaPolicy.PermissionData memory deployTokenVotingPermission = ILlamaPolicy.PermissionData( + address(tokenVotingFactory), LlamaTokenVotingFactory.deployTokenVotingModule.selector, address(STRATEGY) + ); + + vm.prank(address(EXECUTOR)); + POLICY.setRolePermission(CORE_TEAM_ROLE, deployTokenVotingPermission, true); + + // Create an action and queue it to deploy the token voting module. + vm.prank(coreTeam4); + uint256 actionId = CORE.createAction(CORE_TEAM_ROLE, STRATEGY, address(tokenVotingFactory), 0, data, ""); + actionInfo = ActionInfo(actionId, coreTeam4, CORE_TEAM_ROLE, STRATEGY, address(tokenVotingFactory), 0, data); + + vm.prank(coreTeam1); + CORE.castApproval(CORE_TEAM_ROLE, actionInfo, ""); + vm.prank(coreTeam2); + CORE.castApproval(CORE_TEAM_ROLE, actionInfo, ""); + vm.prank(coreTeam3); + CORE.castApproval(CORE_TEAM_ROLE, actionInfo, ""); + } + function test_CanDeployERC20TokenVotingModule() public { // Set up action to call `deployTokenVotingModule` with the ERC20 token. bytes memory data = abi.encodeWithSelector( diff --git a/test/token-voting/LlamaTokenVotingTestSetup.sol b/test/token-voting/LlamaTokenVotingTestSetup.sol index 4f23e5d..53f1ea8 100644 --- a/test/token-voting/LlamaTokenVotingTestSetup.sol +++ b/test/token-voting/LlamaTokenVotingTestSetup.sol @@ -10,9 +10,14 @@ import {LlamaPeripheryTestSetup} from "test/LlamaPeripheryTestSetup.sol"; import {DeployLlamaTokenVotingFactory} from "script/DeployLlamaTokenVotingFactory.s.sol"; +import {ERC20TokenholderActionCreator} from "src/token-voting/ERC20TokenholderActionCreator.sol"; +import {ERC20TokenholderCaster} from "src/token-voting/ERC20TokenholderCaster.sol"; +import {ERC721TokenholderActionCreator} from "src/token-voting/ERC721TokenholderActionCreator.sol"; +import {ERC721TokenholderCaster} from "src/token-voting/ERC721TokenholderCaster.sol"; + contract LlamaTokenVotingTestSetup is LlamaPeripheryTestSetup, DeployLlamaTokenVotingFactory { // ERC20 Token Voting Constants. - uint256 public constant ERC20_CREATION_THRESHOLD = 100; + uint256 public constant ERC20_CREATION_THRESHOLD = 500_000e18; uint256 public constant ERC20_MIN_APPROVAL_PCT = 1000; uint256 public constant ERC20_MIN_DISAPPROVAL_PCT = 1000; @@ -26,8 +31,14 @@ contract LlamaTokenVotingTestSetup is LlamaPeripheryTestSetup, DeployLlamaTokenV MockERC721Votes public erc721VotesToken; // Token holders. - address tokenHolder; - uint256 tokenHolderPrivateKey; + address tokenHolder1; + uint256 tokenHolder1PrivateKey; + address tokenHolder2; + uint256 tokenHolder2PrivateKey; + address tokenHolder3; + uint256 tokenHolder3PrivateKey; + address notTokenHolder; + uint256 notTokenHolderPrivateKey; function setUp() public virtual override { LlamaPeripheryTestSetup.setUp(); @@ -39,20 +50,45 @@ contract LlamaTokenVotingTestSetup is LlamaPeripheryTestSetup, DeployLlamaTokenV erc20VotesToken = new MockERC20Votes(); erc721VotesToken = new MockERC721Votes(); + // Setting up tokenholder addresses and private keys. + (tokenHolder1, tokenHolder1PrivateKey) = makeAddrAndKey("tokenHolder1"); + (tokenHolder2, tokenHolder2PrivateKey) = makeAddrAndKey("tokenHolder2"); + (tokenHolder3, tokenHolder3PrivateKey) = makeAddrAndKey("tokenHolder3"); + (notTokenHolder, notTokenHolderPrivateKey) = makeAddrAndKey("notTokenHolder"); + // Mint tokens to core team members. - erc20VotesToken.mint(coreTeam1, 100); - erc20VotesToken.mint(coreTeam2, 100); - erc20VotesToken.mint(coreTeam3, 100); - erc20VotesToken.mint(coreTeam4, 100); - erc721VotesToken.mint(coreTeam1, 0); - erc721VotesToken.mint(coreTeam2, 1); - erc721VotesToken.mint(coreTeam3, 2); - erc721VotesToken.mint(coreTeam4, 3); - - // Mine block so that the ERC20 and ERC721 supply will be available when doing a past timestamp check at initialize. + erc20VotesToken.mint(tokenHolder1, ERC20_CREATION_THRESHOLD); + erc20VotesToken.mint(tokenHolder2, ERC20_CREATION_THRESHOLD); + erc20VotesToken.mint(tokenHolder3, ERC20_CREATION_THRESHOLD); + erc721VotesToken.mint(tokenHolder1, 0); + erc721VotesToken.mint(tokenHolder2, 1); + erc721VotesToken.mint(tokenHolder3, 2); + + // Mine block so that the ERC20 and ERC721 supply will be available when doing a past timestamp check at initialize + // during deployment. mineBlock(); + } - // Setting up tokenholder addresses and private keys. - (tokenHolder, tokenHolderPrivateKey) = makeAddrAndKey("tokenHolder"); + // ========================= + // ======== Helpers ======== + // ========================= + + function _deployERC20TokenVotingModule() internal returns (ERC20TokenholderActionCreator, ERC20TokenholderCaster) { + vm.prank(address(EXECUTOR)); + (address erc20TokenholderActionCreator, address erc20TokenholderCaster) = tokenVotingFactory.deployTokenVotingModule( + address(erc20VotesToken), true, ERC20_CREATION_THRESHOLD, ERC20_MIN_APPROVAL_PCT, ERC20_MIN_DISAPPROVAL_PCT + ); + return + (ERC20TokenholderActionCreator(erc20TokenholderActionCreator), ERC20TokenholderCaster(erc20TokenholderCaster)); + } + + function _deployERC721TokenVotingModule() internal returns (ERC721TokenholderActionCreator, ERC721TokenholderCaster) { + vm.prank(address(EXECUTOR)); + (address erc721TokenholderActionCreator, address erc721TokenholderCaster) = tokenVotingFactory + .deployTokenVotingModule( + address(erc721VotesToken), false, ERC721_CREATION_THRESHOLD, ERC721_MIN_APPROVAL_PCT, ERC721_MIN_DISAPPROVAL_PCT + ); + return + (ERC721TokenholderActionCreator(erc721TokenholderActionCreator), ERC721TokenholderCaster(erc721TokenholderCaster)); } } From adb8dba0a82391c2fafdf7808e14b38fef833a16 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Fri, 8 Dec 2023 00:01:55 -0500 Subject: [PATCH 18/25] refactor --- test/token-voting/LlamaTokenVotingFactory.t.sol | 8 ++++++++ test/token-voting/LlamaTokenVotingTestSetup.sol | 15 +++------------ 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/test/token-voting/LlamaTokenVotingFactory.t.sol b/test/token-voting/LlamaTokenVotingFactory.t.sol index 500249b..8b8b669 100644 --- a/test/token-voting/LlamaTokenVotingFactory.t.sol +++ b/test/token-voting/LlamaTokenVotingFactory.t.sol @@ -28,6 +28,14 @@ contract LlamaTokenVotingFactoryTest is LlamaTokenVotingTestSetup { function setUp() public override { LlamaTokenVotingTestSetup.setUp(); + + // Mint tokens to tokenholders so that there is an existing supply. + erc20VotesToken.mint(tokenHolder0, ERC20_CREATION_THRESHOLD); + erc721VotesToken.mint(tokenHolder0, 0); + + // Mine block so that the ERC20 and ERC721 supply will be available when doing a past timestamp check at initialize + // during deployment. + mineBlock(); } } diff --git a/test/token-voting/LlamaTokenVotingTestSetup.sol b/test/token-voting/LlamaTokenVotingTestSetup.sol index 53f1ea8..b068dbc 100644 --- a/test/token-voting/LlamaTokenVotingTestSetup.sol +++ b/test/token-voting/LlamaTokenVotingTestSetup.sol @@ -31,6 +31,8 @@ contract LlamaTokenVotingTestSetup is LlamaPeripheryTestSetup, DeployLlamaTokenV MockERC721Votes public erc721VotesToken; // Token holders. + address tokenHolder0; + uint256 tokenHolder0PrivateKey; address tokenHolder1; uint256 tokenHolder1PrivateKey; address tokenHolder2; @@ -51,22 +53,11 @@ contract LlamaTokenVotingTestSetup is LlamaPeripheryTestSetup, DeployLlamaTokenV erc721VotesToken = new MockERC721Votes(); // Setting up tokenholder addresses and private keys. + (tokenHolder0, tokenHolder0PrivateKey) = makeAddrAndKey("tokenHolder0"); (tokenHolder1, tokenHolder1PrivateKey) = makeAddrAndKey("tokenHolder1"); (tokenHolder2, tokenHolder2PrivateKey) = makeAddrAndKey("tokenHolder2"); (tokenHolder3, tokenHolder3PrivateKey) = makeAddrAndKey("tokenHolder3"); (notTokenHolder, notTokenHolderPrivateKey) = makeAddrAndKey("notTokenHolder"); - - // Mint tokens to core team members. - erc20VotesToken.mint(tokenHolder1, ERC20_CREATION_THRESHOLD); - erc20VotesToken.mint(tokenHolder2, ERC20_CREATION_THRESHOLD); - erc20VotesToken.mint(tokenHolder3, ERC20_CREATION_THRESHOLD); - erc721VotesToken.mint(tokenHolder1, 0); - erc721VotesToken.mint(tokenHolder2, 1); - erc721VotesToken.mint(tokenHolder3, 2); - - // Mine block so that the ERC20 and ERC721 supply will be available when doing a past timestamp check at initialize - // during deployment. - mineBlock(); } // ========================= From 8ca6ed7f6c3459ec5f62617083942af413b912b6 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Fri, 8 Dec 2023 11:06:33 -0500 Subject: [PATCH 19/25] create action refactored --- .../ERC20TokenholderActionCreator.t.sol | 1034 +++++++++-------- .../LlamaTokenVotingTestSetup.sol | 6 + 2 files changed, 536 insertions(+), 504 deletions(-) diff --git a/test/token-voting/ERC20TokenholderActionCreator.t.sol b/test/token-voting/ERC20TokenholderActionCreator.t.sol index c05649a..2f83db9 100644 --- a/test/token-voting/ERC20TokenholderActionCreator.t.sol +++ b/test/token-voting/ERC20TokenholderActionCreator.t.sol @@ -3,8 +3,6 @@ pragma solidity ^0.8.23; import {Test, console2} from "forge-std/Test.sol"; -import {ERC20Votes} from "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Votes.sol"; -import {MockERC20Votes} from "test/mock/MockERC20Votes.sol"; import {LlamaTokenVotingTestSetup} from "test/token-voting/LlamaTokenVotingTestSetup.sol"; import {LlamaCoreSigUtils} from "test/utils/LlamaCoreSigUtils.sol"; @@ -33,552 +31,580 @@ contract ERC20TokenholderActionCreatorTest is LlamaTokenVotingTestSetup { event ActionThresholdSet(uint256 newThreshold); - MockERC20Votes mockErc20Votes; - ERC20Votes token; - address tokenHolder1 = makeAddr("tokenHolder1"); - address tokenHolder2 = makeAddr("tokenHolder2"); - address tokenHolder3 = makeAddr("tokenHolder3"); - address notATokenHolder = makeAddr("notATokenHolder"); + ERC20TokenholderActionCreator erc20TokenholderActionCreator; + uint8 erc20TokenholderActionCreatorRole; - function setUp() public virtual override { + function setUp() public override { LlamaTokenVotingTestSetup.setUp(); vm.deal(address(this), 1 ether); vm.deal(address(msg.sender), 1 ether); - mockErc20Votes = new MockERC20Votes(); - token = ERC20Votes(address(mockErc20Votes)); - } -} + // Mint tokens to tokenholders so that there is an existing supply. + erc20VotesToken.mint(tokenHolder0, ERC20_CREATION_THRESHOLD); + erc721VotesToken.mint(tokenHolder0, 0); -contract Constructor is ERC20TokenholderActionCreatorTest { - function test_RevertsIf_InvalidLlamaCore() public { - // With invalid LlamaCore instance, TokenholderActionCreator.InvalidLlamaCoreAddress is unreachable - vm.expectRevert(); - new ERC20TokenholderActionCreator(token, ILlamaCore(makeAddr("invalid-llama-core")), uint256(0)); - } + // Mine block so that the ERC20 and ERC721 supply will be available when doing a past timestamp check at initialize + // during deployment. + mineBlock(); - function test_RevertsIf_InvalidTokenAddress() public { - vm.expectRevert(); // will EvmError: Revert vecause totalSupply fn does not exist - new ERC20TokenholderActionCreator(ERC20Votes(makeAddr("invalid-token")), CORE, uint256(0)); + // Deploy ERC20 Token Voting Module. + (erc20TokenholderActionCreator,) = _deployERC20TokenVotingModule(); } - function test_RevertsIf_CreationThresholdExceedsTotalSupply() public { - mockErc20Votes.mint(tokenHolder1, 1_000_000e18); // we use mockErc20Votes because IVotesToken is an interface - // without the `mint` function - - vm.warp(block.timestamp + 1); - - vm.expectRevert(TokenholderActionCreator.InvalidCreationThreshold.selector); - new ERC20TokenholderActionCreator(token, CORE, 17_000_000_000_000_000_000_000_000); - } - - function test_ProperlySetsConstructorArguments() public { - uint256 threshold = 500_000e18; - mockErc20Votes.mint(tokenHolder1, 1_000_000e18); // we use mockErc20Votes because IVotesToken is an interface - // without the `mint` function - - vm.warp(block.timestamp + 1); - - ERC20TokenholderActionCreator actionCreator = new ERC20TokenholderActionCreator(token, CORE, threshold); - assertEq(address(actionCreator.TOKEN()), address(token)); - assertEq(address(actionCreator.LLAMA_CORE()), address(CORE)); - assertEq(actionCreator.creationThreshold(), threshold); + function _initRoleSetRoleHolderSetRolePermissionToTokenholderActionCreator() internal { + // Init role, assign policy, and assign permission for `setRoleHolder` to the TokenholderActionCreator. + vm.startPrank(address(EXECUTOR)); + POLICY.initializeRole(TOKEN_VOTING_ACTION_CREATOR_ROLE_DESC); + erc20TokenholderActionCreatorRole = POLICY.numRoles(); + POLICY.setRoleHolder( + erc20TokenholderActionCreatorRole, + address(erc20TokenholderActionCreator), + DEFAULT_ROLE_QTY, + DEFAULT_ROLE_EXPIRATION + ); + POLICY.setRolePermission( + erc20TokenholderActionCreatorRole, + ILlamaPolicy.PermissionData(address(POLICY), POLICY.setRoleHolder.selector, address(STRATEGY)), + true + ); + vm.stopPrank(); } } -contract TokenHolderCreateAction is ERC20TokenholderActionCreatorTest { +// contract Constructor is ERC20TokenholderActionCreatorTest { +// function test_RevertsIf_InvalidLlamaCore() public { +// // With invalid LlamaCore instance, TokenholderActionCreator.InvalidLlamaCoreAddress is unreachable +// vm.expectRevert(); +// new ERC20TokenholderActionCreator(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 ERC20TokenholderActionCreator(ERC20Votes(makeAddr("invalid-erc20VotesToken")), CORE, uint256(0)); +// } + +// function test_RevertsIf_CreationThresholdExceedsTotalSupply() public { +// erc20VotesToken.mint(tokenHolder1, 1_000_000e18); // we use erc20VotesToken because IVotesToken is an interface +// // without the `mint` function + +// vm.warp(block.timestamp + 1); + +// vm.expectRevert(TokenholderActionCreator.InvalidCreationThreshold.selector); +// new ERC20TokenholderActionCreator(erc20VotesToken, CORE, 17_000_000_000_000_000_000_000_000); +// } + +// function test_ProperlySetsConstructorArguments() public { +// uint256 threshold = 500_000e18; +// erc20VotesToken.mint(tokenHolder1, 1_000_000e18); // we use erc20VotesToken because IVotesToken is an interface +// // without the `mint` function + +// vm.warp(block.timestamp + 1); + +// ERC20TokenholderActionCreator erc20TokenholderActionCreator = new ERC20TokenholderActionCreator(erc20VotesToken, +// CORE, +// threshold); +// assertEq(address(erc20TokenholderActionCreator.TOKEN()), address(erc20VotesToken)); +// assertEq(address(erc20TokenholderActionCreator.LLAMA_CORE()), address(CORE)); +// assertEq(erc20TokenholderActionCreator.creationThreshold(), threshold); +// } +// } + +contract CreateAction is ERC20TokenholderActionCreatorTest { bytes data = abi.encodeCall( POLICY.setRoleHolder, (CORE_TEAM_ROLE, address(0xdeadbeef), DEFAULT_ROLE_QTY, DEFAULT_ROLE_EXPIRATION) ); - uint256 threshold = 500_000e18; function test_RevertsIf_InsufficientBalance() public { - mockErc20Votes.mint(tokenHolder1, 1_000_000e18); // we use mockErc20Votes because IVotesToken is an interface - // without the `mint` function - - vm.warp(block.timestamp + 1); + erc20VotesToken.mint(tokenHolder1, ERC20_CREATION_THRESHOLD); + vm.prank(tokenHolder1); + erc20VotesToken.delegate(tokenHolder1); - ERC20TokenholderActionCreator actionCreator = new ERC20TokenholderActionCreator(token, CORE, threshold); + mineBlock(); - vm.prank(notATokenHolder); vm.expectRevert(abi.encodeWithSelector(TokenholderActionCreator.InsufficientBalance.selector, 0)); - actionCreator.createAction(CORE_TEAM_ROLE, STRATEGY, address(POLICY), 0, data, ""); + vm.prank(notTokenHolder); + erc20TokenholderActionCreator.createAction(CORE_TEAM_ROLE, STRATEGY, address(POLICY), 0, data, ""); } function test_RevertsIf_TokenholderActionCreatorDoesNotHavePermission() public { - mockErc20Votes.mint(tokenHolder1, threshold); // we use mockErc20Votes because IVotesToken is an - // interface without the `mint` function + erc20VotesToken.mint(tokenHolder1, ERC20_CREATION_THRESHOLD); vm.prank(tokenHolder1); - mockErc20Votes.delegate(tokenHolder1); // we use mockErc20Votes because IVotesToken is an interface without - // the `delegate` function - - vm.warp(block.timestamp + 1); - - ERC20TokenholderActionCreator actionCreator = new ERC20TokenholderActionCreator(token, CORE, threshold); - vm.roll(block.number + 1); - vm.warp(block.timestamp + 1); + erc20VotesToken.delegate(tokenHolder1); - token.getPastVotes(tokenHolder1, block.timestamp - 1); + mineBlock(); vm.expectRevert(ILlamaCore.PolicyholderDoesNotHavePermission.selector); vm.prank(tokenHolder1); - actionCreator.createAction(CORE_TEAM_ROLE, STRATEGY, address(POLICY), 0, data, ""); + erc20TokenholderActionCreator.createAction(CORE_TEAM_ROLE, STRATEGY, address(POLICY), 0, data, ""); } function test_ProperlyCreatesAction() public { - mockErc20Votes.mint(tokenHolder1, threshold); // we use mockErc20Votes because IVotesToken is an - // interface without the `delegate` function - vm.prank(tokenHolder1); - mockErc20Votes.delegate(tokenHolder1); // we use mockErc20Votes because IVotesToken is an interface without - // the `delegate` function - - vm.roll(block.number + 1); - vm.warp(block.timestamp + 1); - - ERC20TokenholderActionCreator actionCreator = new ERC20TokenholderActionCreator(token, CORE, threshold); - - vm.startPrank(address(EXECUTOR)); // init role, assign policy, and assign permission to setRoleHolder to the token - // voting action creator - POLICY.initializeRole(RoleDescription.wrap("Token Voting Action Creator Role")); - uint8 actionCreatorRole = 2; - POLICY.setRoleHolder(actionCreatorRole, address(actionCreator), DEFAULT_ROLE_QTY, DEFAULT_ROLE_EXPIRATION); - POLICY.setRolePermission( - actionCreatorRole, - ILlamaPolicy.PermissionData(address(POLICY), POLICY.setRoleHolder.selector, address(STRATEGY)), - true - ); - vm.stopPrank(); - - vm.roll(block.number + 1); - vm.warp(block.timestamp + 1); - - data = abi.encodeCall( - POLICY.setRoleHolder, (actionCreatorRole, address(0xdeadbeef), DEFAULT_ROLE_QTY, DEFAULT_ROLE_EXPIRATION) - ); - - uint256 actionCount = CORE.actionsCount(); - - vm.expectEmit(); - emit ActionCreated(actionCount, address(tokenHolder1), actionCreatorRole, STRATEGY, address(POLICY), 0, data, ""); + // Assigns Policy to TokenholderActionCreator. + _initRoleSetRoleHolderSetRolePermissionToTokenholderActionCreator(); + // Mint tokens to tokenholder so that they can create action. + erc20VotesToken.mint(tokenHolder1, ERC20_CREATION_THRESHOLD); vm.prank(tokenHolder1); + erc20VotesToken.delegate(tokenHolder1); - uint256 actionId = actionCreator.createAction(actionCreatorRole, STRATEGY, address(POLICY), 0, data, ""); - - Action memory action = CORE.getAction(actionId); - - assertEq(actionId, actionCount); - assertEq(action.creationTime, block.timestamp); - } -} - -contract CancelAction is ERC20TokenholderActionCreatorTest { - uint8 actionCreatorRole = 2; - bytes data = abi.encodeCall( - POLICY.setRoleHolder, (actionCreatorRole, address(0xdeadbeef), DEFAULT_ROLE_QTY, DEFAULT_ROLE_EXPIRATION) - ); - uint256 threshold = 500_000e18; - uint256 actionId; - ERC20TokenholderActionCreator actionCreator; - ActionInfo actionInfo; - - function setUp() public virtual override { - ERC20TokenholderActionCreatorTest.setUp(); - mockErc20Votes.mint(tokenHolder1, threshold); // we use mockErc20Votes because IVotesToken is an - // interface without the `delegate` function - vm.prank(tokenHolder1); - mockErc20Votes.delegate(tokenHolder1); // we use mockErc20Votes because IVotesToken is an interface without - // the `delegate` function - - vm.roll(block.number + 1); - vm.warp(block.timestamp + 1); - - actionCreator = new ERC20TokenholderActionCreator(token, CORE, threshold); - - vm.startPrank(address(EXECUTOR)); // init role, assign policy, and assign permission to setRoleHolder to the token - // voting action creator - POLICY.initializeRole(RoleDescription.wrap("Token Voting Action Creator Role")); - POLICY.setRoleHolder(actionCreatorRole, address(actionCreator), DEFAULT_ROLE_QTY, DEFAULT_ROLE_EXPIRATION); - POLICY.setRolePermission( - actionCreatorRole, - ILlamaPolicy.PermissionData(address(POLICY), POLICY.setRoleHolder.selector, address(STRATEGY)), - true - ); - vm.stopPrank(); - - vm.roll(block.number + 1); - vm.warp(block.timestamp + 1); - - vm.prank(tokenHolder1); - actionId = actionCreator.createAction(actionCreatorRole, STRATEGY, address(POLICY), 0, data, ""); - - actionInfo = ActionInfo(actionId, address(actionCreator), actionCreatorRole, STRATEGY, address(POLICY), 0, data); - } - - function test_PassesIf_CallerIsActionCreator() public { - vm.expectEmit(); - emit ActionCanceled(actionId, tokenHolder1); - vm.prank(tokenHolder1); - actionCreator.cancelAction(actionInfo); - } - - function test_RevertsIf_CallerIsNotActionCreator(address notCreator) public { - vm.assume(notCreator != tokenHolder1); - vm.expectRevert(TokenholderActionCreator.OnlyActionCreator.selector); - vm.prank(notCreator); - actionCreator.cancelAction(actionInfo); - } -} - -contract SetActionThreshold is ERC20TokenholderActionCreatorTest { - function test_SetsCreationThreshold() public { - uint256 threshold = 500_000e18; - mockErc20Votes.mint(address(this), 1_000_000e18); // we use mockErc20Votes because IVotesToken is an interface - // without the `mint` function - - vm.warp(block.timestamp + 1); - - ERC20TokenholderActionCreator actionCreator = new ERC20TokenholderActionCreator(token, CORE, 1_000_000e18); - - assertEq(actionCreator.creationThreshold(), 1_000_000e18); - - vm.expectEmit(); - emit ActionThresholdSet(threshold); - vm.prank(address(EXECUTOR)); - actionCreator.setActionThreshold(threshold); - } - - function test_RevertsIf_CreationThresholdExceedsTotalSupply() public { - uint256 threshold = 1_000_000e18; - mockErc20Votes.mint(address(this), 500_000e18); // we use mockErc20Votes because IVotesToken is an interface - // without the `mint` function - - ERC20TokenholderActionCreator actionCreator = new ERC20TokenholderActionCreator(token, CORE, 500_000e18); - - vm.expectRevert(TokenholderActionCreator.InvalidCreationThreshold.selector); - vm.prank(address(EXECUTOR)); - actionCreator.setActionThreshold(threshold); - } - - function test_RevertsIf_CalledByNotLlamaExecutor(address notLlamaExecutor) public { - vm.assume(notLlamaExecutor != address(EXECUTOR)); - uint256 threshold = 500_000e18; - mockErc20Votes.mint(address(this), 1_000_000e18); // we use mockErc20Votes because IVotesToken is an interface - // without the `mint` function - - ERC20TokenholderActionCreator actionCreator = new ERC20TokenholderActionCreator(token, CORE, 1_000_000e18); - - vm.expectRevert(TokenholderActionCreator.OnlyLlamaExecutor.selector); - vm.prank(notLlamaExecutor); - actionCreator.setActionThreshold(threshold); - } -} - -contract CreateActionBySig is ERC20TokenholderActionCreatorTest, LlamaCoreSigUtils { - ERC20TokenholderActionCreator actionCreator; - uint8 actionCreatorRole; - - function setUp() public virtual override { - ERC20TokenholderActionCreatorTest.setUp(); - mockErc20Votes.mint(tokenHolder, 1000); // we use mockErc20Votes because IVotesToken is an - // interface without the `delegate` function - vm.prank(tokenHolder); - mockErc20Votes.delegate(tokenHolder); // we use mockErc20Votes because IVotesToken is an interface without - // the `delegate` function - - actionCreator = new ERC20TokenholderActionCreator(token, CORE, 1000); - - // Setting Mock Protocol Core's EIP-712 Domain Hash - setDomainHash( - LlamaCoreSigUtils.EIP712Domain({ - name: CORE.name(), - version: "1", - chainId: block.chainid, - verifyingContract: address(actionCreator) - }) - ); - - vm.startPrank(address(EXECUTOR)); // init role, assign policy, and assign permission to setRoleHolder to the token - // voting action creator - POLICY.initializeRole(RoleDescription.wrap("Token Voting Action Creator Role")); - actionCreatorRole = 2; - POLICY.setRoleHolder(actionCreatorRole, address(actionCreator), DEFAULT_ROLE_QTY, DEFAULT_ROLE_EXPIRATION); - POLICY.setRolePermission( - actionCreatorRole, - ILlamaPolicy.PermissionData(address(POLICY), POLICY.initializeRole.selector, address(STRATEGY)), - true - ); - vm.stopPrank(); - - vm.roll(block.number + 1); - vm.warp(block.timestamp + 1); - } - - function createOffchainSignature(uint256 privateKey) internal view returns (uint8 v, bytes32 r, bytes32 s) { - (v, r, s) = createOffchainSignatureWithDescription(privateKey, ""); - } - - function createOffchainSignatureWithDescription(uint256 privateKey, string memory description) - internal - view - returns (uint8 v, bytes32 r, bytes32 s) - { - LlamaCoreSigUtils.CreateActionBySig memory _createAction = LlamaCoreSigUtils.CreateActionBySig({ - role: actionCreatorRole, - strategy: address(STRATEGY), - target: address(POLICY), - value: 0, - data: abi.encodeCall(POLICY.initializeRole, (RoleDescription.wrap("Test Role"))), - description: description, - tokenHolder: tokenHolder, - nonce: 0 - }); - bytes32 digest = getCreateActionBySigTypedDataHash(_createAction); - (v, r, s) = vm.sign(privateKey, digest); - } - - function createActionBySig(uint8 v, bytes32 r, bytes32 s) internal returns (uint256 actionId) { - actionId = actionCreator.createActionBySig( - tokenHolder, - actionCreatorRole, - STRATEGY, - address(POLICY), - 0, - abi.encodeCall(POLICY.initializeRole, (RoleDescription.wrap("Test Role"))), - "", - v, - r, - s - ); - } - - function test_CreatesActionBySig() public { - (uint8 v, bytes32 r, bytes32 s) = createOffchainSignature(tokenHolderPrivateKey); - bytes memory data = abi.encodeCall(POLICY.initializeRole, (RoleDescription.wrap("Test Role"))); - - uint256 actionCount = CORE.actionsCount(); - - vm.expectEmit(); - emit ActionCreated(actionCount, tokenHolder, actionCreatorRole, STRATEGY, address(POLICY), 0, data, ""); - - uint256 actionId = createActionBySig(v, r, s); - Action memory action = CORE.getAction(actionId); - - assertEq(actionId, actionCount); - assertEq(CORE.actionsCount() - 1, actionCount); - assertEq(action.creationTime, block.timestamp); - } - - function test_CreatesActionBySigWithDescription() public { - (uint8 v, bytes32 r, bytes32 s) = - createOffchainSignatureWithDescription(tokenHolderPrivateKey, "# Action 0 \n This is my action."); - bytes memory data = abi.encodeCall(POLICY.initializeRole, (RoleDescription.wrap("Test Role"))); + // Mine block so that the ERC20 supply will be available when doing a past timestamp check at createAction. + mineBlock(); uint256 actionCount = CORE.actionsCount(); vm.expectEmit(); emit ActionCreated( - actionCount, - tokenHolder, - actionCreatorRole, - STRATEGY, - address(POLICY), - 0, - data, - "# Action 0 \n This is my action." + actionCount, address(tokenHolder1), erc20TokenholderActionCreatorRole, STRATEGY, address(POLICY), 0, data, "" ); - - uint256 actionId = actionCreator.createActionBySig( - tokenHolder, - actionCreatorRole, - STRATEGY, - address(POLICY), - 0, - abi.encodeCall(POLICY.initializeRole, (RoleDescription.wrap("Test Role"))), - "# Action 0 \n This is my action.", - v, - r, - s + vm.prank(tokenHolder1); + uint256 actionId = erc20TokenholderActionCreator.createAction( + erc20TokenholderActionCreatorRole, STRATEGY, address(POLICY), 0, data, "" ); - Action memory action = CORE.getAction(actionId); + Action memory action = CORE.getAction(actionId); assertEq(actionId, actionCount); - assertEq(CORE.actionsCount() - 1, actionCount); assertEq(action.creationTime, block.timestamp); } - - function test_CheckNonceIncrements() public { - (uint8 v, bytes32 r, bytes32 s) = createOffchainSignature(tokenHolderPrivateKey); - assertEq(actionCreator.nonces(tokenHolder, ILlamaCore.createActionBySig.selector), 0); - createActionBySig(v, r, s); - assertEq(actionCreator.nonces(tokenHolder, ILlamaCore.createActionBySig.selector), 1); - } - - function test_OperationCannotBeReplayed() public { - (uint8 v, bytes32 r, bytes32 s) = createOffchainSignature(tokenHolderPrivateKey); - createActionBySig(v, r, s); - // Invalid Signature error since the recovered signer address during the second call is not the same as - // policyholder since nonce has increased. - vm.expectRevert(ILlamaCore.InvalidSignature.selector); - createActionBySig(v, r, s); - } - - function test_RevertIf_SignerIsNotPolicyHolder() public { - (, uint256 randomSignerPrivateKey) = makeAddrAndKey("randomSigner"); - (uint8 v, bytes32 r, bytes32 s) = createOffchainSignature(randomSignerPrivateKey); - // Invalid Signature error since the recovered signer address is not the same as the policyholder passed in as - // parameter. - vm.expectRevert(ILlamaCore.InvalidSignature.selector); - createActionBySig(v, r, s); - } - - function test_RevertIf_SignerIsZeroAddress() public { - (uint8 v, bytes32 r, bytes32 s) = createOffchainSignature(tokenHolderPrivateKey); - // Invalid Signature error since the recovered signer address is zero address due to invalid signature values - // (v,r,s). - vm.expectRevert(ILlamaCore.InvalidSignature.selector); - createActionBySig((v + 1), r, s); - } - - function test_RevertIf_PolicyholderIncrementsNonce() public { - (uint8 v, bytes32 r, bytes32 s) = createOffchainSignature(tokenHolderPrivateKey); - - vm.prank(tokenHolder); - actionCreator.incrementNonce(ILlamaCore.createActionBySig.selector); - - // Invalid Signature error since the recovered signer address during the call is not the same as policyholder - // since nonce has increased. - vm.expectRevert(ILlamaCore.InvalidSignature.selector); - createActionBySig(v, r, s); - } } -contract CancelActionBySig is ERC20TokenholderActionCreatorTest, LlamaCoreSigUtils { - bytes data = abi.encodeCall(POLICY.initializeRole, (RoleDescription.wrap("Test Role"))); - uint256 actionId; - ERC20TokenholderActionCreator actionCreator; - ActionInfo actionInfo; - uint8 actionCreatorRole; - - function setUp() public virtual override { - ERC20TokenholderActionCreatorTest.setUp(); - mockErc20Votes.mint(address(tokenHolder), 1000); // we use mockErc20Votes because IVotesToken is an - // interface without the `delegate` function - vm.prank(tokenHolder); - mockErc20Votes.delegate(tokenHolder); // we use mockErc20Votes because IVotesToken is an interface without - // the `delegate` function - - actionCreator = new ERC20TokenholderActionCreator(token, ILlamaCore(address(CORE)), 1000); - - setDomainHash( - LlamaCoreSigUtils.EIP712Domain({ - name: CORE.name(), - version: "1", - chainId: block.chainid, - verifyingContract: address(actionCreator) - }) - ); - - vm.startPrank(address(EXECUTOR)); // init role, assign policy, and assign permission to setRoleHolder to the token - // voting action creator - POLICY.initializeRole(RoleDescription.wrap("Token Voting Action Creator Role")); - actionCreatorRole = 2; - POLICY.setRoleHolder(actionCreatorRole, address(actionCreator), DEFAULT_ROLE_QTY, DEFAULT_ROLE_EXPIRATION); - POLICY.setRolePermission( - actionCreatorRole, - ILlamaPolicy.PermissionData(address(POLICY), POLICY.initializeRole.selector, address(STRATEGY)), - true - ); - vm.stopPrank(); - - vm.roll(block.number + 1); - vm.warp(block.timestamp + 1); - - vm.expectEmit(); - emit ActionCreated(CORE.actionsCount(), tokenHolder, actionCreatorRole, STRATEGY, address(POLICY), 0, data, ""); - vm.prank(tokenHolder); - actionId = actionCreator.createAction(actionCreatorRole, STRATEGY, address(POLICY), 0, data, ""); - - actionInfo = ActionInfo(actionId, address(actionCreator), actionCreatorRole, STRATEGY, address(POLICY), 0, data); - - vm.roll(block.number + 1); - vm.warp(block.timestamp + 1); - } - - function createOffchainSignature(ActionInfo memory _actionInfo, uint256 privateKey) - internal - view - returns (uint8 v, bytes32 r, bytes32 s) - { - LlamaCoreSigUtils.CancelActionBySig memory cancelAction = LlamaCoreSigUtils.CancelActionBySig({ - tokenHolder: tokenHolder, - actionInfo: _actionInfo, - nonce: actionCreator.nonces(tokenHolder, ILlamaCore.cancelActionBySig.selector) - }); - bytes32 digest = getCancelActionBySigTypedDataHash(cancelAction); - (v, r, s) = vm.sign(privateKey, digest); - } - - function cancelActionBySig(ActionInfo memory _actionInfo, uint8 v, bytes32 r, bytes32 s) internal { - actionCreator.cancelActionBySig(tokenHolder, _actionInfo, v, r, s); - } - - function test_CancelActionBySig() public { - (uint8 v, bytes32 r, bytes32 s) = createOffchainSignature(actionInfo, tokenHolderPrivateKey); - - // vm.expectEmit(); - // emit ActionCanceled(actionInfo.id, tokenHolder); - - cancelActionBySig(actionInfo, v, r, s); - - uint256 state = uint256(CORE.getActionState(actionInfo)); - uint256 canceled = uint256(ActionState.Canceled); - assertEq(state, canceled); - } - - function test_CheckNonceIncrements() public { - (uint8 v, bytes32 r, bytes32 s) = createOffchainSignature(actionInfo, tokenHolderPrivateKey); - - assertEq(actionCreator.nonces(tokenHolder, ILlamaCore.cancelActionBySig.selector), 0); - cancelActionBySig(actionInfo, v, r, s); - assertEq(actionCreator.nonces(tokenHolder, ILlamaCore.cancelActionBySig.selector), 1); - } - - function test_OperationCannotBeReplayed() public { - (uint8 v, bytes32 r, bytes32 s) = createOffchainSignature(actionInfo, tokenHolderPrivateKey); - cancelActionBySig(actionInfo, v, r, s); - // Invalid Signature error since the recovered signer address during the second call is not the same as policyholder - // since nonce has increased. - vm.expectRevert(ILlamaCore.InvalidSignature.selector); - cancelActionBySig(actionInfo, v, r, s); - } - - function test_RevertIf_SignerIsNotTokenHolder() public { - (, uint256 randomSignerPrivateKey) = makeAddrAndKey("randomSigner"); - (uint8 v, bytes32 r, bytes32 s) = createOffchainSignature(actionInfo, randomSignerPrivateKey); - // Invalid Signature error since the recovered signer address is not the same as the policyholder passed in as - // parameter. - vm.expectRevert(ILlamaCore.InvalidSignature.selector); - cancelActionBySig(actionInfo, v, r, s); - } - - function test_RevertIf_SignerIsZeroAddress() public { - (uint8 v, bytes32 r, bytes32 s) = createOffchainSignature(actionInfo, tokenHolderPrivateKey); - // Invalid Signature error since the recovered signer address is zero address due to invalid signature values - // (v,r,s). - vm.expectRevert(ILlamaCore.InvalidSignature.selector); - cancelActionBySig(actionInfo, (v + 1), r, s); - } - - function test_RevertIf_PolicyholderIncrementsNonce() public { - (uint8 v, bytes32 r, bytes32 s) = createOffchainSignature(actionInfo, tokenHolderPrivateKey); - - vm.prank(tokenHolder); - actionCreator.incrementNonce(ILlamaCore.cancelActionBySig.selector); - - // Invalid Signature error since the recovered signer address during the call is not the same as policyholder - // since nonce has increased. - vm.expectRevert(ILlamaCore.InvalidSignature.selector); - cancelActionBySig(actionInfo, v, r, s); - } -} +// contract CancelAction is ERC20TokenholderActionCreatorTest { +// uint8 erc20TokenholderActionCreatorRole = 2; +// bytes data = abi.encodeCall( +// POLICY.setRoleHolder, (erc20TokenholderActionCreatorRole, address(0xdeadbeef), DEFAULT_ROLE_QTY, +// DEFAULT_ROLE_EXPIRATION) +// ); +// uint256 threshold = 500_000e18; +// uint256 actionId; +// ERC20TokenholderActionCreator erc20TokenholderActionCreator; +// ActionInfo actionInfo; + +// function setUp() public virtual override { +// ERC20TokenholderActionCreatorTest.setUp(); +// erc20VotesToken.mint(tokenHolder1, threshold); // we use erc20VotesToken because IVotesToken is an +// // interface without the `delegate` function +// vm.prank(tokenHolder1); +// erc20VotesToken.delegate(tokenHolder1); // we use erc20VotesToken because IVotesToken is an interface without +// // the `delegate` function + +// vm.roll(block.number + 1); +// vm.warp(block.timestamp + 1); + +// erc20TokenholderActionCreator = new ERC20TokenholderActionCreator(erc20VotesToken, CORE, threshold); + +// vm.startPrank(address(EXECUTOR)); // init role, assign policy, and assign permission to setRoleHolder to the +// // erc20VotesToken +// // voting action creator +// POLICY.initializeRole(RoleDescription.wrap("Token Voting Action Creator Role")); +// POLICY.setRoleHolder( +// erc20TokenholderActionCreatorRole, address(erc20TokenholderActionCreator), DEFAULT_ROLE_QTY, +// DEFAULT_ROLE_EXPIRATION +// ); +// POLICY.setRolePermission( +// erc20TokenholderActionCreatorRole, +// ILlamaPolicy.PermissionData(address(POLICY), POLICY.setRoleHolder.selector, address(STRATEGY)), +// true +// ); +// vm.stopPrank(); + +// vm.roll(block.number + 1); +// vm.warp(block.timestamp + 1); + +// vm.prank(tokenHolder1); +// actionId = erc20TokenholderActionCreator.createAction(erc20TokenholderActionCreatorRole, STRATEGY, +// address(POLICY), +// 0, data, ""); + +// actionInfo = +// ActionInfo(actionId, address(erc20TokenholderActionCreator), erc20TokenholderActionCreatorRole, STRATEGY, +// address(POLICY), 0, +// data); +// } + +// function test_PassesIf_CallerIsActionCreator() public { +// vm.expectEmit(); +// emit ActionCanceled(actionId, tokenHolder1); +// vm.prank(tokenHolder1); +// erc20TokenholderActionCreator.cancelAction(actionInfo); +// } + +// function test_RevertsIf_CallerIsNotActionCreator(address notCreator) public { +// vm.assume(notCreator != tokenHolder1); +// vm.expectRevert(TokenholderActionCreator.OnlyActionCreator.selector); +// vm.prank(notCreator); +// erc20TokenholderActionCreator.cancelAction(actionInfo); +// } +// } + +// contract SetActionThreshold is ERC20TokenholderActionCreatorTest { +// function test_SetsCreationThreshold() public { +// uint256 threshold = 500_000e18; +// erc20VotesToken.mint(address(this), 1_000_000e18); // we use erc20VotesToken because IVotesToken is an interface +// // without the `mint` function + +// vm.warp(block.timestamp + 1); + +// ERC20TokenholderActionCreator erc20TokenholderActionCreator = +// new ERC20TokenholderActionCreator(erc20VotesToken, CORE, 1_000_000e18); + +// assertEq(erc20TokenholderActionCreator.creationThreshold(), 1_000_000e18); + +// vm.expectEmit(); +// emit ActionThresholdSet(threshold); +// vm.prank(address(EXECUTOR)); +// erc20TokenholderActionCreator.setActionThreshold(threshold); +// } + +// function test_RevertsIf_CreationThresholdExceedsTotalSupply() public { +// uint256 threshold = 1_000_000e18; +// erc20VotesToken.mint(address(this), 500_000e18); // we use erc20VotesToken because IVotesToken is an interface +// // without the `mint` function + +// ERC20TokenholderActionCreator erc20TokenholderActionCreator = +// new ERC20TokenholderActionCreator(erc20VotesToken, CORE, 500_000e18); + +// vm.expectRevert(TokenholderActionCreator.InvalidCreationThreshold.selector); +// vm.prank(address(EXECUTOR)); +// erc20TokenholderActionCreator.setActionThreshold(threshold); +// } + +// function test_RevertsIf_CalledByNotLlamaExecutor(address notLlamaExecutor) public { +// vm.assume(notLlamaExecutor != address(EXECUTOR)); +// uint256 threshold = 500_000e18; +// erc20VotesToken.mint(address(this), 1_000_000e18); // we use erc20VotesToken because IVotesToken is an interface +// // without the `mint` function + +// ERC20TokenholderActionCreator erc20TokenholderActionCreator = +// new ERC20TokenholderActionCreator(erc20VotesToken, CORE, 1_000_000e18); + +// vm.expectRevert(TokenholderActionCreator.OnlyLlamaExecutor.selector); +// vm.prank(notLlamaExecutor); +// erc20TokenholderActionCreator.setActionThreshold(threshold); +// } +// } + +// contract CreateActionBySig is ERC20TokenholderActionCreatorTest, LlamaCoreSigUtils { +// ERC20TokenholderActionCreator erc20TokenholderActionCreator; +// uint8 erc20TokenholderActionCreatorRole; + +// function setUp() public virtual override { +// ERC20TokenholderActionCreatorTest.setUp(); +// erc20VotesToken.mint(tokenHolder, 1000); // we use erc20VotesToken because IVotesToken is an +// // interface without the `delegate` function +// vm.prank(tokenHolder); +// erc20VotesToken.delegate(tokenHolder); // we use erc20VotesToken because IVotesToken is an interface without +// // the `delegate` function + +// erc20TokenholderActionCreator = new ERC20TokenholderActionCreator(erc20VotesToken, CORE, 1000); + +// // Setting Mock Protocol Core's EIP-712 Domain Hash +// setDomainHash( +// LlamaCoreSigUtils.EIP712Domain({ +// name: CORE.name(), +// version: "1", +// chainId: block.chainid, +// verifyingContract: address(erc20TokenholderActionCreator) +// }) +// ); + +// vm.startPrank(address(EXECUTOR)); // init role, assign policy, and assign permission to setRoleHolder to the +// // erc20VotesToken +// // voting action creator +// POLICY.initializeRole(RoleDescription.wrap("Token Voting Action Creator Role")); +// erc20TokenholderActionCreatorRole = 2; +// POLICY.setRoleHolder( +// erc20TokenholderActionCreatorRole, address(erc20TokenholderActionCreator), DEFAULT_ROLE_QTY, +// DEFAULT_ROLE_EXPIRATION +// ); +// POLICY.setRolePermission( +// erc20TokenholderActionCreatorRole, +// ILlamaPolicy.PermissionData(address(POLICY), POLICY.initializeRole.selector, address(STRATEGY)), +// true +// ); +// vm.stopPrank(); + +// vm.roll(block.number + 1); +// vm.warp(block.timestamp + 1); +// } + +// function createOffchainSignature(uint256 privateKey) internal view returns (uint8 v, bytes32 r, bytes32 s) { +// (v, r, s) = createOffchainSignatureWithDescription(privateKey, ""); +// } + +// function createOffchainSignatureWithDescription(uint256 privateKey, string memory description) +// internal +// view +// returns (uint8 v, bytes32 r, bytes32 s) +// { +// LlamaCoreSigUtils.CreateActionBySig memory _createAction = LlamaCoreSigUtils.CreateActionBySig({ +// role: erc20TokenholderActionCreatorRole, +// strategy: address(STRATEGY), +// target: address(POLICY), +// value: 0, +// data: abi.encodeCall(POLICY.initializeRole, (RoleDescription.wrap("Test Role"))), +// description: description, +// tokenHolder: tokenHolder, +// nonce: 0 +// }); +// bytes32 digest = getCreateActionBySigTypedDataHash(_createAction); +// (v, r, s) = vm.sign(privateKey, digest); +// } + +// function createActionBySig(uint8 v, bytes32 r, bytes32 s) internal returns (uint256 actionId) { +// actionId = erc20TokenholderActionCreator.createActionBySig( +// tokenHolder, +// erc20TokenholderActionCreatorRole, +// STRATEGY, +// address(POLICY), +// 0, +// abi.encodeCall(POLICY.initializeRole, (RoleDescription.wrap("Test Role"))), +// "", +// v, +// r, +// s +// ); +// } + +// function test_CreatesActionBySig() public { +// (uint8 v, bytes32 r, bytes32 s) = createOffchainSignature(tokenHolderPrivateKey); +// bytes memory data = abi.encodeCall(POLICY.initializeRole, (RoleDescription.wrap("Test Role"))); + +// uint256 actionCount = CORE.actionsCount(); + +// vm.expectEmit(); +// emit ActionCreated(actionCount, tokenHolder, erc20TokenholderActionCreatorRole, STRATEGY, address(POLICY), 0, +// data, ""); + +// uint256 actionId = createActionBySig(v, r, s); +// Action memory action = CORE.getAction(actionId); + +// assertEq(actionId, actionCount); +// assertEq(CORE.actionsCount() - 1, actionCount); +// assertEq(action.creationTime, block.timestamp); +// } + +// function test_CreatesActionBySigWithDescription() public { +// (uint8 v, bytes32 r, bytes32 s) = +// createOffchainSignatureWithDescription(tokenHolderPrivateKey, "# Action 0 \n This is my action."); +// bytes memory data = abi.encodeCall(POLICY.initializeRole, (RoleDescription.wrap("Test Role"))); + +// uint256 actionCount = CORE.actionsCount(); + +// vm.expectEmit(); +// emit ActionCreated( +// actionCount, +// tokenHolder, +// erc20TokenholderActionCreatorRole, +// STRATEGY, +// address(POLICY), +// 0, +// data, +// "# Action 0 \n This is my action." +// ); + +// uint256 actionId = erc20TokenholderActionCreator.createActionBySig( +// tokenHolder, +// erc20TokenholderActionCreatorRole, +// STRATEGY, +// address(POLICY), +// 0, +// abi.encodeCall(POLICY.initializeRole, (RoleDescription.wrap("Test Role"))), +// "# Action 0 \n This is my action.", +// v, +// r, +// s +// ); +// Action memory action = CORE.getAction(actionId); + +// assertEq(actionId, actionCount); +// assertEq(CORE.actionsCount() - 1, actionCount); +// assertEq(action.creationTime, block.timestamp); +// } + +// function test_CheckNonceIncrements() public { +// (uint8 v, bytes32 r, bytes32 s) = createOffchainSignature(tokenHolderPrivateKey); +// assertEq(erc20TokenholderActionCreator.nonces(tokenHolder, ILlamaCore.createActionBySig.selector), 0); +// createActionBySig(v, r, s); +// assertEq(erc20TokenholderActionCreator.nonces(tokenHolder, ILlamaCore.createActionBySig.selector), 1); +// } + +// function test_OperationCannotBeReplayed() public { +// (uint8 v, bytes32 r, bytes32 s) = createOffchainSignature(tokenHolderPrivateKey); +// createActionBySig(v, r, s); +// // Invalid Signature error since the recovered signer address during the second call is not the same as +// // policyholder since nonce has increased. +// vm.expectRevert(ILlamaCore.InvalidSignature.selector); +// createActionBySig(v, r, s); +// } + +// function test_RevertIf_SignerIsNotPolicyHolder() public { +// (, uint256 randomSignerPrivateKey) = makeAddrAndKey("randomSigner"); +// (uint8 v, bytes32 r, bytes32 s) = createOffchainSignature(randomSignerPrivateKey); +// // Invalid Signature error since the recovered signer address is not the same as the policyholder passed in as +// // parameter. +// vm.expectRevert(ILlamaCore.InvalidSignature.selector); +// createActionBySig(v, r, s); +// } + +// function test_RevertIf_SignerIsZeroAddress() public { +// (uint8 v, bytes32 r, bytes32 s) = createOffchainSignature(tokenHolderPrivateKey); +// // Invalid Signature error since the recovered signer address is zero address due to invalid signature values +// // (v,r,s). +// vm.expectRevert(ILlamaCore.InvalidSignature.selector); +// createActionBySig((v + 1), r, s); +// } + +// function test_RevertIf_PolicyholderIncrementsNonce() public { +// (uint8 v, bytes32 r, bytes32 s) = createOffchainSignature(tokenHolderPrivateKey); + +// vm.prank(tokenHolder); +// erc20TokenholderActionCreator.incrementNonce(ILlamaCore.createActionBySig.selector); + +// // Invalid Signature error since the recovered signer address during the call is not the same as policyholder +// // since nonce has increased. +// vm.expectRevert(ILlamaCore.InvalidSignature.selector); +// createActionBySig(v, r, s); +// } +// } + +// contract CancelActionBySig is ERC20TokenholderActionCreatorTest, LlamaCoreSigUtils { +// bytes data = abi.encodeCall(POLICY.initializeRole, (RoleDescription.wrap("Test Role"))); +// uint256 actionId; +// ERC20TokenholderActionCreator erc20TokenholderActionCreator; +// ActionInfo actionInfo; +// uint8 erc20TokenholderActionCreatorRole; + +// function setUp() public virtual override { +// ERC20TokenholderActionCreatorTest.setUp(); +// erc20VotesToken.mint(address(tokenHolder), 1000); // we use erc20VotesToken because IVotesToken is an +// // interface without the `delegate` function +// vm.prank(tokenHolder); +// erc20VotesToken.delegate(tokenHolder); // we use erc20VotesToken because IVotesToken is an interface without +// // the `delegate` function + +// erc20TokenholderActionCreator = new ERC20TokenholderActionCreator(erc20VotesToken, ILlamaCore(address(CORE)), +// 1000); + +// setDomainHash( +// LlamaCoreSigUtils.EIP712Domain({ +// name: CORE.name(), +// version: "1", +// chainId: block.chainid, +// verifyingContract: address(erc20TokenholderActionCreator) +// }) +// ); + +// vm.startPrank(address(EXECUTOR)); // init role, assign policy, and assign permission to setRoleHolder to the +// // erc20VotesToken +// // voting action creator +// POLICY.initializeRole(RoleDescription.wrap("Token Voting Action Creator Role")); +// erc20TokenholderActionCreatorRole = 2; +// POLICY.setRoleHolder( +// erc20TokenholderActionCreatorRole, address(erc20TokenholderActionCreator), DEFAULT_ROLE_QTY, +// DEFAULT_ROLE_EXPIRATION +// ); +// POLICY.setRolePermission( +// erc20TokenholderActionCreatorRole, +// ILlamaPolicy.PermissionData(address(POLICY), POLICY.initializeRole.selector, address(STRATEGY)), +// true +// ); +// vm.stopPrank(); + +// vm.roll(block.number + 1); +// vm.warp(block.timestamp + 1); + +// vm.expectEmit(); +// emit ActionCreated(CORE.actionsCount(), tokenHolder, erc20TokenholderActionCreatorRole, STRATEGY, +// address(POLICY), +// 0, data, ""); +// vm.prank(tokenHolder); +// actionId = erc20TokenholderActionCreator.createAction(erc20TokenholderActionCreatorRole, STRATEGY, +// address(POLICY), +// 0, data, ""); + +// actionInfo = +// ActionInfo(actionId, address(erc20TokenholderActionCreator), erc20TokenholderActionCreatorRole, STRATEGY, +// address(POLICY), 0, +// data); + +// vm.roll(block.number + 1); +// vm.warp(block.timestamp + 1); +// } + +// function createOffchainSignature(ActionInfo memory _actionInfo, uint256 privateKey) +// internal +// view +// returns (uint8 v, bytes32 r, bytes32 s) +// { +// LlamaCoreSigUtils.CancelActionBySig memory cancelAction = LlamaCoreSigUtils.CancelActionBySig({ +// tokenHolder: tokenHolder, +// actionInfo: _actionInfo, +// nonce: erc20TokenholderActionCreator.nonces(tokenHolder, ILlamaCore.cancelActionBySig.selector) +// }); +// bytes32 digest = getCancelActionBySigTypedDataHash(cancelAction); +// (v, r, s) = vm.sign(privateKey, digest); +// } + +// function cancelActionBySig(ActionInfo memory _actionInfo, uint8 v, bytes32 r, bytes32 s) internal { +// erc20TokenholderActionCreator.cancelActionBySig(tokenHolder, _actionInfo, v, r, s); +// } + +// function test_CancelActionBySig() public { +// (uint8 v, bytes32 r, bytes32 s) = createOffchainSignature(actionInfo, tokenHolderPrivateKey); + +// // vm.expectEmit(); +// // emit ActionCanceled(actionInfo.id, tokenHolder); + +// cancelActionBySig(actionInfo, v, r, s); + +// uint256 state = uint256(CORE.getActionState(actionInfo)); +// uint256 canceled = uint256(ActionState.Canceled); +// assertEq(state, canceled); +// } + +// function test_CheckNonceIncrements() public { +// (uint8 v, bytes32 r, bytes32 s) = createOffchainSignature(actionInfo, tokenHolderPrivateKey); + +// assertEq(erc20TokenholderActionCreator.nonces(tokenHolder, ILlamaCore.cancelActionBySig.selector), 0); +// cancelActionBySig(actionInfo, v, r, s); +// assertEq(erc20TokenholderActionCreator.nonces(tokenHolder, ILlamaCore.cancelActionBySig.selector), 1); +// } + +// function test_OperationCannotBeReplayed() public { +// (uint8 v, bytes32 r, bytes32 s) = createOffchainSignature(actionInfo, tokenHolderPrivateKey); +// cancelActionBySig(actionInfo, v, r, s); +// // Invalid Signature error since the recovered signer address during the second call is not the same as +// policyholder +// // since nonce has increased. +// vm.expectRevert(ILlamaCore.InvalidSignature.selector); +// cancelActionBySig(actionInfo, v, r, s); +// } + +// function test_RevertIf_SignerIsNotTokenHolder() public { +// (, uint256 randomSignerPrivateKey) = makeAddrAndKey("randomSigner"); +// (uint8 v, bytes32 r, bytes32 s) = createOffchainSignature(actionInfo, randomSignerPrivateKey); +// // Invalid Signature error since the recovered signer address is not the same as the policyholder passed in as +// // parameter. +// vm.expectRevert(ILlamaCore.InvalidSignature.selector); +// cancelActionBySig(actionInfo, v, r, s); +// } + +// function test_RevertIf_SignerIsZeroAddress() public { +// (uint8 v, bytes32 r, bytes32 s) = createOffchainSignature(actionInfo, tokenHolderPrivateKey); +// // Invalid Signature error since the recovered signer address is zero address due to invalid signature values +// // (v,r,s). +// vm.expectRevert(ILlamaCore.InvalidSignature.selector); +// cancelActionBySig(actionInfo, (v + 1), r, s); +// } + +// function test_RevertIf_PolicyholderIncrementsNonce() public { +// (uint8 v, bytes32 r, bytes32 s) = createOffchainSignature(actionInfo, tokenHolderPrivateKey); + +// vm.prank(tokenHolder); +// erc20TokenholderActionCreator.incrementNonce(ILlamaCore.cancelActionBySig.selector); + +// // Invalid Signature error since the recovered signer address during the call is not the same as policyholder +// // since nonce has increased. +// vm.expectRevert(ILlamaCore.InvalidSignature.selector); +// cancelActionBySig(actionInfo, v, r, s); +// } +// } diff --git a/test/token-voting/LlamaTokenVotingTestSetup.sol b/test/token-voting/LlamaTokenVotingTestSetup.sol index b068dbc..e35c6fb 100644 --- a/test/token-voting/LlamaTokenVotingTestSetup.sol +++ b/test/token-voting/LlamaTokenVotingTestSetup.sol @@ -10,6 +10,7 @@ import {LlamaPeripheryTestSetup} from "test/LlamaPeripheryTestSetup.sol"; import {DeployLlamaTokenVotingFactory} from "script/DeployLlamaTokenVotingFactory.s.sol"; +import {RoleDescription} from "src/lib/UDVTs.sol"; import {ERC20TokenholderActionCreator} from "src/token-voting/ERC20TokenholderActionCreator.sol"; import {ERC20TokenholderCaster} from "src/token-voting/ERC20TokenholderCaster.sol"; import {ERC721TokenholderActionCreator} from "src/token-voting/ERC721TokenholderActionCreator.sol"; @@ -26,6 +27,11 @@ contract LlamaTokenVotingTestSetup is LlamaPeripheryTestSetup, DeployLlamaTokenV uint256 public constant ERC721_MIN_APPROVAL_PCT = 1; uint256 public constant ERC721_MIN_DISAPPROVAL_PCT = 1; + // Token Voting Role Descriptions. + RoleDescription public constant TOKEN_VOTING_ACTION_CREATOR_ROLE_DESC = + RoleDescription.wrap("Token Voting Action Creator Role"); + RoleDescription public constant TOKEN_VOTING_CASTER_ROLE_DESC = RoleDescription.wrap("Token Voting Caster Role"); + // Votes Tokens MockERC20Votes public erc20VotesToken; MockERC721Votes public erc721VotesToken; From ee717410950fcd9e3fe2d15d7fce61dff6fdde0f Mon Sep 17 00:00:00 2001 From: dd0sxx Date: Fri, 8 Dec 2023 12:11:08 -0500 Subject: [PATCH 20/25] decoupled executor from token voting factory --- src/token-voting/LlamaTokenVotingFactory.sol | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/token-voting/LlamaTokenVotingFactory.sol b/src/token-voting/LlamaTokenVotingFactory.sol index 7c30247..f766c23 100644 --- a/src/token-voting/LlamaTokenVotingFactory.sol +++ b/src/token-voting/LlamaTokenVotingFactory.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.23; import {Clones} from "@openzeppelin/proxy/Clones.sol"; import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; -import {ILlamaExecutor} from "src/interfaces/ILlamaExecutor.sol"; import {ERC20TokenholderActionCreator} from "src/token-voting/ERC20TokenholderActionCreator.sol"; import {ERC20TokenholderCaster} from "src/token-voting/ERC20TokenholderCaster.sol"; import {ERC20Votes} from "@openzeppelin/token/ERC20/extensions/ERC20Votes.sol"; @@ -65,19 +64,21 @@ contract LlamaTokenVotingFactory { ///@param minDisapprovalPct The minimum percentage of tokens required to disapprove an action (set to 0 if not /// deploying caster). function deployTokenVotingModule( + ILlamaCore llamaCore, address token, bool isERC20, uint256 creationThreshold, uint256 minApprovalPct, uint256 minDisapprovalPct ) external returns (address actionCreator, address caster) { - ILlamaCore core = ILlamaCore(ILlamaExecutor(msg.sender).LLAMA_CORE()); if (isERC20) { - actionCreator = address(_deployERC20TokenholderActionCreator(ERC20Votes(token), core, creationThreshold)); - caster = address(_deployERC20TokenholderCaster(ERC20Votes(token), core, 0, minApprovalPct, minDisapprovalPct)); + actionCreator = address(_deployERC20TokenholderActionCreator(ERC20Votes(token), llamaCore, creationThreshold)); + caster = + address(_deployERC20TokenholderCaster(ERC20Votes(token), llamaCore, 0, minApprovalPct, minDisapprovalPct)); } else { - actionCreator = address(_deployERC721TokenholderActionCreator(ERC721Votes(token), core, creationThreshold)); - caster = address(_deployERC721TokenholderCaster(ERC721Votes(token), core, 0, minApprovalPct, minDisapprovalPct)); + actionCreator = address(_deployERC721TokenholderActionCreator(ERC721Votes(token), llamaCore, creationThreshold)); + caster = + address(_deployERC721TokenholderCaster(ERC721Votes(token), llamaCore, 0, minApprovalPct, minDisapprovalPct)); } } From 0c2a573cfa6814d761906d34fb4190306d82e565 Mon Sep 17 00:00:00 2001 From: dd0sxx Date: Sat, 9 Dec 2023 13:25:12 -0500 Subject: [PATCH 21/25] tests are passing --- src/token-voting/LlamaTokenVotingFactory.sol | 20 +++++++++++-------- .../LlamaTokenVotingFactory.t.sol | 2 ++ .../LlamaTokenVotingTestSetup.sol | 2 ++ 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/token-voting/LlamaTokenVotingFactory.sol b/src/token-voting/LlamaTokenVotingFactory.sol index 0bb9b2b..7ab8054 100644 --- a/src/token-voting/LlamaTokenVotingFactory.sol +++ b/src/token-voting/LlamaTokenVotingFactory.sol @@ -76,15 +76,19 @@ contract LlamaTokenVotingFactory { uint256 minDisapprovalPct ) external returns (address actionCreator, address caster) { if (isERC20) { - actionCreator = - address(_deployERC20TokenholderActionCreator(ERC20Votes(token), core, actionCreatorRole, creationThreshold)); - caster = - address(_deployERC20TokenholderCaster(ERC20Votes(token), core, casterRole, minApprovalPct, minDisapprovalPct)); + actionCreator = address( + _deployERC20TokenholderActionCreator(ERC20Votes(token), llamaCore, actionCreatorRole, creationThreshold) + ); + caster = address( + _deployERC20TokenholderCaster(ERC20Votes(token), llamaCore, casterRole, minApprovalPct, minDisapprovalPct) + ); } else { - actionCreator = - address(_deployERC721TokenholderActionCreator(ERC721Votes(token), core, actionCreatorRole, creationThreshold)); - caster = - address(_deployERC721TokenholderCaster(ERC721Votes(token), core, casterRole, minApprovalPct, minDisapprovalPct)); + actionCreator = address( + _deployERC721TokenholderActionCreator(ERC721Votes(token), llamaCore, actionCreatorRole, creationThreshold) + ); + caster = address( + _deployERC721TokenholderCaster(ERC721Votes(token), llamaCore, casterRole, minApprovalPct, minDisapprovalPct) + ); } } diff --git a/test/token-voting/LlamaTokenVotingFactory.t.sol b/test/token-voting/LlamaTokenVotingFactory.t.sol index 75a6811..0a1b13c 100644 --- a/test/token-voting/LlamaTokenVotingFactory.t.sol +++ b/test/token-voting/LlamaTokenVotingFactory.t.sol @@ -89,6 +89,7 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { // Set up action to call `deployTokenVotingModule` with the ERC20 token. bytes memory data = abi.encodeWithSelector( LlamaTokenVotingFactory.deployTokenVotingModule.selector, + CORE, address(erc20VotesToken), true, tokenVotingActionCreatorRole, @@ -141,6 +142,7 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { // Set up action to call `deployTokenVotingModule` with the ERC721 token. bytes memory data = abi.encodeWithSelector( LlamaTokenVotingFactory.deployTokenVotingModule.selector, + CORE, address(erc721VotesToken), false, tokenVotingActionCreatorRole, diff --git a/test/token-voting/LlamaTokenVotingTestSetup.sol b/test/token-voting/LlamaTokenVotingTestSetup.sol index ae0cf7c..27b6c90 100644 --- a/test/token-voting/LlamaTokenVotingTestSetup.sol +++ b/test/token-voting/LlamaTokenVotingTestSetup.sol @@ -96,6 +96,7 @@ contract LlamaTokenVotingTestSetup is LlamaPeripheryTestSetup, DeployLlamaTokenV vm.startPrank(address(EXECUTOR)); // Deploy Token Voting Module (address erc20TokenholderActionCreator, address erc20TokenholderCaster) = tokenVotingFactory.deployTokenVotingModule( + CORE, address(erc20VotesToken), true, tokenVotingActionCreatorRole, @@ -123,6 +124,7 @@ contract LlamaTokenVotingTestSetup is LlamaPeripheryTestSetup, DeployLlamaTokenV // Deploy Token Voting Module (address erc721TokenholderActionCreator, address erc721TokenholderCaster) = tokenVotingFactory .deployTokenVotingModule( + CORE, address(erc721VotesToken), false, tokenVotingActionCreatorRole, From 91baf8f305d449defc75eb2440740d97d412af44 Mon Sep 17 00:00:00 2001 From: dd0sxx Date: Sat, 9 Dec 2023 13:27:10 -0500 Subject: [PATCH 22/25] fixed diff --- test/token-voting/ERC20TokenholderActionCreator.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/token-voting/ERC20TokenholderActionCreator.t.sol b/test/token-voting/ERC20TokenholderActionCreator.t.sol index 199a6b6..3e4a0db 100644 --- a/test/token-voting/ERC20TokenholderActionCreator.t.sol +++ b/test/token-voting/ERC20TokenholderActionCreator.t.sol @@ -241,9 +241,9 @@ contract CreateActionBySig is ERC20TokenholderActionCreatorTest { r, s ); - Action memory action = CORE.getAction(actionId); assertEq(actionId, actionCount); + assertEq(CORE.actionsCount() - 1, actionCount); assertEq(action.creationTime, block.timestamp); } From 6ba20079277ffb22f2bbd114008da0ede9486c85 Mon Sep 17 00:00:00 2001 From: dd0sxx Date: Sat, 9 Dec 2023 13:27:48 -0500 Subject: [PATCH 23/25] diff cleanup --- test/token-voting/ERC721TokenholderCaster.t.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/test/token-voting/ERC721TokenholderCaster.t.sol b/test/token-voting/ERC721TokenholderCaster.t.sol index 3d8eb01..40c7d1a 100644 --- a/test/token-voting/ERC721TokenholderCaster.t.sol +++ b/test/token-voting/ERC721TokenholderCaster.t.sol @@ -38,7 +38,6 @@ contract ERC721TokenholderCasterTest is LlamaTokenVotingTestSetup, LlamaCoreSigU // Mint tokens to tokenholders so that there is an existing supply erc721VotesToken.mint(tokenHolder1, 0); - vm.prank(tokenHolder1); erc721VotesToken.delegate(tokenHolder1); From 48f90a830f868e8d03f40f7afedf102e1aecf023 Mon Sep 17 00:00:00 2001 From: dd0sxx Date: Sat, 9 Dec 2023 13:28:28 -0500 Subject: [PATCH 24/25] diff correction --- test/token-voting/ERC20TokenholderActionCreator.t.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/test/token-voting/ERC20TokenholderActionCreator.t.sol b/test/token-voting/ERC20TokenholderActionCreator.t.sol index 3e4a0db..d0f5a70 100644 --- a/test/token-voting/ERC20TokenholderActionCreator.t.sol +++ b/test/token-voting/ERC20TokenholderActionCreator.t.sol @@ -242,6 +242,7 @@ contract CreateActionBySig is ERC20TokenholderActionCreatorTest { s ); Action memory action = CORE.getAction(actionId); + assertEq(actionId, actionCount); assertEq(CORE.actionsCount() - 1, actionCount); assertEq(action.creationTime, block.timestamp); From 4ed9a295cc5ac6d22b93b46e5024ae81d2c97567 Mon Sep 17 00:00:00 2001 From: dd0sxx Date: Sat, 9 Dec 2023 16:44:28 -0500 Subject: [PATCH 25/25] test passes --- .../LlamaTokenVotingFactory.t.sol | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/test/token-voting/LlamaTokenVotingFactory.t.sol b/test/token-voting/LlamaTokenVotingFactory.t.sol index 0a1b13c..619b95d 100644 --- a/test/token-voting/LlamaTokenVotingFactory.t.sol +++ b/test/token-voting/LlamaTokenVotingFactory.t.sol @@ -190,4 +190,56 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { assertEq(erc721TokenholderCaster.minApprovalPct(), ERC721_MIN_APPROVAL_PCT); assertEq(erc721TokenholderCaster.minDisapprovalPct(), ERC721_MIN_DISAPPROVAL_PCT); } + + function test_CanBeDeployedByAnyone(address randomCaller) public { + vm.assume(randomCaller != address(0)); + vm.deal(randomCaller, 1 ether); + + ERC20TokenholderActionCreator erc20TokenholderActionCreator = ERC20TokenholderActionCreator( + Clones.predictDeterministicAddress( + address(erc20TokenholderActionCreatorLogic), + keccak256(abi.encodePacked(address(erc20VotesToken), randomCaller)), // salt + address(tokenVotingFactory) // deployer + ) + ); + + ERC20TokenholderCaster erc20TokenholderCaster = ERC20TokenholderCaster( + Clones.predictDeterministicAddress( + address(erc20TokenholderCasterLogic), + keccak256(abi.encodePacked(address(erc20VotesToken), randomCaller)), // salt + address(tokenVotingFactory) // deployer + ) + ); + + vm.expectEmit(); + emit ActionThresholdSet(ERC20_CREATION_THRESHOLD); + vm.expectEmit(); + emit ERC20TokenholderActionCreatorCreated(address(erc20TokenholderActionCreator), address(erc20VotesToken)); + vm.expectEmit(); + emit ERC20TokenholderCasterCreated( + address(erc20TokenholderCaster), address(erc20VotesToken), ERC20_MIN_APPROVAL_PCT, ERC20_MIN_DISAPPROVAL_PCT + ); + + vm.prank(randomCaller); + tokenVotingFactory.deployTokenVotingModule( + CORE, + address(erc20VotesToken), + true, + tokenVotingActionCreatorRole, + tokenVotingCasterRole, + ERC20_CREATION_THRESHOLD, + ERC20_MIN_APPROVAL_PCT, + ERC20_MIN_DISAPPROVAL_PCT + ); + + assertEq(address(erc20TokenholderActionCreator.token()), address(erc20VotesToken)); + assertEq(address(erc20TokenholderActionCreator.llamaCore()), address(CORE)); + assertEq(erc20TokenholderActionCreator.role(), tokenVotingActionCreatorRole); + assertEq(erc20TokenholderActionCreator.creationThreshold(), ERC20_CREATION_THRESHOLD); + assertEq(address(erc20TokenholderCaster.token()), address(erc20VotesToken)); + assertEq(address(erc20TokenholderCaster.llamaCore()), address(CORE)); + assertEq(erc20TokenholderCaster.role(), tokenVotingCasterRole); + assertEq(erc20TokenholderCaster.minApprovalPct(), ERC20_MIN_APPROVAL_PCT); + assertEq(erc20TokenholderCaster.minDisapprovalPct(), ERC20_MIN_DISAPPROVAL_PCT); + } }