Proposals: complete VP workflow
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 38s
Details
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 38s
Details
This commit is contained in:
parent
5000ef60fd
commit
a8dcbe7a35
|
@ -67,7 +67,11 @@ contract DAO is ERC20("Reputation", "REP") {
|
|||
|
||||
event PostAdded(uint postIndex);
|
||||
event ValidationPoolInitiated(uint poolIndex);
|
||||
event ValidationPoolResolved(uint poolIndex, bool votePasses);
|
||||
event ValidationPoolResolved(
|
||||
uint poolIndex,
|
||||
bool votePasses,
|
||||
bool quorumMet
|
||||
);
|
||||
|
||||
function addPost(
|
||||
address author,
|
||||
|
@ -184,11 +188,22 @@ contract DAO is ERC20("Reputation", "REP") {
|
|||
}
|
||||
}
|
||||
// Check that quorum is met
|
||||
require(
|
||||
1_000_000_000 * (stakedFor + stakedAgainst) >=
|
||||
totalSupply() * pool.params.quorumPPB,
|
||||
"Quorum for this pool was not met"
|
||||
);
|
||||
if (
|
||||
1_000_000_000 * (stakedFor + stakedAgainst) <=
|
||||
totalSupply() * pool.params.quorumPPB
|
||||
) {
|
||||
// TODO: refund stakes
|
||||
// Callback if requested
|
||||
if (pool.callbackOnValidate) {
|
||||
IOnValidate(pool.sender).onValidate(
|
||||
votePasses,
|
||||
false,
|
||||
pool.callbackData
|
||||
);
|
||||
}
|
||||
emit ValidationPoolResolved(poolIndex, false, false);
|
||||
return false;
|
||||
}
|
||||
// A tie is resolved in favor of the validation pool.
|
||||
// This is especially important so that the DAO's first pool can pass,
|
||||
// when no reputation has yet been minted.
|
||||
|
@ -199,7 +214,7 @@ contract DAO is ERC20("Reputation", "REP") {
|
|||
}
|
||||
pool.resolved = true;
|
||||
pool.outcome = votePasses;
|
||||
emit ValidationPoolResolved(poolIndex, votePasses);
|
||||
emit ValidationPoolResolved(poolIndex, votePasses, true);
|
||||
// Value of losing stakes should be distributed among winners, in proportion to their stakes
|
||||
uint256 amountFromWinners = votePasses ? stakedFor : stakedAgainst;
|
||||
uint256 amountFromLosers = votePasses ? stakedAgainst : stakedFor;
|
||||
|
@ -250,7 +265,11 @@ contract DAO is ERC20("Reputation", "REP") {
|
|||
}
|
||||
// Callback if requested
|
||||
if (pool.callbackOnValidate) {
|
||||
IOnValidate(pool.sender).onValidate(votePasses, pool.callbackData);
|
||||
IOnValidate(pool.sender).onValidate(
|
||||
votePasses,
|
||||
true,
|
||||
pool.callbackData
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,5 +2,9 @@
|
|||
pragma solidity ^0.8.24;
|
||||
|
||||
interface IOnValidate {
|
||||
function onValidate(bool votePasses, bytes calldata callbackData) external;
|
||||
function onValidate(
|
||||
bool votePasses,
|
||||
bool quorumMet,
|
||||
bytes calldata callbackData
|
||||
) external;
|
||||
}
|
||||
|
|
|
@ -41,14 +41,18 @@ contract Onboarding is WorkContract, IOnValidate {
|
|||
}
|
||||
|
||||
/// Callback to be executed when review pool completes
|
||||
function onValidate(bool votePasses, bytes calldata callbackData) external {
|
||||
function onValidate(
|
||||
bool votePasses,
|
||||
bool quorumMet,
|
||||
bytes calldata callbackData
|
||||
) external {
|
||||
require(
|
||||
msg.sender == address(dao),
|
||||
"onValidate may only be called by the DAO contract"
|
||||
);
|
||||
uint requestIndex = abi.decode(callbackData, (uint));
|
||||
WorkRequest storage request = requests[requestIndex];
|
||||
if (!votePasses) {
|
||||
if (!votePasses || !quorumMet) {
|
||||
// refund the customer the remaining amount
|
||||
payable(request.customer).transfer(request.fee / 10);
|
||||
return;
|
||||
|
|
|
@ -15,29 +15,39 @@ contract Proposals is DAOContract {
|
|||
Referendum0,
|
||||
Referendum1,
|
||||
Referendum100,
|
||||
Closed
|
||||
Failed,
|
||||
Accepted
|
||||
}
|
||||
|
||||
struct Referendum {
|
||||
uint duration;
|
||||
uint poolIndex;
|
||||
uint fee;
|
||||
}
|
||||
|
||||
struct Proposal {
|
||||
address sender;
|
||||
uint fee;
|
||||
uint feeRemaining;
|
||||
uint postIndex;
|
||||
uint startTime;
|
||||
Stage stage;
|
||||
mapping(uint => Attestation) attestations;
|
||||
uint attestationCount;
|
||||
Referendum[3] referenda;
|
||||
uint[3] retryCount;
|
||||
}
|
||||
|
||||
mapping(uint => Proposal) public proposals;
|
||||
uint public proposalCount;
|
||||
|
||||
event NewProposal(uint proposalIndex);
|
||||
event ReferendumStarted(uint proposalIndex, uint poolIndex);
|
||||
event ProposalFailed(uint proposalIndex, string reason);
|
||||
event ProposalAccepted(uint proposalIndex);
|
||||
|
||||
uint[3] referendaBindingPercent = [0, 1, 100];
|
||||
bool[3] referendaRedistributeLosingStakes = [false, false, true];
|
||||
|
||||
constructor(DAO dao) DAOContract(dao) {}
|
||||
|
||||
function propose(
|
||||
|
@ -54,7 +64,10 @@ contract Proposals is DAOContract {
|
|||
proposal.referenda[1].duration = referendum1Duration;
|
||||
proposal.referenda[2].duration = referendum100Duration;
|
||||
proposal.fee = msg.value;
|
||||
proposal.feeRemaining = proposal.fee;
|
||||
proposal.referenda[0].fee = proposal.fee / 3;
|
||||
proposal.referenda[1].fee = proposal.fee / 3;
|
||||
proposal.referenda[2].fee = proposal.fee - (proposal.fee * 2) / 3;
|
||||
emit NewProposal(proposalIndex);
|
||||
}
|
||||
|
||||
function attest(uint proposalIndex, uint amount) external {
|
||||
|
@ -73,11 +86,86 @@ contract Proposals is DAOContract {
|
|||
attestation.amount = amount;
|
||||
}
|
||||
|
||||
// todo onValidate() {
|
||||
function initiateValidationPool(
|
||||
uint proposalIndex,
|
||||
uint referendumIndex
|
||||
) internal {
|
||||
uint bindingPercent = referendaBindingPercent[referendumIndex];
|
||||
bool redistributeLosingStakes = referendaRedistributeLosingStakes[
|
||||
referendumIndex
|
||||
];
|
||||
Proposal storage proposal = proposals[proposalIndex];
|
||||
uint poolIndex = dao.initiateValidationPool{
|
||||
value: proposal.referenda[referendumIndex].fee
|
||||
}(
|
||||
proposal.postIndex,
|
||||
proposal.referenda[referendumIndex].duration,
|
||||
1,
|
||||
3,
|
||||
bindingPercent,
|
||||
redistributeLosingStakes,
|
||||
true,
|
||||
abi.encode(proposalIndex)
|
||||
);
|
||||
emit ReferendumStarted(proposalIndex, poolIndex);
|
||||
}
|
||||
|
||||
// This callback will get proposalIndex
|
||||
|
||||
// todo }
|
||||
/// Callback to be executed when referenda pools complete
|
||||
function onValidate(
|
||||
bool votePasses,
|
||||
bool quorumMet,
|
||||
bytes calldata callbackData
|
||||
) external {
|
||||
require(
|
||||
msg.sender == address(dao),
|
||||
"onValidate may only be called by the DAO contract"
|
||||
);
|
||||
uint proposalIndex = abi.decode(callbackData, (uint));
|
||||
Proposal storage proposal = proposals[proposalIndex];
|
||||
if (!quorumMet) {
|
||||
proposal.stage = Stage.Failed;
|
||||
emit ProposalFailed(proposalIndex, "Quorum not met");
|
||||
return;
|
||||
}
|
||||
if (proposal.stage == Stage.Referendum0) {
|
||||
if (votePasses) {
|
||||
proposal.stage = Stage.Referendum1;
|
||||
} else if (proposal.retryCount[0] >= 3) {
|
||||
proposal.stage = Stage.Failed;
|
||||
emit ProposalFailed(proposalIndex, "Retry count exceeded");
|
||||
} else {
|
||||
proposal.retryCount[0] += 1;
|
||||
}
|
||||
} else if (proposal.stage == Stage.Referendum1) {
|
||||
if (votePasses) {
|
||||
proposal.stage = Stage.Referendum100;
|
||||
} else if (proposal.retryCount[1] >= 3) {
|
||||
proposal.stage = Stage.Failed;
|
||||
emit ProposalFailed(proposalIndex, "Retry count exceeded");
|
||||
} else {
|
||||
proposal.retryCount[1] += 1;
|
||||
}
|
||||
} else if (proposal.stage == Stage.Referendum100) {
|
||||
// Note that no retries are attempted for referendum 100%
|
||||
if (votePasses) {
|
||||
// TODO: The proposal has passed all referenda and should become "law"
|
||||
// This is an opportunity for some actions to occur
|
||||
// We should at least emit an event
|
||||
proposal.stage = Stage.Accepted;
|
||||
emit ProposalAccepted(proposalIndex);
|
||||
} else {
|
||||
proposal.stage = Stage.Failed;
|
||||
emit ProposalFailed(proposalIndex, "Binding pool was rejected");
|
||||
}
|
||||
}
|
||||
if (proposal.stage == Stage.Referendum0) {
|
||||
initiateValidationPool(proposalIndex, 0);
|
||||
} else if (proposal.stage == Stage.Referendum1) {
|
||||
initiateValidationPool(proposalIndex, 1);
|
||||
} else if (proposal.stage == Stage.Referendum100) {
|
||||
initiateValidationPool(proposalIndex, 2);
|
||||
}
|
||||
}
|
||||
|
||||
function evaluateAttestation(uint proposalIndex) external returns (bool) {
|
||||
Proposal storage proposal = proposals[proposalIndex];
|
||||
|
@ -93,27 +181,24 @@ contract Proposals is DAOContract {
|
|||
bool expired = block.timestamp > proposal.startTime + 365 days;
|
||||
if (!meetsAttestation) {
|
||||
if (expired) {
|
||||
proposal.stage = Stage.Closed;
|
||||
// Expired without meeting attestation threshold
|
||||
proposal.stage = Stage.Failed;
|
||||
emit ProposalFailed(
|
||||
proposalIndex,
|
||||
"Expired without meeting attestation threshold"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
// Not yet expired, but has not met attestation threshold
|
||||
return false;
|
||||
}
|
||||
// Initiate validation pool
|
||||
|
||||
// Attestation threshold is met.
|
||||
// Note that this may succeed even after expiry
|
||||
// It can only happen once because the stage advances, and we required it above.
|
||||
proposal.stage = Stage.Referendum0;
|
||||
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
|
||||
);
|
||||
// Initiate validation pool
|
||||
initiateValidationPool(proposalIndex, 0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -143,7 +143,7 @@ describe('DAO', () => {
|
|||
expect(await dao.balanceOf(dao.target)).to.equal(110);
|
||||
time.increase(POOL_DURATION + 1);
|
||||
console.log('evaluating second pool');
|
||||
await expect(dao.evaluateOutcome(1)).to.emit(dao, 'ValidationPoolResolved').withArgs(1, true);
|
||||
await expect(dao.evaluateOutcome(1)).to.emit(dao, 'ValidationPoolResolved').withArgs(1, true, true);
|
||||
expect(await dao.balanceOf(dao.target)).to.equal(0);
|
||||
expect(await dao.balanceOf(account1)).to.equal(200);
|
||||
});
|
||||
|
@ -158,7 +158,7 @@ describe('DAO', () => {
|
|||
expect(await dao.balanceOf(account1)).to.equal(90);
|
||||
expect(await dao.balanceOf(dao.target)).to.equal(110);
|
||||
time.increase(POOL_DURATION + 1);
|
||||
await expect(dao.evaluateOutcome(1)).to.emit(dao, 'ValidationPoolResolved').withArgs(1, false);
|
||||
await expect(dao.evaluateOutcome(1)).to.emit(dao, 'ValidationPoolResolved').withArgs(1, false, true);
|
||||
expect(await dao.balanceOf(dao.target)).to.equal(0);
|
||||
expect(await dao.balanceOf(account1)).to.equal(200);
|
||||
const pool = await dao.validationPools(1);
|
||||
|
@ -174,7 +174,7 @@ describe('DAO', () => {
|
|||
it('should be able to evaluate outcome after duration has elapsed', async () => {
|
||||
expect(await dao.balanceOf(dao.target)).to.equal(100);
|
||||
time.increase(POOL_DURATION + 1);
|
||||
await expect(dao.evaluateOutcome(0)).to.emit(dao, 'ValidationPoolResolved').withArgs(0, true);
|
||||
await expect(dao.evaluateOutcome(0)).to.emit(dao, 'ValidationPoolResolved').withArgs(0, true, true);
|
||||
expect(await dao.memberCount()).to.equal(1);
|
||||
expect(await dao.balanceOf(account1)).to.equal(100);
|
||||
const pool = await dao.validationPools(0);
|
||||
|
@ -184,7 +184,7 @@ describe('DAO', () => {
|
|||
|
||||
it('should not be able to evaluate outcome more than once', async () => {
|
||||
time.increase(POOL_DURATION + 1);
|
||||
await expect(dao.evaluateOutcome(0)).to.emit(dao, 'ValidationPoolResolved').withArgs(0, true);
|
||||
await expect(dao.evaluateOutcome(0)).to.emit(dao, 'ValidationPoolResolved').withArgs(0, true, true);
|
||||
await expect(dao.evaluateOutcome(0)).to.be.revertedWith('Pool is already resolved');
|
||||
});
|
||||
|
||||
|
@ -203,21 +203,21 @@ describe('DAO', () => {
|
|||
await expect(init()).to.emit(dao, 'ValidationPoolInitiated').withArgs(1);
|
||||
expect(await dao.validationPoolCount()).to.equal(2);
|
||||
time.increase(POOL_DURATION + 1);
|
||||
await expect(dao.evaluateOutcome(0)).to.emit(dao, 'ValidationPoolResolved').withArgs(0, true);
|
||||
await expect(dao.evaluateOutcome(0)).to.emit(dao, 'ValidationPoolResolved').withArgs(0, true, true);
|
||||
expect(await dao.balanceOf(account1)).to.equal(100);
|
||||
await expect(dao.evaluateOutcome(1)).to.emit(dao, 'ValidationPoolResolved').withArgs(1, true);
|
||||
await expect(dao.evaluateOutcome(1)).to.emit(dao, 'ValidationPoolResolved').withArgs(1, true, true);
|
||||
expect(await dao.balanceOf(account1)).to.equal(200);
|
||||
});
|
||||
|
||||
it('should not be able to evaluate outcome if quorum is not met', async () => {
|
||||
time.increase(POOL_DURATION + 1);
|
||||
await expect(dao.evaluateOutcome(0)).to.emit(dao, 'ValidationPoolResolved').withArgs(0, true);
|
||||
await expect(dao.evaluateOutcome(0)).to.emit(dao, 'ValidationPoolResolved').withArgs(0, true, true);
|
||||
|
||||
const init = () => initiateValidationPool({ quorumNumerator: 1, quorumDenominator: 1 });
|
||||
await expect(init()).to.emit(dao, 'ValidationPoolInitiated').withArgs(1);
|
||||
expect(await dao.validationPoolCount()).to.equal(2);
|
||||
time.increase(POOL_DURATION + 1);
|
||||
await expect(dao.evaluateOutcome(1)).to.be.revertedWith('Quorum for this pool was not met');
|
||||
await expect(dao.evaluateOutcome(1)).to.emit(dao, 'ValidationPoolResolved').withArgs(1, false, false);
|
||||
});
|
||||
|
||||
describe('Validation pool options', () => {
|
||||
|
|
|
@ -243,7 +243,7 @@ describe('Work1', () => {
|
|||
expect(pool.postIndex).to.equal(1);
|
||||
expect(pool.stakeCount).to.equal(3);
|
||||
await time.increase(86401);
|
||||
await expect(dao.evaluateOutcome(1)).to.emit(dao, 'ValidationPoolResolved').withArgs(1, true);
|
||||
await expect(dao.evaluateOutcome(1)).to.emit(dao, 'ValidationPoolResolved').withArgs(1, true, true);
|
||||
});
|
||||
|
||||
it('should be able to submit work disapproval', async () => {
|
||||
|
|
Loading…
Reference in New Issue