Skip to content

Commit

Permalink
Add proposal contract
Browse files Browse the repository at this point in the history
  • Loading branch information
weiqiushi committed Aug 1, 2024
1 parent 09dc6fe commit bc788c4
Show file tree
Hide file tree
Showing 2 changed files with 231 additions and 0 deletions.
125 changes: 125 additions & 0 deletions contracts/proposal.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

import "./dividend.sol";

import "hardhat/console.sol";

contract ProposalContract is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable, OwnableUpgradeable {
DividendContract dividendContract;

struct VoteInfo {
address voter;
bool support;
uint256 amount;
string desc;
}
struct Proposal {
string title;
string desc;
uint256 endtime;
uint256 totalSupport;
uint256 totalOppose;
mapping (address => bool) voted;
VoteInfo[] votes;
}

struct ProposalBrief {
string title;
string desc;
uint256 endtime;
uint256 totalSupport;
uint256 totalOppose;
uint256 totalVotes;
}

mapping (uint256 => Proposal) public proposals;
uint256 curProposalId;

event CreateProposal(uint256 id);
event Vote(uint256 indexed id, address indexed voter, bool support, uint256 amount, string desc);

function initialize(address dividendAddress) public initializer {
__UUPSUpgradeable_init();
__ReentrancyGuard_init();
__Ownable_init(msg.sender);

dividendContract = DividendContract(payable(dividendAddress));
}

function _authorizeUpgrade(address) internal override onlyOwner {}

function createProposal(string calldata _title, string calldata desc, uint256 duration) public {
require(duration <= 3 days, "Duration too long");
uint256 lockedAmount = dividendContract.updateLockState(msg.sender);
require(lockedAmount >= 50000 ether, "Locked amount not enough");
uint256 proposalId = ++curProposalId;

proposals[proposalId].title = _title;
proposals[proposalId].desc = desc;
proposals[proposalId].endtime = block.timestamp + duration;

emit CreateProposal(proposalId);
}

function supportProposal(uint256 id, string calldata desc) public {
require(proposals[id].endtime > block.timestamp, "Proposal ended");
require(proposals[id].voted[msg.sender] == false, "Already voted");

uint256 lockedAmount = dividendContract.updateLockState(msg.sender);
if (lockedAmount == 0) {
return;
}
proposals[id].totalSupport += lockedAmount;
proposals[id].voted[msg.sender] = true;
proposals[id].votes.push(VoteInfo(msg.sender, true, lockedAmount, desc));

emit Vote(id, msg.sender, true, lockedAmount, desc);
}

function opposeProposal(uint256 id, string calldata desc) public {
require(proposals[id].endtime > block.timestamp, "Proposal ended");
require(proposals[id].voted[msg.sender] == false, "Already voted");

uint256 lockedAmount = dividendContract.updateLockState(msg.sender);
if (lockedAmount == 0) {
return;
}
proposals[id].totalOppose += lockedAmount;
proposals[id].voted[msg.sender] = true;
proposals[id].votes.push(VoteInfo(msg.sender, false, lockedAmount, desc));

emit Vote(id, msg.sender, false, lockedAmount, desc);
}

function getProposal(uint256 id) public view returns (ProposalBrief memory) {
Proposal storage proposal = proposals[id];
ProposalBrief memory brief;
brief.title = proposal.title;
brief.desc = proposal.desc;
brief.endtime = proposal.endtime;
brief.totalSupport = proposal.totalSupport;
brief.totalOppose = proposal.totalOppose;
brief.totalVotes = proposal.votes.length;
return brief;
}

function getProposalVotes(uint256 id, uint256 page, uint256 size) public view returns (VoteInfo[] memory) {
Proposal storage proposal = proposals[id];
uint256 start = page * size;
uint256 end = (page + 1) * size;
if (end > proposal.votes.length) {
end = proposal.votes.length;
}
VoteInfo[] memory votes = new VoteInfo[](end - start);
for (uint256 i = start; i < end; i++) {
votes[i - start] = proposal.votes[i];
}
return votes;
}
}
106 changes: 106 additions & 0 deletions test/test_proposal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { ethers, upgrades } from "hardhat";
import { DMC, GWT, DividendContract, ProposalContract } from "../typechain-types";
import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers';
import { expect } from "chai";
import { mine } from "@nomicfoundation/hardhat-network-helpers"

