diff --git a/justfile b/justfile index cd8c8a5..da78346 100644 --- a/justfile +++ b/justfile @@ -18,8 +18,14 @@ run-script script_name flags='' sig='' args='': -vvvv {{flags}} mv _test test +run-deploy-voting-module-script flags: (run-script 'DeployLlamaTokenVotingModule' flags '--sig "run(address,string)"' '$SCRIPT_DEPLOYER_ADDRESS "tokenVotingModuleConfig.json"') + dry-run-deploy: (run-script 'DeployLlamaTokenVotingFactory') deploy: (run-script 'DeployLlamaTokenVotingFactory' '--broadcast --verify --build-info --build-info-path build_info') +dry-run-deploy-voting-module: (run-deploy-voting-module-script '') + +deploy-voting-module: (run-deploy-voting-module-script '--broadcast --verify') + verify: (run-script 'DeployLlamaTokenVotingFactory' '--verify --resume') diff --git a/script/DeployLlamaTokenVotingModule.s.sol b/script/DeployLlamaTokenVotingModule.s.sol new file mode 100644 index 0000000..5876969 --- /dev/null +++ b/script/DeployLlamaTokenVotingModule.s.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +import {Script, stdJson} from "forge-std/Script.sol"; + +import {DeployUtils} from "script/DeployUtils.sol"; + +import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; +import {CasterConfig, LlamaTokenVotingConfig} from "src/lib/Structs.sol"; +import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; +import {LlamaTokenActionCreator} from "src/token-voting/LlamaTokenActionCreator.sol"; +import {LlamaTokenCaster} from "src/token-voting/LlamaTokenCaster.sol"; +import {LlamaTokenVotingFactory} from "src/token-voting/LlamaTokenVotingFactory.sol"; +import {DeployUtils} from "script/DeployUtils.sol"; + +contract DeployLlamaTokenVotingModule is Script { + using stdJson for string; + + function run(address deployer, string memory configFile) public { + string memory jsonInput = DeployUtils.readScriptInput(configFile); + + LlamaTokenVotingFactory factory = LlamaTokenVotingFactory(jsonInput.readAddress(".factory")); + + DeployUtils.print(string.concat("Deploying Llama token voting module to chain:", vm.toString(block.chainid))); + + CasterConfig memory casterConfig = CasterConfig( + abi.decode(jsonInput.parseRaw(".casterConfig.voteQuorumPct"), (uint16)), + abi.decode(jsonInput.parseRaw(".casterConfig.vetoQuorumPct"), (uint16)), + abi.decode(jsonInput.parseRaw(".casterConfig.delayPeriodPct"), (uint16)), + abi.decode(jsonInput.parseRaw(".casterConfig.castingPeriodPct"), (uint16)), + abi.decode(jsonInput.parseRaw(".casterConfig.submissionPeriodPct"), (uint16)) + ); + + LlamaTokenVotingConfig memory config = LlamaTokenVotingConfig( + ILlamaCore(jsonInput.readAddress(".llamaCore")), + ILlamaTokenAdapter(jsonInput.readAddress(".tokenAdapterLogic")), + DeployUtils.readTokenAdapter(jsonInput), + abi.decode(jsonInput.parseRaw(".nonce"), (uint256)), + abi.decode(jsonInput.parseRaw(".actionCreatorRole"), (uint8)), + abi.decode(jsonInput.parseRaw(".casterRole"), (uint8)), + abi.decode(jsonInput.parseRaw(".creationThreshold"), (uint256)), + casterConfig + ); + + vm.broadcast(deployer); + (LlamaTokenActionCreator actionCreator, LlamaTokenCaster caster) = factory.deploy(config); + + DeployUtils.print("Successfully deployed a new Llama token voting module"); + DeployUtils.print(string.concat(" LlamaTokenActionCreator: ", vm.toString(address(actionCreator)))); + DeployUtils.print(string.concat(" LlamaTokenCaster: ", vm.toString(address(caster)))); + } +} diff --git a/script/DeployUtils.sol b/script/DeployUtils.sol index 9a85f35..16486e8 100644 --- a/script/DeployUtils.sol +++ b/script/DeployUtils.sol @@ -1,13 +1,32 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.23; -import {console2} from "forge-std/Script.sol"; +import {VmSafe} from "forge-std/Vm.sol"; +import {console2, stdJson} from "forge-std/Script.sol"; + +import {LlamaTokenAdapterVotesTimestamp} from "src/token-voting/token-adapters/LlamaTokenAdapterVotesTimestamp.sol"; library DeployUtils { + using stdJson for string; + + address internal constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); + VmSafe internal constant VM = VmSafe(VM_ADDRESS); + + function readScriptInput(string memory filename) internal view returns (string memory) { + string memory inputDir = string.concat(VM.projectRoot(), "/script/input/"); + string memory chainDir = string.concat(VM.toString(block.chainid), "/"); + return VM.readFile(string.concat(inputDir, chainDir, filename)); + } + function print(string memory message) internal view { // Avoid getting flooded with logs during tests. Note that fork tests will show logs with this // approach, because there's currently no way to tell which environment we're in, e.g. script // or test. This is being tracked in https://github.com/foundry-rs/foundry/issues/2900. if (block.chainid != 31_337) console2.log(message); } + + function readTokenAdapter(string memory jsonInput) internal pure returns (bytes memory) { + address tokenAddress = jsonInput.readAddress(".tokenAddress"); + return abi.encode(LlamaTokenAdapterVotesTimestamp.Config(tokenAddress)); + } } diff --git a/script/input/11155111/tokenVotingModuleConfig.json b/script/input/11155111/tokenVotingModuleConfig.json new file mode 100644 index 0000000..31eaad1 --- /dev/null +++ b/script/input/11155111/tokenVotingModuleConfig.json @@ -0,0 +1,18 @@ +{ + "comment": "This is an example token voting module deployment on Sepolia.", + "factory": "0x2997f4D6899DC91dE9Ae0FcD98b49CA88b8Fc85e", + "llamaCore": "0xc68046794327490F953EA15522367FFBA0b64f86", + "tokenAdapterLogic": "0x88D63b8c5F8C3e95743F1d26Df8aDd0669614278", + "tokenAddress": "0xf44d44a54440F22e5DC5adb7efA3233645f04007", + "nonce": 0, + "actionCreatorRole": 0, + "casterRole": 0, + "creationThreshold": 10000e18, + "casterConfig": { + "voteQuorumPct": 2000, + "vetoQuorumPct": 1000, + "delayPeriodPct": 2500, + "castingPeriodPct": 5000, + "submissionPeriodPct": 2500 + } +} diff --git a/src/lib/Structs.sol b/src/lib/Structs.sol index 1b30696..08aaabb 100644 --- a/src/lib/Structs.sol +++ b/src/lib/Structs.sol @@ -2,8 +2,10 @@ pragma solidity ^0.8.23; import {ILlamaAccount} from "src/interfaces/ILlamaAccount.sol"; +import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; import {ILlamaActionGuard} from "src/interfaces/ILlamaActionGuard.sol"; import {ILlamaStrategy} from "src/interfaces/ILlamaStrategy.sol"; +import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; import {RoleDescription} from "src/lib/UDVTs.sol"; /// @dev Data required to create an action. @@ -34,6 +36,18 @@ struct Action { uint96 totalDisapprovals; // The total quantity of policyholder disapprovals. } +/// @dev Configuration of a new Llama token voting module. +struct LlamaTokenVotingConfig { + ILlamaCore llamaCore; // The address of the Llama core. + ILlamaTokenAdapter tokenAdapterLogic; // The logic contract of the token adapter. + bytes adapterConfig; // The configuration of the token adapter. + uint256 nonce; // The nonce to be used in the salt of the deterministic deployment. + uint8 actionCreatorRole; // The role required by the `LlamaTokenActionCreator` to create an action. + uint8 casterRole; // The role required by the `LlamaTokenCaster` to cast approvals and disapprovals. + uint256 creationThreshold; // The number of tokens required to create an action. + CasterConfig casterConfig; // The quorum and period data for the `LlamaTokenCaster`. +} + /// @dev Quorum and period data for token voting caster contracts. struct CasterConfig { uint16 voteQuorumPct; diff --git a/src/token-voting/LlamaTokenActionCreator.sol b/src/token-voting/LlamaTokenActionCreator.sol index b0b9c3f..a7906b7 100644 --- a/src/token-voting/LlamaTokenActionCreator.sol +++ b/src/token-voting/LlamaTokenActionCreator.sol @@ -113,7 +113,7 @@ contract LlamaTokenActionCreator is Initializable { _disableInitializers(); } - /// @notice Initializes a new `LlamaERC20TokenActionCreator` clone. + /// @notice Initializes a new `LlamaTokenActionCreator` clone. /// @dev This function is called by the `deploy` function in the `LlamaTokenVotingFactory` contract. /// The `initializer` modifier ensures that this function can be invoked at most once. /// @param _llamaCore The `LlamaCore` contract for this Llama instance. diff --git a/src/token-voting/LlamaTokenCaster.sol b/src/token-voting/LlamaTokenCaster.sol index 7383b64..1864310 100644 --- a/src/token-voting/LlamaTokenCaster.sol +++ b/src/token-voting/LlamaTokenCaster.sol @@ -195,7 +195,7 @@ contract LlamaTokenCaster is Initializable { _disableInitializers(); } - /// @notice Initializes a new `LlamaERC20TokenCaster` clone. + /// @notice Initializes a new `LlamaTokenCaster` clone. /// @dev This function is called by the `deploy` function in the `LlamaTokenVotingFactory` contract. /// The `initializer` modifier ensures that this function can be invoked at most once. /// @param _llamaCore The `LlamaCore` contract for this Llama instance. diff --git a/src/token-voting/LlamaTokenVotingFactory.sol b/src/token-voting/LlamaTokenVotingFactory.sol index a01328a..437cd09 100644 --- a/src/token-voting/LlamaTokenVotingFactory.sol +++ b/src/token-voting/LlamaTokenVotingFactory.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.23; import {Clones} from "@openzeppelin/proxy/Clones.sol"; import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; -import {CasterConfig} from "src/lib/Structs.sol"; +import {CasterConfig, LlamaTokenVotingConfig} from "src/lib/Structs.sol"; import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; import {LlamaTokenActionCreator} from "src/token-voting/LlamaTokenActionCreator.sol"; import {LlamaTokenCaster} from "src/token-voting/LlamaTokenCaster.sol"; @@ -13,22 +13,6 @@ import {LlamaTokenCaster} from "src/token-voting/LlamaTokenCaster.sol"; /// @author Llama (devsdosomething@llama.xyz) /// @notice This contract enables Llama instances to deploy a token voting module. contract LlamaTokenVotingFactory { - // ========================= - // ======== Structs ======== - // ========================= - - /// @dev Configuration of a new Llama token voting module. - struct LlamaTokenVotingConfig { - ILlamaCore llamaCore; // The address of the Llama core. - ILlamaTokenAdapter tokenAdapterLogic; // The logic contract of the token adapter. - bytes adapterConfig; // The configuration of the token adapter. - uint256 nonce; // The nonce to be used in the salt of the deterministic deployment. - uint8 actionCreatorRole; // The role required by the `LlamaTokenActionCreator` to create an action. - uint8 casterRole; // The role required by the `LlamaTokenCaster` to cast approvals and disapprovals. - uint256 creationThreshold; // The number of tokens required to create an action. - CasterConfig casterConfig; // The quorum and period data for the `LlamaTokenCaster`. - } - // ======================== // ======== Errors ======== // ======================== diff --git a/src/token-voting/interfaces/ILlamaTokenAdapter.sol b/src/token-voting/interfaces/ILlamaTokenAdapter.sol index 41ca31e..d3d628c 100644 --- a/src/token-voting/interfaces/ILlamaTokenAdapter.sol +++ b/src/token-voting/interfaces/ILlamaTokenAdapter.sol @@ -23,7 +23,6 @@ interface ILlamaTokenAdapter { function clock() external view returns (uint48 timepoint); /// @notice Reverts if the token's CLOCK_MODE changes from what's in the adapter or if the clock() function doesn't - /// return the correct timepoint based on CLOCK_MODE. function checkIfInconsistentClock() external view; /// @notice Converts a timestamp to timepoint units. @@ -34,9 +33,11 @@ interface ILlamaTokenAdapter { /// @notice Get the voting balance of a token holder at a specified past timepoint. /// @param account The token holder's address. /// @param timepoint The timepoint at which to get the voting balance. + /// @return The number of votes the account had at timepoint. function getPastVotes(address account, uint48 timepoint) external view returns (uint256); /// @notice Get the total supply of a token at a specified past timepoint. /// @param timepoint The timepoint at which to get the total supply. + /// @return The total supply of the token at timepoint. function getPastTotalSupply(uint48 timepoint) external view returns (uint256); } diff --git a/test/token-voting/LlamaTokenVotingFactory.t.sol b/test/token-voting/LlamaTokenVotingFactory.t.sol index f3a5ded..6135b56 100644 --- a/test/token-voting/LlamaTokenVotingFactory.t.sol +++ b/test/token-voting/LlamaTokenVotingFactory.t.sol @@ -7,7 +7,7 @@ import {Clones} from "@openzeppelin/proxy/Clones.sol"; import {LlamaTokenVotingTestSetup} from "test/token-voting/LlamaTokenVotingTestSetup.sol"; -import {ActionInfo, CasterConfig} from "src/lib/Structs.sol"; +import {ActionInfo, LlamaTokenVotingConfig} from "src/lib/Structs.sol"; import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; import {ILlamaPolicy} from "src/interfaces/ILlamaPolicy.sol"; import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol"; @@ -82,7 +82,7 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { function test_CanDeployERC20TokenVotingModule() public { bytes memory adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(erc20VotesToken))); - LlamaTokenVotingFactory.LlamaTokenVotingConfig memory config = LlamaTokenVotingFactory.LlamaTokenVotingConfig( + LlamaTokenVotingConfig memory config = LlamaTokenVotingConfig( CORE, llamaTokenAdapterTimestampLogic, adapterConfig, @@ -159,7 +159,7 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { function test_CanDeployERC721TokenVotingModule() public { bytes memory adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(erc721VotesToken))); - LlamaTokenVotingFactory.LlamaTokenVotingConfig memory config = LlamaTokenVotingFactory.LlamaTokenVotingConfig( + LlamaTokenVotingConfig memory config = LlamaTokenVotingConfig( CORE, llamaTokenAdapterTimestampLogic, adapterConfig, @@ -285,7 +285,7 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { block.chainid ); - LlamaTokenVotingFactory.LlamaTokenVotingConfig memory config = LlamaTokenVotingFactory.LlamaTokenVotingConfig( + LlamaTokenVotingConfig memory config = LlamaTokenVotingConfig( CORE, llamaTokenAdapterTimestampLogic, adapterConfig, @@ -317,7 +317,7 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { ///////////////////// bytes memory adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(erc20VotesToken))); - LlamaTokenVotingFactory.LlamaTokenVotingConfig memory config = LlamaTokenVotingFactory.LlamaTokenVotingConfig( + LlamaTokenVotingConfig memory config = LlamaTokenVotingConfig( CORE, llamaTokenAdapterTimestampLogic, adapterConfig, @@ -380,7 +380,7 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest { ////////////////////// adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(erc20VotesToken))); - config = LlamaTokenVotingFactory.LlamaTokenVotingConfig( + config = LlamaTokenVotingConfig( CORE, llamaTokenAdapterTimestampLogic, adapterConfig, diff --git a/test/token-voting/LlamaTokenVotingTestSetup.sol b/test/token-voting/LlamaTokenVotingTestSetup.sol index 2e28c55..f2c1eb6 100644 --- a/test/token-voting/LlamaTokenVotingTestSetup.sol +++ b/test/token-voting/LlamaTokenVotingTestSetup.sol @@ -10,13 +10,12 @@ import {LlamaPeripheryTestSetup} from "test/LlamaPeripheryTestSetup.sol"; import {DeployLlamaTokenVotingFactory} from "script/DeployLlamaTokenVotingFactory.s.sol"; -import {Action, ActionInfo, CasterConfig} from "src/lib/Structs.sol"; +import {Action, ActionInfo, CasterConfig, LlamaTokenVotingConfig} from "src/lib/Structs.sol"; import {ILlamaPolicy} from "src/interfaces/ILlamaPolicy.sol"; import {ILlamaRelativeStrategyBase} from "src/interfaces/ILlamaRelativeStrategyBase.sol"; import {ILlamaStrategy} from "src/interfaces/ILlamaStrategy.sol"; import {RoleDescription} from "src/lib/UDVTs.sol"; import {LlamaTokenAdapterVotesTimestamp} from "src/token-voting/token-adapters/LlamaTokenAdapterVotesTimestamp.sol"; -import {LlamaTokenVotingFactory} from "src/token-voting/LlamaTokenVotingFactory.sol"; import {LlamaTokenActionCreator} from "src/token-voting/LlamaTokenActionCreator.sol"; import {LlamaTokenCaster} from "src/token-voting/LlamaTokenCaster.sol"; @@ -109,7 +108,7 @@ contract LlamaTokenVotingTestSetup is LlamaPeripheryTestSetup, DeployLlamaTokenV function _deployERC20TokenVotingModuleAndSetRole() internal returns (LlamaTokenActionCreator, LlamaTokenCaster) { bytes memory adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(erc20VotesToken))); - LlamaTokenVotingFactory.LlamaTokenVotingConfig memory config = LlamaTokenVotingFactory.LlamaTokenVotingConfig( + LlamaTokenVotingConfig memory config = LlamaTokenVotingConfig( CORE, llamaTokenAdapterTimestampLogic, adapterConfig, @@ -138,7 +137,7 @@ contract LlamaTokenVotingTestSetup is LlamaPeripheryTestSetup, DeployLlamaTokenV function _deployERC721TokenVotingModuleAndSetRole() internal returns (LlamaTokenActionCreator, LlamaTokenCaster) { bytes memory adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(erc721VotesToken))); - LlamaTokenVotingFactory.LlamaTokenVotingConfig memory config = LlamaTokenVotingFactory.LlamaTokenVotingConfig( + LlamaTokenVotingConfig memory config = LlamaTokenVotingConfig( CORE, llamaTokenAdapterTimestampLogic, adapterConfig,