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

feat: support for minimal proxies #18

Merged
merged 45 commits into from
Dec 9, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
212d048
Initializable
0xrajath Dec 6, 2023
c717781
refactor
0xrajath Dec 6, 2023
f7012d2
fix
0xrajath Dec 6, 2023
c6bf3ce
refactor
0xrajath Dec 6, 2023
49560ef
script
0xrajath Dec 6, 2023
ae21b2e
factor
0xrajath Dec 7, 2023
890ad6c
support
0xrajath Dec 7, 2023
2e2a4ca
print
0xrajath Dec 7, 2023
912b830
Merge branch 'main' into rajath/minimal-proxies
0xrajath Dec 7, 2023
499dc3b
LlamaTokenVotingTestSetup
0xrajath Dec 7, 2023
2503407
LlamaTokenVotingTestSetup.setUp()
0xrajath Dec 7, 2023
0d956f6
sort of working factory test
0xrajath Dec 8, 2023
b3e13e8
cleanup
0xrajath Dec 8, 2023
a23f5be
cleanup
0xrajath Dec 8, 2023
cc5e1cf
factory constructor tests
0xrajath Dec 8, 2023
95caa83
initialize asserts
0xrajath Dec 8, 2023
78abecb
Merge branch 'main' into rajath/minimal-proxies
0xrajath Dec 8, 2023
295d652
tokenholder
0xrajath Dec 8, 2023
ae0b6a9
refactor
0xrajath Dec 8, 2023
adb8dba
refactor
0xrajath Dec 8, 2023
8ca6ed7
create action refactored
0xrajath Dec 8, 2023
d49271d
Mock Protocol
0xrajath Dec 8, 2023
11b608a
CreateActionBySig
0xrajath Dec 8, 2023
996a0ad
CancelAction
0xrajath Dec 8, 2023
a81e558
CancelActionBySig
0xrajath Dec 8, 2023
cdf0e47
SetActionThreshold
0xrajath Dec 8, 2023
a9f69f7
immutable role
0xrajath Dec 8, 2023
715c1a8
fix
0xrajath Dec 8, 2023
d1a1375
temporary fix
0xrajath Dec 8, 2023
797689c
bySig fixes
0xrajath Dec 8, 2023
4d72ca1
refactor
0xrajath Dec 9, 2023
6a97d1d
build fix
0xrajath Dec 9, 2023
548fe31
fix
0xrajath Dec 9, 2023
d22d0b8
fix
0xrajath Dec 9, 2023
3ea371b
CastApproval
0xrajath Dec 9, 2023
e0d42b2
CastApprovalBySig
0xrajath Dec 9, 2023
5a71cc0
CastDisapprovalBySig
0xrajath Dec 9, 2023
0386ba9
CastDisapprovalBySig
0xrajath Dec 9, 2023
349f943
SubmitApprovals
0xrajath Dec 9, 2023
145e145
SubmitDisapprovals
0xrajath Dec 9, 2023
ad1c74a
remove
0xrajath Dec 9, 2023
4e55101
remove unused imports
0xrajath Dec 9, 2023
8230703
ERC721TokenholderActionCreator
0xrajath Dec 9, 2023
7da51e2
ERC721TokenholderCasterTest
0xrajath Dec 9, 2023
571e414
cleanup
0xrajath Dec 9, 2023
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
48 changes: 46 additions & 2 deletions script/DeployLlamaTokenVotingFactory.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,60 @@ pragma solidity 0.8.23;
import {Script} from "forge-std/Script.sol";

import {DeployUtils} from "script/DeployUtils.sol";
import {ERC20TokenholderActionCreator} from "src/token-voting/ERC20TokenholderActionCreator.sol";
import {ERC20TokenholderCaster} from "src/token-voting/ERC20TokenholderCaster.sol";
import {ERC721TokenholderActionCreator} from "src/token-voting/ERC721TokenholderActionCreator.sol";
import {ERC721TokenholderCaster} from "src/token-voting/ERC721TokenholderCaster.sol";
import {LlamaTokenVotingFactory} from "src/token-voting/LlamaTokenVotingFactory.sol";

