Skip to content
This repository has been archived by the owner on Nov 27, 2024. It is now read-only.

refactor: combine ERC-20 and ERC-721 voting token implementations and turn adapter into minimal proxy #65

Merged
merged 24 commits into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 0 additions & 24 deletions src/token-voting/ILlamaTokenClockAdapter.sol

This file was deleted.

37 changes: 7 additions & 30 deletions src/token-voting/LlamaERC20TokenActionCreator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,14 @@ pragma solidity ^0.8.23;
import {ERC20Votes} from "@openzeppelin/token/ERC20/extensions/ERC20Votes.sol";

import {ILlamaCore} from "src/interfaces/ILlamaCore.sol";
import {ILlamaTokenClockAdapter} from "src/token-voting/ILlamaTokenClockAdapter.sol";
import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol";
import {LlamaTokenActionCreator} from "src/token-voting/LlamaTokenActionCreator.sol";

/// @title LlamaERC20TokenActionCreator
/// @author Llama ([email protected])
/// @notice This contract lets holders of a specified `ERC20Votes` token create actions on a llama instance if their
/// token balance is greater than or equal to the creation threshold.
contract LlamaERC20TokenActionCreator is LlamaTokenActionCreator {
/// @notice The ERC20 token to be used for voting.
ERC20Votes public token;

/// @dev This contract is deployed as a minimal proxy from the factory's `deploy` function. The
/// `_disableInitializers` locks the implementation (logic) contract, preventing any future initialization of it.
constructor() {
Expand All @@ -24,38 +21,18 @@ contract LlamaERC20TokenActionCreator is LlamaTokenActionCreator {
/// @notice Initializes a new `LlamaERC20TokenActionCreator` clone.
/// @dev This function is called by the `deploy` function in the `LlamaTokenVotingFactory` contract.
/// The `initializer` modifier ensures that this function can be invoked at most once.
/// @param _token The ERC20 token to be used for voting.
/// @param _llamaCore The `LlamaCore` contract for this Llama instance.
/// @param _role The role used by this contract to cast approvals and disapprovals.
/// @param _creationThreshold The default number of tokens required to create an action. This must
/// be in the same decimals as the token. For example, if the token has 18 decimals and you want a
/// creation threshold of 1000 tokens, pass in 1000e18.
function initialize(
ERC20Votes _token,
ILlamaCore _llamaCore,
ILlamaTokenClockAdapter _clockAdapter,
uint8 _role,
uint256 _creationThreshold
) external initializer {
__initializeLlamaTokenActionCreatorMinimalProxy(_llamaCore, _clockAdapter, _role, _creationThreshold);
token = _token;
uint256 totalSupply = token.getPastTotalSupply(_currentTimepointMinusOne());
function initialize(ILlamaCore _llamaCore, ILlamaTokenAdapter _tokenAdapter, uint8 _role, uint256 _creationThreshold)
external
initializer
{
__initializeLlamaTokenActionCreatorMinimalProxy(_llamaCore, _tokenAdapter, _role, _creationThreshold);
uint256 totalSupply = tokenAdapter.getPastTotalSupply(tokenAdapter.clock() - 1);
if (totalSupply == 0) revert InvalidTokenAddress();
if (_creationThreshold > totalSupply) revert InvalidCreationThreshold();
}

/// @inheritdoc LlamaTokenActionCreator
function _getPastVotes(address account, uint48 timepoint) internal view virtual override returns (uint256) {
return token.getPastVotes(account, timepoint);
}

/// @inheritdoc LlamaTokenActionCreator
function _getPastTotalSupply(uint48 timepoint) internal view virtual override returns (uint256) {
return token.getPastTotalSupply(timepoint);
}

/// @inheritdoc LlamaTokenActionCreator
function _getClockMode() internal view virtual override returns (string memory clockmode) {
return token.CLOCK_MODE();
}
}
29 changes: 4 additions & 25 deletions src/token-voting/LlamaERC20TokenCaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,14 @@ pragma solidity ^0.8.23;
import {ERC20Votes} from "@openzeppelin/token/ERC20/extensions/ERC20Votes.sol";

import {ILlamaCore} from "src/interfaces/ILlamaCore.sol";
import {ILlamaTokenClockAdapter} from "src/token-voting/ILlamaTokenClockAdapter.sol";
import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol";
import {LlamaTokenCaster} from "src/token-voting/LlamaTokenCaster.sol";

/// @title LlamaERC20TokenCaster
/// @author Llama ([email protected])
/// @notice This contract lets holders of a given governance `ERC20Votes` token collectively cast an approval or
/// disapproval on created actions.
contract LlamaERC20TokenCaster is LlamaTokenCaster {
/// @notice The ERC20 token to be used for voting.
ERC20Votes public token;

/// @dev This contract is deployed as a minimal proxy from the factory's `deploy` function. The
/// `_disableInitializers` locks the implementation (logic) contract, preventing any future initialization of it.
constructor() {
Expand All @@ -24,37 +21,19 @@ contract LlamaERC20TokenCaster is LlamaTokenCaster {
/// @notice Initializes a new `LlamaERC20TokenCaster` clone.
/// @dev This function is called by the `deploy` function in the `LlamaTokenVotingFactory` contract.
/// The `initializer` modifier ensures that this function can be invoked at most once.
/// @param _token The ERC20 token to be used for voting.
/// @param _llamaCore The `LlamaCore` contract for this Llama instance.
/// @param _role The role used by this contract to cast approvals and disapprovals.
/// @param _voteQuorumPct The minimum % of votes required to submit an approval to `LlamaCore`.
/// @param _vetoQuorumPct The minimum % of vetoes required to submit a disapproval to `LlamaCore`.
function initialize(
ERC20Votes _token,
ILlamaCore _llamaCore,
ILlamaTokenClockAdapter _clockAdapter,
ILlamaTokenAdapter _tokenAdapter,
uint8 _role,
uint16 _voteQuorumPct,
uint16 _vetoQuorumPct
) external initializer {
__initializeLlamaTokenCasterMinimalProxy(_llamaCore, _clockAdapter, _role, _voteQuorumPct, _vetoQuorumPct);
token = _token;
uint256 totalSupply = token.getPastTotalSupply(_currentTimepointMinusOne());
__initializeLlamaTokenCasterMinimalProxy(_llamaCore, _tokenAdapter, _role, _voteQuorumPct, _vetoQuorumPct);
uint256 totalSupply = tokenAdapter.getPastTotalSupply(tokenAdapter.clock() - 1);
if (totalSupply == 0) revert InvalidTokenAddress();
}

/// @inheritdoc LlamaTokenCaster
function _getPastVotes(address account, uint48 timepoint) internal view virtual override returns (uint256) {
return token.getPastVotes(account, timepoint);
}

/// @inheritdoc LlamaTokenCaster
function _getPastTotalSupply(uint48 timepoint) internal view virtual override returns (uint256) {
return token.getPastTotalSupply(timepoint);
}

/// @inheritdoc LlamaTokenCaster
function _getClockMode() internal view virtual override returns (string memory clockmode) {
return token.CLOCK_MODE();
}
}
38 changes: 7 additions & 31 deletions src/token-voting/LlamaERC721TokenActionCreator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,14 @@ import {ERC721Votes} from "@openzeppelin/token/ERC721/extensions/ERC721Votes.sol
import {IERC721} from "@openzeppelin/token/ERC721/IERC721.sol";

import {ILlamaCore} from "src/interfaces/ILlamaCore.sol";
import {ILlamaTokenClockAdapter} from "src/token-voting/ILlamaTokenClockAdapter.sol";
import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol";
import {LlamaTokenActionCreator} from "src/token-voting/LlamaTokenActionCreator.sol";

/// @title LlamaERC721TokenActionCreator
/// @author Llama ([email protected])
/// @notice This contract lets holders of a given governance ERC721Votes token create actions on the llama instance if
/// they hold enough tokens.
contract LlamaERC721TokenActionCreator is LlamaTokenActionCreator {
/// @notice The ERC721 token to be used for voting.
ERC721Votes public token;

/// @dev This contract is deployed as a minimal proxy from the factory's `deploy` function. The
/// `_disableInitializers` locks the implementation (logic) contract, preventing any future initialization of it.
constructor() {
Expand All @@ -25,39 +22,18 @@ contract LlamaERC721TokenActionCreator is LlamaTokenActionCreator {
/// @notice Initializes a new `LlamaERC721TokenActionCreator` clone.
/// @dev This function is called by the `deploy` function in the `LlamaTokenVotingFactory` contract.
/// The `initializer` modifier ensures that this function can be invoked at most once.
/// @param _token The ERC721 token to be used for voting.
/// @param _llamaCore The `LlamaCore` contract for this Llama instance.
/// @param _role The role used by this contract to cast approvals and disapprovals.
/// @param _creationThreshold The default number of tokens required to create an action. This must
/// be in the same decimals as the token. For example, if the token has 18 decimals and you want a
/// creation threshold of 1000 tokens, pass in 1000e18.
function initialize(
ERC721Votes _token,
ILlamaCore _llamaCore,
ILlamaTokenClockAdapter _clockAdapter,
uint8 _role,
uint256 _creationThreshold
) external initializer {
__initializeLlamaTokenActionCreatorMinimalProxy(_llamaCore, _clockAdapter, _role, _creationThreshold);
token = _token;
if (!token.supportsInterface(type(IERC721).interfaceId)) revert InvalidTokenAddress();
uint256 totalSupply = token.getPastTotalSupply(_currentTimepointMinusOne());
function initialize(ILlamaCore _llamaCore, ILlamaTokenAdapter _tokenAdapter, uint8 _role, uint256 _creationThreshold)
external
initializer
{
__initializeLlamaTokenActionCreatorMinimalProxy(_llamaCore, _tokenAdapter, _role, _creationThreshold);
uint256 totalSupply = tokenAdapter.getPastTotalSupply(tokenAdapter.clock() - 1);
if (totalSupply == 0) revert InvalidTokenAddress();
if (_creationThreshold > totalSupply) revert InvalidCreationThreshold();
}

/// @inheritdoc LlamaTokenActionCreator
function _getPastVotes(address account, uint48 timepoint) internal view virtual override returns (uint256) {
return token.getPastVotes(account, timepoint);
}

/// @inheritdoc LlamaTokenActionCreator
function _getPastTotalSupply(uint48 timepoint) internal view virtual override returns (uint256) {
return token.getPastTotalSupply(timepoint);
}

/// @inheritdoc LlamaTokenActionCreator
function _getClockMode() internal view virtual override returns (string memory clockmode) {
return token.CLOCK_MODE();
}
}
30 changes: 4 additions & 26 deletions src/token-voting/LlamaERC721TokenCaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,14 @@ import {ERC721Votes} from "@openzeppelin/token/ERC721/extensions/ERC721Votes.sol
import {IERC721} from "@openzeppelin/token/ERC721/IERC721.sol";

import {ILlamaCore} from "src/interfaces/ILlamaCore.sol";
import {ILlamaTokenClockAdapter} from "src/token-voting/ILlamaTokenClockAdapter.sol";
import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol";
import {LlamaTokenCaster} from "src/token-voting/LlamaTokenCaster.sol";
/// @title LlamaERC721TokenCaster
/// @author Llama ([email protected])
/// @notice This contract lets holders of a given governance `ERC721Votes` token collectively cast an approval or
/// disapproval on created actions.

contract LlamaERC721TokenCaster is LlamaTokenCaster {
/// @notice The ERC721 token to be used for voting.
ERC721Votes public token;

/// @dev This contract is deployed as a minimal proxy from the factory's `deploy` function. The
/// `_disableInitializers` locks the implementation (logic) contract, preventing any future initialization of it.
constructor() {
Expand All @@ -25,38 +22,19 @@ contract LlamaERC721TokenCaster is LlamaTokenCaster {
/// @notice Initializes a new `LlamaERC721TokenCaster` clone.
/// @dev This function is called by the `deploy` function in the `LlamaTokenVotingFactory` contract.
/// The `initializer` modifier ensures that this function can be invoked at most once.
/// @param _token The ERC721 token to be used for voting.
/// @param _llamaCore The `LlamaCore` contract for this Llama instance.
/// @param _role The role used by this contract to cast approvals and disapprovals.
/// @param _voteQuorumPct The minimum % of votes required to submit an approval to `LlamaCore`.
/// @param _vetoQuorumPct The minimum % of vetoes required to submit a disapproval to `LlamaCore`.
function initialize(
ERC721Votes _token,
ILlamaCore _llamaCore,
ILlamaTokenClockAdapter _clockAdapter,
ILlamaTokenAdapter _tokenAdapter,
uint8 _role,
uint16 _voteQuorumPct,
uint16 _vetoQuorumPct
) external initializer {
__initializeLlamaTokenCasterMinimalProxy(_llamaCore, _clockAdapter, _role, _voteQuorumPct, _vetoQuorumPct);
token = _token;
if (!token.supportsInterface(type(IERC721).interfaceId)) revert InvalidTokenAddress();
uint256 totalSupply = token.getPastTotalSupply(_currentTimepointMinusOne());
__initializeLlamaTokenCasterMinimalProxy(_llamaCore, _tokenAdapter, _role, _voteQuorumPct, _vetoQuorumPct);
uint256 totalSupply = tokenAdapter.getPastTotalSupply(tokenAdapter.clock() - 1);
if (totalSupply == 0) revert InvalidTokenAddress();
}

/// @inheritdoc LlamaTokenCaster
function _getPastVotes(address account, uint48 timepoint) internal view virtual override returns (uint256) {
return token.getPastVotes(account, timepoint);
}

/// @inheritdoc LlamaTokenCaster
function _getPastTotalSupply(uint48 timepoint) internal view virtual override returns (uint256) {
return token.getPastTotalSupply(timepoint);
}

/// @inheritdoc LlamaTokenCaster
function _getClockMode() internal view virtual override returns (string memory clockmode) {
return token.CLOCK_MODE();
}
}
48 changes: 10 additions & 38 deletions src/token-voting/LlamaTokenActionCreator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {Initializable} from "@openzeppelin/proxy/utils/Initializable.sol";

import {ILlamaCore} from "src/interfaces/ILlamaCore.sol";
import {ILlamaStrategy} from "src/interfaces/ILlamaStrategy.sol";
import {ILlamaTokenClockAdapter} from "src/token-voting/ILlamaTokenClockAdapter.sol";
import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol";
import {Action, ActionInfo} from "src/lib/Structs.sol";
import {LlamaUtils} from "src/lib/LlamaUtils.sol";

Expand Down Expand Up @@ -89,7 +89,7 @@ abstract contract LlamaTokenActionCreator is Initializable {
ILlamaCore public llamaCore;

/// @notice The contract that manages the timepoints for this token voting module.
ILlamaTokenClockAdapter public clockAdapter;
ILlamaTokenAdapter public tokenAdapter;

/// @notice The default number of tokens required to create an action.
uint256 public creationThreshold;
Expand Down Expand Up @@ -118,15 +118,15 @@ abstract contract LlamaTokenActionCreator is Initializable {
/// creation threshold of 1000 tokens, pass in 1000e18.
function __initializeLlamaTokenActionCreatorMinimalProxy(
ILlamaCore _llamaCore,
ILlamaTokenClockAdapter _clockAdapter,
ILlamaTokenAdapter _tokenAdapter,
uint8 _role,
uint256 _creationThreshold
) internal {
if (_llamaCore.actionsCount() < 0) revert InvalidLlamaCoreAddress();
if (_role > _llamaCore.policy().numRoles()) revert RoleNotInitialized(_role);

llamaCore = _llamaCore;
clockAdapter = _clockAdapter;
tokenAdapter = _tokenAdapter;
role = _role;
_setActionThreshold(_creationThreshold);
}
Expand Down Expand Up @@ -215,7 +215,9 @@ abstract contract LlamaTokenActionCreator is Initializable {
/// @dev This must be in the same decimals as the token.
function setActionThreshold(uint256 _creationThreshold) external {
if (msg.sender != address(llamaCore.executor())) revert OnlyLlamaExecutor();
if (_creationThreshold > _getPastTotalSupply(_currentTimepointMinusOne())) revert InvalidCreationThreshold();
if (_creationThreshold > tokenAdapter.getPastTotalSupply(tokenAdapter.clock() - 1)) {
AustinGreen marked this conversation as resolved.
Show resolved Hide resolved
revert InvalidCreationThreshold();
}
_setActionThreshold(_creationThreshold);
}

Expand All @@ -242,10 +244,10 @@ abstract contract LlamaTokenActionCreator is Initializable {
bytes calldata data,
string memory description
) internal returns (uint256 actionId) {
/// @dev only timestamp mode is supported for now
_isClockModeSupported(); // reverts if clock mode is not supported
// Reverts if clock or CLOCK_MODE() has changed
tokenAdapter.checkIfInconsistentClock();

uint256 balance = _getPastVotes(tokenHolder, _currentTimepointMinusOne());
uint256 balance = tokenAdapter.getPastVotes(tokenHolder, tokenAdapter.clock() - 1);
AustinGreen marked this conversation as resolved.
Show resolved Hide resolved
if (balance < creationThreshold) revert InsufficientBalance(balance);

actionId = llamaCore.createAction(role, strategy, target, value, data, description);
Expand All @@ -266,36 +268,6 @@ abstract contract LlamaTokenActionCreator is Initializable {
emit ActionThresholdSet(_creationThreshold);
}

///@dev Reverts if the clock mode is not supported.
function _isClockModeSupported() internal view {
if (!_isClockModeTimestamp()) {
string memory clockMode = _getClockMode();
bool supported = clockAdapter.isClockModeSupported(clockMode);
if (!supported) revert ClockModeNotSupported(clockMode);
}
}

/// @dev Returns the current timepoint minus one.
function _currentTimepointMinusOne() internal view returns (uint48) {
if (_isClockModeTimestamp()) return LlamaUtils.toUint48(block.timestamp - 1);
return clockAdapter.clock() - 1;
}

// Returns true if the clock mode is timestamp
function _isClockModeTimestamp() internal view returns (bool) {
string memory clockMode = _getClockMode();
return keccak256(abi.encodePacked(clockMode)) == keccak256(abi.encodePacked("mode=timestamp"));
}

/// @dev Returns the number of votes for a given token holder at a given timestamp.
function _getPastVotes(address account, uint48 timepoint) internal view virtual returns (uint256) {}

/// @dev Returns the total supply of the token at a given timestamp.
function _getPastTotalSupply(uint48 timepoint) internal view virtual returns (uint256) {}

/// @dev Returns the clock mode of the token (https://eips.ethereum.org/EIPS/eip-6372).
function _getClockMode() internal view virtual returns (string memory) {}

/// @dev Returns the current nonce for a given tokenHolder and selector, and increments it. Used to prevent
/// replay attacks.
function _useNonce(address tokenHolder, bytes4 selector) internal returns (uint256 nonce) {
Expand Down
Loading
Loading