-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
231 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"]); | ||
}) | ||
}); |