contract DeployLlamaFactory is Script {
// Logic contracts.
ERC20TokenholderActionCreator erc20TokenholderActionCreatorLogic;
ERC20TokenholderCaster erc20TokenholderCasterLogic;
ERC721TokenholderActionCreator erc721TokenholderActionCreatorLogic;
ERC721TokenholderCaster erc721TokenholderCasterLogic;

// Factory contracts.
LlamaTokenVotingFactory tokenVotingFactory;

function run() public {
DeployUtils.print(string.concat("Deploying Llama token voting factory to chain:", vm.toString(block.chainid)));
DeployUtils.print(
string.concat("Deploying Llama token voting factory and logic contracts to chain:", vm.toString(block.chainid))
);

vm.broadcast();
erc20TokenholderActionCreatorLogic = new ERC20TokenholderActionCreator();
DeployUtils.print(
string.concat(" ERC20TokenholderActionCreatorLogic: ", vm.toString(address(erc20TokenholderActionCreatorLogic)))
);

vm.broadcast();
erc20TokenholderCasterLogic = new ERC20TokenholderCaster();
DeployUtils.print(
string.concat(" ERC20TokenholderCasterLogic: ", vm.toString(address(erc20TokenholderCasterLogic)))
);

vm.broadcast();
erc721TokenholderActionCreatorLogic = new ERC721TokenholderActionCreator();
DeployUtils.print(
string.concat(
" ERC721TokenholderActionCreatorLogic: ", vm.toString(address(erc721TokenholderActionCreatorLogic))
)
);

vm.broadcast();
erc721TokenholderCasterLogic = new ERC721TokenholderCaster();
DeployUtils.print(
string.concat(" ERC721TokenholderCasterLogic: ", vm.toString(address(erc721TokenholderCasterLogic)))
);

vm.broadcast();
tokenVotingFactory = new LlamaTokenVotingFactory();
tokenVotingFactory = new LlamaTokenVotingFactory(
AustinGreen marked this conversation as resolved.
Show resolved Hide resolved
erc20TokenholderActionCreatorLogic,
erc20TokenholderCasterLogic,
erc721TokenholderActionCreatorLogic,
erc721TokenholderCasterLogic
);
DeployUtils.print(string.concat(" LlamaTokenVotingFactory: ", vm.toString(address(tokenVotingFactory))));
}
}
31 changes: 22 additions & 9 deletions src/token-voting/ERC20TokenholderActionCreator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,39 @@ import {ERC20Votes} from "@openzeppelin/token/ERC20/extensions/ERC20Votes.sol";
/// @notice This contract lets holders of a specified `ERC20Votes` token create actions on a llama instance if their
/// token balance is greater than or equal to the creation threshold.
contract ERC20TokenholderActionCreator is TokenholderActionCreator {
ERC20Votes public immutable TOKEN;
ERC20Votes public token;

constructor(ERC20Votes token, ILlamaCore llamaCore, uint256 _creationThreshold)
TokenholderActionCreator(llamaCore, _creationThreshold)
{
TOKEN = token;
uint256 totalSupply = TOKEN.totalSupply();
/// @dev This contract is deployed as a minimal proxy from the factory's `deployTokenVotingModule` function. The
/// `_disableInitializers` locks the implementation (logic) contract, preventing any future initialization of it.
constructor() {
_disableInitializers();
}

/// @notice Initializes a new `ERC20TokenholderActionCreator` clone.
/// @dev This function is called by the `deployTokenVotingModule` function in the `LlamaTokenVotingFactory` contract.
/// The `initializer` modifier ensures that this function can be invoked at most once.
/// @param _token The ERC20 token to be used for voting.
/// @param _llamaCore The `LlamaCore` contract for this Llama instance.
/// @param _creationThreshold The default number of tokens required to create an action. This must
/// be in the same decimals as the token. For example, if the token has 18 decimals and you want a
/// creation threshold of 1000 tokens, pass in 1000e18.
function initialize(ERC20Votes _token, ILlamaCore _llamaCore, uint256 _creationThreshold) external initializer {
__initializeTokenholderActionCreatorMinimalProxy(_llamaCore, _creationThreshold);
token = _token;
uint256 totalSupply = token.totalSupply();
if (totalSupply == 0) revert InvalidTokenAddress();
if (_creationThreshold > totalSupply) revert InvalidCreationThreshold();
}

function _getPastVotes(address account, uint256 timestamp) internal view virtual override returns (uint256) {
return TOKEN.getPastVotes(account, timestamp);
return token.getPastVotes(account, timestamp);
}

function _getPastTotalSupply(uint256 timestamp) internal view virtual override returns (uint256) {
return TOKEN.getPastTotalSupply(timestamp);
return token.getPastTotalSupply(timestamp);
}

function _getClockMode() internal view virtual override returns (string memory) {
return TOKEN.CLOCK_MODE();
return token.CLOCK_MODE();
}
}
37 changes: 28 additions & 9 deletions src/token-voting/ERC20TokenholderCaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,44 @@ import {ERC20Votes} from "@openzeppelin/token/ERC20/extensions/ERC20Votes.sol";
/// @notice This contract lets holders of a given governance ERC20Votes token cast approvals and disapprovals
/// on created actions.
contract ERC20TokenholderCaster is TokenholderCaster {
ERC20Votes public immutable TOKEN;
ERC20Votes public token;

constructor(ERC20Votes token, ILlamaCore llamaCore, uint8 role, uint256 minApprovalPct, uint256 minDisapprovalPct)
TokenholderCaster(llamaCore, role, minApprovalPct, minDisapprovalPct)
{
TOKEN = token;
uint256 totalSupply = TOKEN.totalSupply();
/// @dev This contract is deployed as a minimal proxy from the factory's `deployTokenVotingModule` function. The
/// `_disableInitializers` locks the implementation (logic) contract, preventing any future initialization of it.
constructor() {
_disableInitializers();
}

/// @notice Initializes a new `ERC20TokenholderCaster` clone.
/// @dev This function is called by the `deployTokenVotingModule` function in the `LlamaTokenVotingFactory` contract.
/// The `initializer` modifier ensures that this function can be invoked at most once.
/// @param _token The ERC20 token to be used for voting.
/// @param _llamaCore The `LlamaCore` contract for this Llama instance.
/// @param _role The role used by this contract to cast approvals and disapprovals.
/// @param _minApprovalPct The minimum % of approvals required to submit approvals to `LlamaCore`.
/// @param _minDisapprovalPct The minimum % of disapprovals required to submit disapprovals to `LlamaCore`.
function initialize(
ERC20Votes _token,
ILlamaCore _llamaCore,
uint8 _role,
uint256 _minApprovalPct,
uint256 _minDisapprovalPct
) external initializer {
__initializeTokenholderCasterMinimalProxy(_llamaCore, _role, _minApprovalPct, _minDisapprovalPct);
token = _token;
uint256 totalSupply = token.totalSupply();
if (totalSupply == 0) revert InvalidTokenAddress();
}

function _getPastVotes(address account, uint256 timestamp) internal view virtual override returns (uint256) {
return TOKEN.getPastVotes(account, timestamp);
return token.getPastVotes(account, timestamp);
}

function _getPastTotalSupply(uint256 timestamp) internal view virtual override returns (uint256) {
return TOKEN.getPastTotalSupply(timestamp);
return token.getPastTotalSupply(timestamp);
}

function _getClockMode() internal view virtual override returns (string memory) {
return TOKEN.CLOCK_MODE();
return token.CLOCK_MODE();
}
}
33 changes: 23 additions & 10 deletions src/token-voting/ERC721TokenholderActionCreator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,40 @@ import {IERC721} from "@openzeppelin/token/ERC721/IERC721.sol";
/// @notice This contract lets holders of a given governance ERC721Votes token create actions on the llama instance if
/// they hold enough tokens.
contract ERC721TokenholderActionCreator is TokenholderActionCreator {
ERC721Votes public immutable TOKEN;
ERC721Votes public token;

constructor(ERC721Votes token, ILlamaCore llamaCore, uint256 _creationThreshold)
TokenholderActionCreator(llamaCore, _creationThreshold)
{
TOKEN = token;
if (!TOKEN.supportsInterface(type(IERC721).interfaceId)) revert InvalidTokenAddress();
uint256 totalSupply = TOKEN.getPastTotalSupply(block.timestamp - 1);
/// @dev This contract is deployed as a minimal proxy from the factory's `deployTokenVotingModule` function. The
/// `_disableInitializers` locks the implementation (logic) contract, preventing any future initialization of it.
constructor() {
_disableInitializers();
}

/// @notice Initializes a new `ERC721TokenholderActionCreator` clone.
/// @dev This function is called by the `deployTokenVotingModule` function in the `LlamaTokenVotingFactory` contract.
/// The `initializer` modifier ensures that this function can be invoked at most once.
/// @param _token The ERC721 token to be used for voting.
/// @param _llamaCore The `LlamaCore` contract for this Llama instance.
/// @param _creationThreshold The default number of tokens required to create an action. This must
/// be in the same decimals as the token. For example, if the token has 18 decimals and you want a
/// creation threshold of 1000 tokens, pass in 1000e18.
function initialize(ERC721Votes _token, ILlamaCore _llamaCore, uint256 _creationThreshold) external initializer {
__initializeTokenholderActionCreatorMinimalProxy(_llamaCore, _creationThreshold);
token = _token;
if (!token.supportsInterface(type(IERC721).interfaceId)) revert InvalidTokenAddress();
uint256 totalSupply = token.getPastTotalSupply(block.timestamp - 1);
if (totalSupply == 0) revert InvalidTokenAddress();
if (_creationThreshold > totalSupply) revert InvalidCreationThreshold();
}

function _getPastVotes(address account, uint256 timestamp) internal view virtual override returns (uint256) {
return TOKEN.getPastVotes(account, timestamp);
return token.getPastVotes(account, timestamp);
}

function _getPastTotalSupply(uint256 timestamp) internal view virtual override returns (uint256) {
return TOKEN.getPastTotalSupply(timestamp);
return token.getPastTotalSupply(timestamp);
}

function _getClockMode() internal view virtual override returns (string memory) {
return TOKEN.CLOCK_MODE();
return token.CLOCK_MODE();
}
}
37 changes: 28 additions & 9 deletions src/token-voting/ERC721TokenholderCaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,43 @@ import {IERC721} from "@openzeppelin/token/ERC721/IERC721.sol";
/// @notice This contract lets holders of a given governance ERC721Votes token cast approvals and disapprovals
/// on created actions.
contract ERC721TokenholderCaster is TokenholderCaster {
ERC721Votes public immutable TOKEN;
ERC721Votes public token;

constructor(ERC721Votes token, ILlamaCore llamaCore, uint8 role, uint256 minApprovalPct, uint256 minDisapprovalPct)
TokenholderCaster(llamaCore, role, minApprovalPct, minDisapprovalPct)
{
TOKEN = token;
if (!TOKEN.supportsInterface(type(IERC721).interfaceId)) revert InvalidTokenAddress();
/// @dev This contract is deployed as a minimal proxy from the factory's `deployTokenVotingModule` function. The
/// `_disableInitializers` locks the implementation (logic) contract, preventing any future initialization of it.
constructor() {
_disableInitializers();
}

/// @notice Initializes a new `ERC721TokenholderCaster` clone.
/// @dev This function is called by the `deployTokenVotingModule` function in the `LlamaTokenVotingFactory` contract.
/// The `initializer` modifier ensures that this function can be invoked at most once.
/// @param _token The ERC721 token to be used for voting.
/// @param _llamaCore The `LlamaCore` contract for this Llama instance.
/// @param _role The role used by this contract to cast approvals and disapprovals.
/// @param _minApprovalPct The minimum % of approvals required to submit approvals to `LlamaCore`.
/// @param _minDisapprovalPct The minimum % of disapprovals required to submit disapprovals to `LlamaCore`.
function initialize(
ERC721Votes _token,
ILlamaCore _llamaCore,
uint8 _role,
uint256 _minApprovalPct,
uint256 _minDisapprovalPct
) external initializer {
__initializeTokenholderCasterMinimalProxy(_llamaCore, _role, _minApprovalPct, _minDisapprovalPct);
token = _token;
if (!token.supportsInterface(type(IERC721).interfaceId)) revert InvalidTokenAddress();
}

function _getPastVotes(address account, uint256 timestamp) internal view virtual override returns (uint256) {
return TOKEN.getPastVotes(account, timestamp);
return token.getPastVotes(account, timestamp);
}

function _getPastTotalSupply(uint256 timestamp) internal view virtual override returns (uint256) {
return TOKEN.getPastTotalSupply(timestamp);
return token.getPastTotalSupply(timestamp);
}

function _getClockMode() internal view virtual override returns (string memory) {
return TOKEN.CLOCK_MODE();
return token.CLOCK_MODE();
}
}
55 changes: 51 additions & 4 deletions src/token-voting/LlamaTokenVotingFactory.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import {Clones} from "@openzeppelin/proxy/Clones.sol";

import {ILlamaCore} from "src/interfaces/ILlamaCore.sol";
import {ILlamaExecutor} from "src/interfaces/ILlamaExecutor.sol";
import {ERC20TokenholderActionCreator} from "src/token-voting/ERC20TokenholderActionCreator.sol";
Expand All @@ -25,6 +27,31 @@ contract LlamaTokenVotingFactory {
address caster, address indexed token, uint256 minApprovalPct, uint256 minDisapprovalPct
);

/// @notice The ERC20 Tokenholder Action Creator (logic) contract.
ERC20TokenholderActionCreator public immutable ERC20_TOKENHOLDER_ACTION_CREATOR_LOGIC;

/// @notice The ERC20 Tokenholder Caster (logic) contract.
ERC20TokenholderCaster public immutable ERC20_TOKENHOLDER_CASTER_LOGIC;

/// @notice The ERC721 Tokenholder Action Creator (logic) contract.
ERC721TokenholderActionCreator public immutable ERC721_TOKENHOLDER_ACTION_CREATOR_LOGIC;

/// @notice The ERC721 Tokenholder Caster (logic) contract.
ERC721TokenholderCaster public immutable ERC721_TOKENHOLDER_CASTER_LOGIC;

/// @dev Set the logic contracts used to deploy Token Voting modules.
constructor(
ERC20TokenholderActionCreator erc20TokenholderActionCreatorLogic,
ERC20TokenholderCaster erc20TokenholderCasterLogic,
ERC721TokenholderActionCreator erc721TokenholderActionCreatorLogic,
ERC721TokenholderCaster erc721TokenholderCasterLogic
) {
ERC20_TOKENHOLDER_ACTION_CREATOR_LOGIC = erc20TokenholderActionCreatorLogic;
ERC20_TOKENHOLDER_CASTER_LOGIC = erc20TokenholderCasterLogic;
ERC721_TOKENHOLDER_ACTION_CREATOR_LOGIC = erc721TokenholderActionCreatorLogic;
ERC721_TOKENHOLDER_CASTER_LOGIC = erc721TokenholderCasterLogic;
}

///@notice Deploys a token voting module in a single function so it can be deployed in a single llama action.
///@dev This method CAN NOT be used in tandem with `delegateCallDeployTokenVotingModuleWithRoles`. You must use one or
/// the other due to the delegateCallDeployTokenVotingModuleWithRoles method requring the contract to be authorized as
Expand Down Expand Up @@ -62,15 +89,25 @@ contract LlamaTokenVotingFactory {
internal
returns (ERC20TokenholderActionCreator actionCreator)
{
actionCreator = new ERC20TokenholderActionCreator(token, llamaCore, creationThreshold);
actionCreator = ERC20TokenholderActionCreator(
Clones.cloneDeterministic(
address(ERC20_TOKENHOLDER_ACTION_CREATOR_LOGIC), keccak256(abi.encodePacked(address(token), msg.sender))
)
);
actionCreator.initialize(token, llamaCore, creationThreshold);
emit ERC20TokenholderActionCreatorCreated(address(actionCreator), address(token));
}

function _deployERC721TokenholderActionCreator(ERC721Votes token, ILlamaCore llamaCore, uint256 creationThreshold)
internal
returns (ERC721TokenholderActionCreator actionCreator)
{
actionCreator = new ERC721TokenholderActionCreator(token, llamaCore, creationThreshold);
actionCreator = ERC721TokenholderActionCreator(
Clones.cloneDeterministic(
address(ERC721_TOKENHOLDER_ACTION_CREATOR_LOGIC), keccak256(abi.encodePacked(address(token), msg.sender))
)
);
actionCreator.initialize(token, llamaCore, creationThreshold);
emit ERC721TokenholderActionCreatorCreated(address(actionCreator), address(token));
}

Expand All @@ -81,7 +118,12 @@ contract LlamaTokenVotingFactory {
uint256 minApprovalPct,
uint256 minDisapprovalPct
) internal returns (ERC20TokenholderCaster caster) {
caster = new ERC20TokenholderCaster(token, llamaCore, role, minApprovalPct, minDisapprovalPct);
caster = ERC20TokenholderCaster(
Clones.cloneDeterministic(
address(ERC20_TOKENHOLDER_CASTER_LOGIC), keccak256(abi.encodePacked(address(token), msg.sender))
AustinGreen marked this conversation as resolved.
Show resolved Hide resolved
)
);
caster.initialize(token, llamaCore, role, minApprovalPct, minDisapprovalPct);
emit ERC20TokenholderCasterCreated(address(caster), address(token), minApprovalPct, minDisapprovalPct);
}

Expand All @@ -92,7 +134,12 @@ contract LlamaTokenVotingFactory {
uint256 minApprovalPct,
uint256 minDisapprovalPct
) internal returns (ERC721TokenholderCaster caster) {
caster = new ERC721TokenholderCaster(token, llamaCore, role, minApprovalPct, minDisapprovalPct);
caster = ERC721TokenholderCaster(
Clones.cloneDeterministic(
address(ERC721_TOKENHOLDER_CASTER_LOGIC), keccak256(abi.encodePacked(address(token), msg.sender))
)
);
caster.initialize(token, llamaCore, role, minApprovalPct, minDisapprovalPct);
emit ERC721TokenholderCasterCreated(address(caster), address(token), minApprovalPct, minDisapprovalPct);
}
}
Loading
Loading