Skip to content

Commit

Permalink
feat: token, and full governance layout
Browse files Browse the repository at this point in the history
  • Loading branch information
yarre-uk committed Jun 18, 2024
1 parent 769a6c2 commit 520150c
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 696 deletions.
32 changes: 32 additions & 0 deletions contracts/Governance/GovernanceToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.20;

import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { ERC20Permit, Nonces } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import { ERC20Votes } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";

contract GovernanceToken is ERC20, ERC20Permit, ERC20Votes, Ownable {
constructor(
address initialOwner
)
ERC20("GovernanceToken", "GTK")
ERC20Permit("GovernanceToken")
Ownable(initialOwner)
{}

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

function nonces(
address owner
) public view override(ERC20Permit, Nonces) returns (uint256) {
return super.nonces(owner);
}
}
146 changes: 131 additions & 15 deletions contracts/Governance/MyGovernance.sol
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import { YarreToken } from "../ERC20/YarreToken.sol";
import { GovernanceToken } from "./GovernanceToken.sol";
import { RaffleExtended } from "../Raffle/RaffleExtended.sol";
import { ProposalStorage, Proposal, State } from "./ProposalStorage.sol";
import { ProposalStorage, Proposal, ProposalStorageState, ProposalState } from "./ProposalStorage.sol";

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol";
Expand All @@ -12,21 +12,26 @@ import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable
contract MyGovernance is Ownable, AccessControl, Initializable {
bytes32 public constant EXECUTER_ROLE = keccak256("EXECUTER_ROLE");

YarreToken public token;
GovernanceToken public token;
RaffleExtended public raffle;

uint256 public percentageForProposal;

uint256 public blockBeforeVoting;
uint256 public blockBeforeExecution;
uint256 public blocksBeforeVoting;
uint256 public blocksBeforeExecution;

State private state;
ProposalStorageState private _proposalsState;
mapping(bytes32 => mapping(address => bool)) private _votes;

using ProposalStorage for State;
using ProposalStorage for ProposalStorageState;

event ProposalCreated(bytes32 id, uint256 percentage);
event ProposalVoted(bytes32 id, address voter);
event ProposalExecuted(bytes32 id);
event ProposalCreated(bytes32 indexed id, address indexed sender);
event ProposalVoted(bytes32 indexed id, address indexed voter);
event ProposalProcessed(
bytes32 indexed id,
address indexed executer,
ProposalState indexed state
);

error UnauthorizedExecuter(address caller);

Expand All @@ -36,23 +41,134 @@ contract MyGovernance is Ownable, AccessControl, Initializable {
address payable _token,
address _raffle
) public initializer onlyOwner {
token = YarreToken(_token);
token = GovernanceToken(_token);
raffle = RaffleExtended(_raffle);

percentageForProposal = 100;
blockBeforeVoting = 300;
blockBeforeExecution = 300;
blocksBeforeVoting = 300;
blocksBeforeExecution = 300;
}

modifier hasEnoughPercentage(uint256 _percentageCap) {
require(
token.userPercentage() >= _percentageCap,
"MyGovernance: not enough percentage"
token.balanceOf(msg.sender) >=
(token.totalSupply() * _percentageCap) / 10000,
"MyGovernance: Sender does not have enough percentage"
);
_;
}

function grantRoleExecuter(address account) public onlyOwner {
grantRole(EXECUTER_ROLE, account);
}

function revokeRoleExecuter(address account) public onlyOwner {
revokeRole(EXECUTER_ROLE, account);
}

function createProposal(
bytes32[] memory _actions,
bytes32 _description
) public hasEnoughPercentage(percentageForProposal) {
Proposal memory proposal = Proposal({
sender: msg.sender,
signatures: _actions,
calldatas: _actions,
proposedAt: block.number,
description: _description,
votingStartedAt: 0,
forVotes: 0,
againstVotes: 0,
state: ProposalState.Created
});

bytes32 id = _proposalsState.addData(proposal);

emit ProposalCreated(id, msg.sender);
}

/**
* @dev Allows a voter to vote on a proposal.
* @param id The ID of the proposal.
* @param voteInFavor The decision of the voter (true for voting in favor, false for voting against).
* @notice This function requires the proposal to be in the voting period and the voter to have non-zero weight.
* @notice The voter cannot be the proposer and cannot have already voted on the proposal.
* @notice The weight of the voter is determined by the number of tokens they hold at the time of the proposal.
*/
function voteProposal(bytes32 id, bool voteInFavor) public {
Proposal storage proposal = _proposalsState.getData(id);

require(
proposal.sender != msg.sender,
"MyGovernance: Sender is the proposer"
);
require(
_votes[id][msg.sender] == false,
"MyGovernance: Sender has already voted"
);
require(
proposal.votingStartedAt + block.number > blocksBeforeVoting,
"MyGovernance: Voting period has not started"
);

uint256 weight = token.getPastVotes(msg.sender, proposal.proposedAt);

require(weight > 0, "MyGovernance: Voter does not have any weight");

if (proposal.votingStartedAt == 0) {
proposal.votingStartedAt = block.number;
}

if (voteInFavor) {
proposal.forVotes += weight;
} else {
proposal.againstVotes += weight;
}

emit ProposalVoted(id, msg.sender);
}

function _cancelProposal(bytes32 id, Proposal storage proposal) internal {
proposal.state = ProposalState.Canceled;

emit ProposalProcessed(id, msg.sender, ProposalState.Canceled);
}

function _executeProposal(bytes32 id, Proposal storage proposal) internal {
proposal.state = ProposalState.Executed;

for (uint256 i = 0; i < proposal.signatures.length; i++) {
(bool success, ) = address(raffle).call(
abi.encodePacked(proposal.signatures[i], proposal.calldatas[i])
);

require(success, "MyGovernance: Proposal execution failed");
}

emit ProposalProcessed(id, msg.sender, ProposalState.Executed);
}

function processProposal(bytes32 id) public onlyRole(EXECUTER_ROLE) {
Proposal storage proposal = _proposalsState.getData(id);

require(
proposal.votingStartedAt + block.number > blocksBeforeExecution,
"MyGovernance: Execution period has not started"
);
require(
proposal.forVotes > proposal.againstVotes,
"MyGovernance: Proposal has not passed"
);
require(
proposal.state != ProposalState.Executed ||
proposal.state != ProposalState.Canceled,
"MyGovernance: Proposal has been processed"
);

if (proposal.forVotes <= proposal.againstVotes) {
_cancelProposal(id, proposal);
} else {
_executeProposal(id, proposal);
}
}
}
54 changes: 26 additions & 28 deletions contracts/Governance/ProposalStorage.sol
Original file line number Diff line number Diff line change
@@ -1,67 +1,65 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

