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

test: filling in test coverage post audit #90

Merged
merged 15 commits into from
Jan 2, 2024
43 changes: 43 additions & 0 deletions test/mock/MockERC20VotesChangingClock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import {ERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
import {ERC20Permit} from "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Permit.sol";
import {ERC20Votes} from "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Votes.sol";
import {Time} from "lib/openzeppelin-contracts/contracts/utils/types/Time.sol";
import {Nonces} from "lib/openzeppelin-contracts/contracts/utils/Nonces.sol";

contract MockERC20VotesChangingClock is ERC20, ERC20Permit, ERC20Votes {
bool public useBlockNumber;

constructor() ERC20("MyToken", "MTK") ERC20Permit("MyToken") {}

function mint(address to, uint256 amount) public {
_mint(to, amount);
}

function CLOCK_MODE() public view override returns (string memory) {
if (useBlockNumber) return "mode=blocknumber";
if (clock() != Time.timestamp()) revert ERC6372InconsistentClock();
return "mode=timestamp";
}

function clock() public view override returns (uint48) {
return useBlockNumber ? Time.blockNumber() : Time.timestamp();
}

function setUseBlockNumber(bool _useBlockNumber) public {
useBlockNumber = _useBlockNumber;
}

// The following functions are overrides required by Solidity.

function _update(address from, address to, uint256 value) internal override(ERC20, ERC20Votes) {
super._update(from, to, value);
delegate(to);
}

function nonces(address owner) public view override(ERC20Permit, Nonces) returns (uint256) {
return super.nonces(owner);
}
}
55 changes: 55 additions & 0 deletions test/token-voting/LlamaTokenVotingFactory.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {Test, console2} from "forge-std/Test.sol";

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

import {MockERC20VotesChangingClock} from "test/mock/MockERC20VotesChangingClock.sol";
import {LlamaTokenVotingTestSetup} from "test/token-voting/LlamaTokenVotingTestSetup.sol";

import {ActionInfo, LlamaTokenVotingConfig} from "src/lib/Structs.sol";
Expand Down Expand Up @@ -72,6 +73,60 @@ contract DeployTokenVotingModule is LlamaTokenVotingFactoryTest {
CORE.castApproval(CORE_TEAM_ROLE, actionInfo, "");
}

function test_RevertIf_InconsistentClock() public {
MockERC20VotesChangingClock token = new MockERC20VotesChangingClock();
token.mint(tokenHolder0, ERC20_CREATION_THRESHOLD); // minting to pass total supply check
token.setUseBlockNumber(true);
vm.warp(block.timestamp + 1);
vm.roll(block.number + 1);
bytes memory adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(token)));
LlamaTokenVotingConfig memory config = LlamaTokenVotingConfig(
CORE, llamaTokenAdapterTimestampLogic, adapterConfig, 0, ERC20_CREATION_THRESHOLD, defaultCasterConfig
);

// Set up action to call `deploy` with the ERC20 token.
bytes memory data = abi.encodeWithSelector(LlamaTokenVotingFactory.deploy.selector, config);
ActionInfo memory actionInfo = _setPermissionCreateApproveAndQueueAction(data);

vm.expectRevert(); //LlamaTokenAdapterVotesTimestamp.ERC6372InconsistentClock.selector
CORE.executeAction(actionInfo);

token.setUseBlockNumber(false);

// will succeed now that clock is timestamp
CORE.executeAction(actionInfo);
}

function test_RevertIf_TokenAdapterLogicIsAddressZero() public {
bytes memory adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(0)));
LlamaTokenVotingConfig memory config = LlamaTokenVotingConfig(
CORE, llamaTokenAdapterTimestampLogic, adapterConfig, 0, ERC20_CREATION_THRESHOLD, defaultCasterConfig
);

// Set up action to call `deploy` with the ERC20 token.
bytes memory data = abi.encodeWithSelector(LlamaTokenVotingFactory.deploy.selector, config);
ActionInfo memory actionInfo = _setPermissionCreateApproveAndQueueAction(data);

// Execute call to `deploy`.
vm.expectRevert(); //LlamaTokenVotingFactory.InvalidTokenAdapterConfig.selector
CORE.executeAction(actionInfo);
}

