Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

jury_test.gno

6.63 Kb · 175 lines
  1package jury
  2
  3import (
  4	"strings"
  5	"testing"
  6
  7	"gno.land/p/nym-moultest123/agents/commit/v0"
  8	"gno.land/p/nt/testutils/v0"
  9	"gno.land/p/nt/ufmt/v0"
 10	"gno.land/p/nt/uassert/v0"
 11)
 12
 13var (
 14	opener = testutils.TestAddress("opener")
 15	j1     = testutils.TestAddress("juror1")
 16	j2     = testutils.TestAddress("juror2")
 17	j3     = testutils.TestAddress("juror3")
 18	nobody = testutils.TestAddress("nobody")
 19)
 20
 21// Full happy path: 3 jurors commit blind, then reveal; majority upholds.
 22func TestCommitRevealMajority(cur realm, t *testing.T) {
 23	testing.SetRealm(testing.NewUserRealm(opener))
 24	id := OpenCase(cross(cur), "receipt#7", j1.String()+","+j2.String()+", "+j3.String())
 25	uassert.Equal(t, string(PhaseCommit), string(Get(id).Phase))
 26
 27	// Each juror commits a hidden verdict (hash computed off-chain).
 28	commitAs(cur, j1, id, true, "s1")
 29	commitAs(cur, j2, id, false, "s2")
 30	uassert.Equal(t, string(PhaseCommit), string(Get(id).Phase)) // not all in yet
 31	commitAs(cur, j3, id, true, "s3")
 32	uassert.Equal(t, string(PhaseReveal), string(Get(id).Phase)) // auto-advanced
 33
 34	revealAs(cur, j1, id, true, "s1")
 35	revealAs(cur, j2, id, false, "s2")
 36	revealAs(cur, j3, id, true, "s3")
 37
 38	c := Get(id)
 39	uassert.Equal(t, string(PhaseClosed), string(c.Phase))
 40	uassert.Equal(t, "upheld", c.Outcome) // 2 yes vs 1 no
 41	uassert.Equal(t, 2, c.Yes)
 42	uassert.Equal(t, 1, c.No)
 43
 44	uassert.True(t, len(Render("")) > 0)
 45	uassert.True(t, len(Render("1")) > 0)
 46}
 47
 48// A juror cannot change their vote at reveal time: the commitment binds it.
 49func TestRevealMustMatchCommitment(cur realm, t *testing.T) {
 50	testing.SetRealm(testing.NewUserRealm(opener))
 51	id := OpenCase(cross(cur), "claim#3", j1.String())
 52	commitAs(cur, j1, id, true, "salt")
 53
 54	testing.SetRealm(testing.NewUserRealm(j1))
 55	uassert.AbortsWithMessage(t, cur, "reveal does not match commitment", func(cur realm) {
 56		Reveal(cur, id, false, "salt") // flipped verdict
 57	})
 58}
 59
 60func TestOnlyPanelJurors(cur realm, t *testing.T) {
 61	testing.SetRealm(testing.NewUserRealm(opener))
 62	id := OpenCase(cross(cur), "x", j1.String())
 63	testing.SetRealm(testing.NewUserRealm(nobody))
 64	uassert.AbortsWithMessage(t, cur, "caller is not on this jury", func(cur realm) {
 65		Commit(cur, id, commit.Verdict(true, "s"))
 66	})
 67}
 68
 69func TestNoDuplicateJurors(cur realm, t *testing.T) {
 70	testing.SetRealm(testing.NewUserRealm(opener))
 71	uassert.AbortsWithMessage(t, cur, "duplicate juror: "+j1.String(), func(cur realm) {
 72		OpenCase(cur, "x", j1.String()+","+j1.String())
 73	})
 74}
 75
 76// helpers
 77
 78func commitAs(cur realm, juror address, id uint64, verdict bool, salt string) {
 79	testing.SetRealm(testing.NewUserRealm(juror))
 80	Commit(cross(cur), id, commit.Verdict(verdict, salt))
 81}
 82
 83func revealAs(cur realm, juror address, id uint64, verdict bool, salt string) {
 84	testing.SetRealm(testing.NewUserRealm(juror))
 85	Reveal(cross(cur), id, verdict, salt)
 86}
 87
 88// Commitments are public, so a juror who copies another's hash would reveal
 89// the same (verdict, salt) once the first reveal lands. The copy is refused.
 90func TestCopiedCommitmentIsRefused(cur realm, t *testing.T) {
 91	testing.SetRealm(testing.NewUserRealm(opener))
 92	id := OpenCase(cross(cur), "copy", j1.String()+","+j2.String())
 93	commitAs(cur, j1, id, true, "s1")
 94	copied := Get(id).Jurors[0].Commitment
 95	testing.SetRealm(testing.NewUserRealm(j2))
 96	uassert.AbortsContains(t, cur, "commitment already used", func() { Commit(cross(cur), id, copied) })
 97}
 98
 99// The panel arrives as one string, which is what a wallet can send.
