Skip to content

Commit

Permalink
Merge branch 'main' into security-council-mgmt--base
Browse files Browse the repository at this point in the history
  • Loading branch information
DZGoldman committed Sep 6, 2023
2 parents 7edec60 + ff3e39c commit 5441036
Show file tree
Hide file tree
Showing 13 changed files with 2,437 additions and 1,425 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ dist/
report/
lcov.info
lcov.info.pruned
types/
types/
proposalState.json
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ This project contains smart contracts for Arbitrum token and governance. Please
* [Security Council Elections](./docs/security-council-mgmt.md)
* [Security Audit](./audits/trail_of_bits_governance_report_1_6_2023.pdf)
* [Gotchas](./docs/gotchas.md)
* [Proposal Monitor](./docs/proposalMonitor.md)

## Run Foundry unit tests

Expand Down
17 changes: 17 additions & 0 deletions docs/proposalMonitor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Proposal monitor UI
Monitors the core and treasury governors for new proposals, then tracks them through each of their stages.

The UI has two components:
1. Proposal Monitor CLI - this is a long running service that generates a json file of proposal events
2. propMonUI.html - a basic UI that displays the json file generated by the proposal monitor service

Note: You can run via ts-node installed globally, or compile with tsc and run with node.

To run both components together:
```
yarn
yarn build
ETH_RPC=<your ethereum rpc here> yarn propmon
```

Then navigate to `localhost:8080/propMonUi/propMonUi.html` in a browser. You will need to wait around 30 seconds whilst the propmon discovers all proposals and their stages. The propmon creates a proposalStage.json file that contains a json representation of the data you see on the web page.
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@
"coverage:htmlreport": "rm -rf ./report && genhtml ./lcov.info.pruned -o report --branch-coverage",
"coverage:open-report": "node -e 'const opn = process.platform === \"darwin\" ? \"open\" : \"xdg-open\"; require(\"child_process\").exec(`${opn} ./report/index.html`)'",
"coverage:report": "yarn coverage:filtered-report && yarn coverage:htmlreport && yarn coverage:open-report",
"coverage:refresh": "yarn coverage:filtered-report && yarn coverage:htmlreport"
"coverage:refresh": "yarn coverage:filtered-report && yarn coverage:htmlreport",
"propmon:ui": "cd src-ts && http-server . -p 8080",
"propmon:service": "ts-node ./src-ts/proposalMonitorCli.ts --jsonOutputLocation ./src-ts/propMonUi/proposalState.json --l1RpcUrl $ETH_RPC --govChainRpcUrl https://arb1.arbitrum.io/rpc --novaRpcUrl https://nova.arbitrum.io/rpc --coreGovernorAddress 0xf07DeD9dC292157749B6Fd268E37DF6EA38395B9 --treasuryGovernorAddress 0x789fC99093B09aD01C34DC7251D0C89ce743e5a4 --sevenTwelveCouncil 0x895c9fc6bcf06e553b54A9fE11D948D67a9B76FA --pollingIntervalSeconds 1",
"propmon": "yarn propmon:service & yarn propmon:ui"
},
"devDependencies": {
"@arbitrum/sdk": "^3.1.6",
Expand All @@ -63,6 +66,7 @@
"ethers": "^5.7.2",
"hardhat": "^2.12.6",
"hardhat-gas-reporter": "^1.0.9",
"http-server": "^14.1.1",
"solidity-coverage": "^0.8.2",
"ts-node": "^10.9.1",
"typechain": "^8.1.0",
Expand Down
249 changes: 249 additions & 0 deletions src-ts/propMonUi/propMonUi.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Proposal States</title>
<script src="https://d3js.org/d3.v6.min.js"></script>
<style>
body {
font-family: Arial, sans-serif;
background-color: #FFFFFF;
color: #1A2231;
margin: 20px;
}

h1,
h2,
h3 {
margin: 15px 0;
color: #1A2231;
}

table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
background-color: #FFFFFF;
color: #1A2231;
border: 1px solid #000000;
}

th,
td {
border: 1px solid #000000;
padding: 8px;
text-align: left;
}

th {
background-color: #1A2231;
color: #FFFFFF;
}

caption {
caption-side: top;
font-weight: bold;
padding: 10px;
color: #1A2231;
background-color: #f9f9f9;
}

tr:nth-child(even) {
background-color: #f9f9f9;
}

a {
color: #000000;
}

.governor-separator {
border-top: 4px solid #28A0F0;
margin: 20px 0;
}

.status-executed {
background-color: #d4f7d8;
}

.status-pending {
background-color: #fcf6c8;
}

.status-terminated {
background-color: #fde0c8;
}