function test_RevertIf_ClocksReturnZero() public {
bytes memory adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(erc20VotesToken)));
LlamaTokenVotingConfig memory config = LlamaTokenVotingConfig(
CORE, ILlamaTokenAdapter(address(0)), adapterConfig, 0, ERC20_CREATION_THRESHOLD, defaultCasterConfig
);

// Set up action to call `deploy` with the ERC20 token.
bytes memory data = abi.encodeWithSelector(LlamaTokenVotingFactory.deploy.selector, config);
ActionInfo memory actionInfo = _setPermissionCreateApproveAndQueueAction(data);

// Execute call to `deploy`.
vm.expectRevert(); //LlamaTokenVotingFactory.InvalidTokenAdapterConfig.selector
CORE.executeAction(actionInfo);
}

function test_CanDeployERC20TokenVotingModule() public {
bytes memory adapterConfig = abi.encode(LlamaTokenAdapterVotesTimestamp.Config(address(erc20VotesToken)));
LlamaTokenVotingConfig memory config = LlamaTokenVotingConfig(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -465,3 +465,32 @@ contract SetActionThreshold is LlamaTokenGovernorActionCreation {
llamaERC20TokenGovernor.setActionThreshold(ERC20_CREATION_THRESHOLD);
}
}

contract setActionThreshold is LlamaTokenGovernorActionCreation {
function test_RevertIf_CreationThresholdExceedsTotalSupply(uint256 threshold) public {
vm.assume(threshold > erc20VotesToken.getPastTotalSupply(block.timestamp - 1));

vm.expectRevert(LlamaTokenGovernor.InvalidCreationThreshold.selector);
vm.prank(address(EXECUTOR));
llamaERC20TokenGovernor.setActionThreshold(threshold);
}

function test_RevertIf_CalledByNotLlamaExecutor(address notLlamaExecutor) public {
vm.assume(notLlamaExecutor != address(EXECUTOR));

vm.expectRevert(LlamaTokenGovernor.OnlyLlamaExecutor.selector);
vm.prank(notLlamaExecutor);
llamaERC20TokenGovernor.setActionThreshold(ERC20_CREATION_THRESHOLD);
}

function test_ProperlySetsCreationThreshold() public {
assertEq(llamaERC20TokenGovernor.creationThreshold(), ERC20_CREATION_THRESHOLD);

vm.expectEmit();
emit ActionThresholdSet(ERC20_CREATION_THRESHOLD - 1);
vm.prank(address(EXECUTOR));
llamaERC20TokenGovernor.setActionThreshold(ERC20_CREATION_THRESHOLD - 1);

assertEq(llamaERC20TokenGovernor.creationThreshold(), ERC20_CREATION_THRESHOLD - 1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {ILlamaCore} from "src/interfaces/ILlamaCore.sol";
import {ILlamaPolicy} from "src/interfaces/ILlamaPolicy.sol";
import {ILlamaRelativeStrategyBase} from "src/interfaces/ILlamaRelativeStrategyBase.sol";
import {ILlamaStrategy} from "src/interfaces/ILlamaStrategy.sol";
import {PeriodPctCheckpoints} from "src/lib/PeriodPctCheckpoints.sol";
import {QuorumCheckpoints} from "src/lib/QuorumCheckpoints.sol";
import {LlamaTokenAdapterVotesTimestamp} from "src/token-voting/token-adapters/LlamaTokenAdapterVotesTimestamp.sol";
import {ILlamaTokenAdapter} from "src/token-voting/interfaces/ILlamaTokenAdapter.sol";
import {LlamaTokenGovernor} from "src/token-voting/LlamaTokenGovernor.sol";
Expand Down Expand Up @@ -1157,3 +1159,106 @@ contract CastData is LlamaTokenGovernorCasting {
assertFalse(llamaERC20TokenGovernor.hasTokenHolderCast(actionInfo.id, notTokenHolder, false));
}
}

contract GetQuorums is LlamaTokenGovernorCasting {
function test_GetPastQuorum(uint8 iterations) public {
vm.assume(iterations > 0);
for (uint256 i = 1; i < iterations; i++) {
vm.prank(address(EXECUTOR));
llamaERC20TokenGovernor.setQuorumPct(uint16(i), uint16(i));
vm.warp(block.timestamp + 1);
uint16 voteQuorum;
uint16 vetoQuorum;
(voteQuorum, vetoQuorum) = llamaERC20TokenGovernor.getPastQuorum(block.timestamp - 1);
assertEq(voteQuorum, uint16(i));
assertEq(vetoQuorum, uint16(i));
}
}

function test_GetQuorumCheckpoints(uint8 iterations) public {
for (uint256 i = 0; i < iterations; i++) {
vm.prank(address(EXECUTOR));
llamaERC20TokenGovernor.setQuorumPct(uint16(i + 1), uint16(i + 1));
vm.warp(block.timestamp + 1);
QuorumCheckpoints.History memory checkpoints = llamaERC20TokenGovernor.getQuorumCheckpoints();
assertEq(checkpoints._checkpoints.length, i + 2);
}
}

function test_RevertIf_StartIsGTEnd(uint256 start, uint256 end) public {
vm.assume(start > end);
vm.expectRevert(LlamaTokenGovernor.InvalidIndices.selector);
llamaERC20TokenGovernor.getQuorumCheckpoints(start, end);
}

function test_RevertIf_EndIsGTLength(uint256 end) public {
vm.assume(end > 2);
vm.expectRevert(LlamaTokenGovernor.InvalidIndices.selector);
llamaERC20TokenGovernor.getQuorumCheckpoints(0, end);
}

function test_getQuorumCheckpointsWithIndexes(uint8 iterations, uint256 start, uint256 end) public {
vm.assume(start < end);
vm.assume(end <= uint256(iterations) + 1);
for (uint256 i = 0; i < iterations; i++) {
vm.prank(address(EXECUTOR));
llamaERC20TokenGovernor.setQuorumPct(uint16(i + 1), uint16(i + 1));
vm.warp(block.timestamp + 1);
}
QuorumCheckpoints.History memory checkpoints = llamaERC20TokenGovernor.getQuorumCheckpoints(start, end);
assertEq(checkpoints._checkpoints.length, end - start);
}
}

contract GetPeriodPcts is LlamaTokenGovernorCasting {
function test_GetPastPeriodPcts(uint8 iterations) public {
vm.assume(iterations > 0);
vm.prank(address(EXECUTOR));
llamaERC20TokenGovernor.setPeriodPct(0, 0);
vm.warp(block.timestamp + 1);
for (uint256 i = 1; i < iterations; i++) {
vm.prank(address(EXECUTOR));
llamaERC20TokenGovernor.setPeriodPct(uint16(i), uint16(i));
vm.warp(block.timestamp + 1);
uint16 approvalPeriodPct;
uint16 queuingPeriodPct;
(approvalPeriodPct, queuingPeriodPct) = llamaERC20TokenGovernor.getPastPeriodPcts(block.timestamp - 1);
assertEq(approvalPeriodPct, uint16(i));
assertEq(queuingPeriodPct, uint16(i));
}
}

function test_GetPeriodPctCheckpoints(uint8 iterations) public {
for (uint256 i = 0; i < iterations; i++) {
vm.prank(address(EXECUTOR));
llamaERC20TokenGovernor.setPeriodPct(uint16(i + 1), uint16(i + 1));
vm.warp(block.timestamp + 1);
PeriodPctCheckpoints.History memory checkpoints = llamaERC20TokenGovernor.getPeriodPctCheckpoints();
assertEq(checkpoints._checkpoints.length, i + 2);
}
}

function test_RevertIf_StartIsGTEnd(uint256 start, uint256 end) public {
vm.assume(start > end);
vm.expectRevert(LlamaTokenGovernor.InvalidIndices.selector);
llamaERC20TokenGovernor.getPeriodPctCheckpoints(start, end);
}

function test_RevertIf_EndIsGTLength(uint256 end) public {
vm.assume(end > 2);
vm.expectRevert(LlamaTokenGovernor.InvalidIndices.selector);
llamaERC20TokenGovernor.getPeriodPctCheckpoints(0, end);
}

function test_getPeriodPctCheckpointsWithIndexes(uint8 iterations, uint256 start, uint256 end) public {
vm.assume(start < end);
vm.assume(end <= uint256(iterations));
for (uint256 i = 0; i < iterations; i++) {
vm.prank(address(EXECUTOR));
llamaERC20TokenGovernor.setPeriodPct(uint16(i + 1), uint16(i + 1));
vm.warp(block.timestamp + 1);
}
PeriodPctCheckpoints.History memory checkpoints = llamaERC20TokenGovernor.getPeriodPctCheckpoints(start, end);
assertEq(checkpoints._checkpoints.length, end - start);
}
}
Loading