struct State {
enum ProposalState {
Created,
Executed,
Canceled
}

struct ProposalStorageState {
mapping(bytes32 => Proposal) proposals;
bytes32 lastProposalId;
}

// Timestamps are in block numbers
struct Proposal {
uint256 id;
address sender;
bytes32[] signatures;
bytes32[] calldatas;
uint256 proposedAt;
bytes32 description;
//---
uint256 votingStartedAt;
bool executed;
uint256 forVotes;
uint256 againstVotes;
ProposalState state;
}

library ProposalStorage {
function getId(Proposal memory _params) internal pure returns (bytes32) {
return
keccak256(
abi.encodePacked(
_params.id,
_params.sender,
_params.signatures,
_params.calldatas,
_params.proposedAt,
_params.votingStartedAt,
_params.executed
_params.description
)
);
}

function isEmpty(
State storage state,
ProposalStorageState storage state,
bytes32 id
) internal view returns (bool) {
return state.proposals[id].sender == address(0);
}

function addData(
State storage state,
Proposal memory _Proposal
) internal returns (bytes32) {
bytes32 id = getId(_Proposal);
state.proposals[id] = _Proposal;
ProposalStorageState storage state,
Proposal memory proposal
) internal returns (bytes32 id) {
id = getId(proposal);
state.proposals[id] = proposal;
state.lastProposalId = id;

return id;
}

function getData(
State storage state,
ProposalStorageState storage state,
bytes32 id
) internal view returns (Proposal memory) {
) internal view returns (Proposal storage) {
return state.proposals[id];
}

function updateData(
State storage state,
bytes32 id,
Proposal memory _Proposal
) internal {
state.proposals[id] = _Proposal;
}

function removeData(State storage state, bytes32 id) internal {
delete state.proposals[id];
}
}
23 changes: 19 additions & 4 deletions contracts/Raffle/RaffleExtended.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@
pragma solidity 0.8.24;

import { Raffle, RaffleStatus } from "./Raffle.sol";
import { TransferHelper } from "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol";
import { Deposit } from "./DepositStorage.sol";

import { TransferHelper } from "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol";

contract RaffleExtended is Raffle {
uint256 public X;

Check failure on line 10 in contracts/Raffle/RaffleExtended.sol

View workflow job for this annotation

GitHub Actions / codestyle

Variable name must be in mixedCase

Check failure on line 10 in contracts/Raffle/RaffleExtended.sol

View workflow job for this annotation

GitHub Actions / codestyle

Variable name must be in mixedCase
uint256 public Y;

Check failure on line 11 in contracts/Raffle/RaffleExtended.sol

View workflow job for this annotation

GitHub Actions / codestyle

Variable name must be in mixedCase

Check failure on line 11 in contracts/Raffle/RaffleExtended.sol

View workflow job for this annotation

GitHub Actions / codestyle

Variable name must be in mixedCase
uint256 public Z;
address public founder;
address public staking;

address public governor;

event XChanged(uint256 X);
event YChanged(uint256 Y);
event ZChanged(uint256 Z);
Expand All @@ -30,6 +33,10 @@ contract RaffleExtended is Raffle {
whitelist = _whitelist;
}

function setGovernor(address _governor) public onlyOwner {
governor = _governor;
}

function getChance(
bytes32[] memory _ids
) public view returns (uint256 total) {
Expand All @@ -56,17 +63,25 @@ contract RaffleExtended is Raffle {
return deposits;
}

function setX(uint256 _X) public onlyOwner {
modifier onlyOwnerOrGovernor() {
require(
msg.sender == owner() || msg.sender == governor,
"RaffleExtended: Caller is not the owner or governor"
);
_;
}

function setX(uint256 _X) public onlyOwnerOrGovernor {
X = _X;
emit XChanged(_X);
}

function setY(uint256 _Y) public onlyOwner {
function setY(uint256 _Y) public onlyOwnerOrGovernor {
Y = _Y;
emit YChanged(_Y);
}

function setZ(uint256 _Z) public onlyOwner {
function setZ(uint256 _Z) public onlyOwnerOrGovernor {
Z = _Z;
emit ZChanged(_Z);
}
Expand Down
Loading

0 comments on commit 520150c

Please sign in to comment.