395 lines
16 KiB
JavaScript
395 lines
16 KiB
JavaScript
// /*global contract, config, it, assert, artifacts*/
|
|
let StakingPoolDAO = artifacts.require('StakingPoolDAO');
|
|
const SNT = artifacts.require('SNT');
|
|
|
|
let iuri, jonathan, richard, michael, pascal, eric, andre;
|
|
const VoteStatus = {
|
|
NONE: 0,
|
|
YES: 1,
|
|
NO: 2
|
|
};
|
|
|
|
// For documentation please see https://embark.status.im/docs/contracts_testing.html
|
|
config({
|
|
contracts: {
|
|
deploy:
|
|
{
|
|
"MiniMeToken": {"deploy": false},
|
|
"MiniMeTokenFactory": {},
|
|
"SNT": {
|
|
"instanceOf": "MiniMeToken",
|
|
"args": [
|
|
"$MiniMeTokenFactory",
|
|
"0x0000000000000000000000000000000000000000",
|
|
0,
|
|
"TestMiniMeToken",
|
|
18,
|
|
"STT",
|
|
true
|
|
]
|
|
},
|
|
"StakingPoolDAO": {
|
|
"deploy": false,
|
|
}
|
|
}
|
|
}
|
|
}, (_err, accounts) => {
|
|
iuri = accounts[0];
|
|
jonathan = accounts[1];
|
|
richard = accounts[2];
|
|
pascal = accounts[3];
|
|
michael = accounts[4];
|
|
eric = accounts[5];
|
|
andre = accounts[6];
|
|
});
|
|
|
|
// TODO: add asserts for balances
|
|
|
|
let StakingPool;
|
|
|
|
contract("StakingPoolDAO", function () {
|
|
this.timeout(0);
|
|
|
|
before(async () => {
|
|
// distribute SNT
|
|
await SNT.methods.generateTokens(iuri, "10000000000").send({from: iuri});
|
|
await SNT.methods.generateTokens(jonathan, "10000000000").send({from: iuri});
|
|
await SNT.methods.generateTokens(richard, "10000000000").send({from: iuri});
|
|
await SNT.methods.generateTokens(pascal, "10000000000").send({from: iuri});
|
|
await SNT.methods.generateTokens(michael, "10000000000").send({from: iuri});
|
|
await SNT.methods.generateTokens(eric, "10000000000").send({from: iuri});
|
|
|
|
// Deploy Staking Pool
|
|
StakingPool = await StakingPoolDAO.deploy({ arguments: [SNT.options.address, 30, 20, 0, 10, 0, 0, 0] }).send();
|
|
const encodedCall = StakingPool.methods.stake("10000000000").encodeABI();
|
|
|
|
await web3.eth.sendTransaction({from: iuri, to: StakingPool.options.address, value: "100000000000000000"});
|
|
|
|
await SNT.methods.approveAndCall(StakingPool.options.address, "10000000000", encodedCall).send({from: iuri});
|
|
await SNT.methods.approveAndCall(StakingPool.options.address, "10000000000", encodedCall).send({from: jonathan});
|
|
await SNT.methods.approveAndCall(StakingPool.options.address, "10000000000", encodedCall).send({from: richard});
|
|
await SNT.methods.approveAndCall(StakingPool.options.address, "10000000000", encodedCall).send({from: pascal});
|
|
await SNT.methods.approveAndCall(StakingPool.options.address, "10000000000", encodedCall).send({from: michael});
|
|
await SNT.methods.approveAndCall(StakingPool.options.address, "10000000000", encodedCall).send({from: eric});
|
|
|
|
// Mine 100 blocks
|
|
for(let i = 0; i < 30; i++){
|
|
await mineAtTimestamp(12345678);
|
|
}
|
|
})
|
|
|
|
describe("proposal creation", () => {
|
|
it("non token holders can not submit proposals", async () => {
|
|
const toSend = StakingPool.methods.addProposal(andre, 1, "0x", "0x");
|
|
await assert.reverts(toSend, {from: andre}, "Returned error: VM Exception while processing transaction: revert Token balance is required to perform this operation");
|
|
});
|
|
|
|
it("token holders can create proposals", async () => {
|
|
const receipt = await StakingPool.methods.addProposal(richard, 1, "0x", "0x").send({from: richard});
|
|
assert.eventEmitted(receipt, 'NewProposal');
|
|
});
|
|
});
|
|
|
|
describe("voting", () => {
|
|
before(async () => {
|
|
const balance = await StakingPool.methods.balanceOf(jonathan).call();
|
|
await StakingPool.methods.transfer(eric, balance).send({from: jonathan});
|
|
});
|
|
|
|
let proposalId;
|
|
beforeEach(async () => {
|
|
const receipt = await StakingPool.methods.addProposal(richard, 1, "0x", "0x").send({from: richard});
|
|
proposalId = receipt.events.NewProposal.returnValues.proposalId;
|
|
});
|
|
|
|
it("only those having balance can vote on a proposal", async () => {
|
|
const toSend = StakingPool.methods.vote(proposalId, true);
|
|
await assert.reverts(toSend, {from: jonathan}, "Returned error: VM Exception while processing transaction: revert Not enough tokens at the moment of proposal creation");
|
|
await StakingPool.methods.vote(proposalId, true).send({from: eric});
|
|
});
|
|
|
|
it("accounts can vote more than once and results are affected accordingly", async () => {
|
|
let votesY = await StakingPool.methods.votes(proposalId, true).call();
|
|
let votesN = await StakingPool.methods.votes(proposalId, true).call();
|
|
let myVote = await StakingPool.methods.voteOf(richard, proposalId).call();
|
|
|
|
assert.strictEqual(votesY, "0");
|
|
assert.strictEqual(votesN, "0");
|
|
|
|
let receipt = await StakingPool.methods.vote(proposalId, true).send({from: richard});
|
|
|
|
votesY = await StakingPool.methods.votes(proposalId, true).call();
|
|
votesN = await StakingPool.methods.votes(proposalId, false).call();
|
|
|
|
|
|
assert.strictEqual(votesY, "10000000000");
|
|
assert.strictEqual(votesN, "0");
|
|
|
|
receipt = await StakingPool.methods.vote(proposalId, false).send({from: richard});
|
|
|
|
votesY = await StakingPool.methods.votes(proposalId, true).call();
|
|
votesN = await StakingPool.methods.votes(proposalId, false).call();
|
|
|
|
assert.strictEqual(votesY, "0");
|
|
assert.strictEqual(votesN, "10000000000");
|
|
});
|
|
|
|
it("voting is only valid during the period it is active", async () => {
|
|
const toSend = await StakingPool.methods.vote(proposalId, false);
|
|
|
|
await toSend.send({from: richard});
|
|
|
|
// Mine 20 blocks
|
|
for(let i = 0; i < 20; i++){
|
|
await mineAtTimestamp(12345678);
|
|
}
|
|
|
|
await assert.reverts(toSend, {from: richard}, "Returned error: VM Exception while processing transaction: revert Proposal voting has already ended");
|
|
});
|
|
|
|
it("check that vote result matches what was voted", async () => {
|
|
await StakingPool.methods.vote(proposalId, true).send({from: eric});
|
|
await StakingPool.methods.vote(proposalId, true).send({from: michael});
|
|
await StakingPool.methods.vote(proposalId, false).send({from: pascal});
|
|
await StakingPool.methods.vote(proposalId, false).send({from: richard});
|
|
|
|
// Mine 20 blocks
|
|
for(let i = 0; i < 20; i++){
|
|
await mineAtTimestamp(12345678);
|
|
}
|
|
|
|
let votesY = await StakingPool.methods.votes(proposalId, true).call();
|
|
let votesN = await StakingPool.methods.votes(proposalId, false).call();
|
|
|
|
const result = await StakingPool.methods.proposalStatus(proposalId).call();
|
|
|
|
assert.strictEqual(votesY, "30000000000");
|
|
assert.strictEqual(votesN, "20000000000");
|
|
assert.strictEqual(result.approved, true);
|
|
assert.strictEqual(result.executed, false);
|
|
});
|
|
});
|
|
|
|
describe("proposal execution", () => {
|
|
let proposalId;
|
|
beforeEach(async () => {
|
|
const receipt = await StakingPool.methods.addProposal("0x00000000000000000000000000000000000000AA", 12345, "0x", "0x").send({from: richard});
|
|
proposalId = receipt.events.NewProposal.returnValues.proposalId;
|
|
});
|
|
|
|
it("active voting proposals cant be executed", async () => {
|
|
await StakingPool.methods.vote(proposalId, true).send({from: richard});
|
|
const toSend = StakingPool.methods.executeTransaction(proposalId);
|
|
await assert.reverts(toSend, {from: richard}, "Returned error: VM Exception while processing transaction: revert Voting is still active");
|
|
});
|
|
|
|
it("unapproved proposals cant be executed", async () => {
|
|
await StakingPool.methods.vote(proposalId, false).send({from: richard});
|
|
|
|
// Mine 20 blocks
|
|
for(let i = 0; i < 20; i++){
|
|
await mineAtTimestamp(12345678);
|
|
}
|
|
|
|
const toSend = StakingPool.methods.executeTransaction(proposalId);
|
|
await assert.reverts(toSend, {from: iuri}, "Returned error: VM Exception while processing transaction: revert Proposal wasn't approved");
|
|
});
|
|
|
|
it("approved proposals can be executed", async () => {
|
|
await StakingPool.methods.vote(proposalId, true).send({from: richard});
|
|
|
|
// Mine 20 blocks
|
|
for(let i = 0; i < 20; i++){
|
|
await mineAtTimestamp(12345678);
|
|
}
|
|
|
|
let result = await StakingPool.methods.proposalStatus(proposalId).call();
|
|
assert.strictEqual(result.approved, true);
|
|
assert.strictEqual(result.executed, false);
|
|
|
|
const receipt = await StakingPool.methods.executeTransaction(proposalId).send({from: iuri});
|
|
|
|
const destinationBalance = await web3.eth.getBalance("0x00000000000000000000000000000000000000AA");
|
|
assert.strictEqual(destinationBalance, "12345");
|
|
|
|
result = await StakingPool.methods.proposalStatus(proposalId).call();
|
|
assert.strictEqual(result.executed, true);
|
|
});
|
|
|
|
it("approved proposals can't be executed twice", async () => {
|
|
await StakingPool.methods.vote(proposalId, true).send({from: richard});
|
|
|
|
// Mine 20 blocks
|
|
for(let i = 0; i < 20; i++){
|
|
await mineAtTimestamp(12345678);
|
|
}
|
|
|
|
await StakingPool.methods.executeTransaction(proposalId).send({from: iuri});
|
|
await assert.reverts(StakingPool.methods.executeTransaction(proposalId), {from: iuri}, "Returned error: VM Exception while processing transaction: revert Proposal already executed");
|
|
});
|
|
|
|
it("approved proposals can't be executed after they expire", async () => {
|
|
await StakingPool.methods.vote(proposalId, true).send({from: richard});
|
|
// Mine 40 blocks
|
|
for(let i = 0; i < 40; i++){
|
|
await mineAtTimestamp(12345678);
|
|
}
|
|
await assert.reverts(StakingPool.methods.executeTransaction(proposalId), {from: iuri}, "Returned error: VM Exception while processing transaction: revert Proposal is already expired");
|
|
});
|
|
|
|
|
|
it("proposals can execute contract functions", async () => {
|
|
const initialBalance = await SNT.methods.balanceOf("0xAA000000000000000000000000000000000000AA").call();
|
|
assert.strictEqual(initialBalance, "0");
|
|
|
|
const encodedCall = SNT.methods.transfer("0xAA000000000000000000000000000000000000AA", "12345").encodeABI();
|
|
const receipt = await StakingPool.methods.addProposal(SNT.options.address, 0, encodedCall, "0x").send({from: richard});
|
|
proposalId = receipt.events.NewProposal.returnValues.proposalId;
|
|
|
|
await StakingPool.methods.vote(proposalId, true).send({from: richard});
|
|
|
|
// Mine 20 blocks
|
|
for(let i = 0; i < 20; i++){
|
|
await mineAtTimestamp(12345678);
|
|
}
|
|
|
|
await StakingPool.methods.executeTransaction(proposalId).send({from: iuri});
|
|
|
|
const finalBalance = await SNT.methods.balanceOf("0xAA000000000000000000000000000000000000AA").call();
|
|
assert.strictEqual(finalBalance, "12345");
|
|
});
|
|
|
|
it("requires a minimum participation to execute a proposal", async () => {
|
|
// Change minimum participation
|
|
await StakingPool.methods.setMinimumParticipation("5000").send();
|
|
|
|
const encodedCall = SNT.methods.transfer("0xAA000000000000000000000000000000000000BB", "12345").encodeABI();
|
|
const receipt = await StakingPool.methods.addProposal(SNT.options.address, 0, encodedCall, "0x").send({from: richard});
|
|
proposalId = receipt.events.NewProposal.returnValues.proposalId;
|
|
|
|
await StakingPool.methods.vote(proposalId, true).send({from: richard});
|
|
|
|
// Mine 20 blocks
|
|
for(let i = 0; i < 20; i++){
|
|
await mineAtTimestamp(12345678);
|
|
}
|
|
|
|
await assert.reverts(StakingPool.methods.executeTransaction(proposalId), {from: iuri}, "Returned error: VM Exception while processing transaction: revert Did not meet the minimum required participation");
|
|
});
|
|
|
|
it("proposal can be executed if it meets the minimum participation", async () => {
|
|
const encodedCall = SNT.methods.transfer("0xAA000000000000000000000000000000000000BB", "12345").encodeABI();
|
|
const receipt = await StakingPool.methods.addProposal(SNT.options.address, 0, encodedCall, "0x").send({from: richard});
|
|
proposalId = receipt.events.NewProposal.returnValues.proposalId;
|
|
|
|
await StakingPool.methods.vote(proposalId, true).send({from: iuri});
|
|
await StakingPool.methods.vote(proposalId, true).send({from: richard});
|
|
await StakingPool.methods.vote(proposalId, true).send({from: pascal});
|
|
|
|
// Mine 20 blocks
|
|
for(let i = 0; i < 20; i++){
|
|
await mineAtTimestamp(12345678);
|
|
}
|
|
|
|
await StakingPool.methods.executeTransaction(proposalId).send({from: iuri});
|
|
});
|
|
});
|
|
|
|
describe("proposal cancelation", () => {
|
|
let proposalId;
|
|
|
|
before(async () => {
|
|
// Setting values
|
|
await StakingPool.methods.setProposalCancelLength("10").send();
|
|
await StakingPool.methods.setMinimumParticipationForCancel("5000").send();
|
|
await StakingPool.methods.setMinimumCancelApprovalPercentage("7500").send();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
const receipt = await StakingPool.methods.addProposal("0x00000000000000000000000000000000000000CC", 12345, "0x", "0x").send({from: richard});
|
|
proposalId = receipt.events.NewProposal.returnValues.proposalId;
|
|
});
|
|
|
|
it("can not execute a proposal that is still in the cancelation period", async () => {
|
|
await StakingPool.methods.vote(proposalId, true).send({from: iuri});
|
|
await StakingPool.methods.vote(proposalId, true).send({from: richard});
|
|
await StakingPool.methods.vote(proposalId, true).send({from: pascal});
|
|
|
|
// Mine 25 blocks
|
|
for(let i = 0; i < 25; i++){
|
|
await mineAtTimestamp(12345678);
|
|
}
|
|
|
|
const toSend = StakingPool.methods.executeTransaction(proposalId);
|
|
await assert.reverts(toSend, {from: richard}, "Returned error: VM Exception while processing transaction: revert Voting is still active");
|
|
});
|
|
|
|
it("can execute a proposal with no cancelation votes", async() => {
|
|
await StakingPool.methods.vote(proposalId, true).send({from: iuri});
|
|
await StakingPool.methods.vote(proposalId, true).send({from: richard});
|
|
await StakingPool.methods.vote(proposalId, true).send({from: pascal});
|
|
|
|
// Mine 30 blocks
|
|
for(let i = 0; i < 30; i++){
|
|
await mineAtTimestamp(12345678);
|
|
}
|
|
|
|
await StakingPool.methods.executeTransaction(proposalId).send({from: richard});
|
|
});
|
|
|
|
it("can not vote to cancel a proposal before the voting period for it is enabled", async () => {
|
|
const toSend = StakingPool.methods.cancel(proposalId, true);
|
|
await assert.reverts(toSend, {from: richard}, "Returned error: VM Exception while processing transaction: revert Proposal cancel period has not started yet");
|
|
});
|
|
|
|
it("can execute a proposal that did not reach the required participation minimum", async () => {
|
|
await StakingPool.methods.vote(proposalId, true).send({from: iuri});
|
|
await StakingPool.methods.vote(proposalId, true).send({from: richard});
|
|
await StakingPool.methods.vote(proposalId, true).send({from: pascal});
|
|
|
|
// Mine 20 blocks
|
|
for(let i = 0; i < 20; i++){
|
|
await mineAtTimestamp(12345678);
|
|
}
|
|
|
|
await StakingPool.methods.cancel(proposalId, true).send({from: iuri});
|
|
await StakingPool.methods.cancel(proposalId, true).send({from: richard});
|
|
|
|
for(let i = 0; i < 10; i++){
|
|
await mineAtTimestamp(12345678);
|
|
}
|
|
|
|
await StakingPool.methods.executeTransaction(proposalId).send({from: richard});
|
|
});
|
|
|
|
it("cannot execute a proposal that reach the required participation and cancel votes", async () => {
|
|
await StakingPool.methods.vote(proposalId, true).send({from: iuri});
|
|
await StakingPool.methods.vote(proposalId, true).send({from: richard});
|
|
await StakingPool.methods.vote(proposalId, true).send({from: pascal});
|
|
|
|
// Mine 20 blocks
|
|
for(let i = 0; i < 20; i++){
|
|
await mineAtTimestamp(12345678);
|
|
}
|
|
|
|
await StakingPool.methods.cancel(proposalId, true).send({from: iuri});
|
|
await StakingPool.methods.cancel(proposalId, true).send({from: richard});
|
|
await StakingPool.methods.cancel(proposalId, true).send({from: pascal});
|
|
|
|
for(let i = 0; i < 10; i++){
|
|
await mineAtTimestamp(12345678);
|
|
}
|
|
|
|
const toSend = StakingPool.methods.executeTransaction(proposalId);
|
|
await assert.reverts(toSend, {from: richard}, "Returned error: VM Exception while processing transaction: revert Proposal was canceled");
|
|
|
|
const result = await StakingPool.methods.proposalStatus(proposalId).call();
|
|
|
|
assert.strictEqual(result.approved, true);
|
|
assert.strictEqual(result.canceled, true);
|
|
assert.strictEqual(result.executed, false);
|
|
});
|
|
|
|
});
|
|
});
|