100func TestOpenCaseRejectsAnInvalidJuror(cur realm, t *testing.T) {
101	testing.SetRealm(testing.NewUserRealm(opener))
102	uassert.AbortsContains(t, cur, "invalid juror address", func() {
103		OpenCase(cross(cur), "x", j1.String()+",notanaddress")
104	})
105	uassert.AbortsContains(t, cur, "invalid juror address", func() { OpenCase(cross(cur), "x", "") })
106}
107
108// The subject is wallet-controlled now that OpenCase is callable, so it is
109// escaped on both pages that show it.
110func TestSubjectIsEscaped(cur realm, t *testing.T) {
111	testing.SetRealm(testing.NewUserRealm(opener))
112	id := OpenCase(cross(cur), "x | [claim](https://evil.example)", j1.String())
113	idx, page := Render(""), Render(ufmt.Sprintf("%d", id))
114	uassert.False(t, strings.Contains(idx, "[claim](https://evil.example)"), idx)
115	uassert.False(t, strings.Contains(page, "[claim](https://evil.example)"), page)
116}
117
118// An even panel is allowed; a split vote closes as a tie rather than
119// stalling or picking a side.
120func TestEvenPanelSplitIsATie(cur realm, t *testing.T) {
121	testing.SetRealm(testing.NewUserRealm(opener))
122	id := OpenCase(cross(cur), "even", j1.String()+","+j2.String())
123	commitAs(cur, j1, id, true, "t1")
124	commitAs(cur, j2, id, false, "t2")
125	revealAs(cur, j1, id, true, "t1")
126	revealAs(cur, j2, id, false, "t2")
127	uassert.Equal(t, "tie", Outcome(id))
128}
129
130// OpenCase is callable by any wallet, so what it admits is bounded, and the
131// index renders a fixed window whatever the case count.
132func TestOpenCaseIsBounded(cur realm, t *testing.T) {
133	testing.SetRealm(testing.NewUserRealm(opener))
134	uassert.AbortsContains(t, cur, "subject too long", func() {
135		OpenCase(cross(cur), strings.Repeat("s", MaxSubject+1), j1.String())
136	})
137	panel := ""
138	for i := 0; i <= MaxJurors; i++ {
139		if i > 0 {
140			panel += ","
141		}
142		panel += testutils.TestAddress(ufmt.Sprintf("juror%d", i+10)).String()
143	}
144	uassert.AbortsContains(t, cur, "panel too large", func() { OpenCase(cross(cur), "big", panel) })
145
146	for len(cases) < IndexRecent+5 {
147		OpenCase(cross(cur), "filler", j1.String())
148	}
149	idx := Render("")
150	uassert.True(t, strings.Contains(idx, ufmt.Sprintf("The %d most recent of %d cases", IndexRecent, len(cases))), idx)
151	uassert.Equal(t, IndexRecent, strings.Count(idx, "](/r/nym-moultest123/agents/jury/v0:"))
152}
153
154// The case count is bounded, and a subject that sanitizes to nothing is empty.
155func TestCaseCountIsBoundedAndBlankSubjectRefused(cur realm, t *testing.T) {
156	testing.SetRealm(testing.NewUserRealm(opener))
157	uassert.AbortsContains(t, cur, "empty subject", func() { OpenCase(cross(cur), "​", j1.String()) })
158	before := len(cases)
159	for len(cases) < MaxCases {
160		cases = append(cases, &Case{ID: uint64(len(cases) + 1), Subject: "filler", Phase: PhaseCommit})
161	}
162	defer func() { cases = cases[:before] }()
163	uassert.AbortsContains(t, cur, "the jury is full", func() { OpenCase(cross(cur), "one more", j1.String()) })
164}
165
166// A commitment is a hash, and a juror cannot store an arbitrary blob as one.
167func TestCommitmentIsBounded(cur realm, t *testing.T) {
168	testing.SetRealm(testing.NewUserRealm(opener))
169	id := OpenCase(cross(cur), "sized", j1.String())
170	testing.SetRealm(testing.NewUserRealm(j1))
171	uassert.AbortsContains(t, cur, "commitment too long", func() {
172		Commit(cross(cur), id, strings.Repeat("a", MaxCommitment+1))
173	})
174	uassert.Equal(t, 64, len(commit.Verdict(true, "s")))
175}