Skip to content

Commit

Permalink
Add Contested Proposals filtering (#84)
Browse files Browse the repository at this point in the history
* Add Contested Proposals category

To help "dust away" and disincentivise spam or malicious proposals, this commit ensures proposals with less than 25% net consensus (i.e; 75% downvotes) are considered Contested Proposals, and put in to a lower display state on MPW, with slightly lowered opacity and stuffed at the bottom of the Governance menu, along with an explainer sub-text to educate the user on these proposals.

* Add minimum req + refactor 'finished' proposals

A minimum requirement for votes has been added before a proposal will be considered for the Contested category (100 votes) to prevent young proposals being abused in the DAO, and the filtering for 'finished' proposals was lightly refactored to include filtering directly, instead of post-call manually.

* Add default param value to getProposals()

As suggested by Duddino

Co-authored-by: Duddino <47313600+Duddino@users.noreply.github.com>

* Run Prettier

---------

Co-authored-by: Duddino <47313600+Duddino@users.noreply.github.com>
  • Loading branch information
JSKitty and Duddino committed Feb 22, 2023
1 parent 8f502c9 commit 1d2783c
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 44 deletions.
14 changes: 14 additions & 0 deletions index.template.html
Expand Up @@ -757,6 +757,20 @@ <h3 class="pivx-bold-title center-text">Governance</h3>
</thead>
<tbody id="proposalsTableBody" style="text-align: center; vertical-align: middle;"></tbody>
</table>
<hr>
<br>
<h3 data-i18n="contestedProposalsTitle" style="width: 100%;text-align: center;">Contested Proposals</h3>
<p data-i18n="contestedProposalsDesc" style="width: 100%;text-align: center;">These are proposals that received an overwhelming amount of downvotes, making it likely spam or a highly contestable proposal.</p>
<br>
<table id="proposalsContestedTable" class="table table-hover table-dark bg-transparent" style="width: 100%; opacity: 0.8;">
<thead>
<td class="text-center"><b> Name </b></td>
<td class="text-center"><b> Payment </b></td>
<td class="text-center"><b> Votes </b></td>
<td class="text-center"><b> Vote </b></td>
</thead>
<tbody id="proposalsContestedTableBody" style="text-align: center; vertical-align: middle;"></tbody>
</table>
</div>
<div id="Masternode" class="tabcontent">
<div class="col-md-12 title-section float-left rm-pd">
Expand Down
4 changes: 4 additions & 0 deletions locale/en/translation.js
Expand Up @@ -108,6 +108,10 @@ export const en_translation = {
stakeUnstake:"Unstake", //
stakeLoadMore:"Load more", //

// Governance
contestedProposalsTitle:"Contested Proposals",
contestedProposalsDesc:"These are proposals that received an overwhelming amount of downvotes, making it likely spam or a highly contestable proposal.",

// Settings
settingsCurrency:"Choose a display currency:",
settingsExplorer:"Choose an explorer:", //
Expand Down
4 changes: 4 additions & 0 deletions locale/template/translation.js
Expand Up @@ -126,6 +126,10 @@ var translation = {
stakeUnstake:"", //Unstake
stakeLoadMore:"", //Load more

// Governance
contestedProposalsTitle:"", //Contested Proposals
contestedProposalsDesc:"", //These are proposals that received an overwhelming amount of downvotes, making it likely spam or a highly contestable proposal.

// Settings
settingsCurrency:"", //Choose a display currency:
settingsExplorer:"", //Choose an explorer:
Expand Down
4 changes: 4 additions & 0 deletions locale/uwu/translation.js
Expand Up @@ -108,6 +108,10 @@ export const uwu_translation = {
stakeUnstake:"", //Unstake
stakeLoadMore:"Lowoad Mowore", //Load more

// Governance
contestedProposalsTitle:"Contwested Pwoposals",
contestedProposalsDesc:"Dees are pwoposals dat received an overwhelming ameownt of downwotes, making it likely spam or a highly contwestable pwoposal.",

// Settings
settingsCurrency:"Chowose a dispway cuwwency:",
settingsExplorer:"Chowose an expwower:", //Choose an explorer:
Expand Down
119 changes: 81 additions & 38 deletions scripts/global.js
Expand Up @@ -84,6 +84,12 @@ export function start() {
//GOVERNANCE ELEMENTS
domGovProposalsTable: document.getElementById('proposalsTable'),
domGovProposalsTableBody: document.getElementById('proposalsTableBody'),
domGovProposalsContestedTable: document.getElementById(
'proposalsContestedTable'
),
domGovProposalsContestedTableBody: document.getElementById(
'proposalsContestedTableBody'
),
//MASTERNODE ELEMENTS
domCreateMasternode: document.getElementById('createMasternode'),
domControlMasternode: document.getElementById('controlMasternode'),
Expand Down Expand Up @@ -963,52 +969,89 @@ export async function restoreWallet() {
}
}

/**
* Fetch Governance data and re-render the Governance UI
*/
async function updateGovernanceTab() {
const proposals = await Masternode.getProposals();
doms.domGovProposalsTableBody.innerHTML = '';
for (const proposal of proposals) {
if (proposal.RemainingPaymentCount === 0) {
continue;
}
const tr = doms.domGovProposalsTableBody.insertRow();
const td1 = tr.insertCell();
// IMPORTANT: We must sanite all of our HTML or a rogue server or malicious proposal could perform a cross side scripting attack
td1.innerHTML = `<a class="active" href="${sanitizeHTML(
proposal.URL
)}"><b>${sanitizeHTML(proposal.Name)}</b></a>`;
const td2 = tr.insertCell();
td2.innerHTML = `<b>${sanitizeHTML(proposal.MonthlyPayment)}</b> ${
cChainParams.current.TICKER
} <br>
// Fetch all proposals from the network
const arrProposals = await Masternode.getProposals({
fAllowFinished: false,
});

/* Sort proposals into two categories
- Standard (Proposal is either new with <100 votes, or has a healthy vote count)
- Contested (When a proposal may be considered spam, malicious, or simply highly contestable)
*/
const arrStandard = arrProposals.filter(
(a) => a.Yeas + a.Nays < 100 || a.Ratio > 0.25
);
const arrContested = arrProposals.filter(
(a) => a.Yeas + a.Nays >= 100 && a.Ratio <= 0.25
);

// Render Proposals
renderProposals(arrStandard, false);
renderProposals(arrContested, true);
}

/**
* Render Governance proposal objects to a given Proposal category
* @param {Array<object>} arrProposals - The proposals to render
* @param {boolean} fContested - The proposal category
*/
function renderProposals(arrProposals, fContested) {
// Select the table based on the proposal category
const domTable = fContested
? doms.domGovProposalsContestedTableBody
: doms.domGovProposalsTableBody;

// Render the proposals in the relevent table
domTable.innerHTML = '';
for (const cProposal of arrProposals) {
const domRow = domTable.insertRow();

// Name and URL hyperlink
const domNameAndURL = domRow.insertCell();
// IMPORTANT: Sanitise all of our HTML or a rogue server or malicious proposal could perform a cross-site scripting attack
domNameAndURL.innerHTML = `<a class="active" href="${sanitizeHTML(
cProposal.URL
)}"><b>${sanitizeHTML(cProposal.Name)}</b></a>`;

// Payment Schedule and Amounts
const domPayments = domRow.insertCell();
domPayments.innerHTML = `<b>${sanitizeHTML(
cProposal.MonthlyPayment
)}</b> ${cChainParams.current.TICKER} <br>
<small> ${sanitizeHTML(
proposal['RemainingPaymentCount']
)} payments remaining of <b>${sanitizeHTML(proposal.TotalPayment)}</b> ${
cProposal['RemainingPaymentCount']
)} payments remaining of <b>${sanitizeHTML(cProposal.TotalPayment)}</b> ${
cChainParams.current.TICKER
} total</small>`;
const td3 = tr.insertCell();
let { Yeas, Nays } = proposal;
Yeas = parseInt(Yeas);
Nays = parseInt(Nays);
const percentage = Yeas + Nays !== 0 ? (Yeas / (Yeas + Nays)) * 100 : 0;

td3.innerHTML = `<b>${percentage.toFixed(2)}%</b> <br>
// Vote Counts and Consensus Percentages
const domVoteCounters = domRow.insertCell();
const { Yeas, Nays } = cProposal;
const nPercent = cProposal.Ratio * 100;

domVoteCounters.innerHTML = `<b>${nPercent.toFixed(2)}%</b> <br>
<small> <b><div class="text-success" style="display:inline;"> ${Yeas} </div></b> /
<b><div class="text-danger" style="display:inline;"> ${Nays} </div></b>
`;
const td4 = tr.insertCell();
//append vote buttons
const buttonNo = document.createElement('button');
buttonNo.className = 'pivx-button-big';
buttonNo.innerText = 'No';
buttonNo.onclick = () => govVote(proposal.Hash, 2);

const buttonYes = document.createElement('button');
buttonYes.className = 'pivx-button-big';
buttonYes.innerText = 'Yes';
buttonYes.onclick = () => govVote(proposal.Hash, 1);

td4.appendChild(buttonNo);
td4.appendChild(buttonYes);

// Voting Buttons for Masternode owners (MNOs)
const domVoteBtns = domRow.insertCell();
const domNoBtn = document.createElement('button');
domNoBtn.className = 'pivx-button-big';
domNoBtn.innerText = 'No';
domNoBtn.onclick = () => govVote(cProposal.Hash, 2);

const domYesBtn = document.createElement('button');
domYesBtn.className = 'pivx-button-big';
domYesBtn.innerText = 'Yes';
domYesBtn.onclick = () => govVote(cProposal.Hash, 1);

domVoteBtns.appendChild(domNoBtn);
domVoteBtns.appendChild(domYesBtn);
}
}

Expand Down
17 changes: 14 additions & 3 deletions scripts/masternode.js
Expand Up @@ -271,11 +271,22 @@ export default class Masternode {
}

/**
* @return {Promise<Array>} A list of currently active proposal
*
* @param {object} options
* @param {bool} options.fAllowFinished - Pass `true` to stop filtering proposals if finished
* @return {Promise<Array<object>} A list of currently active proposal
*/
static async getProposals() {
static async getProposals({ fAllowFinished = false } = {}) {
const url = `${cNode.url}/getbudgetinfo`;
return await (await fetch(url)).json();
let arrProposals = await (await fetch(url)).json();

// Apply optional filters
if (!fAllowFinished) {
arrProposals = arrProposals.filter(
(a) => a.RemainingPaymentCount > 0
);
}
return arrProposals;
}

/**
Expand Down
6 changes: 3 additions & 3 deletions scripts/network.js
Expand Up @@ -169,9 +169,9 @@ export async function sendTransaction(hex, msg = '') {
if (data.result && data.result.length === 64) {
console.log('Transaction sent! ' + data.result);
doms.domTxOutput.innerHTML =
'<h4 style="color:green; font-family:mono !important;">' +
data.result +
'</h4>';
'<h4 style="color:green; font-family:mono !important;">' +
data.result +
'</h4>';
doms.domSimpleTXs.style.display = 'none';
doms.domAddress1s.value = '';
doms.domValue1s.innerHTML = '';
Expand Down

0 comments on commit 1d2783c

Please sign in to comment.