Skip to content

Commit

Permalink
Removing the start and end dates from the Optimistic voting plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
brickpop committed May 16, 2024
1 parent 42718d8 commit 9d3089e
Show file tree
Hide file tree
Showing 8 changed files with 63 additions and 114 deletions.
4 changes: 2 additions & 2 deletions script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {PluginSetupProcessor} from "@aragon/osx/framework/plugin/setup/PluginSet
import {IDAO} from "@aragon/osx/core/dao/IDAO.sol";
import {DAO} from "@aragon/osx/core/dao/DAO.sol";
import {createERC1967Proxy} from "@aragon/osx/utils/Proxy.sol";
import {ITaikoEssentialContract} from "../src/interfaces/ITaikoEssentialContract.sol";
import {TaikoL1} from "../src/adapted-dependencies/TaikoL1.sol";

contract Deploy is Script {
DAO immutable daoImplementation;
Expand Down Expand Up @@ -243,7 +243,7 @@ contract Deploy is Script {
votingSettings,
tokenSettings,
mintSettings,
ITaikoEssentialContract(taikoL1),
TaikoL1(taikoL1),
taikoBridge,
minStdProposalDelay,
stdProposer,
Expand Down
99 changes: 31 additions & 68 deletions src/OptimisticTokenVotingPlugin.sol
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,15 @@ contract OptimisticTokenVotingPlugin is
}

/// @notice A container for the proposal parameters at the time of proposal creation.
/// @param startDate The start date of the proposal vote.
/// @param endDate The end date of the proposal vote.
/// @param vetoEndDate The end date of the proposal vote.
/// @param snapshotTimestamp The timestamp prior to the proposal creation.
/// @param minVetoRatio The minimum veto ratio needed to defeat the proposal, as a fraction of 1_000_000.
/// @param unavailableL2WhenCreated Whether the L2 was unavailable when the proposal was created.
/// @param skipL2 True if the L2 was unavailable when the proposal was created.
struct ProposalParameters {
uint64 startDate;
uint64 endDate;
uint64 vetoEndDate;
uint64 snapshotTimestamp;
uint32 minVetoRatio;
bool unavailableL2WhenCreated;
bool skipL2;
}

/// @notice The ID of the permission required to create a proposal.
Expand Down Expand Up @@ -184,9 +182,9 @@ contract OptimisticTokenVotingPlugin is
}