describe("Proposal", function () {
let dmc: DMC
let gwt: GWT
let dividend: DividendContract;
let proposal: ProposalContract;
let signers: HardhatEthersSigner[];
before(async function () {
signers = await ethers.getSigners();
dmc = await (await ethers.deployContract("DMC", [ethers.parseEther("1000000000"), [signers[0].address], [ethers.parseEther("1000000")]])).waitForDeployment()
gwt = await (await ethers.deployContract("GWT", [[], []])).waitForDeployment()

dividend = await upgrades.deployProxy(
await ethers.getContractFactory("DividendContract"),
[await dmc.getAddress(), 1000, [await gwt.getAddress()], 60 * 60 * 24 * 3, ethers.ZeroAddress]
) as unknown as DividendContract;

proposal = await upgrades.deployProxy(
await ethers.getContractFactory("ProposalContract"),
[await dividend.getAddress()]) as unknown as ProposalContract;

await (await dividend.updateProposalContract(await proposal.getAddress())).wait();
await (await dmc.transfer(signers[1].address, ethers.parseEther("100000"))).wait();
await (await dmc.transfer(signers[2].address, ethers.parseEther("100000"))).wait();
await (await dmc.transfer(signers[3].address, ethers.parseEther("100000"))).wait();

for (let i = 0; i < 4; i++) {
console.log(`signer ${i}: ${signers[i].address}`);
}
});

it("create proposal fail", async () => {
await expect(proposal.createProposal(
"testTitle",
"testContent",
4 * 24 * 60 * 60)).to.be.revertedWith("Duration too long");

await expect(proposal.createProposal(
"testTitle",
"testContent",
3 * 24 * 60 * 60)).to.be.revertedWith("Locked amount not enough");
});

it("create proposal", async () => {
await (await dmc.approve(await dividend.getAddress(), ethers.parseEther("60000"))).wait();
await (await dividend.stake(ethers.parseEther("60000"))).wait();

// pass 1 day
await mine(2, {interval: 24*60*60})

await expect(proposal.createProposal("testTitle", "testContent", 2.25*24*60*60)).to.emit(proposal, "CreateProposal").withArgs(1);

// pass 2 days
await mine(2, {interval: 2*24*60*60})

// cant unstack because lock time extended
await expect(dividend.unstake(ethers.parseEther("10000"))).to.be.revertedWith("Unstake is locked");
});

it("vote proposal", async () => {
await (await dmc.connect(signers[1]).approve(await dividend.getAddress(), 700)).wait();
await (await dividend.connect(signers[1]).stake(700)).wait();

await (await dmc.connect(signers[2]).approve(await dividend.getAddress(), 5000)).wait();
await (await dividend.connect(signers[2]).stake(5000)).wait();

await (await dmc.connect(signers[3]).approve(await dividend.getAddress(), 3000)).wait();
await (await dividend.connect(signers[3]).stake(3000)).wait();


await expect(proposal.createProposal("testTitle", "testContent", 1*24*60*60)).to.emit(proposal, "CreateProposal").withArgs(2);

// pass 2 days
await mine(2, {interval: 2*24*60*60})

await expect(proposal.connect(signers[1]).supportProposal(2, "signer 1 support")).to.revertedWith("Proposal ended");

await expect(proposal.createProposal("testTitle", "testContent", 3*24*60*60)).to.emit(proposal, "CreateProposal").withArgs(3);
await expect(proposal.connect(signers[1]).supportProposal(3, "signer 1 support")).to.emit(proposal, "Vote")
.withArgs(3, signers[1].address, true, 700, "signer 1 support");
await expect(proposal.connect(signers[1]).supportProposal(3, "signer 1 support again")).to.revertedWith("Already voted");

await expect(proposal.connect(signers[2]).opposeProposal(3, "signer 2 support")).to.emit(proposal, "Vote")
.withArgs(3, signers[2].address, false, 5000, "signer 2 support");

await expect(proposal.connect(signers[3]).supportProposal(3, "signer 3 support")).to.emit(proposal, "Vote")
.withArgs(3, signers[3].address, true, 3000, "signer 3 support");

let brief = await proposal.getProposal(3);

expect(brief.totalOppose).to.equal(5000);
expect(brief.totalSupport).to.equal(3700);
expect(brief.totalVotes).to.equal(3);

let votes = await proposal.getProposalVotes(3, 0, 10);
expect(votes.length).to.equal(3);
expect(votes[0]).to.deep.equal([signers[1].address, true, 700, "signer 1 support"]);
expect(votes[1]).to.deep.equal([signers[2].address, false, 5000, "signer 2 support"]);
expect(votes[2]).to.deep.equal([signers[3].address, true, 3000, "signer 3 support"]);
})
});

0 comments on commit bc788c4

Please sign in to comment.