Skip to content

Commit

Permalink
refactor zodiac
Browse files Browse the repository at this point in the history
  • Loading branch information
nginnever committed Aug 17, 2021
1 parent 7c97f61 commit a4154b6
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 60 deletions.
91 changes: 46 additions & 45 deletions contracts/ProposalModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@gnosis/zodiac/contracts/core/Module.sol";
import "@gnosis/zodiac/contracts/core/Modifier.sol";
import "./interfaces/IVoting.sol";
import "./interfaces/IRoles.sol";

/// @title Gnosis Safe DAO Proposal Module - A gnosis wallet module for introducing fully decentralized token weighted governance.
/// @author Nathan Ginnever - <[email protected]>
contract ProposalModule is Module {
contract ProposalModule is Modifier {
bytes32 public constant DOMAIN_SEPARATOR_TYPEHASH =
0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218;
// keccak256(
Expand Down Expand Up @@ -40,9 +40,9 @@ contract ProposalModule is Module {
uint256 private _proposalTime;
uint256 private _gracePeriod = 60 seconds; //3 days;
uint256 private _threshold;
uint256 private _minimumProposalAmount; // amount of gov tokens needed to participate
address private _votingModule;
address private _roleModule;
// uint256 private _minimumProposalAmount; // amount of gov tokens needed to participate
// address private _votingModule;
// address private _roleModule;

// mapping of proposal id to proposal
mapping(uint256 => Proposal) public proposals;
Expand Down Expand Up @@ -71,14 +71,17 @@ contract ProposalModule is Module {

constructor(
uint256 proposalTime_,
uint256 threshold_,
uint256 minimumProposalAmount_
uint256 threshold_
// uint256 minimumProposalAmount_
) {
//executor = executor_;
__Ownable_init();
// should always be unset and only called during instiation anyway
//require(modules[SENTINEL_MODULES] == address(0), "GS100");
modules[SENTINEL_MODULES] = SENTINEL_MODULES;
_proposalTime = proposalTime_ * 1 minutes; //days;
_threshold = threshold_;
_minimumProposalAmount = minimumProposalAmount_;
// _minimumProposalAmount = minimumProposalAmount_;
}

// getters
Expand All @@ -98,13 +101,13 @@ contract ProposalModule is Module {
return _proposalTime;
}

function minimumProposalAmount() public view returns (uint256) {
return _minimumProposalAmount;
}
// function minimumProposalAmount() public view returns (uint256) {
// return _minimumProposalAmount;
// }

function votingModule() public view returns (address) {
return _votingModule;
}
// function votingModule() public view returns (address) {
// return _votingModule;
// }

function isExecuted(uint256 proposalId, uint256 index) public view returns (bool) {
return proposals[proposalId].executed[index];
Expand All @@ -114,48 +117,46 @@ contract ProposalModule is Module {
return proposals[proposalId].txHashes[index];
}

function registerVoteModule(address module) external onlyExecutor {
_votingModule = module;
}
// function registerVoteModule(address module) external onlyExecutor {
// _votingModule = module;
// }

function registerRoleModule(address module) external onlyExecutor {
_roleModule = module;
}
// function registerRoleModule(address module) external onlyExecutor {
// _roleModule = module;
// }

function vote(uint256 proposalId, bool vote) external {
if (_roleModule != address(0)) {
require(IRoles(_roleModule).checkMembership(msg.sender), "TW028");
}
require(_votingModule != address(0), "TW006");
require(proposals[proposalId].hasVoted[msg.sender] == false, "TW007");
function receiveVote(address voter, uint256 proposalId, bool vote, uint256 weight) moduleOnly external {
// if (_roleModule != address(0)) {
// require(IRoles(_roleModule).checkMembership(msg.sender), "TW028");
// }
//require(_votingModule != address(0), "TW006");
require(proposals[proposalId].hasVoted[voter] == false, "TW007");
require(proposals[proposalId].canceled == false, "TW008");
require(proposals[proposalId].deadline >= block.timestamp, "TW010");

proposals[proposalId].hasVoted[msg.sender] = true;
IVoting(_votingModule).startVoting(msg.sender);
require(IVoting(_votingModule).checkBlock(msg.sender), "TW021");
proposals[proposalId].hasVoted[voter] = true;
// IVoting(_votingModule).startVoting(msg.sender);
// require(IVoting(_votingModule).checkBlock(msg.sender), "TW021");

if (vote == true) {
proposals[proposalId].yesVotes =
proposals[proposalId].yesVotes +
IVoting(_votingModule).calculateWeight(msg.sender);
proposals[proposalId].yesVotes + weight;
} else {
proposals[proposalId].noVotes =
proposals[proposalId].noVotes +
IVoting(_votingModule).calculateWeight(msg.sender);
proposals[proposalId].noVotes + weight;
}
}

function updateThreshold(uint256 threshold) external onlyExecutor {
_threshold = threshold;
}

function updateMinimumProposalAmount(uint256 minimumProposalAmount)
external
onlyExecutor
{
_minimumProposalAmount = minimumProposalAmount;
}
// function updateMinimumProposalAmount(uint256 minimumProposalAmount)
// external
// onlyExecutor
// {
// _minimumProposalAmount = minimumProposalAmount;
// }

function updateProposalTime(uint256 newTime) external onlyExecutor {
_proposalTime = newTime;
Expand All @@ -171,19 +172,19 @@ contract ProposalModule is Module {
proposals[_totalProposalCount].executed.push(false);
}

require(_votingModule != address(0), "TW022");
// require(_votingModule != address(0), "TW022");
require(_activeProposal[msg.sender] == false, "TW011");
uint256 total = IVoting(_votingModule).calculateWeight(msg.sender);
require(total >= _minimumProposalAmount, "TW012");
IVoting(_votingModule).startVoting(msg.sender);
// uint256 total = IVoting(_votingModule).calculateWeight(msg.sender);
// require(total >= _minimumProposalAmount, "TW012");
// IVoting(_votingModule).startVoting(msg.sender);
proposals[_totalProposalCount].executionCounter = txHashes.length;
proposals[_totalProposalCount].txHashes = txHashes;
proposals[_totalProposalCount].yesVotes = total; // the total number of YES votes for this proposal
// proposals[_totalProposalCount].yesVotes = total; // the total number of YES votes for this proposal
proposals[_totalProposalCount].deadline =
block.timestamp +
_proposalTime;
proposals[_totalProposalCount].proposer = msg.sender;
proposals[_totalProposalCount].hasVoted[msg.sender] = true;
// proposals[_totalProposalCount].hasVoted[msg.sender] = true;

_activeProposal[msg.sender] = true;
_totalProposalCount++;
Expand Down
58 changes: 52 additions & 6 deletions contracts/VotingModules/LinearVoting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "../common/Enum.sol";
import "../interfaces/IProposal.sol";

contract LinearVoting {
using SafeMath for uint256;
Expand All @@ -21,11 +22,19 @@ contract LinearVoting {
address private _governanceToken;
address private _proposalModule;
uint256 private _undelegateDelay;
address private _roleModule;
/// @dev Address that this module will pass transactions to.
address public executor;

mapping(address => Delegation) public delegations;

modifier onlyProposalModule() {
require(msg.sender == _proposalModule, "TW023");
// modifier onlyProposalModule() {
// require(msg.sender == _proposalModule, "TW023");
// _;
// }

modifier onlyExecutor() {
require(msg.sender == executor, "TW001");
_;
}

Expand All @@ -35,11 +44,23 @@ contract LinearVoting {
constructor(
address governanceToken_,
address proposalModule_,
uint256 undelgateDelay_
uint256 undelgateDelay_,
address executor_
) {
_governanceToken = governanceToken_;
_proposalModule = proposalModule_;
_undelegateDelay = undelgateDelay_;
executor = executor_;
}

/// @dev Sets the executor to a new account (`newExecutor`).
/// @notice Can only be called by the current owner.
function setExecutor(address _executor) public onlyExecutor {
executor = _executor;
}

function registerRoleModule(address module) external onlyExecutor {
_roleModule = module;
}

function governanceToken() public view virtual returns (address) {
Expand Down Expand Up @@ -75,14 +96,39 @@ contract LinearVoting {
delegations[delegatee].total = delegations[delegatee].total.sub(amount);
}

function startVoting(address delegatee) external onlyProposalModule {
function vote(uint256 proposalId, bool vote) external {
// if (_roleModule != address(0)) {
// require(IRoles(_roleModule).checkMembership(msg.sender), "TW028");
// }
// require(_votingModule != address(0), "TW006");
// require(proposals[proposalId].hasVoted[msg.sender] == false, "TW007");
// require(proposals[proposalId].canceled == false, "TW008");
// require(proposals[proposalId].deadline >= block.timestamp, "TW010");

// proposals[proposalId].hasVoted[msg.sender] = true;
startVoting(msg.sender);
require(checkBlock(msg.sender), "TW021");
IProposal(_proposalModule).receiveVote(proposalId, vote, calculateWeight(msg.sender));

// if (vote == true) {
// proposals[proposalId].yesVotes =
// proposals[proposalId].yesVotes +
// IVoting(_votingModule).calculateWeight(msg.sender);
// } else {
// proposals[proposalId].noVotes =
// proposals[proposalId].noVotes +
// IVoting(_votingModule).calculateWeight(msg.sender);
// }
}

function startVoting(address delegatee) internal {
delegations[delegatee].undelegateDelay =
block.timestamp +
_undelegateDelay;
}

function calculateWeight(address delegatee)
external
public
view
returns (uint256)
{
Expand All @@ -92,7 +138,7 @@ contract LinearVoting {
}

function checkBlock(address delegatee)
external
public
view
returns (bool)
{
Expand Down
7 changes: 7 additions & 0 deletions contracts/interfaces/IProposal.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity ^0.8.0;

interface IProposal {
function receiveVote(uint256 proposalId, bool vote, uint256 weight) external;
}
12 changes: 5 additions & 7 deletions test/governanceModuleTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ describe("proposalModule:", () => {
"50000000000000000000000"
);
expect(await proposalModule.totalProposalCount()).to.equal(0);
expect(await proposalModule.owner()).to.equal(wallet_0.address)
expect(await proposalModule.owner()).to.equal(safe.address)
expect(await proposalModule.proposalTime()).to.equal(60);
expect(await proposalModule.gracePeriod()).to.equal(60);
expect(await proposalModule.threshold()).to.equal("1000000000000000000");
Expand All @@ -78,16 +78,16 @@ describe("proposalModule:", () => {
await executeContractCallWithSigners(
safe,
proposalModule,
"registerVoteModule",
"enableModule",
[linearVoting.address],
[wallet_0]
);
expect(await proposalModule.votingModule()).to.equal(linearVoting.address);
expect(await proposalModule.isModuleEnabled(linearVoting.address)).to.equal(true);
});

it("only Safe can register linear voting module", async () => {
const { proposalModule, linearVoting } = daoFixture;
await expect(proposalModule.registerVoteModule(linearVoting.address)).to.be.revertedWith("TW001");
await expect(proposalModule.enableModule(linearVoting.address)).to.be.revertedWith("Ownable: caller is not the owner");
});

it("can delegate votes to self", async () => {
Expand All @@ -100,14 +100,12 @@ describe("proposalModule:", () => {
expect(await govToken.balanceOf(linearVoting.address)).to.equal(1000);
});

it("can undelegate votes to self", async () => {
it.only("can undelegate votes to self", async () => {
const { proposalModule, linearVoting, safe, govToken, weth } = daoFixture;
const bal = await govToken.balanceOf(wallet_0.address);
await govToken.approve(linearVoting.address, 1000);
await linearVoting.delegateVotes(wallet_0.address, 1000);
const delegatation = await linearVoting.delegations(wallet_0.address);
expect(delegatation.total).to.equal(1000);
expect(await govToken.balanceOf(linearVoting.address)).to.equal(1000);
await linearVoting.undelegateVotes(wallet_0.address, 1000);
const undelegatation = await linearVoting.delegations(wallet_0.address);
expect(undelegatation.total).to.equal(0);
Expand Down
9 changes: 7 additions & 2 deletions test/shared/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,18 @@ export async function getFixtureWithParams(
const proposalModule = await proposalContract.deploy(
ethers.BigNumber.from(1), // number of days proposals are active
ethers.BigNumber.from('1000000000000000000'), // number of votes wieghted to pass
ethers.BigNumber.from('10000'), // min proposal gov token amt
)
await proposalModule.setExecutor(safe.address);
await proposalModule.transferOwnership(safe.address);
console.log('deployed House ERC20 DAO: ', proposalModule.address)

const linearContract = await ethers.getContractFactory("LinearVoting")
const linearVoting = await linearContract.deploy(govToken.address, proposalModule.address, 180)
const linearVoting = await linearContract.deploy(
govToken.address,
proposalModule.address,
180,
safe.address
)

await govToken.transfer(safe.address, ethers.BigNumber.from('50000000000000000000000'))

Expand Down

0 comments on commit a4154b6

Please sign in to comment.