Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Contested Proposals filtering #84

Merged
merged 5 commits into from Feb 22, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 14 additions & 0 deletions index.template.html
Expand Up @@ -778,6 +778,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 @@ -111,6 +111,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
settingsExplorer:"Choose an explorer", //
settingsLanguage:"Choose an Language:", //
Expand Down
4 changes: 4 additions & 0 deletions locale/template/translation.js
Expand Up @@ -129,6 +129,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
settingsExplorer:"", //Choose an explorer
settingsLanguage:"", //Choose an Language:
Expand Down
4 changes: 4 additions & 0 deletions locale/uwu/translation.js
Expand Up @@ -111,6 +111,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
settingsExplorer:"Chowose an expwower", //Choose an explorer
settingsLanguage:"Chowose a Languwuage!", //Choose an Language:
Expand Down
103 changes: 67 additions & 36 deletions scripts/global.js
Expand Up @@ -73,6 +73,8 @@ 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 @@ -921,52 +923,81 @@ 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> ${
// 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
15 changes: 12 additions & 3 deletions scripts/masternode.js
Expand Up @@ -271,11 +271,20 @@ 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 }) {
JSKitty marked this conversation as resolved.
Show resolved Hide resolved
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