.status-ready {
background-color: #28A0F0;
}

.flashing-div {
background-color: #28A0F0;
animation: flash 1s infinite;
}

@keyframes flash {

0%,
100% {
background-color: #28A0F0;
}

50% {
background-color: #FFFFFF;
}
}
</style>
</head>

<body>
<h1>Proposal States</h1>
<div id="data-container"></div>
<script>
function fetchData() {
fetch(`./proposalState.json?${Date.now()}`)
.then(response => response.json())
.then(data => {
function tTree(stage, x, y, func) {
// welcome to the jungle
func(stage, x, y);
for (let i = 0; i < stage.children.length; i++) {
if (i !== 0) {
// we got fun and games
y++;
}

const coords = tTree(stage.children[i], x + 1, y, func);
y = coords.y;
}

return { x, y };
};

function convertToTable(stage) {
let maxes = {
x: 0,
y: 0,
};

tTree(stage, 0, 0, (_, x, y) => {
if (x > maxes.x) maxes.x = x;
if (y > maxes.y) maxes.y = y;
});

const table = [];
for (let y = 0; y < maxes.y + 1; y++) {
table.push(new Array(maxes.x + 1).fill(""));
}

tTree(stage, 0, 0, (s, x, y) => {
table[y][x] = { name: s.name, status: s.status, identfier: s.identifier, explorerLink: s.explorerLink };
});

const headers = [];

tTree(stage, 0, 0, (s, x, y) => {
headers[x] = s.name;
});

return { headers, table };
};

const container = document.getElementById('data-container');
container.innerHTML = '';

const separator = document.createElement('div');
separator.className = 'governor-separator';
container.appendChild(separator);

Object.keys(data).forEach(governorAddress => {
const governorDiv = document.createElement('div');
container.appendChild(governorDiv);

let govName;
if (governorAddress.toLowerCase() === '0xf07DeD9dC292157749B6Fd268E37DF6EA38395B9'.toLowerCase()) {
govName = 'Core Governor';
} else if (governorAddress.toLowerCase() === '0x789fC99093B09aD01C34DC7251D0C89ce743e5a4'.toLowerCase()) {
govName = 'Treasury Governor';
} else {
govName = 'Unknown Governor';
}

governorDiv.innerHTML = `<h2>${govName}: ${governorAddress}</h2>`;


data[governorAddress].forEach((stage, i, arr) => {
// Convert the tree data to table data
const tableData = convertToTable(stage);

const proposalDiv = document.createElement('div');
governorDiv.appendChild(proposalDiv);
if (stage.proposalLink) {
proposalDiv.innerHTML = `<h3><a target="_blank" href="${stage.proposalLink}">Proposal ID: ${stage.identifier}</a></h3>`;
} else {
proposalDiv.innerHTML = `<h3>Proposal ID: ${stage.identifier}</h3>`;
}

// Identify unique stages for the current proposal
const table = document.createElement('table');
proposalDiv.appendChild(table);

let thead = document.createElement('thead');
table.appendChild(thead);
thead.innerHTML = `<tr>${tableData.headers.map(stage => `<th>${stage}</th>`).join('')}</tr>`;

let tbody = document.createElement('tbody');
table.appendChild(tbody);

tableData.table.forEach(row => {
let tr = document.createElement('tr');
tbody.appendChild(tr);

tr.innerHTML = Array.from(row).map(item => {
const status = item.status || '';
let statusClass;
switch (status) {
case 'EXECUTED':
statusClass = 'status-executed';
break;
case 'PENDING':
statusClass = 'status-pending';
break;
case 'TERMINATED':
statusClass = 'status-terminated';
break;
case 'READY':
statusClass = 'flashing-div';
break;
default:
statusClass = '';
}
if (status === "EXECUTED" && item.explorerLink) {
return `<td class="${statusClass}"><a target="_blank" href="${item.explorerLink}">${status}</a></td>`;
} else {
return `<td class="${statusClass}">${status}</td>`;
}
}).join('');
});
});

const separator = document.createElement('div');
separator.className = 'governor-separator';
container.appendChild(separator);

});
})
.catch(error => console.error('An error occurred:', error)
)
}

window.onload = function () {
fetchData(); // Fetch the data when the page loads
setInterval(fetchData, 5000); // Re-fetch the data every 5 seconds
};
</script>
</body>

</html>
1 change: 0 additions & 1 deletion src-ts/proposalCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,6 @@ export class RoundTripProposalCreator {
)
);
// this value gets ignored from xchain upgrades

l1Values.push(BigNumber.from(0));
} else {
l1Targets.push(upgradeExecutorTo);
Expand Down
Loading

0 comments on commit 5441036

Please sign in to comment.