small fixes
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 32s
Details
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 32s
Details
This commit is contained in:
parent
3c839a8546
commit
046aba2c48
|
@ -121,9 +121,9 @@ function App() {
|
|||
};
|
||||
|
||||
const fetchValidationPools = async () => {
|
||||
// TODO: Pagination
|
||||
// TODO: Memoization
|
||||
// TODO: Caching
|
||||
// TODO: Pagination
|
||||
// TODO: Memoization
|
||||
// TODO: Caching
|
||||
const count = await DAOContract.methods.validationPoolCount().call();
|
||||
const promises = [];
|
||||
dispatchValidationPool({ type: 'refresh' });
|
||||
|
@ -234,7 +234,7 @@ function App() {
|
|||
).send({
|
||||
from: account,
|
||||
gas: 1000000,
|
||||
value: 100,
|
||||
value: 10000,
|
||||
});
|
||||
}, [provider, DAO, account]);
|
||||
|
||||
|
@ -274,10 +274,15 @@ function App() {
|
|||
/* --------------------------- END UI ACTIONS ------------------------------------- */
|
||||
/* -------------------------------------------------------------------------------- */
|
||||
|
||||
const getAdressName = useCallback((address) => {
|
||||
const getAddressName = useCallback((address) => {
|
||||
const contractName = getContractNameByAddress(chainId, address);
|
||||
if (contractName) return `${contractName} Contract`;
|
||||
return address;
|
||||
const addressParts = [
|
||||
address.slice(0, 7),
|
||||
|
||||
address.slice(address.length - 5),
|
||||
];
|
||||
return addressParts.join('...');
|
||||
}, [chainId]);
|
||||
|
||||
return (
|
||||
|
@ -355,8 +360,8 @@ function App() {
|
|||
{posts.filter((x) => !!x).map((post) => (
|
||||
<tr key={post.id}>
|
||||
<td>{post.id.toString()}</td>
|
||||
<td>{post.author}</td>
|
||||
<td>{getAdressName(post.sender)}</td>
|
||||
<td>{getAddressName(post.author)}</td>
|
||||
<td>{getAddressName(post.sender)}</td>
|
||||
<td>
|
||||
<Button onClick={() => handleShowViewPost(post)}>
|
||||
View Post
|
||||
|
@ -393,6 +398,14 @@ function App() {
|
|||
<th>Post ID</th>
|
||||
<th>Sender</th>
|
||||
<th>Fee</th>
|
||||
<th>Binding</th>
|
||||
<th>Quorum</th>
|
||||
<th>WinRatio</th>
|
||||
<th>
|
||||
Redistribute
|
||||
<br />
|
||||
Losing Stakes
|
||||
</th>
|
||||
<th>Duration</th>
|
||||
<th>End Time</th>
|
||||
<th>
|
||||
|
@ -409,9 +422,16 @@ function App() {
|
|||
<tr key={pool.id}>
|
||||
<td>{pool.id.toString()}</td>
|
||||
<td>{pool.postIndex.toString()}</td>
|
||||
<td>{getAdressName(pool.sender)}</td>
|
||||
<td>{getAddressName(pool.sender)}</td>
|
||||
<td>{pool.fee.toString()}</td>
|
||||
<td>{pool.duration.toString()}</td>
|
||||
<td>
|
||||
{pool.params.bindingPercent.toString()}
|
||||
%
|
||||
</td>
|
||||
<td>{`${pool.params.quorum[0].toString()} / ${pool.params.quorum[1].toString()}`}</td>
|
||||
<td>{`${pool.params.winRatio[0].toString()} / ${pool.params.winRatio[1].toString()}`}</td>
|
||||
<td>{pool.params.redistributeLosingStakes.toString()}</td>
|
||||
<td>{pool.params.duration.toString()}</td>
|
||||
<td>{new Date(Number(pool.endTime) * 1000).toLocaleString()}</td>
|
||||
<td>{pool.stakeCount.toString()}</td>
|
||||
<td>{pool.status}</td>
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -4,12 +4,25 @@ import {
|
|||
import { Web3 } from 'web3';
|
||||
|
||||
import Button from 'react-bootstrap/esm/Button';
|
||||
import Stack from 'react-bootstrap/esm/Stack';
|
||||
import useList from '../utils/List';
|
||||
import Web3Context from '../contexts/Web3Context';
|
||||
import ProposalsArtifact from '../assets/Proposals.json';
|
||||
import { getContractAddressByChainId } from '../utils/contract-config';
|
||||
import AddPostModal from './posts/AddPostModal';
|
||||
|
||||
const getProposalStatus = (proposal) => {
|
||||
switch (Number(proposal.stage)) {
|
||||
case 0: return 'Proposal';
|
||||
case 1: return 'Referendum 0%';
|
||||
case 2: return 'Referendum 1%';
|
||||
case 3: return 'Referendum 100%';
|
||||
case 4: return 'Failed';
|
||||
case 5: return 'Accepted';
|
||||
default: return 'Unknown';
|
||||
}
|
||||
};
|
||||
|
||||
function Proposals() {
|
||||
const {
|
||||
provider, chainId, account, reputation,
|
||||
|
@ -20,6 +33,7 @@ function Proposals() {
|
|||
|
||||
const fetchProposal = useCallback(async (proposalIndex) => {
|
||||
const proposal = await proposalsContract.current.methods.proposals(proposalIndex).call();
|
||||
proposal.pools = await proposalsContract.current.methods.getPools(proposalIndex).call();
|
||||
dispatchProposal({
|
||||
type: 'update',
|
||||
item: {
|
||||
|
@ -28,6 +42,7 @@ function Proposals() {
|
|||
},
|
||||
|
||||
});
|
||||
console.log('proposal.pools', proposal.pools);
|
||||
}, [proposalsContract, dispatchProposal]);
|
||||
|
||||
const fetchProposals = useCallback(async () => {
|
||||
|
@ -41,9 +56,8 @@ function Proposals() {
|
|||
}, [proposalsContract, fetchProposal, dispatchProposal]);
|
||||
|
||||
// Initial data load and event subscription
|
||||
// TODO: unsubscribe on unmount
|
||||
useEffect(() => {
|
||||
if (chainId === undefined) return;
|
||||
if (chainId === undefined) return () => {};
|
||||
const web3 = new Web3(provider);
|
||||
const ProposalsAddress = getContractAddressByChainId(chainId, 'Proposals');
|
||||
const contract = new web3.eth.Contract(ProposalsArtifact.abi, ProposalsAddress);
|
||||
|
@ -51,10 +65,17 @@ function Proposals() {
|
|||
|
||||
fetchProposals();
|
||||
|
||||
contract.events.NewProposal({ fromBlock: 'latest' }).on('data', (event) => {
|
||||
const onNewProposal = (event) => {
|
||||
console.log('event: new proposal', event);
|
||||
fetchProposal(event.returnValues.proposalIndex);
|
||||
});
|
||||
};
|
||||
|
||||
contract.events.NewProposal({ fromBlock: 'latest' }).on('data', onNewProposal);
|
||||
|
||||
// unsubscribe on unmount
|
||||
return () => {
|
||||
contract.events.NewProposal().off(onNewProposal);
|
||||
};
|
||||
}, [provider, chainId, proposalsContract, fetchProposals, fetchProposal]);
|
||||
|
||||
const handleShowAddProposal = () => setShowAddProposal(true);
|
||||
|
@ -82,6 +103,38 @@ function Proposals() {
|
|||
});
|
||||
}, [proposalsContract, account]);
|
||||
|
||||
const getReferenda = (proposal) => {
|
||||
if (!proposal || !proposal.pools) return [];
|
||||
console.log('proposal.pools', proposal.pools);
|
||||
const referenda = [];
|
||||
for (let referendumIndex = 0; referendumIndex < 3; referendumIndex += 1) {
|
||||
for (let i = 0; i < 3; i += 1) {
|
||||
const pool = proposal.pools[referendumIndex][i];
|
||||
if (pool.started) {
|
||||
console.log('pool', pool);
|
||||
referenda.push(
|
||||
<div key={`${referendumIndex}.{i}`}>
|
||||
{`${referendumIndex}.${i}. `}
|
||||
{!pool.completed && (
|
||||
<span>
|
||||
In Progress
|
||||
</span>
|
||||
)}
|
||||
{pool.completed && (
|
||||
<span>
|
||||
{`${pool.stakedFor.toString()} U / ${pool.stakedAgainst.toString()} D`}
|
||||
{', '}
|
||||
{pool.votePasses ? 'Accepted' : 'Rejected'}
|
||||
{pool.quorumMet || ': Quorum not met'}
|
||||
</span>
|
||||
)}
|
||||
</div>,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return referenda;
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<AddPostModal title="New Proposal" show={showAddProposal} setShow={setShowAddProposal} onSubmit={onSubmitProposal} />
|
||||
|
@ -97,22 +150,28 @@ function Proposals() {
|
|||
<th>Fee</th>
|
||||
<th>Stage</th>
|
||||
<th>Attestation</th>
|
||||
<th>Referenda</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{proposals.filter((x) => !!x).map((request) => (
|
||||
<tr key={request.id}>
|
||||
<td>{request.id.toString()}</td>
|
||||
<td>{request.fee.toString()}</td>
|
||||
<td>{request.stage.toString()}</td>
|
||||
<td>{request.attestationTotal.toString()}</td>
|
||||
{proposals.filter((x) => !!x).map((proposal) => (
|
||||
<tr key={proposal.id}>
|
||||
<td>{proposal.id.toString()}</td>
|
||||
<td>{proposal.fee.toString()}</td>
|
||||
<td>{getProposalStatus(proposal)}</td>
|
||||
<td>{proposal.attestationTotal.toString()}</td>
|
||||
<td>
|
||||
{request.stage === 0n && (
|
||||
<Stack direction="vertical">
|
||||
{getReferenda(proposal)}
|
||||
</Stack>
|
||||
</td>
|
||||
<td>
|
||||
{proposal.stage === 0n && (
|
||||
<>
|
||||
<Button onClick={() => handleAttest(request.id)}>Attest</Button>
|
||||
<Button onClick={() => handleAttest(proposal.id)}>Attest</Button>
|
||||
{' '}
|
||||
<Button onClick={() => handleEvaluateAttestation(request.id)}>
|
||||
<Button onClick={() => handleEvaluateAttestation(proposal.id)}>
|
||||
Evaluate Attestation
|
||||
</Button>
|
||||
</>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"localhost": {
|
||||
"DAO": "0x65B0922fe7F0c4012aa38704071f26aeF6F22650",
|
||||
"Work1": "0x95673D8710A8eD59f8551e9B12509D6812e0623e",
|
||||
"Onboarding": "0xc6b3b8A641c52F7bC13a9D444e1f0759CA3b87b4",
|
||||
"Proposals": "0x859cd550d5b3BDdde4Cf0ca71D060f945E9E42DD"
|
||||
"DAO": "0x84A5F75A39e25bD39B69F7d096d159557EaF2a59",
|
||||
"Work1": "0xaB3Bf8f9BE69289B0dd2a154a6390c8D9F780c59",
|
||||
"Onboarding": "0xf10E261AFF9Aa8b05716002bFf44D3e990401C02",
|
||||
"Proposals": "0xBD616B6331e0953Fc20281a54A684E614D8C4026"
|
||||
},
|
||||
"sepolia": {
|
||||
"DAO": "0x8Cb4ab513A863ac29e855c85064ea53dec7dA24C",
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"localhost": {
|
||||
"DAO": "0x65B0922fe7F0c4012aa38704071f26aeF6F22650",
|
||||
"Work1": "0x95673D8710A8eD59f8551e9B12509D6812e0623e",
|
||||
"Onboarding": "0xc6b3b8A641c52F7bC13a9D444e1f0759CA3b87b4",
|
||||
"Proposals": "0x859cd550d5b3BDdde4Cf0ca71D060f945E9E42DD"
|
||||
"DAO": "0x84A5F75A39e25bD39B69F7d096d159557EaF2a59",
|
||||
"Work1": "0xaB3Bf8f9BE69289B0dd2a154a6390c8D9F780c59",
|
||||
"Onboarding": "0xf10E261AFF9Aa8b05716002bFf44D3e990401C02",
|
||||
"Proposals": "0xBD616B6331e0953Fc20281a54A684E614D8C4026"
|
||||
},
|
||||
"sepolia": {
|
||||
"DAO": "0x8Cb4ab513A863ac29e855c85064ea53dec7dA24C",
|
||||
|
|
|
@ -23,10 +23,11 @@ struct Stake {
|
|||
}
|
||||
|
||||
struct ValidationPoolParams {
|
||||
uint quorumPPB;
|
||||
uint duration;
|
||||
uint[2] quorum; // [ Numerator, Denominator ]
|
||||
uint[2] winRatio; // [ Numerator, Denominator ]
|
||||
uint bindingPercent;
|
||||
bool redistributeLosingStakes;
|
||||
uint[2] winRatio; // [ Numerator, Denominator ]
|
||||
}
|
||||
|
||||
struct ValidationPool {
|
||||
|
@ -37,7 +38,6 @@ struct ValidationPool {
|
|||
uint stakeCount;
|
||||
ValidationPoolParams params;
|
||||
uint256 fee;
|
||||
uint duration;
|
||||
uint endTime;
|
||||
bool resolved;
|
||||
bool outcome;
|
||||
|
@ -62,9 +62,9 @@ contract DAO is ERC20("Reputation", "REP") {
|
|||
// TODO: possible parameter for winningRatio
|
||||
// TODO: Add forum parameters
|
||||
|
||||
uint public constant minDuration = 1; // 1 second
|
||||
uint public constant maxDuration = 365_000_000 days; // 1 million years
|
||||
uint public constant minQuorumPPB = 100_000_000; // Parts per billion
|
||||
uint constant minDuration = 1; // 1 second
|
||||
uint constant maxDuration = 365_000_000 days; // 1 million years
|
||||
uint[2] minQuorum = [1, 10];
|
||||
|
||||
event PostAdded(uint postIndex);
|
||||
event ValidationPoolInitiated(uint poolIndex);
|
||||
|
@ -74,6 +74,10 @@ contract DAO is ERC20("Reputation", "REP") {
|
|||
bool quorumMet
|
||||
);
|
||||
|
||||
function decimals() public pure override returns (uint8) {
|
||||
return 9;
|
||||
}
|
||||
|
||||
function addPost(
|
||||
address author,
|
||||
string calldata contentId
|
||||
|
@ -103,7 +107,7 @@ 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 * quorum[0]) / quorum[1] >= minQuorumPPB,
|
||||
minQuorum[1] * quorum[0] >= minQuorum[0] * quorum[1],
|
||||
"Quorum is below minimum"
|
||||
);
|
||||
require(quorum[0] <= quorum[1], "Quorum is greater than one");
|
||||
|
@ -116,11 +120,11 @@ contract DAO is ERC20("Reputation", "REP") {
|
|||
pool.sender = msg.sender;
|
||||
pool.postIndex = postIndex;
|
||||
pool.fee = msg.value;
|
||||
pool.params.quorumPPB = (1_000_000_000 * quorum[0]) / quorum[1];
|
||||
pool.params.quorum = quorum;
|
||||
pool.params.winRatio = winRatio;
|
||||
pool.params.bindingPercent = bindingPercent;
|
||||
pool.params.redistributeLosingStakes = redistributeLosingStakes;
|
||||
pool.duration = duration;
|
||||
pool.params.duration = duration;
|
||||
pool.endTime = block.timestamp + duration;
|
||||
pool.id = poolIndex;
|
||||
pool.callbackOnValidate = callbackOnValidate;
|
||||
|
@ -186,22 +190,39 @@ contract DAO is ERC20("Reputation", "REP") {
|
|||
}
|
||||
// Check that quorum is met
|
||||
if (
|
||||
1_000_000_000 * (stakedFor + stakedAgainst) <=
|
||||
totalSupply() * pool.params.quorumPPB
|
||||
pool.params.quorum[1] * (stakedFor + stakedAgainst) <=
|
||||
totalSupply() * pool.params.quorum[0]
|
||||
) {
|
||||
// TODO: refund fee
|
||||
// TODO: refund stakes
|
||||
// Callback if requested
|
||||
if (pool.callbackOnValidate) {
|
||||
console.log("quorum not met, calling onValidate");
|
||||
IOnValidate(pool.sender).onValidate(
|
||||
votePasses,
|
||||
false,
|
||||
stakedFor,
|
||||
stakedAgainst,
|
||||
pool.callbackData
|
||||
);
|
||||
// Refund fee
|
||||
// TODO: this could be made available for the sender to withdraw
|
||||
// payable(pool.sender).transfer(pool.fee);
|
||||
|
||||
// Refund stakes
|
||||
for (uint i = 0; i < pool.stakeCount; i++) {
|
||||
s = pool.stakes[i];
|
||||
// TODO: ensure this can't be repeated
|
||||
_update(address(this), s.sender, s.amount);
|
||||
}
|
||||
|
||||
// Callback if requested
|
||||
|
||||
if (pool.callbackOnValidate) {
|
||||
try
|
||||
IOnValidate(pool.sender).onValidate(
|
||||
votePasses,
|
||||
false,
|
||||
stakedFor,
|
||||
stakedAgainst,
|
||||
pool.callbackData
|
||||
)
|
||||
{
|
||||
console.log("callbackOnValidate succeed");
|
||||
} catch Error(string memory reason) {
|
||||
console.log("callbackOnValidate failed:", reason);
|
||||
}
|
||||
}
|
||||
|
||||
pool.resolved = true;
|
||||
emit ValidationPoolResolved(poolIndex, false, false);
|
||||
return false;
|
||||
}
|
||||
|
@ -227,12 +248,12 @@ contract DAO is ERC20("Reputation", "REP") {
|
|||
uint256 totalAllocated;
|
||||
for (uint i = 0; i < pool.stakeCount; i++) {
|
||||
s = pool.stakes[i];
|
||||
bool redistributeLosingStakes = s.fromMint ||
|
||||
pool.params.redistributeLosingStakes;
|
||||
uint bindingPercent = s.fromMint ? 100 : pool.params.bindingPercent;
|
||||
if (votePasses == s.inFavor) {
|
||||
// Winning stake
|
||||
// If this stake is from the minted fee, always redistribute it to the winners
|
||||
bool redistributeLosingStakes = s.fromMint ||
|
||||
pool.params.redistributeLosingStakes;
|
||||
uint reward = redistributeLosingStakes
|
||||
? ((s.amount * amountFromLosers) / amountFromWinners) *
|
||||
(bindingPercent / 100)
|
||||
|
@ -241,18 +262,20 @@ contract DAO is ERC20("Reputation", "REP") {
|
|||
totalAllocated += reward;
|
||||
} else {
|
||||
// Losing stake
|
||||
uint refund = (s.amount * (100 - bindingPercent)) / 100;
|
||||
if (refund > 0) {
|
||||
_update(address(this), s.sender, refund);
|
||||
}
|
||||
// If this stake is from the minted fee, don't burn it
|
||||
if (!redistributeLosingStakes) {
|
||||
uint amountToBurn = (s.amount *
|
||||
pool.params.bindingPercent) / 100;
|
||||
_burn(address(this), amountToBurn);
|
||||
totalAllocated += amountToBurn;
|
||||
if (!s.fromMint) {
|
||||
uint refund = (s.amount * (100 - bindingPercent)) / 100;
|
||||
if (refund > 0) {
|
||||
_update(address(this), s.sender, refund);
|
||||
}
|
||||
if (!pool.params.redistributeLosingStakes) {
|
||||
uint amountToBurn = (s.amount *
|
||||
pool.params.bindingPercent) / 100;
|
||||
_burn(address(this), amountToBurn);
|
||||
totalAllocated += amountToBurn;
|
||||
}
|
||||
totalAllocated += refund;
|
||||
}
|
||||
totalAllocated += refund;
|
||||
}
|
||||
}
|
||||
// Due to rounding, there may be some REP left over. Include this as a reward to the author.
|
||||
|
@ -269,14 +292,19 @@ contract DAO is ERC20("Reputation", "REP") {
|
|||
}
|
||||
// Callback if requested
|
||||
if (pool.callbackOnValidate) {
|
||||
console.log("calling onValidate");
|
||||
IOnValidate(pool.sender).onValidate(
|
||||
votePasses,
|
||||
true,
|
||||
stakedFor,
|
||||
stakedAgainst,
|
||||
pool.callbackData
|
||||
);
|
||||
try
|
||||
IOnValidate(pool.sender).onValidate(
|
||||
votePasses,
|
||||
true,
|
||||
stakedFor,
|
||||
stakedAgainst,
|
||||
pool.callbackData
|
||||
)
|
||||
{
|
||||
console.log("callbackOnValidate succeed");
|
||||
} catch Error(string memory reason) {
|
||||
console.log("callbackOnValidate failed:", reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,14 +18,18 @@ contract Proposals is DAOContract, IOnValidate {
|
|||
|
||||
struct Pool {
|
||||
uint poolIndex;
|
||||
bool started;
|
||||
bool completed;
|
||||
uint stakedFor;
|
||||
uint stakedAgainst;
|
||||
bool votePasses;
|
||||
bool quorumMet;
|
||||
}
|
||||
|
||||
struct Referendum {
|
||||
uint duration;
|
||||
// Each referendum may retry up to 3x
|
||||
Pool[] pools;
|
||||
Pool[3] pools;
|
||||
uint retryCount;
|
||||
}
|
||||
|
||||
|
@ -45,12 +49,15 @@ contract Proposals is DAOContract, IOnValidate {
|
|||
uint public proposalCount;
|
||||
|
||||
event NewProposal(uint proposalIndex);
|
||||
event Attestation(uint proposalIndex);
|
||||
event ReferendumStarted(uint proposalIndex, uint poolIndex);
|
||||
event ProposalFailed(uint proposalIndex, string reason);
|
||||
event ProposalAccepted(uint proposalIndex);
|
||||
|
||||
constructor(DAO dao) DAOContract(dao) {}
|
||||
|
||||
// TODO receive : we want to be able to accept refunds from validation pools
|
||||
|
||||
function propose(
|
||||
string calldata contentId,
|
||||
uint referendum0Duration,
|
||||
|
@ -70,6 +77,15 @@ contract Proposals is DAOContract, IOnValidate {
|
|||
emit NewProposal(proposalIndex);
|
||||
}
|
||||
|
||||
function getPools(
|
||||
uint proposalIndex
|
||||
) public view returns (Pool[3][3] memory pools) {
|
||||
Proposal storage proposal = proposals[proposalIndex];
|
||||
pools[0] = proposal.referenda[0].pools;
|
||||
pools[1] = proposal.referenda[1].pools;
|
||||
pools[2] = proposal.referenda[2].pools;
|
||||
}
|
||||
|
||||
/// External function for reputation holders to attest toward a given proposal;
|
||||
/// This is non-binding and non-encumbering, so it does not transfer any reputation.
|
||||
function attest(uint proposalIndex, uint amount) external {
|
||||
|
@ -83,6 +99,7 @@ contract Proposals is DAOContract, IOnValidate {
|
|||
proposal.attestationTotal -= proposal.attestations[msg.sender];
|
||||
proposal.attestations[msg.sender] = amount;
|
||||
proposal.attestationTotal += amount;
|
||||
emit Attestation(proposalIndex);
|
||||
}
|
||||
|
||||
// --- Sequences of validation pool parameters ---
|
||||
|
@ -114,8 +131,10 @@ contract Proposals is DAOContract, IOnValidate {
|
|||
true,
|
||||
abi.encode(proposalIndex, referendumIndex, fee)
|
||||
);
|
||||
Pool storage pool = proposal.referenda[referendumIndex].pools.push();
|
||||
Referendum storage referendum = proposal.referenda[referendumIndex];
|
||||
Pool storage pool = referendum.pools[referendum.retryCount];
|
||||
pool.poolIndex = poolIndex;
|
||||
pool.started = true;
|
||||
emit ReferendumStarted(proposalIndex, poolIndex);
|
||||
}
|
||||
|
||||
|
@ -136,18 +155,22 @@ contract Proposals is DAOContract, IOnValidate {
|
|||
(uint, uint, uint)
|
||||
);
|
||||
Proposal storage proposal = proposals[proposalIndex];
|
||||
Referendum storage referendum = proposal.referenda[referendumIndex];
|
||||
Pool storage pool = referendum.pools[referendum.retryCount];
|
||||
|
||||
// Make a record of this result
|
||||
pool.completed = true;
|
||||
pool.stakedFor = stakedFor;
|
||||
pool.stakedAgainst = stakedAgainst;
|
||||
pool.quorumMet = quorumMet;
|
||||
pool.votePasses = votePasses;
|
||||
|
||||
if (!quorumMet) {
|
||||
proposal.stage = Stage.Failed;
|
||||
emit ProposalFailed(proposalIndex, "Quorum not met");
|
||||
proposal.remainingFee += fee;
|
||||
// TODO: Refund remaining fee
|
||||
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;
|
||||
|
||||
// Participation threshold of 50%
|
||||
bool participationAboveThreshold = 2 * (stakedFor + stakedAgainst) >=
|
||||
|
@ -235,4 +258,16 @@ contract Proposals is DAOContract, IOnValidate {
|
|||
initiateValidationPool(proposalIndex, 0, proposal.fee / 10);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// External function to reclaim remaining fees after a proposal has completed all referenda
|
||||
function reclaimRemainingFee(uint proposalIndex) external {
|
||||
Proposal storage proposal = proposals[proposalIndex];
|
||||
require(
|
||||
proposal.stage == Stage.Failed || proposal.stage == Stage.Accepted,
|
||||
"Remaining fees can only be reclaimed when proposal has been accepted or failed"
|
||||
);
|
||||
uint amount = proposal.remainingFee;
|
||||
proposal.remainingFee = 0;
|
||||
payable(msg.sender).transfer(amount);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,7 +117,7 @@ describe('DAO', () => {
|
|||
it('Should be able to fetch pool instance', async () => {
|
||||
const pool = await dao.validationPools(0);
|
||||
expect(pool).to.exist;
|
||||
expect(pool.duration).to.equal(POOL_DURATION);
|
||||
expect(pool.params.duration).to.equal(POOL_DURATION);
|
||||
expect(pool.postIndex).to.equal(0);
|
||||
expect(pool.resolved).to.be.false;
|
||||
expect(pool.sender).to.equal(account1);
|
||||
|
@ -295,6 +295,25 @@ describe('DAO', () => {
|
|||
expect(await dao.balanceOf(dao.target)).to.equal(0);
|
||||
expect(await dao.totalSupply()).to.equal(295);
|
||||
});
|
||||
|
||||
it('If redistributeLosingStakes is false and bindingPercent is 0, accounts should recover initial balances', async () => {
|
||||
const init = () => initiateValidationPool({
|
||||
bindingPercent: 0,
|
||||
redistributeLosingStakes: false,
|
||||
});
|
||||
await expect(init()).to.emit(dao, 'ValidationPoolInitiated').withArgs(2);
|
||||
await dao.connect(account1).stake(2, 10, true);
|
||||
await dao.connect(account2).stake(2, 10, false);
|
||||
expect(await dao.balanceOf(account1)).to.equal(90);
|
||||
expect(await dao.balanceOf(account2)).to.equal(90);
|
||||
expect(await dao.balanceOf(dao.target)).to.equal(120);
|
||||
time.increase(POOL_DURATION + 1);
|
||||
await dao.evaluateOutcome(2);
|
||||
expect(await dao.balanceOf(account1)).to.equal(200);
|
||||
expect(await dao.balanceOf(account2)).to.equal(100);
|
||||
expect(await dao.balanceOf(dao.target)).to.equal(0);
|
||||
expect(await dao.totalSupply()).to.equal(300);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -158,6 +158,14 @@ describe('Proposal', () => {
|
|||
.to.emit(dao, 'ValidationPoolInitiated').withArgs(3);
|
||||
proposal = await proposals.proposals(0);
|
||||
expect(proposal.stage).to.equal(1);
|
||||
|
||||
const pools = await proposals.getPools(0);
|
||||
expect(pools[0][0].started).to.be.true;
|
||||
expect(pools[0][1].started).to.be.true;
|
||||
expect(pools[0][2].started).to.be.false;
|
||||
expect(pools[0][0].completed).to.be.true;
|
||||
expect(pools[0][1].completed).to.be.false;
|
||||
expect(pools[0][2].completed).to.be.false;
|
||||
});
|
||||
|
||||
it('proposal fails if a referendum fails to meet participation rate 3 times', async () => {
|
||||
|
@ -210,6 +218,11 @@ describe('Proposal', () => {
|
|||
console.log('evaluated pool 2');
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
const pool = await dao.validationPools(3);
|
||||
expect(pool.resolved).to.be.true;
|
||||
});
|
||||
|
||||
it('proposal dies if it fails to meet quorum', async () => {
|
||||
await time.increase(21);
|
||||
await expect(dao.evaluateOutcome(3)).to.emit(dao, 'ValidationPoolResolved').withArgs(3, false, false);
|
||||
|
@ -273,7 +286,7 @@ describe('Proposal', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Referendum 10% binding', () => {
|
||||
describe('Referendum 100% binding', () => {
|
||||
beforeEach(async () => {
|
||||
await proposals.attest(0, 200);
|
||||
await expect(proposals.evaluateAttestation(0)).to.emit(dao, 'ValidationPoolInitiated').withArgs(2);
|
||||
|
@ -291,6 +304,11 @@ describe('Proposal', () => {
|
|||
expect(proposal.stage).to.equal(3);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
const pool = await dao.validationPools(4);
|
||||
expect(pool.resolved).to.be.true;
|
||||
});
|
||||
|
||||
it('proposal dies if it fails to meet quorum', async () => {
|
||||
await time.increase(21);
|
||||
await expect(dao.evaluateOutcome(4)).to.emit(dao, 'ValidationPoolResolved').withArgs(4, false, false);
|
||||
|
|
Loading…
Reference in New Issue