/// @inheritdoc IOptimisticTokenVoting
function effectiveVotingPower(uint256 _timestamp, bool _isL2Available) public view returns (uint256) {
function effectiveVotingPower(uint256 _timestamp, bool _includeL2VotingPower) public view returns (uint256) {
uint256 _totalVotingPower = totalVotingPower(_timestamp);
if (!_isL2Available) {
if (!_includeL2VotingPower) {
return _totalVotingPower - bridgedVotingPower(_timestamp);
}
return _totalVotingPower;
Expand Down Expand Up @@ -268,9 +266,9 @@ contract OptimisticTokenVotingPlugin is
function isMinVetoRatioReached(uint256 _proposalId) public view virtual returns (bool) {
Proposal storage proposal_ = proposals[_proposalId];

uint256 totalVotingPower_ =
effectiveVotingPower(proposal_.parameters.snapshotTimestamp, _proposalCanUseL2VotingPower(proposal_));
uint256 _minVetoPower = _applyRatioCeiled(totalVotingPower_, proposal_.parameters.minVetoRatio);
bool _usingL2VotingPower = _proposalCanUseL2VotingPower(proposal_);
uint256 _totalVotingPower = effectiveVotingPower(proposal_.parameters.snapshotTimestamp, _usingL2VotingPower);
uint256 _minVetoPower = _applyRatioCeiled(_totalVotingPower, proposal_.parameters.minVetoRatio);
return proposal_.vetoTally >= _minVetoPower;
}

Expand Down Expand Up @@ -320,40 +318,46 @@ contract OptimisticTokenVotingPlugin is
bytes calldata _metadata,
IDAO.Action[] calldata _actions,
uint256 _allowFailureMap,
uint64 _startDate,
uint64 _endDate
uint64 _duration
) external auth(PROPOSER_PERMISSION_ID) returns (uint256 proposalId) {
uint256 snapshotTimestamp;
unchecked {
snapshotTimestamp = block.timestamp - 1; // The snapshot timestamp must in the past to protect the transaction against backrunning transactions causing census changes.
}

bool _isL2Available = isL2Available();
if (effectiveVotingPower(snapshotTimestamp, _isL2Available) == 0) {
// Checks
bool _enableL2 = isL2Available();
if (effectiveVotingPower(snapshotTimestamp, _enableL2) == 0) {
revert NoVotingPower();
}

(_startDate, _endDate) = _validateProposalDates(_startDate, _endDate);
if (_duration < governanceSettings.minDuration) {
revert DateOutOfBounds({limit: governanceSettings.minDuration, actual: _duration});
}
uint64 _now = block.timestamp.toUint64();
uint64 _vetoEndDate = _now + _duration; // Since `minDuration` will be less than 1 year, `startDate + minDuration` can only overflow if the `startDate` is after `type(uint64).max - minDuration`. In this case, the proposal creation will revert and another date can be picked.

proposalId = _createProposal({
_creator: _msgSender(),
_metadata: _metadata,
_startDate: _startDate,
_endDate: _endDate,
_startDate: _now,
_endDate: _vetoEndDate,
_actions: _actions,
_allowFailureMap: _allowFailureMap
});

// Store proposal related information
Proposal storage proposal_ = proposals[proposalId];

proposal_.parameters.startDate = _startDate;
proposal_.parameters.endDate = _endDate;
proposal_.parameters.vetoEndDate = _vetoEndDate;
proposal_.parameters.snapshotTimestamp = snapshotTimestamp.toUint64();
proposal_.parameters.minVetoRatio = minVetoRatio();
proposal_.parameters.unavailableL2WhenCreated = !_isL2Available;

// Save gas
// We skip the L2 bridging grace period if the L2 was down on creation or if
// an emergency multisig proposal is passed (_duration == 0)
if (!_enableL2 || _duration == 0) {
proposal_.parameters.skipL2 = true;
}
if (_allowFailureMap != 0) {
proposal_.allowFailureMap = _allowFailureMap;
}
Expand Down Expand Up @@ -447,7 +451,7 @@ contract OptimisticTokenVotingPlugin is
IDAO.Action[] calldata _actions,
uint256 _allowFailureMap
) internal override returns (uint256 proposalId) {
// Returns an autoincremental number
// Returns an autoincremental number with the start and end dates
proposalId = _makeProposalId(_startDate, _endDate);

emit ProposalCreated({
Expand Down Expand Up @@ -492,10 +496,7 @@ contract OptimisticTokenVotingPlugin is
/// @param proposal_ The proposal struct.
/// @return True if the proposal vote is open, false otherwise.
function _isProposalOpen(Proposal storage proposal_) internal view virtual returns (bool) {
uint64 currentTime = block.timestamp.toUint64();

return proposal_.parameters.startDate <= currentTime && currentTime < proposal_.parameters.endDate
&& !proposal_.executed;
return block.timestamp.toUint64() < proposal_.parameters.vetoEndDate && !proposal_.executed;
}

/// @notice Internal function to check if a proposal already ended.
Expand All @@ -504,7 +505,7 @@ contract OptimisticTokenVotingPlugin is
function _isProposalEnded(Proposal storage proposal_) internal view virtual returns (bool) {
uint64 currentTime = block.timestamp.toUint64();

return currentTime >= proposal_.parameters.endDate;
return currentTime >= proposal_.parameters.vetoEndDate;
}

/// @notice Determines whether the proposal has L2 voting enabled or not.
Expand All @@ -523,49 +524,11 @@ contract OptimisticTokenVotingPlugin is
/// @param proposal_ The proposal struct.
/// @return True if the proposal may still receive L2 bridged votes, false otherwise.
function _proposalL2VetoingOpen(Proposal storage proposal_) internal view virtual returns (bool) {
if (proposal_.parameters.unavailableL2WhenCreated) {
if (proposal_.parameters.skipL2) {
return false;
}

uint64 currentTime = block.timestamp.toUint64();
return currentTime >= proposal_.parameters.startDate
&& currentTime < proposal_.parameters.endDate + L2_AGGREGATION_PERIOD;
}

/// @notice Validates and returns the proposal vote dates.
/// @param _start The start date of the proposal vote. If 0, the current timestamp is used and the vote starts immediately.
/// @param _end The end date of the proposal vote. If 0, `_start + minDuration` is used.
/// @return startDate The validated start date of the proposal vote.
/// @return endDate The validated end date of the proposal vote.
function _validateProposalDates(uint64 _start, uint64 _end)
internal
view
virtual
returns (uint64 startDate, uint64 endDate)
{
uint64 currentTimestamp = block.timestamp.toUint64();

if (_start == 0) {
startDate = currentTimestamp;
} else {
startDate = _start;

if (startDate < currentTimestamp) {
revert DateOutOfBounds({limit: currentTimestamp, actual: startDate});
}
}

uint64 earliestEndDate = startDate + governanceSettings.minDuration; // Since `minDuration` will be less than 1 year, `startDate + minDuration` can only overflow if the `startDate` is after `type(uint64).max - minDuration`. In this case, the proposal creation will revert and another date can be picked.

if (_end == 0) {
endDate = earliestEndDate;
} else {
endDate = _end;

if (endDate < earliestEndDate) {
revert DateOutOfBounds({limit: earliestEndDate, actual: endDate});
}
}
return block.timestamp.toUint64() < proposal_.parameters.vetoEndDate + L2_AGGREGATION_PERIOD;
}

/// @notice This empty reserved space is put in place to allow future versions to add new variables without shifting down storage in the inheritance chain (see [OpenZeppelin's guide about storage gaps](https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps)).
Expand Down
6 changes: 2 additions & 4 deletions src/interfaces/IOptimisticTokenVoting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,13 @@ interface IOptimisticTokenVoting {
/// @param _metadata The metadata of the proposal.
/// @param _actions The actions that will be executed after the proposal passes.
/// @param _allowFailureMap Allows proposal to succeed even if an action reverts. Uses bitmap representation. If the bit at index `x` is 1, the tx succeeds even if the action at `x` failed. Passing 0 will be treated as atomic execution.
/// @param _startDate The start date of the proposal vote. If 0, the current timestamp is used and the vote starts immediately.
/// @param _endDate The end date of the proposal vote. If 0, `_startDate + minDuration` is used.
/// @param _duration The amount of seconds to allow token holders to veto.
/// @return proposalId The ID of the proposal.
function createProposal(
bytes calldata _metadata,
IDAO.Action[] calldata _actions,
uint256 _allowFailureMap,
uint64 _startDate,
uint64 _endDate
uint64 _duration
) external returns (uint256 proposalId);

/// @notice Checks if an account can participate on an optimistic proposal. This can be because the proposal
Expand Down
4 changes: 2 additions & 2 deletions src/setup/OptimisticTokenVotingPluginSetup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {GovernanceWrappedERC20} from "@aragon/osx/token/ERC20/governance/Governa
import {IGovernanceWrappedERC20} from "@aragon/osx/token/ERC20/governance/IGovernanceWrappedERC20.sol";
import {OptimisticTokenVotingPlugin} from "../OptimisticTokenVotingPlugin.sol";
import {StandardProposalCondition} from "../conditions/StandardProposalCondition.sol";
import {ITaikoEssentialContract} from "../interfaces/ITaikoEssentialContract.sol";
import {TaikoL1} from "../adapted-dependencies/TaikoL1.sol";
// import {EssentialContract as TaikoEssentialContract} from "@taikoxyz/taiko-mono/common/EssentialContract.sol";

/// @title OptimisticTokenVotingPluginSetup
Expand Down Expand Up @@ -53,7 +53,7 @@ contract OptimisticTokenVotingPluginSetup is PluginSetup {
TokenSettings tokenSettings;
// only used for GovernanceERC20 (when token is not passed)
GovernanceERC20.MintSettings mintSettings;
ITaikoEssentialContract taikoL1;
TaikoL1 taikoL1;
address taikoBridge;
uint64 stdProposalMinDelay;
address stdProposer;
Expand Down
10 changes: 5 additions & 5 deletions test/OptimisticTokenVotingPlugin.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {IMembership} from "@aragon/osx/core/plugin/membership/IMembership.sol";
import {RATIO_BASE, RatioOutOfBounds} from "@aragon/osx/plugins/utils/Ratio.sol";
import {DaoUnauthorized} from "@aragon/osx/core/utils/auth.sol";
import {ERC20VotesMock} from "./mocks/ERC20VotesMock.sol";
import {ITaikoEssentialContract} from "../src/interfaces/ITaikoEssentialContract.sol";
import {TaikoL1} from "../src/adapted-dependencies/TaikoL1.sol";
import {IERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/IERC165Upgradeable.sol";
import {IERC1822ProxiableUpgradeable} from
"@openzeppelin/contracts-upgradeable/interfaces/draft-IERC1822Upgradeable.sol";
Expand All @@ -22,7 +22,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest {
DAO public dao;
OptimisticTokenVotingPlugin public plugin;
ERC20VotesMock votingToken;
ITaikoEssentialContract taikoL1;
TaikoL1 taikoL1;

// Events from external contracts
event Initialized(uint8 version);
Expand Down Expand Up @@ -164,9 +164,9 @@ contract OptimisticTokenVotingPluginTest is AragonTest {
}

function test_GetVotingTokenReturnsTheRightAddress() public {
assertEq(address(plugin.getVotingToken()), address(votingToken), "Incorrect voting token");
assertEq(address(plugin.votingToken()), address(votingToken), "Incorrect voting token");

address oldToken = address(plugin.getVotingToken());
address oldToken = address(plugin.votingToken());

// New token
votingToken = ERC20VotesMock(
Expand All @@ -186,7 +186,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest {
)
);

assertEq(address(plugin.getVotingToken()), address(votingToken), "Incorrect voting token");
assertEq(address(plugin.votingToken()), address(votingToken), "Incorrect voting token");
assertEq(address(votingToken) != oldToken, true, "The token address sould have changed");
}

Expand Down
14 changes: 7 additions & 7 deletions test/OptimisticTokenVotingPluginSetup.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {IPluginSetup} from "@aragon/osx/framework/plugin/setup/PluginSetup.sol";
import {PermissionLib} from "@aragon/osx/core/permission/PermissionLib.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import {ERC20Mock} from "./mocks/ERC20Mock.sol";
import {ITaikoEssentialContract} from "../src/interfaces/ITaikoEssentialContract.sol";
import {TaikoL1} from "../src/adapted-dependencies/TaikoL1.sol";

contract OptimisticTokenVotingPluginSetupTest is Test {
OptimisticTokenVotingPluginSetup public pluginSetup;
Expand All @@ -27,7 +27,7 @@ contract OptimisticTokenVotingPluginSetupTest is Test {
OptimisticTokenVotingPlugin.OptimisticGovernanceSettings votingSettings;
OptimisticTokenVotingPluginSetup.TokenSettings tokenSettings;
GovernanceERC20.MintSettings mintSettings;
ITaikoEssentialContract taikoL1;
TaikoL1 taikoL1;
address taikoBridge;
uint64 stdProposalMinDuration;
address stdProposer;
Expand Down Expand Up @@ -65,7 +65,7 @@ contract OptimisticTokenVotingPluginSetupTest is Test {
symbol: "wTK"
});
mintSettings = GovernanceERC20.MintSettings({receivers: new address[](0), amounts: new uint256[](0)});
taikoL1 = ITaikoEssentialContract(address(0x66666666));
taikoL1 = TaikoL1(address(0x66666666));
taikoBridge = address(0x55555555);
stdProposalMinDuration = 10 days;
stdProposer = address(0x1234567890);
Expand Down Expand Up @@ -154,7 +154,7 @@ contract OptimisticTokenVotingPluginSetupTest is Test {
votingSettings,
tokenSettings,
mintSettings,
ITaikoEssentialContract(address(0x1234)),
TaikoL1(address(0x1234)),
address(0x5678), // taikoBridge
5 days, // stdProposalMinDuration
stdProposer,
Expand All @@ -176,7 +176,7 @@ contract OptimisticTokenVotingPluginSetupTest is Test {
votingSettings,
tokenSettings,
mintSettings,
ITaikoEssentialContract(address(0x1111)),
TaikoL1(address(0x1111)),
address(0x2222), // taikoBridge
15 days, // stdProposalMinDuration
stdProposer,
Expand Down Expand Up @@ -216,7 +216,7 @@ contract OptimisticTokenVotingPluginSetupTest is Test {
tokenSettings,
// only used for GovernanceERC20 (when a token is not passed)
mintSettings,
ITaikoEssentialContract(address(0x9999)),
TaikoL1(address(0x9999)),
address(0xaaaa),
stdProposalMinDuration,
stdProposer,
Expand Down Expand Up @@ -288,7 +288,7 @@ contract OptimisticTokenVotingPluginSetupTest is Test {
tokenSettings,
// only used for GovernanceERC20 (when a token is not passed)
mintSettings,
ITaikoEssentialContract(address(0xbbbb)),
TaikoL1(address(0xbbbb)),
address(0xcccc),
stdProposalMinDuration,
stdProposer,
Expand Down
6 changes: 3 additions & 3 deletions test/base/AragonTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.s
import {createProxyAndCall} from "../helpers.sol";
import {RATIO_BASE} from "@aragon/osx/plugins/utils/Ratio.sol";
import {TaikoL1UnpausedMock} from "../mocks/TaikoL1Mock.sol";
import {ITaikoEssentialContract} from "../../src/interfaces/ITaikoEssentialContract.sol";
import {TaikoL1} from "../../src/adapted-dependencies/TaikoL1.sol";

import {Test} from "forge-std/Test.sol";

Expand Down Expand Up @@ -42,7 +42,7 @@ contract AragonTest is Test {

function makeDaoWithOptimisticTokenVoting(address owner)
internal
returns (DAO, OptimisticTokenVotingPlugin, ERC20VotesMock, ITaikoEssentialContract)
returns (DAO, OptimisticTokenVotingPlugin, ERC20VotesMock, TaikoL1)
{
// Deploy a DAO with owner as root
DAO dao = DAO(
Expand Down Expand Up @@ -83,7 +83,7 @@ contract AragonTest is Test {
vm.label(address(dao), "dao");
vm.label(address(optimisticPlugin), "optimisticPlugin");

return (dao, optimisticPlugin, votingToken, ITaikoEssentialContract(taikoL1));
return (dao, optimisticPlugin, votingToken, TaikoL1(taikoL1));
}

/// @notice Creates a mock DAO with a multisig and an optimistic token voting plugin.
Expand Down
Loading

0 comments on commit 9d3089e

Please sign in to comment.