diff --git a/lib/forge-std b/lib/forge-std new file mode 160000 index 0000000..2f11269 --- /dev/null +++ b/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 2f112697506eab12d433a65fdc31a639548fe365 diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts new file mode 160000 index 0000000..932fddf --- /dev/null +++ b/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit 932fddf69a699a9a80fd2396fd1a2ab91cdda123 diff --git a/src/interfaces/ILlamaAccount.sol b/src/interfaces/ILlamaAccount.sol index 47116a2..c9d059a 100644 --- a/src/interfaces/ILlamaAccount.sol +++ b/src/interfaces/ILlamaAccount.sol @@ -5,19 +5,19 @@ pragma solidity ^0.8.23; /// @author Llama (devsdosomething@llama.xyz) /// @notice This is the interface for Llama accounts which can be used to hold assets for a Llama instance. interface ILlamaAccount { - // -------- For Inspection -------- + // -------- For Inspection -------- - /// @notice Returns the address of the Llama instance's executor. - function llamaExecutor() external view returns (address); + /// @notice Returns the address of the Llama instance's executor. + function llamaExecutor() external view returns (address); - // -------- At Account Creation -------- + // -------- At Account Creation -------- - /// @notice Initializes a new clone of the account. - /// @dev This function is called by the `_deployAccounts` function in the `LlamaCore` contract. The `initializer` - /// modifier ensures that this function can be invoked at most once. - /// @param config The account configuration, encoded as bytes to support differing constructor arguments in - /// different account logic contracts. - /// @return This return statement must be hardcoded to `true` to ensure that initializing an EOA - /// (like the zero address) will revert. - function initialize(bytes memory config) external returns (bool); + /// @notice Initializes a new clone of the account. + /// @dev This function is called by the `_deployAccounts` function in the `LlamaCore` contract. The `initializer` + /// modifier ensures that this function can be invoked at most once. + /// @param config The account configuration, encoded as bytes to support differing constructor arguments in + /// different account logic contracts. + /// @return This return statement must be hardcoded to `true` to ensure that initializing an EOA + /// (like the zero address) will revert. + function initialize(bytes memory config) external returns (bool); } diff --git a/src/interfaces/ILlamaActionGuard.sol b/src/interfaces/ILlamaActionGuard.sol index 3f91eaa..c648f1c 100644 --- a/src/interfaces/ILlamaActionGuard.sol +++ b/src/interfaces/ILlamaActionGuard.sol @@ -16,17 +16,17 @@ import {ActionInfo} from "src/lib/Structs.sol"; /// - Verify the USD value of an account has not decreased by more than a certain amount during /// execution, i.e. between `validatePreActionExecution` and `validatePostActionExecution`. interface ILlamaActionGuard { - /// @notice Reverts if action creation is not allowed. - /// @param actionInfo Data required to create an action. - function validateActionCreation(ActionInfo calldata actionInfo) external; + /// @notice Reverts if action creation is not allowed. + /// @param actionInfo Data required to create an action. + function validateActionCreation(ActionInfo calldata actionInfo) external; - /// @notice Called immediately before action execution, and reverts if the action is not allowed - /// to be executed. - /// @param actionInfo Data required to create an action. - function validatePreActionExecution(ActionInfo calldata actionInfo) external; + /// @notice Called immediately before action execution, and reverts if the action is not allowed + /// to be executed. + /// @param actionInfo Data required to create an action. + function validatePreActionExecution(ActionInfo calldata actionInfo) external; - /// @notice Called immediately after action execution, and reverts if the just-executed - /// action should not have been allowed to execute. - /// @param actionInfo Data required to create an action. - function validatePostActionExecution(ActionInfo calldata actionInfo) external; + /// @notice Called immediately after action execution, and reverts if the just-executed + /// action should not have been allowed to execute. + /// @param actionInfo Data required to create an action. + function validatePostActionExecution(ActionInfo calldata actionInfo) external; } diff --git a/src/interfaces/ILlamaCore.sol b/src/interfaces/ILlamaCore.sol index 481c552..5aeb62f 100644 --- a/src/interfaces/ILlamaCore.sol +++ b/src/interfaces/ILlamaCore.sol @@ -7,87 +7,85 @@ pragma solidity ^0.8.23; import {ILlamaPolicy} from "src/interfaces/ILlamaPolicy.sol"; import {ILlamaStrategy} from "src/interfaces/ILlamaStrategy.sol"; import { - Action, - ActionInfo, - LlamaInstanceConfig, - LlamaPolicyConfig, - PermissionData, - RoleHolderData, - RolePermissionData + Action, + ActionInfo, + LlamaInstanceConfig, + LlamaPolicyConfig, + PermissionData, + RoleHolderData, + RolePermissionData } from "src/lib/Structs.sol"; /// @title LlamaCore Interface /// @author Llama (devsdosomething@llama.xyz) /// @notice This is the interface for LlamaCore. interface ILlamaCore { - function actionGuard(address target, bytes4 selector) external view returns (address guard); - function actionsCount() external view returns (uint256); - function approvals(uint256 actionId, address policyholder) external view returns (bool hasApproved); - function authorizedAccountLogics(address accountLogic) external view returns (bool isAuthorized); - function authorizedScripts(address script) external view returns (bool isAuthorized); - function authorizedStrategyLogics(ILlamaStrategy strategyLogic) external view returns (bool isAuthorized); - function cancelAction(ActionInfo memory actionInfo) external; - function cancelActionBySig(address policyholder, ActionInfo memory actionInfo, uint8 v, bytes32 r, bytes32 s) - external; - function castApproval(uint8 role, ActionInfo memory actionInfo, string memory reason) external returns (uint96); - function castApprovalBySig( - address policyholder, - uint8 role, - ActionInfo memory actionInfo, - string memory reason, - uint8 v, - bytes32 r, - bytes32 s - ) external returns (uint96); - function castDisapproval(uint8 role, ActionInfo memory actionInfo, string memory reason) - external - returns (uint96); - function castDisapprovalBySig( - address policyholder, - uint8 role, - ActionInfo memory actionInfo, - string memory reason, - uint8 v, - bytes32 r, - bytes32 s - ) external returns (uint96); - function createAccounts(address llamaAccountLogic, bytes[] memory accountConfigs) external; - function createAction( - uint8 role, - ILlamaStrategy strategy, - address target, - uint256 value, - bytes memory data, - string memory description - ) external returns (uint256 actionId); - function createActionBySig( - address policyholder, - uint8 role, - ILlamaStrategy strategy, - address target, - uint256 value, - bytes memory data, - string memory description, - uint8 v, - bytes32 r, - bytes32 s - ) external returns (uint256 actionId); - function createStrategies(address llamaStrategyLogic, bytes[] memory strategyConfigs) external; - function disapprovals(uint256 actionId, address policyholder) external view returns (bool hasDisapproved); - function executeAction(ActionInfo memory actionInfo) external payable; - function executor() external view returns (address); - function getAction(uint256 actionId) external view returns (Action memory); - function getActionState(ActionInfo memory actionInfo) external view returns (uint8); - function incrementNonce(bytes4 selector) external; - function initialize(LlamaInstanceConfig memory config, address policyLogic, address policyMetadataLogic) external; - function name() external view returns (string memory); - function nonces(address policyholder, bytes4 selector) external view returns (uint256 currentNonce); - function policy() external view returns (ILlamaPolicy); - function queueAction(ActionInfo memory actionInfo) external; - function setAccountLogicAuthorization(address accountLogic, bool authorized) external; - function setGuard(address target, bytes4 selector, address guard) external; - function setScriptAuthorization(address script, bool authorized) external; - function setStrategyAuthorization(ILlamaStrategy strategy, bool authorized) external; - function setStrategyLogicAuthorization(ILlamaStrategy strategyLogic, bool authorized) external; - function strategies(ILlamaStrategy strategy) external view returns (bool deployed, bool authorized); + function actionGuard(address target, bytes4 selector) external view returns (address guard); + function actionsCount() external view returns (uint256); + function approvals(uint256 actionId, address policyholder) external view returns (bool hasApproved); + function authorizedAccountLogics(address accountLogic) external view returns (bool isAuthorized); + function authorizedScripts(address script) external view returns (bool isAuthorized); + function authorizedStrategyLogics(ILlamaStrategy strategyLogic) external view returns (bool isAuthorized); + function cancelAction(ActionInfo memory actionInfo) external; + function cancelActionBySig(address policyholder, ActionInfo memory actionInfo, uint8 v, bytes32 r, bytes32 s) + external; + function castApproval(uint8 role, ActionInfo memory actionInfo, string memory reason) external returns (uint96); + function castApprovalBySig( + address policyholder, + uint8 role, + ActionInfo memory actionInfo, + string memory reason, + uint8 v, + bytes32 r, + bytes32 s + ) external returns (uint96); + function castDisapproval(uint8 role, ActionInfo memory actionInfo, string memory reason) external returns (uint96); + function castDisapprovalBySig( + address policyholder, + uint8 role, + ActionInfo memory actionInfo, + string memory reason, + uint8 v, + bytes32 r, + bytes32 s + ) external returns (uint96); + function createAccounts(address llamaAccountLogic, bytes[] memory accountConfigs) external; + function createAction( + uint8 role, + ILlamaStrategy strategy, + address target, + uint256 value, + bytes memory data, + string memory description + ) external returns (uint256 actionId); + function createActionBySig( + address policyholder, + uint8 role, + ILlamaStrategy strategy, + address target, + uint256 value, + bytes memory data, + string memory description, + uint8 v, + bytes32 r, + bytes32 s + ) external returns (uint256 actionId); + function createStrategies(address llamaStrategyLogic, bytes[] memory strategyConfigs) external; + function disapprovals(uint256 actionId, address policyholder) external view returns (bool hasDisapproved); + function executeAction(ActionInfo memory actionInfo) external payable; + function executor() external view returns (address); + function getAction(uint256 actionId) external view returns (Action memory); + function getActionState(ActionInfo memory actionInfo) external view returns (uint8); + function incrementNonce(bytes4 selector) external; + function initialize(LlamaInstanceConfig memory config, address policyLogic, address policyMetadataLogic) external; + function name() external view returns (string memory); + function nonces(address policyholder, bytes4 selector) external view returns (uint256 currentNonce); + function policy() external view returns (ILlamaPolicy); + function queueAction(ActionInfo memory actionInfo) external; + function setAccountLogicAuthorization(address accountLogic, bool authorized) external; + function setGuard(address target, bytes4 selector, address guard) external; + function setScriptAuthorization(address script, bool authorized) external; + function setStrategyAuthorization(ILlamaStrategy strategy, bool authorized) external; + function setStrategyLogicAuthorization(ILlamaStrategy strategyLogic, bool authorized) external; + function strategies(ILlamaStrategy strategy) external view returns (bool deployed, bool authorized); } diff --git a/src/interfaces/ILlamaExecutor.sol b/src/interfaces/ILlamaExecutor.sol index 38ee182..6fe5fae 100644 --- a/src/interfaces/ILlamaExecutor.sol +++ b/src/interfaces/ILlamaExecutor.sol @@ -5,9 +5,9 @@ pragma solidity ^0.8.23; /// @author Llama (devsdosomething@llama.xyz) /// @notice This is the interface for LlamaExecutor. interface ILlamaExecutor { - function LLAMA_CORE() external view returns (address); - function execute(address target, bool isScript, bytes calldata data) - external - payable - returns (bool success, bytes memory result); + function LLAMA_CORE() external view returns (address); + function execute(address target, bool isScript, bytes calldata data) + external + payable + returns (bool success, bytes memory result); } diff --git a/src/interfaces/ILlamaPolicy.sol b/src/interfaces/ILlamaPolicy.sol index 3380428..491eab1 100644 --- a/src/interfaces/ILlamaPolicy.sol +++ b/src/interfaces/ILlamaPolicy.sol @@ -9,112 +9,109 @@ import {RoleDescription} from "../lib/UDVTs.sol"; /// @author Llama (devsdosomething@llama.xyz) /// @notice This is the interface for LlamaPolicy. interface ILlamaPolicy { - event Approval(address indexed owner, address indexed spender, uint256 indexed id); - event ApprovalForAll(address indexed owner, address indexed operator, bool approved); - event ExpiredRoleRevoked(address indexed caller, address indexed policyholder, uint8 indexed role); - event Initialized(uint8 version); - event PolicyMetadataSet(address policyMetadata, address indexed policyMetadataLogic, bytes initializationData); - event RoleAssigned(address indexed policyholder, uint8 indexed role, uint64 expiration, uint96 quantity); - event RoleInitialized(uint8 indexed role, bytes32 description); - event RolePermissionAssigned( - uint8 indexed role, bytes32 indexed permissionId, PermissionData permissionData, bool hasPermission - ); - event Transfer(address indexed from, address indexed to, uint256 indexed id); + event Approval(address indexed owner, address indexed spender, uint256 indexed id); + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + event ExpiredRoleRevoked(address indexed caller, address indexed policyholder, uint8 indexed role); + event Initialized(uint8 version); + event PolicyMetadataSet(address policyMetadata, address indexed policyMetadataLogic, bytes initializationData); + event RoleAssigned(address indexed policyholder, uint8 indexed role, uint64 expiration, uint96 quantity); + event RoleInitialized(uint8 indexed role, bytes32 description); + event RolePermissionAssigned( + uint8 indexed role, bytes32 indexed permissionId, PermissionData permissionData, bool hasPermission + ); + event Transfer(address indexed from, address indexed to, uint256 indexed id); - struct LlamaPolicyConfig { - RoleDescription[] roleDescriptions; - RoleHolderData[] roleHolders; - RolePermissionData[] rolePermissions; - string color; - string logo; - } + struct LlamaPolicyConfig { + RoleDescription[] roleDescriptions; + RoleHolderData[] roleHolders; + RolePermissionData[] rolePermissions; + string color; + string logo; + } - struct PermissionData { - address target; - bytes4 selector; - address strategy; - } + struct PermissionData { + address target; + bytes4 selector; + address strategy; + } - struct Checkpoint { - uint64 timestamp; - uint64 expiration; - uint96 quantity; - } + struct Checkpoint { + uint64 timestamp; + uint64 expiration; + uint96 quantity; + } - struct History { - Checkpoint[] _checkpoints; - } + struct History { + Checkpoint[] _checkpoints; + } - struct RoleHolderData { - uint8 role; - address policyholder; - uint96 quantity; - uint64 expiration; - } + struct RoleHolderData { + uint8 role; + address policyholder; + uint96 quantity; + uint64 expiration; + } - struct RolePermissionData { - uint8 role; - PermissionData permissionData; - bool hasPermission; - } + struct RolePermissionData { + uint8 role; + PermissionData permissionData; + bool hasPermission; + } - function approve(address, uint256) external pure; - function balanceOf(address owner) external view returns (uint256); - function canCreateAction(uint8 role, bytes32 permissionId) external view returns (bool hasPermission); - function contractURI() external view returns (string memory); - function getApproved(uint256) external view returns (address); - function getPastQuantity(address policyholder, uint8 role, uint256 timestamp) external view returns (uint96); - function getPastRoleSupplyAsNumberOfHolders(uint8 role, uint256 timestamp) - external - view - returns (uint96 numberOfHolders); - function getPastRoleSupplyAsQuantitySum(uint8 role, uint256 timestamp) - external - view - returns (uint96 totalQuantity); - function getQuantity(address policyholder, uint8 role) external view returns (uint96); - function getRoleSupplyAsNumberOfHolders(uint8 role) external view returns (uint96 numberOfHolders); - function getRoleSupplyAsQuantitySum(uint8 role) external view returns (uint96 totalQuantity); - function hasPermissionId(address policyholder, uint8 role, bytes32 permissionId) external view returns (bool); - function hasRole(address policyholder, uint8 role) external view returns (bool); - function hasRole(address policyholder, uint8 role, uint256 timestamp) external view returns (bool); - function initialize( - string memory _name, - LlamaPolicyConfig memory config, - address policyMetadataLogic, - address executor, - PermissionData memory bootstrapPermissionData - ) external; - function initializeRole(RoleDescription description) external; - function isApprovedForAll(address, address) external view returns (bool); - function isRoleExpired(address policyholder, uint8 role) external view returns (bool); - function llamaExecutor() external view returns (address); - function llamaPolicyMetadata() external view returns (address); - function name() external view returns (string memory); - function numRoles() external view returns (uint8); - function ownerOf(uint256 id) external view returns (address owner); - function revokeExpiredRole(uint8 role, address policyholder) external; - function revokePolicy(address policyholder) external; - function roleBalanceCheckpoints(address policyholder, uint8 role, uint256 start, uint256 end) - external - view - returns (History memory); - function roleBalanceCheckpoints(address policyholder, uint8 role) external view returns (History memory); - function roleBalanceCheckpointsLength(address policyholder, uint8 role) external view returns (uint256); - function roleExpiration(address policyholder, uint8 role) external view returns (uint64); - function roleSupplyCheckpoints(uint8 role, uint256 start, uint256 end) external view returns (History memory); - function roleSupplyCheckpoints(uint8 role) external view returns (History memory); - function roleSupplyCheckpointsLength(uint8 role) external view returns (uint256); - function safeTransferFrom(address, address, uint256) external pure; - function safeTransferFrom(address, address, uint256, bytes memory) external pure; - function setAndInitializePolicyMetadata(address llamaPolicyMetadataLogic, bytes memory config) external; - function setApprovalForAll(address, bool) external pure; - function setRoleHolder(uint8 role, address policyholder, uint96 quantity, uint64 expiration) external; - function setRolePermission(uint8 role, PermissionData memory permissionData, bool hasPermission) external; - function supportsInterface(bytes4 interfaceId) external view returns (bool); - function symbol() external view returns (string memory); - function tokenURI(uint256 tokenId) external view returns (string memory); - function totalSupply() external view returns (uint256); - function transferFrom(address, address, uint256) external pure; - function updateRoleDescription(uint8 role, RoleDescription description) external; + function approve(address, uint256) external pure; + function balanceOf(address owner) external view returns (uint256); + function canCreateAction(uint8 role, bytes32 permissionId) external view returns (bool hasPermission); + function contractURI() external view returns (string memory); + function getApproved(uint256) external view returns (address); + function getPastQuantity(address policyholder, uint8 role, uint256 timestamp) external view returns (uint96); + function getPastRoleSupplyAsNumberOfHolders(uint8 role, uint256 timestamp) + external + view + returns (uint96 numberOfHolders); + function getPastRoleSupplyAsQuantitySum(uint8 role, uint256 timestamp) external view returns (uint96 totalQuantity); + function getQuantity(address policyholder, uint8 role) external view returns (uint96); + function getRoleSupplyAsNumberOfHolders(uint8 role) external view returns (uint96 numberOfHolders); + function getRoleSupplyAsQuantitySum(uint8 role) external view returns (uint96 totalQuantity); + function hasPermissionId(address policyholder, uint8 role, bytes32 permissionId) external view returns (bool); + function hasRole(address policyholder, uint8 role) external view returns (bool); + function hasRole(address policyholder, uint8 role, uint256 timestamp) external view returns (bool); + function initialize( + string memory _name, + LlamaPolicyConfig memory config, + address policyMetadataLogic, + address executor, + PermissionData memory bootstrapPermissionData + ) external; + function initializeRole(RoleDescription description) external; + function isApprovedForAll(address, address) external view returns (bool); + function isRoleExpired(address policyholder, uint8 role) external view returns (bool); + function llamaExecutor() external view returns (address); + function llamaPolicyMetadata() external view returns (address); + function name() external view returns (string memory); + function numRoles() external view returns (uint8); + function ownerOf(uint256 id) external view returns (address owner); + function revokeExpiredRole(uint8 role, address policyholder) external; + function revokePolicy(address policyholder) external; + function roleBalanceCheckpoints(address policyholder, uint8 role, uint256 start, uint256 end) + external + view + returns (History memory); + function roleBalanceCheckpoints(address policyholder, uint8 role) external view returns (History memory); + function roleBalanceCheckpointsLength(address policyholder, uint8 role) external view returns (uint256); + function roleExpiration(address policyholder, uint8 role) external view returns (uint64); + function roleSupplyCheckpoints(uint8 role, uint256 start, uint256 end) external view returns (History memory); + function roleSupplyCheckpoints(uint8 role) external view returns (History memory); + function roleSupplyCheckpointsLength(uint8 role) external view returns (uint256); + function safeTransferFrom(address, address, uint256) external pure; + function safeTransferFrom(address, address, uint256, bytes memory) external pure; + function setAndInitializePolicyMetadata(address llamaPolicyMetadataLogic, bytes memory config) external; + function setApprovalForAll(address, bool) external pure; + function setRoleHolder(uint8 role, address policyholder, uint96 quantity, uint64 expiration) external; + function setRolePermission(uint8 role, PermissionData memory permissionData, bool hasPermission) external; + function supportsInterface(bytes4 interfaceId) external view returns (bool); + function symbol() external view returns (string memory); + function tokenURI(uint256 tokenId) external view returns (string memory); + function totalSupply() external view returns (uint256); + function transferFrom(address, address, uint256) external pure; + function updateRoleDescription(uint8 role, RoleDescription description) external; } diff --git a/src/interfaces/ILlamaPolicyMetadata.sol b/src/interfaces/ILlamaPolicyMetadata.sol index 18c35d7..d8d75d7 100644 --- a/src/interfaces/ILlamaPolicyMetadata.sol +++ b/src/interfaces/ILlamaPolicyMetadata.sol @@ -5,23 +5,23 @@ pragma solidity ^0.8.23; /// @author Llama (devsdosomething@llama.xyz) /// @notice Interface for utility contract to compute Llama policy metadata. interface ILlamaPolicyMetadata { - /// @notice Initializes a new clone of the policy metadata contract. - /// @dev This function is called by the `_setAndInitializePolicyMetadata` function in the `LlamaPolicy` contract. The - /// `initializer` modifier ensures that this function can be invoked at most once. - /// @param config The policy metadata configuration, encoded as bytes to support differing initialization arguments in - /// different policy metadata logic contracts. - /// @return This return statement must be hardcoded to `true` to ensure that initializing an EOA - /// (like the zero address) will revert. - function initialize(bytes memory config) external returns (bool); + /// @notice Initializes a new clone of the policy metadata contract. + /// @dev This function is called by the `_setAndInitializePolicyMetadata` function in the `LlamaPolicy` contract. The + /// `initializer` modifier ensures that this function can be invoked at most once. + /// @param config The policy metadata configuration, encoded as bytes to support differing initialization arguments in + /// different policy metadata logic contracts. + /// @return This return statement must be hardcoded to `true` to ensure that initializing an EOA + /// (like the zero address) will revert. + function initialize(bytes memory config) external returns (bool); - /// @notice Returns the token URI for a given Llama policy ID. - /// @param name The name of the Llama instance. - /// @param executor The executor of the Llama instance. - /// @param tokenId The token ID of the Llama policyholder. - function getTokenURI(string memory name, address executor, uint256 tokenId) external view returns (string memory); + /// @notice Returns the token URI for a given Llama policy ID. + /// @param name The name of the Llama instance. + /// @param executor The executor of the Llama instance. + /// @param tokenId The token ID of the Llama policyholder. + function getTokenURI(string memory name, address executor, uint256 tokenId) external view returns (string memory); - /// @notice Returns the contract URI for a Llama instance's policies. - /// @param name The name of the Llama instance. - /// @param executor The executor of the Llama instance. - function getContractURI(string memory name, address executor) external view returns (string memory); + /// @notice Returns the contract URI for a Llama instance's policies. + /// @param name The name of the Llama instance. + /// @param executor The executor of the Llama instance. + function getContractURI(string memory name, address executor) external view returns (string memory); } diff --git a/src/interfaces/ILlamaRelativeStrategyBase.sol b/src/interfaces/ILlamaRelativeStrategyBase.sol index 3a6a7d3..5d5b41d 100644 --- a/src/interfaces/ILlamaRelativeStrategyBase.sol +++ b/src/interfaces/ILlamaRelativeStrategyBase.sol @@ -7,61 +7,55 @@ pragma solidity ^0.8.23; /// @author Llama (devsdosomething@llama.xyz) /// @notice This is the interface for LlamaRelativeStrategyBase. interface ILlamaRelativeStrategyBase { - type ActionState is uint8; + type ActionState is uint8; - struct ActionInfo { - uint256 id; - address creator; - uint8 creatorRole; - address strategy; - address target; - uint256 value; - bytes data; - } + struct ActionInfo { + uint256 id; + address creator; + uint8 creatorRole; + address strategy; + address target; + uint256 value; + bytes data; + } - error CannotCancelInState(ActionState currentState); - error DisapprovalDisabled(); - error InvalidActionInfo(); - error InvalidMinApprovalPct(uint256 minApprovalPct); - error InvalidRole(uint8 role); - error OnlyActionCreator(); - error RoleHasZeroSupply(uint8 role); - error RoleNotInitialized(uint8 role); - error UnsafeCast(uint256 n); + error CannotCancelInState(ActionState currentState); + error DisapprovalDisabled(); + error InvalidActionInfo(); + error InvalidMinApprovalPct(uint256 minApprovalPct); + error InvalidRole(uint8 role); + error OnlyActionCreator(); + error RoleHasZeroSupply(uint8 role); + error RoleNotInitialized(uint8 role); + error UnsafeCast(uint256 n); - event Initialized(uint8 version); + event Initialized(uint8 version); - function approvalEndTime(ActionInfo memory actionInfo) external view returns (uint256); - function approvalPeriod() external view returns (uint64); - function approvalRole() external view returns (uint8); - function checkIfApprovalEnabled(ActionInfo memory, address, uint8 role) external view; - function checkIfDisapprovalEnabled(ActionInfo memory, address, uint8 role) external view; - function disapprovalRole() external view returns (uint8); - function expirationPeriod() external view returns (uint64); - function forceApprovalRole(uint8 role) external view returns (bool isForceApproval); - function forceDisapprovalRole(uint8 role) external view returns (bool isForceDisapproval); - function getApprovalQuantityAt(address policyholder, uint8 role, uint256 timestamp) - external - view - returns (uint96); - function getApprovalSupply(ActionInfo memory actionInfo) external view returns (uint96); - function getDisapprovalQuantityAt(address policyholder, uint8 role, uint256 timestamp) - external - view - returns (uint96); - function getDisapprovalSupply(ActionInfo memory actionInfo) external view returns (uint96); - function initialize(bytes memory config) external returns (bool); - function isActionActive(ActionInfo memory actionInfo) external view returns (bool); - function isActionApproved(ActionInfo memory actionInfo) external view returns (bool); - function isActionDisapproved(ActionInfo memory actionInfo) external view returns (bool); - function isActionExpired(ActionInfo memory actionInfo) external view returns (bool); - function isFixedLengthApprovalPeriod() external view returns (bool); - function llamaCore() external view returns (address); - function minApprovalPct() external view returns (uint16); - function minDisapprovalPct() external view returns (uint16); - function minExecutionTime(ActionInfo memory) external view returns (uint64); - function policy() external view returns (address); - function queuingPeriod() external view returns (uint64); - function validateActionCancelation(ActionInfo memory actionInfo, address caller) external view; - function validateActionCreation(ActionInfo memory actionInfo) external view; + function approvalEndTime(ActionInfo memory actionInfo) external view returns (uint256); + function approvalPeriod() external view returns (uint64); + function approvalRole() external view returns (uint8); + function checkIfApprovalEnabled(ActionInfo memory, address, uint8 role) external view; + function checkIfDisapprovalEnabled(ActionInfo memory, address, uint8 role) external view; + function disapprovalRole() external view returns (uint8); + function expirationPeriod() external view returns (uint64); + function forceApprovalRole(uint8 role) external view returns (bool isForceApproval); + function forceDisapprovalRole(uint8 role) external view returns (bool isForceDisapproval); + function getApprovalQuantityAt(address policyholder, uint8 role, uint256 timestamp) external view returns (uint96); + function getApprovalSupply(ActionInfo memory actionInfo) external view returns (uint96); + function getDisapprovalQuantityAt(address policyholder, uint8 role, uint256 timestamp) external view returns (uint96); + function getDisapprovalSupply(ActionInfo memory actionInfo) external view returns (uint96); + function initialize(bytes memory config) external returns (bool); + function isActionActive(ActionInfo memory actionInfo) external view returns (bool); + function isActionApproved(ActionInfo memory actionInfo) external view returns (bool); + function isActionDisapproved(ActionInfo memory actionInfo) external view returns (bool); + function isActionExpired(ActionInfo memory actionInfo) external view returns (bool); + function isFixedLengthApprovalPeriod() external view returns (bool); + function llamaCore() external view returns (address); + function minApprovalPct() external view returns (uint16); + function minDisapprovalPct() external view returns (uint16); + function minExecutionTime(ActionInfo memory) external view returns (uint64); + function policy() external view returns (address); + function queuingPeriod() external view returns (uint64); + function validateActionCancelation(ActionInfo memory actionInfo, address caller) external view; + function validateActionCreation(ActionInfo memory actionInfo) external view; } diff --git a/src/interfaces/ILlamaStrategy.sol b/src/interfaces/ILlamaStrategy.sol index 12e35c4..55d2f58 100644 --- a/src/interfaces/ILlamaStrategy.sol +++ b/src/interfaces/ILlamaStrategy.sol @@ -10,106 +10,98 @@ import {ILlamaPolicy} from "src/interfaces/ILlamaPolicy.sol"; /// @notice This is the interface for Llama strategies which determine the rules of an action's process. /// @dev The interface is sorted by the stage of the action's lifecycle in which the method's are used. interface ILlamaStrategy { - // -------- For Inspection -------- - // These are not strictly required by the core, but are useful for inspecting a strategy contract. - - /// @notice Returns the address of the Llama core that this strategy is registered to. - function llamaCore() external view returns (ILlamaCore); - - /// @notice Returns the name of the Llama policy that this strategy is registered to. - function policy() external view returns (ILlamaPolicy); - - // -------- At Strategy Creation -------- - - /// @notice Initializes a new clone of the strategy. - /// @dev This function is called by the `_deployStrategies` function in the `LlamaCore` contract. The `initializer` - /// modifier ensures that this function can be invoked at most once. - /// @param config The strategy configuration, encoded as bytes to support differing constructor arguments in - /// different strategies. - /// @return This return statement must be hardcoded to `true` to ensure that initializing an EOA - /// (like the zero address) will revert. - function initialize(bytes memory config) external returns (bool); - - // -------- At Action Creation -------- - - /// @notice Reverts if action creation is not allowed. - /// @param actionInfo Data required to create an action. - function validateActionCreation(ActionInfo calldata actionInfo) external view; - - // -------- When Casting Approval -------- - - /// @notice Reverts if approvals are not allowed with this strategy for the given policyholder when approving with - /// role. - /// @param actionInfo Data required to create an action. - /// @param policyholder Address of the policyholder. - /// @param role The role of the policyholder being used to cast approval. - function checkIfApprovalEnabled(ActionInfo calldata actionInfo, address policyholder, uint8 role) external view; - - /// @notice Get the quantity of an approval of a policyholder at a specific timestamp. - /// @param policyholder Address of the policyholder. - /// @param role The role to check quantity for. - /// @param timestamp The timestamp at which to get the approval quantity. - /// @return The quantity of the policyholder's approval. - function getApprovalQuantityAt(address policyholder, uint8 role, uint256 timestamp) - external - view - returns (uint96); - - // -------- When Casting Disapproval -------- - - /// @notice Reverts if disapprovals are not allowed with this strategy for the given policyholder when disapproving - /// with role. - /// @param actionInfo Data required to create an action. - /// @param policyholder Address of the policyholder. - /// @param role The role of the policyholder being used to cast disapproval. - function checkIfDisapprovalEnabled(ActionInfo calldata actionInfo, address policyholder, uint8 role) - external - view; - - /// @notice Get the quantity of a disapproval of a policyholder at a specific timestamp. - /// @param policyholder Address of the policyholder. - /// @param role The role to check quantity for. - /// @param timestamp The timestamp at which to get the disapproval quantity. - /// @return The quantity of the policyholder's disapproval. - function getDisapprovalQuantityAt(address policyholder, uint8 role, uint256 timestamp) - external - view - returns (uint96); - - // -------- When Queueing -------- - - /// @notice Returns the earliest timestamp, in seconds, at which an action can be executed. - /// @param actionInfo Data required to create an action. - /// @return The earliest timestamp at which an action can be executed. - function minExecutionTime(ActionInfo calldata actionInfo) external view returns (uint64); - - // -------- When Canceling -------- - - /// @notice Reverts if the action cannot be canceled. - /// @param actionInfo Data required to create an action. - /// @param caller Policyholder initiating the cancelation. - function validateActionCancelation(ActionInfo calldata actionInfo, address caller) external view; - - // -------- When Determining Action State -------- - // These are used during casting of approvals and disapprovals, when queueing, and when executing. - - /// @notice Get whether an action is currently active. - /// @param actionInfo Data required to create an action. - /// @return Boolean value that is `true` if the action is currently active, `false` otherwise. - function isActionActive(ActionInfo calldata actionInfo) external view returns (bool); - - /// @notice Get whether an action has passed the approval process. - /// @param actionInfo Data required to create an action. - /// @return Boolean value that is `true` if the action has passed the approval process. - function isActionApproved(ActionInfo calldata actionInfo) external view returns (bool); - - /// @notice Get whether an action has been vetoed during the disapproval process. - /// @param actionInfo Data required to create an action. - /// @return Boolean value that is `true` if the action has been vetoed during the disapproval process. - function isActionDisapproved(ActionInfo calldata actionInfo) external view returns (bool); - - /// @notice Returns `true` if the action is expired, `false` otherwise. - /// @param actionInfo Data required to create an action. - /// @return Boolean value that is `true` if the action is expired. - function isActionExpired(ActionInfo calldata actionInfo) external view returns (bool); + // -------- For Inspection -------- + // These are not strictly required by the core, but are useful for inspecting a strategy contract. + + /// @notice Returns the address of the Llama core that this strategy is registered to. + function llamaCore() external view returns (ILlamaCore); + + /// @notice Returns the name of the Llama policy that this strategy is registered to. + function policy() external view returns (ILlamaPolicy); + + // -------- At Strategy Creation -------- + + /// @notice Initializes a new clone of the strategy. + /// @dev This function is called by the `_deployStrategies` function in the `LlamaCore` contract. The `initializer` + /// modifier ensures that this function can be invoked at most once. + /// @param config The strategy configuration, encoded as bytes to support differing constructor arguments in + /// different strategies. + /// @return This return statement must be hardcoded to `true` to ensure that initializing an EOA + /// (like the zero address) will revert. + function initialize(bytes memory config) external returns (bool); + + // -------- At Action Creation -------- + + /// @notice Reverts if action creation is not allowed. + /// @param actionInfo Data required to create an action. + function validateActionCreation(ActionInfo calldata actionInfo) external view; + + // -------- When Casting Approval -------- + + /// @notice Reverts if approvals are not allowed with this strategy for the given policyholder when approving with + /// role. + /// @param actionInfo Data required to create an action. + /// @param policyholder Address of the policyholder. + /// @param role The role of the policyholder being used to cast approval. + function checkIfApprovalEnabled(ActionInfo calldata actionInfo, address policyholder, uint8 role) external view; + + /// @notice Get the quantity of an approval of a policyholder at a specific timestamp. + /// @param policyholder Address of the policyholder. + /// @param role The role to check quantity for. + /// @param timestamp The timestamp at which to get the approval quantity. + /// @return The quantity of the policyholder's approval. + function getApprovalQuantityAt(address policyholder, uint8 role, uint256 timestamp) external view returns (uint96); + + // -------- When Casting Disapproval -------- + + /// @notice Reverts if disapprovals are not allowed with this strategy for the given policyholder when disapproving + /// with role. + /// @param actionInfo Data required to create an action. + /// @param policyholder Address of the policyholder. + /// @param role The role of the policyholder being used to cast disapproval. + function checkIfDisapprovalEnabled(ActionInfo calldata actionInfo, address policyholder, uint8 role) external view; + + /// @notice Get the quantity of a disapproval of a policyholder at a specific timestamp. + /// @param policyholder Address of the policyholder. + /// @param role The role to check quantity for. + /// @param timestamp The timestamp at which to get the disapproval quantity. + /// @return The quantity of the policyholder's disapproval. + function getDisapprovalQuantityAt(address policyholder, uint8 role, uint256 timestamp) external view returns (uint96); + + // -------- When Queueing -------- + + /// @notice Returns the earliest timestamp, in seconds, at which an action can be executed. + /// @param actionInfo Data required to create an action. + /// @return The earliest timestamp at which an action can be executed. + function minExecutionTime(ActionInfo calldata actionInfo) external view returns (uint64); + + // -------- When Canceling -------- + + /// @notice Reverts if the action cannot be canceled. + /// @param actionInfo Data required to create an action. + /// @param caller Policyholder initiating the cancelation. + function validateActionCancelation(ActionInfo calldata actionInfo, address caller) external view; + + // -------- When Determining Action State -------- + // These are used during casting of approvals and disapprovals, when queueing, and when executing. + + /// @notice Get whether an action is currently active. + /// @param actionInfo Data required to create an action. + /// @return Boolean value that is `true` if the action is currently active, `false` otherwise. + function isActionActive(ActionInfo calldata actionInfo) external view returns (bool); + + /// @notice Get whether an action has passed the approval process. + /// @param actionInfo Data required to create an action. + /// @return Boolean value that is `true` if the action has passed the approval process. + function isActionApproved(ActionInfo calldata actionInfo) external view returns (bool); + + /// @notice Get whether an action has been vetoed during the disapproval process. + /// @param actionInfo Data required to create an action. + /// @return Boolean value that is `true` if the action has been vetoed during the disapproval process. + function isActionDisapproved(ActionInfo calldata actionInfo) external view returns (bool); + + /// @notice Returns `true` if the action is expired, `false` otherwise. + /// @param actionInfo Data required to create an action. + /// @return Boolean value that is `true` if the action is expired. + function isActionExpired(ActionInfo calldata actionInfo) external view returns (bool); } diff --git a/src/lib/Enums.sol b/src/lib/Enums.sol index 5789ed5..041da78 100644 --- a/src/lib/Enums.sol +++ b/src/lib/Enums.sol @@ -3,11 +3,11 @@ pragma solidity ^0.8.23; /// @dev Possible states of an action during its lifecycle. enum ActionState { - Active, // Action created and approval period begins. - Canceled, // Action canceled by creator. - Failed, // Action approval failed. - Approved, // Action approval succeeded and ready to be queued. - Queued, // Action queued for queueing duration and disapproval period begins. - Expired, // block.timestamp is greater than Action's executionTime + expirationDelay. - Executed // Action has executed successfully. + Active, // Action created and approval period begins. + Canceled, // Action canceled by creator. + Failed, // Action approval failed. + Approved, // Action approval succeeded and ready to be queued. + Queued, // Action queued for queueing duration and disapproval period begins. + Expired, // block.timestamp is greater than Action's executionTime + expirationDelay. + Executed // Action has executed successfully. } diff --git a/src/lib/LlamaUtils.sol b/src/lib/LlamaUtils.sol index b120c1a..9ae5d4f 100644 --- a/src/lib/LlamaUtils.sol +++ b/src/lib/LlamaUtils.sol @@ -5,30 +5,30 @@ import {PermissionData} from "src/lib/Structs.sol"; /// @dev Shared helper methods for Llama's contracts. library LlamaUtils { - /// @dev Thrown when a value cannot be safely casted to a smaller type. - error UnsafeCast(uint256 n); + /// @dev Thrown when a value cannot be safely casted to a smaller type. + error UnsafeCast(uint256 n); - /// @dev Reverts if `n` does not fit in a `uint64`. - function toUint64(uint256 n) internal pure returns (uint64) { - if (n > type(uint64).max) revert UnsafeCast(n); - return uint64(n); - } + /// @dev Reverts if `n` does not fit in a `uint64`. + function toUint64(uint256 n) internal pure returns (uint64) { + if (n > type(uint64).max) revert UnsafeCast(n); + return uint64(n); + } - /// @dev Reverts if `n` does not fit in a `uint96`. - function toUint96(uint256 n) internal pure returns (uint96) { - if (n > type(uint96).max) revert UnsafeCast(n); - return uint96(n); - } + /// @dev Reverts if `n` does not fit in a `uint96`. + function toUint96(uint256 n) internal pure returns (uint96) { + if (n > type(uint96).max) revert UnsafeCast(n); + return uint96(n); + } - /// @dev Increments a `uint256` without checking for overflow. - function uncheckedIncrement(uint256 i) internal pure returns (uint256) { - unchecked { - return i + 1; - } + /// @dev Increments a `uint256` without checking for overflow. + function uncheckedIncrement(uint256 i) internal pure returns (uint256) { + unchecked { + return i + 1; } + } - /// @dev Hashes a permission to return the corresponding permission ID. - function computePermissionId(PermissionData memory permission) internal pure returns (bytes32) { - return keccak256(abi.encode(permission)); - } + /// @dev Hashes a permission to return the corresponding permission ID. + function computePermissionId(PermissionData memory permission) internal pure returns (bytes32) { + return keccak256(abi.encode(permission)); + } } diff --git a/src/lib/Structs.sol b/src/lib/Structs.sol index 08c79d7..8f0b9e4 100644 --- a/src/lib/Structs.sol +++ b/src/lib/Structs.sol @@ -8,70 +8,70 @@ import {RoleDescription} from "src/lib/UDVTs.sol"; /// @dev Data required to create an action. struct ActionInfo { - uint256 id; // ID of the action. - address creator; // Address that created the action. - uint8 creatorRole; // The role that created the action. - ILlamaStrategy strategy; // Strategy used to govern the action. - address target; // Contract being called by an action. - uint256 value; // Value in wei to be sent when the action is executed. - bytes data; // Data to be called on the target when the action is executed. + uint256 id; // ID of the action. + address creator; // Address that created the action. + uint8 creatorRole; // The role that created the action. + ILlamaStrategy strategy; // Strategy used to govern the action. + address target; // Contract being called by an action. + uint256 value; // Value in wei to be sent when the action is executed. + bytes data; // Data to be called on the target when the action is executed. } /// @dev Data that represents an action. struct Action { - // Instead of storing all data required to execute an action in storage, we only save the hash to - // make action creation cheaper. The hash is computed by taking the keccak256 hash of the concatenation of each - // field in the `ActionInfo` struct. - bytes32 infoHash; - bool executed; // Has action executed. - bool canceled; // Is action canceled. - bool isScript; // Is the action's target a script. - ILlamaActionGuard guard; // The action's guard. This is the address(0) if no guard is set on the action's target and - // selector pair. - uint64 creationTime; // The timestamp when action was created (used for policy snapshots). - uint64 minExecutionTime; // Only set when an action is queued. The timestamp when action execution can begin. - uint96 totalApprovals; // The total quantity of policyholder approvals. - uint96 totalDisapprovals; // The total quantity of policyholder disapprovals. + // Instead of storing all data required to execute an action in storage, we only save the hash to + // make action creation cheaper. The hash is computed by taking the keccak256 hash of the concatenation of each + // field in the `ActionInfo` struct. + bytes32 infoHash; + bool executed; // Has action executed. + bool canceled; // Is action canceled. + bool isScript; // Is the action's target a script. + ILlamaActionGuard guard; // The action's guard. This is the address(0) if no guard is set on the action's target and + // selector pair. + uint64 creationTime; // The timestamp when action was created (used for policy snapshots). + uint64 minExecutionTime; // Only set when an action is queued. The timestamp when action execution can begin. + uint96 totalApprovals; // The total quantity of policyholder approvals. + uint96 totalDisapprovals; // The total quantity of policyholder disapprovals. } /// @dev Data that represents a permission. struct PermissionData { - address target; // Contract being called by an action. - bytes4 selector; // Selector of the function being called by an action. - ILlamaStrategy strategy; // Strategy used to govern the action. + address target; // Contract being called by an action. + bytes4 selector; // Selector of the function being called by an action. + ILlamaStrategy strategy; // Strategy used to govern the action. } /// @dev Data required to assign/revoke a role to/from a policyholder. struct RoleHolderData { - uint8 role; // ID of the role to set (uint8 ensures onchain enumerability when burning policies). - address policyholder; // Policyholder to assign the role to. - uint96 quantity; // Quantity of the role to assign to the policyholder, i.e. their (dis)approval quantity. - uint64 expiration; // When the role expires. + uint8 role; // ID of the role to set (uint8 ensures onchain enumerability when burning policies). + address policyholder; // Policyholder to assign the role to. + uint96 quantity; // Quantity of the role to assign to the policyholder, i.e. their (dis)approval quantity. + uint64 expiration; // When the role expires. } /// @dev Data required to assign/revoke a permission to/from a role. struct RolePermissionData { - uint8 role; // ID of the role to set (uint8 ensures onchain enumerability when burning policies). - PermissionData permissionData; // The `(target, selector, strategy)` tuple that will be keccak256 hashed to - // generate the permission ID to assign or unassign to the role - bool hasPermission; // Whether to assign the permission or remove the permission. + uint8 role; // ID of the role to set (uint8 ensures onchain enumerability when burning policies). + PermissionData permissionData; // The `(target, selector, strategy)` tuple that will be keccak256 hashed to + // generate the permission ID to assign or unassign to the role + bool hasPermission; // Whether to assign the permission or remove the permission. } /// @dev Configuration of a new Llama instance. struct LlamaInstanceConfig { - string name; // The name of the Llama instance. - ILlamaStrategy strategyLogic; // The initial strategy implementation (logic) contract. - ILlamaAccount accountLogic; // The initial account implementation (logic) contract. - bytes[] initialStrategies; // Array of initial strategy configurations. - bytes[] initialAccounts; // Array of initial account configurations. - LlamaPolicyConfig policyConfig; // Configuration of the instance's policy. + string name; // The name of the Llama instance. + ILlamaStrategy strategyLogic; // The initial strategy implementation (logic) contract. + ILlamaAccount accountLogic; // The initial account implementation (logic) contract. + bytes[] initialStrategies; // Array of initial strategy configurations. + bytes[] initialAccounts; // Array of initial account configurations. + LlamaPolicyConfig policyConfig; // Configuration of the instance's policy. } /// @dev Configuration of a new Llama policy. struct LlamaPolicyConfig { - RoleDescription[] roleDescriptions; // The initial role descriptions. - RoleHolderData[] roleHolders; // The `role`, `policyholder`, `quantity` and `expiration` of the initial role holders. - RolePermissionData[] rolePermissions; // The `role`, `permissionData`, and the `hasPermission` boolean. - string color; // The primary color of the SVG representation of the instance's policy (e.g. #00FF00). - string logo; // The SVG string representing the logo for the deployed Llama instance's NFT. + RoleDescription[] roleDescriptions; // The initial role descriptions. + RoleHolderData[] roleHolders; // The `role`, `policyholder`, `quantity` and `expiration` of the initial role holders. + RolePermissionData[] rolePermissions; // The `role`, `permissionData`, and the `hasPermission` boolean. + string color; // The primary color of the SVG representation of the instance's policy (e.g. #00FF00). + string logo; // The SVG string representing the logo for the deployed Llama instance's NFT. } diff --git a/src/llama-scripts/LlamaBaseScript.sol b/src/llama-scripts/LlamaBaseScript.sol index b593e7d..6e33630 100644 --- a/src/llama-scripts/LlamaBaseScript.sol +++ b/src/llama-scripts/LlamaBaseScript.sol @@ -3,21 +3,21 @@ pragma solidity ^0.8.19; /// @dev This script is a template for creating new scripts, and should not be used directly. abstract contract LlamaBaseScript { - /// @dev Thrown if you try to CALL a function that has the `onlyDelegatecall` modifier. - error OnlyDelegateCall(); + /// @dev Thrown if you try to CALL a function that has the `onlyDelegatecall` modifier. + error OnlyDelegateCall(); - /// @dev Add this to your script's methods to ensure the script can only be used via delegatecall, and not a regular - /// call. - modifier onlyDelegateCall() { - if (address(this) == SELF) revert OnlyDelegateCall(); - _; - } + /// @dev Add this to your script's methods to ensure the script can only be used via delegatecall, and not a regular + /// call. + modifier onlyDelegateCall() { + if (address(this) == SELF) revert OnlyDelegateCall(); + _; + } - /// @dev Address of the script contract. We save it off because during a delegatecall `address(this)` refers to the - /// caller's address, not this script's address. - address internal immutable SELF; + /// @dev Address of the script contract. We save it off because during a delegatecall `address(this)` refers to the + /// caller's address, not this script's address. + address internal immutable SELF; - constructor() { - SELF = address(this); - } + constructor() { + SELF = address(this); + } } diff --git a/src/llama-scripts/LlamaSingleUseScript.sol b/src/llama-scripts/LlamaSingleUseScript.sol index 2e5e4af..0f28d49 100644 --- a/src/llama-scripts/LlamaSingleUseScript.sol +++ b/src/llama-scripts/LlamaSingleUseScript.sol @@ -9,19 +9,19 @@ import {ILlamaExecutor} from "src/interfaces/ILlamaExecutor.sol"; /// @dev This script is meant to be delegatecalled by the executor contract, with the script leveraging the /// `unauthorizeAfterRun` modifier to ensure it can only be used once. abstract contract LlamaSingleUseScript is LlamaBaseScript { - /// @dev Add this to your script's methods to unauthorize itself after it has been run once. Any subsequent calls will - /// fail unless the script is reauthorized. Best if used in tandem with the `onlyDelegateCall` from `BaseScript.sol`. - modifier unauthorizeAfterRun() { - _; - ILlamaCore core = ILlamaCore(EXECUTOR.LLAMA_CORE()); - core.setScriptAuthorization(SELF, false); - } + /// @dev Add this to your script's methods to unauthorize itself after it has been run once. Any subsequent calls will + /// fail unless the script is reauthorized. Best if used in tandem with the `onlyDelegateCall` from `BaseScript.sol`. + modifier unauthorizeAfterRun() { + _; + ILlamaCore core = ILlamaCore(EXECUTOR.LLAMA_CORE()); + core.setScriptAuthorization(SELF, false); + } - /// @dev Address of the executor contract. We save it off in order to access the setScriptAuthorization method in - /// `LlamaCore`. - ILlamaExecutor internal immutable EXECUTOR; + /// @dev Address of the executor contract. We save it off in order to access the setScriptAuthorization method in + /// `LlamaCore`. + ILlamaExecutor internal immutable EXECUTOR; - constructor(ILlamaExecutor executor) { - EXECUTOR = executor; - } + constructor(ILlamaExecutor executor) { + EXECUTOR = executor; + } } diff --git a/src/token-voting/ERC20TokenholderActionCreator.sol b/src/token-voting/ERC20TokenholderActionCreator.sol index cdef166..4e48d6f 100644 --- a/src/token-voting/ERC20TokenholderActionCreator.sol +++ b/src/token-voting/ERC20TokenholderActionCreator.sol @@ -10,26 +10,26 @@ 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 immutable TOKEN; - constructor(ERC20Votes token, ILlamaCore llamaCore, uint256 _creationThreshold) - TokenholderActionCreator(llamaCore, _creationThreshold) - { - TOKEN = token; - uint256 totalSupply = TOKEN.totalSupply(); - if (totalSupply == 0) revert InvalidTokenAddress(); - if (_creationThreshold > totalSupply) revert InvalidCreationThreshold(); - } + constructor(ERC20Votes token, ILlamaCore llamaCore, uint256 _creationThreshold) + TokenholderActionCreator(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); - } + function _getPastVotes(address account, uint256 timestamp) internal view virtual override returns (uint256) { + return TOKEN.getPastVotes(account, timestamp); + } - function _getPastTotalSupply(uint256 timestamp) internal view virtual override returns (uint256) { - return TOKEN.getPastTotalSupply(timestamp); - } + function _getPastTotalSupply(uint256 timestamp) internal view virtual override returns (uint256) { + return TOKEN.getPastTotalSupply(timestamp); + } - function _getClockMode() internal view virtual override returns (string memory) { - return TOKEN.CLOCK_MODE(); - } + function _getClockMode() internal view virtual override returns (string memory) { + return TOKEN.CLOCK_MODE(); + } } diff --git a/src/token-voting/ERC20TokenholderCaster.sol b/src/token-voting/ERC20TokenholderCaster.sol index 7593126..f77e3a4 100644 --- a/src/token-voting/ERC20TokenholderCaster.sol +++ b/src/token-voting/ERC20TokenholderCaster.sol @@ -10,25 +10,25 @@ 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 immutable TOKEN; - constructor(ERC20Votes token, ILlamaCore llamaCore, uint8 role, uint256 minApprovalPct, uint256 minDisapprovalPct) - TokenholderCaster(llamaCore, role, minApprovalPct, minDisapprovalPct) - { - TOKEN = token; - uint256 totalSupply = TOKEN.totalSupply(); - if (totalSupply == 0) revert InvalidTokenAddress(); - } + constructor(ERC20Votes token, ILlamaCore llamaCore, uint8 role, uint256 minApprovalPct, uint256 minDisapprovalPct) + TokenholderCaster(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); - } + function _getPastVotes(address account, uint256 timestamp) internal view virtual override returns (uint256) { + return TOKEN.getPastVotes(account, timestamp); + } - function _getPastTotalSupply(uint256 timestamp) internal view virtual override returns (uint256) { - return TOKEN.getPastTotalSupply(timestamp); - } + function _getPastTotalSupply(uint256 timestamp) internal view virtual override returns (uint256) { + return TOKEN.getPastTotalSupply(timestamp); + } - function _getClockMode() internal view virtual override returns (string memory) { - return TOKEN.CLOCK_MODE(); - } + function _getClockMode() internal view virtual override returns (string memory) { + return TOKEN.CLOCK_MODE(); + } } diff --git a/src/token-voting/ERC721TokenholderActionCreator.sol b/src/token-voting/ERC721TokenholderActionCreator.sol index db6b971..7be92ce 100644 --- a/src/token-voting/ERC721TokenholderActionCreator.sol +++ b/src/token-voting/ERC721TokenholderActionCreator.sol @@ -11,27 +11,27 @@ 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 immutable TOKEN; - constructor(ERC721Votes token, ILlamaCore llamaCore, uint256 _creationThreshold) - TokenholderActionCreator(llamaCore, _creationThreshold) - { - TOKEN = token; - uint256 totalSupply = TOKEN.getPastTotalSupply(block.timestamp - 1); - if (totalSupply == 0) revert InvalidTokenAddress(); - if (_creationThreshold > totalSupply) revert InvalidCreationThreshold(); - if (!TOKEN.supportsInterface(type(IERC721).interfaceId)) revert InvalidTokenAddress(); - } + constructor(ERC721Votes token, ILlamaCore llamaCore, uint256 _creationThreshold) + TokenholderActionCreator(llamaCore, _creationThreshold) + { + TOKEN = token; + uint256 totalSupply = TOKEN.getPastTotalSupply(block.timestamp - 1); + if (totalSupply == 0) revert InvalidTokenAddress(); + if (_creationThreshold > totalSupply) revert InvalidCreationThreshold(); + 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); - } + function _getPastVotes(address account, uint256 timestamp) internal view virtual override returns (uint256) { + return TOKEN.getPastVotes(account, timestamp); + } - function _getPastTotalSupply(uint256 timestamp) internal view virtual override returns (uint256) { - return TOKEN.getPastTotalSupply(timestamp); - } + function _getPastTotalSupply(uint256 timestamp) internal view virtual override returns (uint256) { + return TOKEN.getPastTotalSupply(timestamp); + } - function _getClockMode() internal view virtual override returns (string memory) { - return TOKEN.CLOCK_MODE(); - } + function _getClockMode() internal view virtual override returns (string memory) { + return TOKEN.CLOCK_MODE(); + } } diff --git a/src/token-voting/ERC721TokenholderCaster.sol b/src/token-voting/ERC721TokenholderCaster.sol index cde4d1a..ada49ea 100644 --- a/src/token-voting/ERC721TokenholderCaster.sol +++ b/src/token-voting/ERC721TokenholderCaster.sol @@ -11,24 +11,24 @@ 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 immutable 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(); - } + 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(); + } - function _getPastVotes(address account, uint256 timestamp) internal view virtual override returns (uint256) { - return TOKEN.getPastVotes(account, timestamp); - } + function _getPastVotes(address account, uint256 timestamp) internal view virtual override returns (uint256) { + return TOKEN.getPastVotes(account, timestamp); + } - function _getPastTotalSupply(uint256 timestamp) internal view virtual override returns (uint256) { - return TOKEN.getPastTotalSupply(timestamp); - } + function _getPastTotalSupply(uint256 timestamp) internal view virtual override returns (uint256) { + return TOKEN.getPastTotalSupply(timestamp); + } - function _getClockMode() internal view virtual override returns (string memory) { - return TOKEN.CLOCK_MODE(); - } + function _getClockMode() internal view virtual override returns (string memory) { + return TOKEN.CLOCK_MODE(); + } } diff --git a/src/token-voting/LlamaTokenVotingFactory.sol b/src/token-voting/LlamaTokenVotingFactory.sol index f1eb61b..e539f0d 100644 --- a/src/token-voting/LlamaTokenVotingFactory.sol +++ b/src/token-voting/LlamaTokenVotingFactory.sol @@ -17,178 +17,172 @@ import {LlamaBaseScript} from "src/llama-scripts/LlamaBaseScript.sol"; /// @author Llama (devsdosomething@llama.xyz) /// @notice This contract lets llama instances deploy a token voting module in a single llama action. contract LlamaTokenVotingFactory is LlamaBaseScript { - error NoModulesDeployed(); + error NoModulesDeployed(); - event ERC20TokenholderActionCreatorCreated(address actionCreator, address indexed token); - event ERC721TokenholderActionCreatorCreated(address actionCreator, address indexed token); - event ERC20TokenholderCasterCreated( - address caster, address indexed token, uint256 minApprovalPct, uint256 minDisapprovalPct + event ERC20TokenholderActionCreatorCreated(address actionCreator, address indexed token); + event ERC721TokenholderActionCreatorCreated(address actionCreator, address indexed token); + event ERC20TokenholderCasterCreated( + address caster, address indexed token, uint256 minApprovalPct, uint256 minDisapprovalPct + ); + event ERC721TokenholderCasterCreated( + address caster, address indexed token, uint256 minApprovalPct, uint256 minDisapprovalPct + ); + + ///@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 + /// a script. + ///@param token The address of the token to be used for voting. + ///@param isERC20 Whether the token is an ERC20 or ERC721. + ///@param deployActionCreator Whether to deploy the action creator. + ///@param deployCaster Whether to deploy the caster. + ///@param creationThreshold The number of tokens required to create an action (set to 0 if not deploying action + /// creator). + ///@param minApprovalPct The minimum percentage of tokens required to approve an action (set to 0 if not deploying + /// caster). + ///@param minDisapprovalPct The minimum percentage of tokens required to disapprove an action (set to 0 if not + /// deploying caster). + function deployTokenVotingModule( + address token, + bool isERC20, + bool deployActionCreator, + bool deployCaster, + uint256 creationThreshold, + uint256 minApprovalPct, + uint256 minDisapprovalPct + ) public returns (address, address) { + return _deployTokenVotingModule( + ILlamaExecutor(msg.sender), + token, + isERC20, + deployActionCreator, + deployCaster, + creationThreshold, + minApprovalPct, + minDisapprovalPct ); - event ERC721TokenholderCasterCreated( - address caster, address indexed token, uint256 minApprovalPct, uint256 minDisapprovalPct + } + + ///@notice A llama script that deploys a token voting module and inittializes/issues roles to the token voting action + /// creator and caster in a single function so it can be deployed in a single llama action. + ///@dev This contract must be authorized as a script in the core contract before it can be used (invoke the + /// `LlamaCore::setScriptAuthorization` function to authorize). + ///@dev This method CAN NOT be used in tandem with `deployTokenVotingModule`. You must use one or the other due to + /// this method requring the contract to be authorized as a script. + ///@param token The address of the token to be used for voting. + ///@param isERC20 Whether the token is an ERC20 or ERC721. + ///@param deployActionCreator Whether to deploy the action creator. + ///@param deployCaster Whether to deploy the caster. + ///@param creationThreshold The number of tokens required to create an action (set to 0 if not deploying action + /// creator). + ///@param minApprovalPct The minimum percentage of tokens required to approve an action (set to 0 if not deploying + /// caster). + ///@param minDisapprovalPct The minimum percentage of tokens required to disapprove an action (set to 0 if not + /// deploying caster). + function delegateCallDeployTokenVotingModuleWithRoles( + address token, + bool isERC20, + bool deployActionCreator, + bool deployCaster, + uint256 creationThreshold, + uint256 minApprovalPct, + uint256 minDisapprovalPct + ) public onlyDelegateCall { + (address actionCreator, address caster) = _deployTokenVotingModule( + ILlamaExecutor(address(this)), + token, + isERC20, + deployActionCreator, + deployCaster, + creationThreshold, + minApprovalPct, + minDisapprovalPct ); - ///@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 - /// a script. - ///@param token The address of the token to be used for voting. - ///@param isERC20 Whether the token is an ERC20 or ERC721. - ///@param deployActionCreator Whether to deploy the action creator. - ///@param deployCaster Whether to deploy the caster. - ///@param creationThreshold The number of tokens required to create an action (set to 0 if not deploying action - /// creator). - ///@param minApprovalPct The minimum percentage of tokens required to approve an action (set to 0 if not deploying - /// caster). - ///@param minDisapprovalPct The minimum percentage of tokens required to disapprove an action (set to 0 if not - /// deploying caster). - function deployTokenVotingModule( - address token, - bool isERC20, - bool deployActionCreator, - bool deployCaster, - uint256 creationThreshold, - uint256 minApprovalPct, - uint256 minDisapprovalPct - ) public returns (address, address) { - return _deployTokenVotingModule( - ILlamaExecutor(msg.sender), - token, - isERC20, - deployActionCreator, - deployCaster, - creationThreshold, - minApprovalPct, - minDisapprovalPct - ); + ILlamaExecutor executor = ILlamaExecutor(address(this)); + ILlamaCore core = ILlamaCore(executor.LLAMA_CORE()); + ILlamaPolicy policy = ILlamaPolicy(core.policy()); + uint8 numRoles = policy.numRoles(); + string memory name; + isERC20 ? name = ERC20Votes(token).name() : name = ERC721Votes(token).name(); + if (actionCreator != address(0)) { + policy.initializeRole(RoleDescription.wrap(bytes32(abi.encodePacked("Action Creator Role: ", name)))); + policy.setRoleHolder(numRoles + 1, actionCreator, 1, type(uint64).max); } - - ///@notice A llama script that deploys a token voting module and inittializes/issues roles to the token voting action - /// creator and caster in a single function so it can be deployed in a single llama action. - ///@dev This contract must be authorized as a script in the core contract before it can be used (invoke the - /// `LlamaCore::setScriptAuthorization` function to authorize). - ///@dev This method CAN NOT be used in tandem with `deployTokenVotingModule`. You must use one or the other due to - /// this method requring the contract to be authorized as a script. - ///@param token The address of the token to be used for voting. - ///@param isERC20 Whether the token is an ERC20 or ERC721. - ///@param deployActionCreator Whether to deploy the action creator. - ///@param deployCaster Whether to deploy the caster. - ///@param creationThreshold The number of tokens required to create an action (set to 0 if not deploying action - /// creator). - ///@param minApprovalPct The minimum percentage of tokens required to approve an action (set to 0 if not deploying - /// caster). - ///@param minDisapprovalPct The minimum percentage of tokens required to disapprove an action (set to 0 if not - /// deploying caster). - function delegateCallDeployTokenVotingModuleWithRoles( - address token, - bool isERC20, - bool deployActionCreator, - bool deployCaster, - uint256 creationThreshold, - uint256 minApprovalPct, - uint256 minDisapprovalPct - ) public onlyDelegateCall { - (address actionCreator, address caster) = _deployTokenVotingModule( - ILlamaExecutor(address(this)), - token, - isERC20, - deployActionCreator, - deployCaster, - creationThreshold, - minApprovalPct, - minDisapprovalPct - ); - - ILlamaExecutor executor = ILlamaExecutor(address(this)); - ILlamaCore core = ILlamaCore(executor.LLAMA_CORE()); - ILlamaPolicy policy = ILlamaPolicy(core.policy()); - uint8 numRoles = policy.numRoles(); - string memory name; - isERC20 ? name = ERC20Votes(token).name() : name = ERC721Votes(token).name(); - if (actionCreator != address(0)) { - policy.initializeRole(RoleDescription.wrap(bytes32(abi.encodePacked("Action Creator Role: ", name)))); - policy.setRoleHolder(numRoles + 1, actionCreator, 1, type(uint64).max); - } - if (caster != address(0)) { - policy.initializeRole(RoleDescription.wrap(bytes32(abi.encodePacked("Caster Role: ", name)))); - policy.setRoleHolder(actionCreator == address(0) ? numRoles + 1 : numRoles + 2, caster, 1, type(uint64).max); - } + if (caster != address(0)) { + policy.initializeRole(RoleDescription.wrap(bytes32(abi.encodePacked("Caster Role: ", name)))); + policy.setRoleHolder(actionCreator == address(0) ? numRoles + 1 : numRoles + 2, caster, 1, type(uint64).max); } + } - // ==================================== - // ======== Internal Functions ======== - // ==================================== + // ==================================== + // ======== Internal Functions ======== + // ==================================== - function _deployTokenVotingModule( - ILlamaExecutor executor, - address token, - bool isERC20, - bool deployActionCreator, - bool deployCaster, - uint256 creationThreshold, - uint256 minApprovalPct, - uint256 minDisapprovalPct - ) internal returns (address actionCreator, address caster) { - if (!deployActionCreator && !deployCaster) revert NoModulesDeployed(); - ILlamaCore core = ILlamaCore(executor.LLAMA_CORE()); - if (isERC20) { - if (deployActionCreator) { - actionCreator = - address(_deployERC20TokenholderActionCreator(ERC20Votes(token), core, creationThreshold)); - } - if (deployCaster) { - caster = address( - _deployERC20TokenholderCaster(ERC20Votes(token), core, 0, minApprovalPct, minDisapprovalPct) - ); - } - } else { - if (deployActionCreator) { - actionCreator = - address(_deployERC721TokenholderActionCreator(ERC721Votes(token), core, creationThreshold)); - } - if (deployCaster) { - caster = address( - _deployERC721TokenholderCaster(ERC721Votes(token), core, 0, minApprovalPct, minDisapprovalPct) - ); - } - } + function _deployTokenVotingModule( + ILlamaExecutor executor, + address token, + bool isERC20, + bool deployActionCreator, + bool deployCaster, + uint256 creationThreshold, + uint256 minApprovalPct, + uint256 minDisapprovalPct + ) internal returns (address actionCreator, address caster) { + if (!deployActionCreator && !deployCaster) revert NoModulesDeployed(); + ILlamaCore core = ILlamaCore(executor.LLAMA_CORE()); + if (isERC20) { + if (deployActionCreator) { + actionCreator = address(_deployERC20TokenholderActionCreator(ERC20Votes(token), core, creationThreshold)); + } + if (deployCaster) { + caster = address(_deployERC20TokenholderCaster(ERC20Votes(token), core, 0, minApprovalPct, minDisapprovalPct)); + } + } else { + if (deployActionCreator) { + actionCreator = address(_deployERC721TokenholderActionCreator(ERC721Votes(token), core, creationThreshold)); + } + if (deployCaster) { + caster = address(_deployERC721TokenholderCaster(ERC721Votes(token), core, 0, minApprovalPct, minDisapprovalPct)); + } } + } - function _deployERC20TokenholderActionCreator(ERC20Votes token, ILlamaCore llamaCore, uint256 creationThreshold) - internal - returns (ERC20TokenholderActionCreator actionCreator) - { - actionCreator = new ERC20TokenholderActionCreator(token, llamaCore, creationThreshold); - emit ERC20TokenholderActionCreatorCreated(address(actionCreator), address(token)); - } + function _deployERC20TokenholderActionCreator(ERC20Votes token, ILlamaCore llamaCore, uint256 creationThreshold) + internal + returns (ERC20TokenholderActionCreator actionCreator) + { + actionCreator = new ERC20TokenholderActionCreator(token, llamaCore, creationThreshold); + emit ERC20TokenholderActionCreatorCreated(address(actionCreator), address(token)); + } - function _deployERC721TokenholderActionCreator(ERC721Votes token, ILlamaCore llamaCore, uint256 creationThreshold) - internal - returns (ERC721TokenholderActionCreator actionCreator) - { - actionCreator = new ERC721TokenholderActionCreator(token, llamaCore, creationThreshold); - emit ERC721TokenholderActionCreatorCreated(address(actionCreator), address(token)); - } + function _deployERC721TokenholderActionCreator(ERC721Votes token, ILlamaCore llamaCore, uint256 creationThreshold) + internal + returns (ERC721TokenholderActionCreator actionCreator) + { + actionCreator = new ERC721TokenholderActionCreator(token, llamaCore, creationThreshold); + emit ERC721TokenholderActionCreatorCreated(address(actionCreator), address(token)); + } - function _deployERC20TokenholderCaster( - ERC20Votes token, - ILlamaCore llamaCore, - uint8 role, - uint256 minApprovalPct, - uint256 minDisapprovalPct - ) internal returns (ERC20TokenholderCaster caster) { - caster = new ERC20TokenholderCaster(token, llamaCore, role, minApprovalPct, minDisapprovalPct); - emit ERC20TokenholderCasterCreated(address(caster), address(token), minApprovalPct, minDisapprovalPct); - } + function _deployERC20TokenholderCaster( + ERC20Votes token, + ILlamaCore llamaCore, + uint8 role, + uint256 minApprovalPct, + uint256 minDisapprovalPct + ) internal returns (ERC20TokenholderCaster caster) { + caster = new ERC20TokenholderCaster(token, llamaCore, role, minApprovalPct, minDisapprovalPct); + emit ERC20TokenholderCasterCreated(address(caster), address(token), minApprovalPct, minDisapprovalPct); + } - function _deployERC721TokenholderCaster( - ERC721Votes token, - ILlamaCore llamaCore, - uint8 role, - uint256 minApprovalPct, - uint256 minDisapprovalPct - ) internal returns (ERC721TokenholderCaster caster) { - caster = new ERC721TokenholderCaster(token, llamaCore, role, minApprovalPct, minDisapprovalPct); - emit ERC721TokenholderCasterCreated(address(caster), address(token), minApprovalPct, minDisapprovalPct); - } + function _deployERC721TokenholderCaster( + ERC721Votes token, + ILlamaCore llamaCore, + uint8 role, + uint256 minApprovalPct, + uint256 minDisapprovalPct + ) internal returns (ERC721TokenholderCaster caster) { + caster = new ERC721TokenholderCaster(token, llamaCore, role, minApprovalPct, minDisapprovalPct); + emit ERC721TokenholderCasterCreated(address(caster), address(token), minApprovalPct, minDisapprovalPct); + } }