Proposals contract: attestation
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 32s Details

This commit is contained in:
Ladd Hoffman 2024-03-26 21:32:41 -05:00
parent 790ff5db62
commit 1306fe2414
3 changed files with 148 additions and 11 deletions

View File

@ -218,13 +218,11 @@ contract DAO is ERC20("Reputation", "REP") {
? ((s.amount * amountFromLosers) / amountFromWinners) *
(bindingPercent / 100)
: 0;
uint balance = balanceOf(address(this));
_update(address(this), s.sender, s.amount + reward);
totalAllocated += reward;
} else {
// Losing stake
uint refund = (s.amount * (100 - bindingPercent)) / 100;
uint balance = balanceOf(address(this));
if (refund > 0) {
_update(address(this), s.sender, refund);
}

View File

@ -2,6 +2,7 @@
pragma solidity ^0.8.24;
import "./DAO.sol";
import "hardhat/console.sol";
contract Proposals is DAOContract {
struct Attestation {
@ -16,27 +17,44 @@ contract Proposals is DAOContract {
Referendum100,
Closed
}
struct Referendum {
uint duration;
uint poolIndex;
}
struct Proposal {
address sender;
string contentId;
uint fee;
uint feeRemaining;
uint postIndex;
uint startTime;
Stage stage;
mapping(uint => Attestation) attestations;
uint attestationCount;
Referendum[3] referenda;
}
mapping(uint => Proposal) proposals;
uint proposalCount;
mapping(uint => Proposal) public proposals;
uint public proposalCount;
constructor(DAO dao) DAOContract(dao) {}
function propose(
string calldata contentId
) external returns (uint proposalIndex) {
uint postIndex,
uint referendum0Duration,
uint referendum1Duration,
uint referendum100Duration
) external payable returns (uint proposalIndex) {
proposalIndex = proposalCount++;
Proposal storage proposal = proposals[proposalIndex];
proposal.postIndex = postIndex;
proposal.startTime = block.timestamp;
proposal.contentId = contentId;
proposal.referenda[0].duration = referendum0Duration;
proposal.referenda[1].duration = referendum1Duration;
proposal.referenda[2].duration = referendum100Duration;
proposal.fee = msg.value;
proposal.feeRemaining = proposal.fee;
}
function attest(uint proposalIndex, uint amount) external {
@ -55,6 +73,12 @@ contract Proposals is DAOContract {
attestation.amount = amount;
}
// todo onValidate() {
// This callback will get proposalIndex
// todo }
function evaluateAttestation(uint proposalIndex) external returns (bool) {
Proposal storage proposal = proposals[proposalIndex];
require(
@ -66,16 +90,30 @@ contract Proposals is DAOContract {
totalAttestation += proposal.attestations[i].amount;
}
bool meetsAttestation = 10 * totalAttestation >= dao.totalSupply();
bool expired = block.timestamp > proposal.startTime + 365 days;
if (!meetsAttestation) {
if (block.timestamp > proposal.startTime + 365 days) {
if (expired) {
proposal.stage = Stage.Closed;
return false;
}
return false;
}
// Initiate validation pool
proposal.stage = Stage.Referendum0;
// TODO: make referendum0 duration a parameter
uint thisFee = proposal.fee / 3;
proposal.feeRemaining -= thisFee;
proposal.referenda[0].poolIndex = dao.initiateValidationPool{
value: thisFee
}(
proposal.postIndex, // uint postIndex,
proposal.referenda[0].duration, // uint duration,
1, // uint quorumNumerator,
3, // uint quorumDenominator,
0, // uint bindingPercent,
false, // bool redistributeLosingStakes,
false, // TODO bool callbackOnValidate : true,
"" // TODO bytes calldata callbackData : This should probably be proposalIndex
);
return true;
}
}

101
ethereum/test/Proposals.js Normal file
View File

@ -0,0 +1,101 @@
const {
time,
loadFixture,
} = require('@nomicfoundation/hardhat-toolbox/network-helpers');
const { expect } = require('chai');
const { ethers } = require('hardhat');
const { beforeEach } = require('mocha');
describe('Proposal', () => {
async function deploy() {
// Contracts are deployed using the first signer/account by default
const [account1, account2] = await ethers.getSigners();
const DAO = await ethers.getContractFactory('DAO');
const dao = await DAO.deploy();
const Proposals = await ethers.getContractFactory('Proposals');
const proposals = await Proposals.deploy(dao.target);
await dao.addPost(account1, 'some-content-id');
await dao.addPost(account2, 'some-other-content-id');
const callbackData = ethers.AbiCoder.defaultAbiCoder().encode([], []);
await dao.initiateValidationPool(0, 60, 1, 3, 100, true, false, callbackData, { value: 100 });
await dao.initiateValidationPool(1, 60, 1, 3, 100, true, false, callbackData, { value: 100 });
await time.increase(61);
await dao.evaluateOutcome(0);
await dao.evaluateOutcome(1);
return {
dao, proposals, account1, account2,
};
}
it('Should deploy', async () => {
const { dao, proposals, account1 } = await loadFixture(deploy);
expect(dao).to.exist;
expect(proposals).to.exist;
expect(await dao.memberCount()).to.equal(2);
expect(await dao.balanceOf(account1)).to.equal(100);
expect(await dao.totalSupply()).to.equal(200);
expect(await proposals.proposalCount()).to.equal(0);
});
describe('Attestation', () => {
let dao;
let proposals;
let account1;
// let account2;
let proposal;
beforeEach(async () => {
({
dao,
proposals,
account1,
// account2,
} = await loadFixture(deploy));
console.log('postCount', await dao.postCount());
await dao.addPost(account1, 'proposal-content-id');
const postIndex = await dao.postCount() - BigInt(1);
const post = await dao.posts(postIndex);
expect(await post.contentId).to.equal('proposal-content-id');
await proposals.propose(postIndex, 20, 20, 20, { value: 100 });
expect(await proposals.proposalCount()).to.equal(1);
proposal = await proposals.proposals(0);
expect(proposal.postIndex).to.equal(postIndex);
expect(proposal.stage).to.equal(0);
});
it('Can submit a proposal', async () => {
// Nothing to do here -- this just tests our beforeEach
});
it('Can attest for a proposal', async () => {
await proposals.connect(account1).attest(0, 50);
// Nonbinding, non-encumbering
expect(await dao.balanceOf(account1)).to.equal(100);
});
describe('Evaluate attestation', () => {
it('when threshold is met, advance to referendum 0% binding', async () => {
await proposals.attest(0, 20);
await proposals.evaluateAttestation(0);
proposal = await proposals.proposals(0);
expect(proposal.stage).to.equal(1);
});
it('when threshold is not met, and duration has not elapsed, do nothing', async () => {
await proposals.evaluateAttestation(0);
expect(proposal.stage).to.equal(0);
});
it('when threshold is not met, and duration has elapsed, close the proposal', async () => {
await time.increase(365 * 86400 + 1); // 1 year + 1 second
await proposals.evaluateAttestation(0);
proposal = await proposals.proposals(0);
expect(proposal.stage).to.equal(4); // Stage.Closed
});
});
});
});