Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Governance #5

Draft
wants to merge 14 commits into
base: academy/milestone6
Choose a base branch
from
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
INFURA_API_KEY="<string?>"
PRIVATE_KEY="<string?>"
ETHERSCAN_API="<string?>"
PRIVATE_KEY_OWNER="<string?>"
MNEMONICS="<string?>"
2 changes: 2 additions & 0 deletions .github/workflows/static-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ jobs:
INFURA_API_KEY: ${{ secrets.INFURA_API_KEY }}
PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }}
ETHERSCAN_API: ${{ secrets.ETHERSCAN_API }}
MNEMONICS: ${{ secrets.MNEMONICS }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
Expand All @@ -39,6 +40,7 @@ jobs:
INFURA_API_KEY: ${{ secrets.INFURA_API_KEY }}
PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }}
ETHERSCAN_API: ${{ secrets.ETHERSCAN_API }}
MNEMONICS: ${{ secrets.MNEMONICS }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
Expand Down
2 changes: 1 addition & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
echo "Running tests..."

y | yarn codestyle:fix
yarn codestyle:fix
yarn lint-staged
yarn test

Expand Down
2 changes: 1 addition & 1 deletion contracts/ERC20/BaseERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity ^0.8.20;

import { IERC20 } from "./IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";

abstract contract BaseERC20 is IERC20, Ownable {
uint256 internal _totalSupply;
Expand All @@ -27,7 +27,7 @@
}

modifier validAddress(address _address) {
require(_address != address(0), "Invalid address");

Check warning on line 30 in contracts/ERC20/BaseERC20.sol

View workflow job for this annotation

GitHub Actions / codestyle

GC: Use Custom Errors instead of require statements
_;
}

Expand All @@ -39,7 +39,7 @@
address to,
uint256 value
) public virtual validAddress(to) returns (bool) {
require(_balances[msg.sender] >= value, "Insufficient balance");

Check warning on line 42 in contracts/ERC20/BaseERC20.sol

View workflow job for this annotation

GitHub Actions / codestyle

GC: Use Custom Errors instead of require statements

_balances[msg.sender] = _balances[msg.sender] - value;
_balances[to] = _balances[to] + value;
Expand Down Expand Up @@ -77,8 +77,8 @@
validAddress(to)
returns (bool)
{
require(_balances[from] >= value, "Insufficient balance");

Check warning on line 80 in contracts/ERC20/BaseERC20.sol

View workflow job for this annotation

GitHub Actions / codestyle

GC: Use Custom Errors instead of require statements
require(_allowances[from][to] >= value, "Insufficient allowance");

Check warning on line 81 in contracts/ERC20/BaseERC20.sol

View workflow job for this annotation

GitHub Actions / codestyle

GC: Use Custom Errors instead of require statements

_balances[from] = _balances[from] - value;
_allowances[from][to] = _allowances[from][to] - value;
Expand Down
10 changes: 2 additions & 8 deletions contracts/ERC20/TradableERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
) VotingERC20(_initialSupply, _initialPrice, _name, _symbol, _decimals) {}

modifier hasNotVoted() {
require(

Check warning on line 18 in contracts/ERC20/TradableERC20.sol

View workflow job for this annotation

GitHub Actions / codestyle

Error message for require is too long: 37 counted / 32 allowed

Check warning on line 18 in contracts/ERC20/TradableERC20.sol

View workflow job for this annotation

GitHub Actions / codestyle

GC: Use Custom Errors instead of require statements
userVote[votingId][msg.sender] == 0,
"Can't use this functions after voting"
);
Expand All @@ -28,8 +28,8 @@
uint256 _prevAmount,
uint256 _price
) internal {
require(isVoting, "Voting is not started");

Check warning on line 31 in contracts/ERC20/TradableERC20.sol

View workflow job for this annotation

GitHub Actions / codestyle

GC: Use Custom Errors instead of require statements
require(userVote[votingId][_user] != 0, "User has not voted yet");

Check warning on line 32 in contracts/ERC20/TradableERC20.sol

View workflow job for this annotation

GitHub Actions / codestyle

GC: Use Custom Errors instead of require statements

uint256 newAmount = _balances[_user];

Expand All @@ -38,12 +38,6 @@

deleteNode(oldId);

// console.log(_price, oldListAmount, _prevAmount, newAmount);

// console.logBytes32(oldId);
// console.logUint(price);
// console.logUint(oldListAmount - oldAmount + _newAmount);

insert(
votingId,
_prevId,
Expand All @@ -59,7 +53,7 @@
}

function buy() public payable hasNotVoted {
require(msg.value > 0, "Ether value must be greater than 0");

Check warning on line 56 in contracts/ERC20/TradableERC20.sol

View workflow job for this annotation

GitHub Actions / codestyle

Error message for require is too long: 34 counted / 32 allowed

Check warning on line 56 in contracts/ERC20/TradableERC20.sol

View workflow job for this annotation

GitHub Actions / codestyle

GC: Use Custom Errors instead of require statements

uint256 amount = msg.value * price;
uint256 fee = (amount * feePercentage) / 10000;
Expand Down Expand Up @@ -156,14 +150,14 @@
_transferFromInternal(msg.sender, address(this), _amount);
_burn(address(this), _amount - fee);

payable(msg.sender).transfer(value);

_updateVoteOnInteraction(
_id,
msg.sender,
_balances[msg.sender] + _amount,
userVote[votingId][msg.sender]
);

payable(msg.sender).transfer(value);
}

function transfer(
Expand Down
192 changes: 192 additions & 0 deletions contracts/Governance/Governance.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import { GovernanceToken } from "./GovernanceToken.sol";
import { RaffleExtended } from "../Raffle/RaffleExtended.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";
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";

import "hardhat/console.sol";

contract Governance is Ownable, AccessControl, Initializable {
bytes32 public constant EXECUTER_ROLE = keccak256("EXECUTER_ROLE");

GovernanceToken public token;
RaffleExtended public raffle;

uint256 public percentageForProposal;

uint256 public blocksBeforeVoting;
uint256 public blocksBeforeExecution;

ProposalStorageState internal _proposalsState;
mapping(bytes32 => mapping(address => bool)) internal _votes;

using ProposalStorage for ProposalStorageState;

event ProposalCreated(bytes32 indexed id, address indexed sender);
event ProposalVoted(
bytes32 indexed id,
address indexed voter,
bool voteInFavor
);
event ProposalProcessed(
bytes32 indexed id,
address indexed executer,
ProposalState indexed state
);

constructor() Ownable(msg.sender) {}

function initialize(
address payable _token,
address _raffle,
address _defaultExecuter,
uint256 _percentageForProposal,
uint256 _blocksBeforeVoting,
uint256 _blocksBeforeExecution
) public virtual initializer onlyOwner {
token = GovernanceToken(_token);
raffle = RaffleExtended(_raffle);

_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(EXECUTER_ROLE, _defaultExecuter);

percentageForProposal = _percentageForProposal;
blocksBeforeVoting = _blocksBeforeVoting;
blocksBeforeExecution = _blocksBeforeExecution;
}

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

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

id = _proposalsState.addData(proposal);

emit ProposalCreated(id, msg.sender);
}

function voteForProposal(bytes32 id, bool voteInFavor) public virtual {
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.proposedAt + blocksBeforeVoting < block.number,
"MyGovernance: Voting period has not started"
);
require(
proposal.votingStartedAt + blocksBeforeExecution > block.number ||
proposal.votingStartedAt == 0,
"MyGovernance: Voting period has ended"
);
require(
proposal.state == ProposalState.Created,
"MyGovernance: Proposal has been processed"
);

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

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;
}

_votes[id][msg.sender] = true;

emit ProposalVoted(id, msg.sender, voteInFavor);
}

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

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

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

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

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

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

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

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

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

import { Governance } from "./Governance.sol";
import { GovernanceToken } from "./GovernanceToken.sol";
import { RaffleExtended } from "../Raffle/RaffleExtended.sol";
import { Proposal, ProposalStorage, ProposalStorageState } from "./ProposalStorage.sol";

import "hardhat/console.sol";

contract GovernanceExtended is Governance {
using ProposalStorage for ProposalStorageState;

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

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

function setTokenAddress(GovernanceToken _token) public onlyOwner {
token = _token;
}

function setRaffleAddress(RaffleExtended _raffle) public onlyOwner {
raffle = _raffle;
}

function setPercentageForProposal(
uint256 _percentageForProposal
) public onlyOwner {
percentageForProposal = _percentageForProposal;
}

function setBlocksBeforeVoting(
uint256 _blocksBeforeVoting
) public onlyOwner {
blocksBeforeVoting = _blocksBeforeVoting;
}

function setBlocksBeforeExecution(
uint256 _blocksBeforeExecution
) public onlyOwner {
blocksBeforeExecution = _blocksBeforeExecution;
}

function getProposal(
bytes32 _id
) public view returns (Proposal memory proposal) {
return _proposalsState.getData(_id);
}

function getProposals(
bytes32[] memory _ids
) public view returns (Proposal[] memory) {
Proposal[] memory proposals = new Proposal[](_ids.length);

for (uint256 i = 0; i < _ids.length; i++) {
proposals[i] = _proposalsState.getData(_ids[i]);
}

console.log(3);

return proposals;
}
}
43 changes: 43 additions & 0 deletions contracts/Governance/GovernanceToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// 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";
import { ERC20Burnable } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";

import "hardhat/console.sol";

contract GovernanceToken is
ERC20,
ERC20Burnable,
ERC20Permit,
ERC20Votes,
Ownable
{
constructor()
ERC20("GovernanceToken", "GTK")
ERC20Permit("GovernanceToken")
Ownable(msg.sender)
{}

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);
}

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