Add win ratio VP param
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 32s Details

This commit is contained in:
Ladd Hoffman 2024-03-28 15:06:14 -05:00
parent 41a0025311
commit 7ffd532292
11 changed files with 184 additions and 43 deletions

View File

@ -218,8 +218,8 @@ function App() {
await DAO.methods.initiateValidationPool(
postIndex,
poolDuration ?? 3600,
1,
3,
[1, 3],
[1, 2],
100,
true,
false,

View File

@ -0,0 +1,54 @@
import { useContext, useMemo } from 'react';
import { PropTypes } from 'prop-types';
import useList from '../utils/List';
import WorkContractContext from '../contexts/WorkContractContext';
import AvailabilityStakes from './work-contracts/AvailabilityStakes';
import WorkRequests from './work-contracts/WorkRequests';
import Web3Context from '../contexts/Web3Context';
function Proposals() {
const [proposals, dispatchProposal] = useList();
const {provider} = useContext(Web3Context);
return (
<>
<h2>Proposals</h2>
<div>
<table className="table">
<thead>
<tr>
<th>ID</th>
{/* <th>Pool ID</th> */}
</tr>
</thead>
<tbody>
{proposals.filter((x) => !!x).map((request) => (
<tr key={request.id}>
<td>{request.id.toString()}</td>
</tr>
))}
</tbody>
</table>
</div>
</>
);
}
WorkContract.propTypes = {
workContract: PropTypes.any.isRequired, // eslint-disable-line react/forbid-prop-types
showRequestWork: PropTypes.bool,
title: PropTypes.string.isRequired,
verb: PropTypes.string.isRequired,
showAvailabilityActions: PropTypes.bool,
showAvailabilityAmount: PropTypes.bool,
onlyShowAvailable: PropTypes.bool,
};
WorkContract.defaultProps = {
showRequestWork: false,
showAvailabilityActions: true,
showAvailabilityAmount: true,
onlyShowAvailable: false,
};
export default WorkContract;

View File

@ -26,6 +26,7 @@ struct ValidationPoolParams {
uint quorumPPB;
uint bindingPercent;
bool redistributeLosingStakes;
uint[2] winRatio; // [ Numerator, Denominator ]
}
struct ValidationPool {
@ -91,8 +92,8 @@ contract DAO is ERC20("Reputation", "REP") {
function initiateValidationPool(
uint postIndex,
uint duration,
uint quorumNumerator,
uint quorumDenominator,
uint[2] calldata quorum, // [Numerator, Denominator]
uint[2] calldata winRatio, // [Numerator, Denominator]
uint bindingPercent,
bool redistributeLosingStakes,
bool callbackOnValidate,
@ -102,14 +103,11 @@ contract DAO is ERC20("Reputation", "REP") {
require(duration >= minDuration, "Duration is too short");
require(duration <= maxDuration, "Duration is too long");
require(
(1_000_000_000 * quorumNumerator) / quorumDenominator >=
minQuorumPPB,
(1_000_000_000 * quorum[0]) / quorum[1] >= minQuorumPPB,
"Quorum is below minimum"
);
require(
quorumNumerator <= quorumDenominator,
"Quorum is greater than one"
);
require(quorum[0] <= quorum[1], "Quorum is greater than one");
require(winRatio[0] <= winRatio[1], "Win ratio is greater than one");
require(bindingPercent <= 100, "Binding percent must be <= 100");
Post storage post = posts[postIndex];
require(post.author != address(0), "Target post not found");
@ -118,9 +116,8 @@ contract DAO is ERC20("Reputation", "REP") {
pool.sender = msg.sender;
pool.postIndex = postIndex;
pool.fee = msg.value;
pool.params.quorumPPB =
(1_000_000_000 * quorumNumerator) /
quorumDenominator;
pool.params.quorumPPB = (1_000_000_000 * quorum[0]) / quorum[1];
pool.params.winRatio = winRatio;
pool.params.bindingPercent = bindingPercent;
pool.params.redistributeLosingStakes = redistributeLosingStakes;
pool.duration = duration;
@ -198,6 +195,8 @@ contract DAO is ERC20("Reputation", "REP") {
IOnValidate(pool.sender).onValidate(
votePasses,
false,
stakedFor,
stakedAgainst,
pool.callbackData
);
}
@ -207,7 +206,18 @@ contract DAO is ERC20("Reputation", "REP") {
// 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.
votePasses = stakedFor >= stakedAgainst;
// jconsole.log(
// "staked for %d against %d, win ratio %d / %d",
console.log("stakedFor", stakedFor);
console.log("stakedAgainst", stakedAgainst);
console.log(
"winRatio",
pool.params.winRatio[0],
pool.params.winRatio[1]
);
votePasses =
stakedFor * pool.params.winRatio[1] >=
(stakedFor + stakedAgainst) * pool.params.winRatio[0];
if (votePasses && !isMember[post.author]) {
members[memberCount++] = post.author;
isMember[post.author] = true;
@ -268,6 +278,8 @@ contract DAO is ERC20("Reputation", "REP") {
IOnValidate(pool.sender).onValidate(
votePasses,
true,
stakedFor,
stakedAgainst,
pool.callbackData
);
}

View File

@ -5,6 +5,8 @@ interface IOnValidate {
function onValidate(
bool votePasses,
bool quorumMet,
uint stakedFor,
uint stakedAgainst,
bytes calldata callbackData
) external;
}

View File

@ -30,8 +30,8 @@ contract Onboarding is WorkContract, IOnValidate {
}(
postIndex,
POOL_DURATION,
1,
3,
[uint256(1), uint256(3)],
[uint256(1), uint256(2)],
100,
true,
true,
@ -44,6 +44,8 @@ contract Onboarding is WorkContract, IOnValidate {
function onValidate(
bool votePasses,
bool quorumMet,
uint,
uint,
bytes calldata callbackData
) external {
require(
@ -64,8 +66,8 @@ contract Onboarding is WorkContract, IOnValidate {
dao.initiateValidationPool{value: request.fee / 10}(
postIndex,
POOL_DURATION,
1,
3,
[uint256(1), uint256(3)],
[uint256(1), uint256(2)],
100,
true,
false,

View File

@ -2,9 +2,11 @@
pragma solidity ^0.8.24;
import "./DAO.sol";
import "./IOnValidate.sol";
import "hardhat/console.sol";
contract Proposals is DAOContract {
contract Proposals is DAOContract, IOnValidate {
struct Attestation {
address sender;
uint amount;
@ -19,9 +21,18 @@ contract Proposals is DAOContract {
Accepted
}
struct Pool {
uint poolIndex;
uint stakedFor;
uint stakedAgainst;
}
struct Referendum {
uint duration;
uint fee;
// Each referendum may retry up to 3x
Pool[] pools;
uint[3] retryCount;
}
struct Proposal {
@ -33,7 +44,6 @@ contract Proposals is DAOContract {
mapping(uint => Attestation) attestations;
uint attestationCount;
Referendum[3] referenda;
uint[3] retryCount;
}
mapping(uint => Proposal) public proposals;
@ -91,6 +101,8 @@ contract Proposals is DAOContract {
bool[3] referendaRedistributeLosingStakes = [false, false, true];
// For each referendum, a numerator-denominator pair representing its quorum
uint[2][3] referendaQuora = [[1, 10], [1, 2], [1, 3]];
// Win ratios
uint[2][3] referendaWinRatio = [[2, 3], [2, 3], [2, 3]];
/// Internal convenience function to wrap our call to dao.initiateValidationPool
/// and to emit an event
@ -108,13 +120,15 @@ contract Proposals is DAOContract {
}(
proposal.postIndex,
proposal.referenda[referendumIndex].duration,
referendaQuora[referendumIndex][0],
referendaQuora[referendumIndex][1],
referendaQuora[referendumIndex],
referendaWinRatio[referendumIndex],
bindingPercent,
redistributeLosingStakes,
true,
abi.encode(proposalIndex)
abi.encode(proposalIndex, referendumIndex)
);
Pool storage pool = proposal.referenda[referendumIndex].pools.push();
pool.poolIndex = poolIndex;
emit ReferendumStarted(proposalIndex, poolIndex);
}
@ -122,37 +136,54 @@ contract Proposals is DAOContract {
function onValidate(
bool votePasses,
bool quorumMet,
uint stakedFor,
uint stakedAgainst,
bytes calldata callbackData
) external {
require(
msg.sender == address(dao),
"onValidate may only be called by the DAO contract"
);
uint proposalIndex = abi.decode(callbackData, (uint));
(uint proposalIndex, uint referendumIndex) = abi.decode(
callbackData,
(uint, uint)
);
Proposal storage proposal = proposals[proposalIndex];
if (!quorumMet) {
proposal.stage = Stage.Failed;
emit ProposalFailed(proposalIndex, "Quorum not met");
return;
}
Referendum storage referendum = proposal.referenda[referendumIndex];
Pool storage pool = referendum.pools[referendum.pools.length - 1];
// Make a record of this result
pool.stakedFor = stakedFor;
pool.stakedAgainst = stakedAgainst;
// Handle Referendum 0%
if (proposal.stage == Stage.Referendum0) {
if (votePasses) {
bool participationAboveThreshold = 2 *
(stakedFor + stakedAgainst) >=
dao.totalSupply();
// If vote passes (2/3 majority) and has >= 50% participation
if (votePasses && participationAboveThreshold) {
proposal.stage = Stage.Referendum1;
} else if (proposal.retryCount[0] >= 3) {
} else if (referendum.retryCount[0] >= 3) {
proposal.stage = Stage.Failed;
emit ProposalFailed(proposalIndex, "Retry count exceeded");
} else {
proposal.retryCount[0] += 1;
referendum.retryCount[0] += 1;
}
// Handle Referendum 1%
} else if (proposal.stage == Stage.Referendum1) {
if (votePasses) {
proposal.stage = Stage.Referendum100;
} else if (proposal.retryCount[1] >= 3) {
} else if (referendum.retryCount[1] >= 3) {
proposal.stage = Stage.Failed;
emit ProposalFailed(proposalIndex, "Retry count exceeded");
} else {
proposal.retryCount[1] += 1;
referendum.retryCount[1] += 1;
}
// Handle Referendum 100%
} else if (proposal.stage == Stage.Referendum100) {
// Note that no retries are attempted for referendum 100%
if (votePasses) {

View File

@ -176,8 +176,8 @@ abstract contract WorkContract is DAOContract, IAcceptAvailability {
uint poolIndex = dao.initiateValidationPool{value: request.fee}(
postIndex,
POOL_DURATION,
1,
3,
[uint256(1), uint256(3)],
[uint256(1), uint256(2)],
100,
true,
false,

View File

@ -51,14 +51,14 @@ describe('DAO', () => {
const initiateValidationPool = ({
postIndex, duration,
quorumNumerator, quorumDenominator, bindingPercent,
quorum, winRatio, bindingPercent,
redistributeLosingStakes, callbackOnValidate,
callbackData, fee,
} = {}) => dao.initiateValidationPool(
postIndex ?? 0,
duration ?? POOL_DURATION,
quorumNumerator ?? 1,
quorumDenominator ?? 3,
quorum ?? [1, 3],
winRatio ?? [1, 2],
bindingPercent ?? 100,
redistributeLosingStakes ?? true,
callbackOnValidate ?? false,
@ -84,12 +84,12 @@ describe('DAO', () => {
});
it('should not be able to initiate a validation pool with a quorum below the minimum', async () => {
const init = () => initiateValidationPool({ quorumNumerator: 1, quorumDenominator: 11 });
const init = () => initiateValidationPool({ quorum: [1, 11] });
await expect(init()).to.be.revertedWith('Quorum is below minimum');
});
it('should not be able to initiate a validation pool with a quorum greater than 1', async () => {
const init = () => initiateValidationPool({ quorumNumerator: 11, quorumDenominator: 10 });
const init = () => initiateValidationPool({ quorum: [11, 10] });
await expect(init()).to.be.revertedWith('Quorum is greater than one');
});
@ -192,8 +192,8 @@ describe('DAO', () => {
const init = () => dao.initiateValidationPool(
0,
POOL_DURATION,
1,
3,
[1, 3],
[1, 2],
100,
true,
false,
@ -213,7 +213,7 @@ describe('DAO', () => {
time.increase(POOL_DURATION + 1);
await expect(dao.evaluateOutcome(0)).to.emit(dao, 'ValidationPoolResolved').withArgs(0, true, true);
const init = () => initiateValidationPool({ quorumNumerator: 1, quorumDenominator: 1 });
const init = () => initiateValidationPool({ quorum: [1, 1] });
await expect(init()).to.emit(dao, 'ValidationPoolInitiated').withArgs(1);
expect(await dao.validationPoolCount()).to.equal(2);
time.increase(POOL_DURATION + 1);

View File

@ -19,7 +19,17 @@ describe('Onboarding', () => {
await dao.addPost(account1, 'content-id');
const callbackData = ethers.AbiCoder.defaultAbiCoder().encode([], []);
await dao.initiateValidationPool(0, 60, 1, 3, 100, true, false, callbackData, { value: 100 });
await dao.initiateValidationPool(
0,
60,
[1, 3],
[1, 2],
100,
true,
false,
callbackData,
{ value: 100 },
);
await time.increase(61);
await dao.evaluateOutcome(0);
expect(await dao.balanceOf(account1)).to.equal(100);

View File

@ -19,8 +19,28 @@ describe('Proposal', () => {
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 dao.initiateValidationPool(
0,
60,
[1, 3],
[1, 2],
100,
true,
false,
callbackData,
{ value: 100 },
);
await dao.initiateValidationPool(
1,
60,
[1, 3],
[1, 2],
100,
true,
false,
callbackData,
{ value: 100 },
);
await time.increase(61);
await dao.evaluateOutcome(0);
await dao.evaluateOutcome(1);

View File

@ -19,7 +19,17 @@ describe('Work1', () => {
await dao.addPost(account1, 'some-content-id');
const callbackData = ethers.AbiCoder.defaultAbiCoder().encode([], []);
await dao.initiateValidationPool(0, 60, 1, 3, 100, true, false, callbackData, { value: 100 });
await dao.initiateValidationPool(
0,
60,
[1, 3],
[1, 2],
100,
true,
false,
callbackData,
{ value: 100 },
);
await time.increase(61);
await dao.evaluateOutcome(0);