PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/p/moul/grants/v0

Package
Open in gnoweb ↗

Overview

Kind
Pure package
Name
v0
Namespace
moul / grants
Files
9 (README)(gnomod.toml)
Exported functions
n/a — not supported for pure packages by the node (vm/qfuncs)
Module
gno.land/p/moul/grants/v0
gno
0.9

Files (9)

  • README.mdmarkdown
  • gnomod.tomltoml
  • grants.gnogno
  • lifecycle.gnogno
  • program.gnogno
  • render.gnogno
  • grants_test.gnogno
  • program_test.gnogno
  • render_test.gnogno
  • grants_test.gnogno
    1package grants23import (4	"testing"56	"gno.land/p/nt/testutils/v0"7	"gno.land/p/nt/uassert/v0"8

    Functions

    not supported for pure packages by the node (vm/qfuncs)

    Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.

    "gno.land/p/nt/urequire/v0"
    9)
    10
    11var (
    12 alice = testutils.TestAddress("alice") // member
    13 bob = testutils.TestAddress("bob") // member
    14 carol = testutils.TestAddress("carol") // member
    15 dave = testutils.TestAddress("dave") // applicant, not a member
    16 mallory = testutils.TestAddress("mallory") // nobody
    17)
    18
    19// board3 is the fixture most tests want: three members, so a majority of the
    20// eligible set is two whenever the applicant is an outsider.
    21func board3() *Board { return New(alice, bob, carol) }
    22
    23// submitOne files a two-milestone grant from dave, the outsider applicant.
    24func submitOne(t *testing.T, b *Board) *Request {
    25 t.Helper()
    26 r, err := b.Submit(dave, "Port the thing", "with tests",
    27 []*Milestone{NewMilestone("design", 100), NewMilestone("ship", 400)}, 10)
    28 urequire.NoError(t, err)
    29 return r
    30}
    31
    32func TestNewSeedsFoundersAtHeightZero(t *testing.T) {
    33 b := board3()
    34 uassert.Equal(t, 3, b.MemberCount())
    35 for _, m := range []address{alice, bob, carol} {
    36 uassert.True(t, b.IsMember(m), m.String()+" should be a founder")
    37 uassert.Equal(t, int64(0), b.JoinedAt(m))
    38 }
    39 uassert.False(t, b.IsMember(dave))
    40 uassert.Equal(t, int64(-1), b.JoinedAt(dave))
    41
    42 // Members is address-ordered, and the order is what avl gives, not
    43 // insertion order. Only the stability matters, so compare two calls.
    44 first, second := b.Members(), b.Members()
    45 urequire.Equal(t, 3, len(first))
    46 for i := range first {
    47 uassert.Equal(t, first[i].String(), second[i].String())
    48 }
    49}
    50
    51func TestNewSkipsInvalidFounders(t *testing.T) {
    52 b := New(alice, address(""), alice)
    53 uassert.Equal(t, 1, b.MemberCount(), "empty address dropped, duplicate collapsed")
    54}
    55
    56func TestSubmitValidation(t *testing.T) {
    57 long := ""
    58 for i := 0; i < MaxTitle+1; i++ {
    59 long += "x"
    60 }
    61 body := ""
    62 for i := 0; i < MaxBody+1; i++ {
    63 body += "y"
    64 }
    65 many := make([]*Milestone, MaxMilestones+1)
    66 for i := range many {
    67 many[i] = NewMilestone("m", 1)
    68 }
    69
    70 cases := []struct {
    71 name string
    72 title string
    73 body string
    74 ms []*Milestone
    75 want error
    76 }{
    77 {"ok", "t", "b", []*Milestone{NewMilestone("m", 1)}, nil},
    78 {"empty title", "", "b", []*Milestone{NewMilestone("m", 1)}, ErrBadTitle},
    79 {"long title", long, "b", []*Milestone{NewMilestone("m", 1)}, ErrBadTitle},
    80 {"long body", "t", body, []*Milestone{NewMilestone("m", 1)}, ErrBadBody},
    81 {"no milestone", "t", "b", nil, ErrBadMilestones},
    82 {"too many milestones", "t", "b", many, ErrBadMilestones},
    83 {"zero amount", "t", "b", []*Milestone{NewMilestone("m", 0)}, ErrBadAmount},
    84 {"negative amount", "t", "b", []*Milestone{NewMilestone("m", -1)}, ErrBadAmount},
    85 {"untitled milestone", "t", "b", []*Milestone{NewMilestone("", 1)}, ErrBadTitle},
    86 }
    87 for _, tc := range cases {
    88 _, err := board3().Submit(dave, tc.title, tc.body, tc.ms, 1)
    89 if tc.want == nil {
    90 uassert.NoError(t, err, tc.name)
    91 continue
    92 }
    93 uassert.ErrorIs(t, err, tc.want, tc.name)
    94 }
    95
    96 _, err := board3().Submit(address(""), "t", "b", []*Milestone{NewMilestone("m", 1)}, 1)
    97 uassert.ErrorIs(t, err, ErrBadAddress, "invalid applicant")
    98}
    99
    100func TestRequestArithmetic(t *testing.T) {
    101 b := board3()
    102 r := submitOne(t, b)
    103 uassert.Equal(t, int64(500), r.Total())
    104 uassert.Equal(t, int64(0), r.Paid())
    105 uassert.Equal(t, int64(0), r.Outstanding(), "a pending grant commits nothing")
    106 uassert.Equal(t, 0, r.Next())
    107 uassert.Equal(t, "pending", r.Status.String())
    108 uassert.Equal(t, "grant", r.Kind.String())
    109 uassert.True(t, r.Excludes(dave), "the applicant never votes on their own grant")
    110 uassert.False(t, r.Excludes(alice))
    111 uassert.Equal(t, dave.String(), r.Payee().String(), "a self request pays its applicant")
    112 uassert.False(t, r.OnBehalf())
    113 uassert.Equal(t, 1, r.ID)
    114 uassert.Equal(t, r.ID, b.Get(1).ID)
    115 uassert.True(t, b.Get(42) == nil, "unknown id")
    116}
    117
    118func TestMajorityCountsOnlyEligibleMembers(t *testing.T) {
    119 b := board3()
    120
    121 outsider := submitOne(t, b)
    122 uassert.Equal(t, 3, b.Eligible(outsider))
    123 uassert.Equal(t, 2, b.Majority(outsider))
    124
    125 // A member applying for their own grant shrinks the eligible set, and
    126 // with it the bar: two of the remaining two must agree.
    127 insider, err := b.Submit(alice, "self", "", []*Milestone{NewMilestone("m", 1)}, 1)
    128 urequire.NoError(t, err)
    129 uassert.Equal(t, 2, b.Eligible(insider))
    130 uassert.Equal(t, 2, b.Majority(insider))
    131}
    132
    133func TestApprovalNeedsAMajority(t *testing.T) {
    134 b := board3()
    135 r := submitOne(t, b)
    136
    137 st, err := b.Vote(alice, r.ID, true, "good", 11)
    138 urequire.NoError(t, err)
    139 uassert.Equal(t, "pending", st.String(), "one of three is not a majority")
    140
    141 st, err = b.Vote(bob, r.ID, true, "agreed", 12)
    142 urequire.NoError(t, err)
    143 uassert.Equal(t, "approved", st.String())
    144 uassert.Equal(t, int64(12), r.DecidedAt)
    145 uassert.Equal(t, int64(500), r.Outstanding(), "approval commits the full ask")
    146 uassert.Equal(t, int64(500), b.Committed())
    147
    148 yes, no := r.Tally()
    149 uassert.Equal(t, 2, yes)
    150 uassert.Equal(t, 0, no)
    151 uassert.Equal(t, "good", r.BallotOf(alice).Reason)
    152 uassert.True(t, r.BallotOf(carol) == nil, "carol never voted")
    153}
    154
    155func TestRejectionNeedsAMajorityToo(t *testing.T) {
    156 b := board3()
    157 r := submitOne(t, b)
    158 urequire.NoError(t, errOf(b.Vote(alice, r.ID, false, "out of scope", 11)))
    159 st, err := b.Vote(bob, r.ID, false, "same", 12)
    160 urequire.NoError(t, err)
    161 uassert.Equal(t, "rejected", st.String())
    162 uassert.Equal(t, int64(0), b.Committed(), "a rejected grant commits nothing")
    163}
    164
    165func TestVoteRefusals(t *testing.T) {
    166 b := board3()
    167 r := submitOne(t, b)
    168 self, err := b.Submit(alice, "self", "", []*Milestone{NewMilestone("m", 1)}, 1)
    169 urequire.NoError(t, err)
    170
    171 _, err = b.Vote(alice, 999, true, "", 1)
    172 uassert.ErrorIs(t, err, ErrNoRequest)
    173
    174 _, err = b.Vote(mallory, r.ID, true, "", 1)
    175 uassert.ErrorIs(t, err, ErrNotMember)
    176
    177 _, err = b.Vote(alice, self.ID, true, "vote for myself", 1)
    178 uassert.ErrorIs(t, err, ErrConflict)
    179
    180 long := ""
    181 for i := 0; i < MaxReason+1; i++ {
    182 long += "z"
    183 }
    184 _, err = b.Vote(alice, r.ID, true, long, 1)
    185 uassert.ErrorIs(t, err, ErrBadReason)
    186
    187 urequire.NoError(t, errOf(b.Vote(alice, r.ID, true, "yes", 1)))
    188 _, err = b.Vote(alice, r.ID, false, "changed my mind", 2)
    189 uassert.ErrorIs(t, err, ErrAlreadyVoted, "a ballot is permanent")
    190
    191 urequire.NoError(t, errOf(b.Vote(bob, r.ID, true, "yes", 3)))
    192 _, err = b.Vote(carol, r.ID, true, "too late", 4)
    193 uassert.ErrorIs(t, err, ErrNotPending, "decided requests take no more ballots")
    194}
    195
    196func TestDecidesPreviewsWithoutCasting(t *testing.T) {
    197 b := board3()
    198 r := submitOne(t, b)
    199
    200 settles, to := b.Decides(r.ID, alice, true)
    201 uassert.False(t, settles, "the first of three does not settle it")
    202 uassert.Equal(t, 0, len(r.Votes), "previewing casts nothing")
    203
    204 urequire.NoError(t, errOf(b.Vote(alice, r.ID, true, "", 11)))
    205 settles, to = b.Decides(r.ID, bob, true)
    206 uassert.True(t, settles)
    207 uassert.Equal(t, "approved", to.String())
    208
    209 settles, _ = b.Decides(r.ID, bob, false)
    210 uassert.False(t, settles, "a no here only makes it one-all")
    211
    212 settles, _ = b.Decides(r.ID, mallory, true)
    213 uassert.False(t, settles, "a non-member decides nothing")
    214 settles, _ = b.Decides(r.ID, alice, true)
    215 uassert.False(t, settles, "a member who already voted decides nothing")
    216 settles, _ = b.Decides(999, bob, true)
    217 uassert.False(t, settles, "an unknown request decides nothing")
    218}
    219
    220func TestWithdraw(t *testing.T) {
    221 b := board3()
    222 r := submitOne(t, b)
    223
    224 uassert.ErrorIs(t, b.Withdraw(mallory, r.ID, 5), ErrNotApplicant)
    225 uassert.ErrorIs(t, b.Withdraw(dave, 999, 5), ErrNoRequest)
    226 urequire.NoError(t, b.Withdraw(dave, r.ID, 5))
    227 uassert.Equal(t, "withdrawn", r.Status.String())
    228 uassert.ErrorIs(t, b.Withdraw(dave, r.ID, 6), ErrNotPending)
    229}
    230
    231func TestProofRefusals(t *testing.T) {
    232 b := board3()
    233 r := submitOne(t, b)
    234 good := Proof{Kind: "url", Ref: "https://example.com/pr/1", Height: 20}
    235
    236 uassert.ErrorIs(t, b.SubmitProof(dave, r.ID, 0, good), ErrNotApproved, "not approved yet")
    237
    238 urequire.NoError(t, errOf(b.Vote(alice, r.ID, true, "", 11)))
    239 urequire.NoError(t, errOf(b.Vote(bob, r.ID, true, "", 12)))
    240
    241 uassert.ErrorIs(t, b.SubmitProof(mallory, r.ID, 0, good), ErrNotApplicant)
    242 uassert.ErrorIs(t, b.SubmitProof(dave, r.ID, 7, good), ErrNoMilestone)
    243 uassert.ErrorIs(t, b.SubmitProof(dave, r.ID, 1, good), ErrOutOfOrder, "milestone 1 before 0")
    244
    245 bad := []Proof{
    246 {Kind: "screenshot", Ref: "x"},
    247 {Kind: "url", Ref: ""},
    248 {Kind: "text", Ref: "ok", Note: longString(MaxNote + 1)},
    249 {Kind: "url", Ref: longString(MaxRef + 1)},
    250 }
    251 for _, p := range bad {
    252 uassert.ErrorIs(t, b.SubmitProof(dave, r.ID, 0, p), ErrBadProof, p.Kind)
    253 }
    254
    255 urequire.NoError(t, b.SubmitProof(dave, r.ID, 0, good))
    256 uassert.ErrorIs(t, b.SubmitProof(dave, r.ID, 0, good), ErrProofPending, "one at a time")
    257}
    258
    259func TestReleaseRunsThroughToCompletion(t *testing.T) {
    260 b := board3()
    261 r := submitOne(t, b)
    262 urequire.NoError(t, errOf(b.Vote(alice, r.ID, true, "", 11)))
    263 urequire.NoError(t, errOf(b.Vote(bob, r.ID, true, "", 12)))
    264
    265 // Milestone 0.
    266 urequire.NoError(t, b.SubmitProof(dave, r.ID, 0, Proof{Kind: "url", Ref: "https://x/1", Height: 20}))
    267 out, err := b.Review(alice, r.ID, 0, true, "looks done", 21)
    268 urequire.NoError(t, err)
    269 uassert.Equal(t, "under review", out.String(), "one reviewer is not a majority")
    270 uassert.False(t, r.Milestones[0].Released)
    271
    272 out, err = b.Review(carol, r.ID, 0, true, "confirmed", 22)
    273 urequire.NoError(t, err)
    274 uassert.Equal(t, "accepted", out.String())
    275 uassert.True(t, r.Milestones[0].Released)
    276 uassert.Equal(t, int64(22), r.Milestones[0].ReleasedAt)
    277 uassert.Equal(t, int64(100), r.Paid())
    278 uassert.Equal(t, int64(400), r.Outstanding())
    279 uassert.Equal(t, "approved", r.Status.String(), "one tranche left")
    280 uassert.Equal(t, 1, r.Next())
    281
    282 // Milestone 1 completes the grant.
    283 urequire.NoError(t, b.SubmitProof(dave, r.ID, 1, Proof{Kind: "hash", Ref: "deadbeef", Height: 30}))
    284 urequire.NoError(t, errOf2(b.Review(alice, r.ID, 1, true, "", 31)))
    285 out, err = b.Review(bob, r.ID, 1, true, "", 32)
    286 urequire.NoError(t, err)
    287 uassert.Equal(t, "accepted", out.String())
    288 uassert.Equal(t, "completed", r.Status.String())
    289 uassert.Equal(t, -1, r.Next())
    290 uassert.Equal(t, int64(500), r.Paid())
    291 uassert.Equal(t, int64(0), r.Outstanding(), "a completed grant commits nothing")
    292 uassert.Equal(t, int64(500), b.Disbursed())
    293 uassert.Equal(t, int64(0), b.Committed())
    294}
    295
    296func TestRefusedProofKeepsTheGrantAliveAndOnTheRecord(t *testing.T) {
    297 b := board3()
    298 r := submitOne(t, b)
    299 urequire.NoError(t, errOf(b.Vote(alice, r.ID, true, "", 11)))
    300 urequire.NoError(t, errOf(b.Vote(bob, r.ID, true, "", 12)))
    301
    302 urequire.NoError(t, b.SubmitProof(dave, r.ID, 0, Proof{Kind: "url", Ref: "https://x/draft", Height: 20}))
    303 urequire.NoError(t, errOf2(b.Review(alice, r.ID, 0, false, "this is a stub", 21)))
    304 out, err := b.Review(bob, r.ID, 0, false, "agreed, no tests", 22)
    305 urequire.NoError(t, err)
    306 uassert.Equal(t, "refused", out.String())
    307 uassert.False(t, r.Milestones[0].Released)
    308 uassert.Equal(t, "approved", r.Status.String(), "a refused proof does not kill the grant")
    309
    310 // The refused attempt stays readable, reasons and all.
    311 m := r.Milestones[0]
    312 urequire.Equal(t, 1, len(m.Attempts))
    313 uassert.Equal(t, "this is a stub", m.Attempts[0].Reviews[0].Reason)
    314 uassert.True(t, m.Current() == nil, "the attempt is closed")
    315
    316 // And a second attempt can carry.
    317 urequire.NoError(t, b.SubmitProof(dave, r.ID, 0, Proof{Kind: "url", Ref: "https://x/final", Height: 30}))
    318 urequire.NoError(t, errOf2(b.Review(alice, r.ID, 0, true, "now it is done", 31)))
    319 urequire.NoError(t, errOf2(b.Review(bob, r.ID, 0, true, "", 32)))
    320 uassert.True(t, r.Milestones[0].Released)
    321 uassert.Equal(t, 2, len(m.Attempts), "both attempts are kept")
    322}
    323
    324func TestReviewRefusals(t *testing.T) {
    325 b := board3()
    326 r := submitOne(t, b)
    327 urequire.NoError(t, errOf(b.Vote(alice, r.ID, true, "", 11)))
    328 urequire.NoError(t, errOf(b.Vote(bob, r.ID, true, "", 12)))
    329
    330 uassert.ErrorIs(t, errOf2(b.Review(alice, r.ID, 0, true, "", 20)), ErrNoProof)
    331 urequire.NoError(t, b.SubmitProof(dave, r.ID, 0, Proof{Kind: "text", Ref: "done", Height: 20}))
    332
    333 uassert.ErrorIs(t, errOf2(b.Review(mallory, r.ID, 0, true, "", 21)), ErrNotMember)
    334 uassert.ErrorIs(t, errOf2(b.Review(alice, 999, 0, true, "", 21)), ErrNoRequest)
    335 uassert.ErrorIs(t, errOf2(b.Review(alice, r.ID, 9, true, "", 21)), ErrNoMilestone)
    336 urequire.NoError(t, errOf2(b.Review(alice, r.ID, 0, true, "", 21)))
    337 uassert.ErrorIs(t, errOf2(b.Review(alice, r.ID, 0, false, "", 22)), ErrAlreadyVoted)
    338}
    339
    340func TestApplicantMayNotReviewTheirOwnProof(t *testing.T) {
    341 b := New(alice, bob, dave) // dave is a member AND the applicant here
    342 r, err := b.Submit(dave, "self funded", "", []*Milestone{NewMilestone("m", 10)}, 1)
    343 urequire.NoError(t, err)
    344 urequire.NoError(t, errOf(b.Vote(alice, r.ID, true, "", 2)))
    345 urequire.NoError(t, errOf(b.Vote(bob, r.ID, true, "", 3)))
    346 urequire.NoError(t, b.SubmitProof(dave, r.ID, 0, Proof{Kind: "text", Ref: "done", Height: 4}))
    347 uassert.ErrorIs(t, errOf2(b.Review(dave, r.ID, 0, true, "ship it", 5)), ErrConflict)
    348}
    349
    350func TestMembershipRequestAddsAndRemoves(t *testing.T) {
    351 b := board3()
    352
    353 add, err := b.SubmitMemberChange(alice, dave, true, "he reviews everything anyway", 5)
    354 urequire.NoError(t, err)
    355 uassert.Equal(t, "membership", add.Kind.String())
    356 uassert.Equal(t, "Add "+dave.String(), add.Title)
    357 uassert.Equal(t, int64(0), add.Total(), "a membership request asks for no money")
    358
    359 urequire.NoError(t, errOf(b.Vote(alice, add.ID, true, "", 6)))
    360 st, err := b.Vote(bob, add.ID, true, "", 7)
    361 urequire.NoError(t, err)
    362 uassert.Equal(t, "completed", st.String(), "membership executes on approval")
    363 uassert.True(t, b.IsMember(dave))
    364 uassert.Equal(t, int64(7), b.JoinedAt(dave), "joined at the deciding height")
    365
    366 // Now four members: a majority excluding the subject is still two.
    367 rm, err := b.SubmitMemberChange(alice, carol, false, "inactive", 8)
    368 urequire.NoError(t, err)
    369 uassert.Equal(t, 3, b.Eligible(rm))
    370 uassert.ErrorIs(t, errOf(b.Vote(carol, rm.ID, false, "no", 9)), ErrConflict, "the subject may not vote")
    371 urequire.NoError(t, errOf(b.Vote(alice, rm.ID, true, "", 10)))
    372 urequire.NoError(t, errOf(b.Vote(bob, rm.ID, true, "", 11)))
    373 uassert.False(t, b.IsMember(carol))
    374 uassert.Equal(t, 3, b.MemberCount())
    375}
    376
    377func TestMemberChangeRefusals(t *testing.T) {
    378 b := board3()
    379
    380 _, err := b.SubmitMemberChange(mallory, dave, true, "", 1)
    381 uassert.ErrorIs(t, err, ErrNotMember, "only members touch the board's composition")
    382 _, err = b.SubmitMemberChange(alice, alice, true, "", 1)
    383 uassert.ErrorIs(t, err, ErrIsMember)
    384 _, err = b.SubmitMemberChange(alice, dave, false, "", 1)
    385 uassert.ErrorIs(t, err, ErrNotMember, "cannot remove a non-member")
    386 _, err = b.SubmitMemberChange(alice, address(""), true, "", 1)
    387 uassert.ErrorIs(t, err, ErrBadAddress)
    388
    389 solo := New(alice)
    390 _, err = solo.SubmitMemberChange(alice, alice, false, "", 1)
    391 uassert.ErrorIs(t, err, ErrLastMember, "the board cannot empty itself")
    392}
    393
    394// Standing is the whole reason a removed member's yes does not keep carrying
    395// decisions they are no longer part of.
    396func TestStandingDropsBallotsFromFormerMembers(t *testing.T) {
    397 b := board3()
    398 r := submitOne(t, b)
    399 urequire.NoError(t, errOf(b.Vote(carol, r.ID, true, "I like it", 11)))
    400
    401 rm, err := b.SubmitMemberChange(alice, carol, false, "left the project", 12)
    402 urequire.NoError(t, err)
    403 urequire.NoError(t, errOf(b.Vote(alice, rm.ID, true, "", 13)))
    404 urequire.NoError(t, errOf(b.Vote(bob, rm.ID, true, "", 14)))
    405 urequire.Equal(t, 2, b.MemberCount())
    406
    407 yes, _ := r.Tally()
    408 uassert.Equal(t, 1, yes, "the ballot stays on the record")
    409 yes, _ = b.Standing(r)
    410 uassert.Equal(t, 0, yes, "but it no longer counts")
    411
    412 // With two members and an outsider applicant the bar is two, so alice
    413 // alone still cannot carry it.
    414 uassert.Equal(t, 2, b.Majority(r))
    415 st, err := b.Vote(alice, r.ID, true, "", 15)
    416 urequire.NoError(t, err)
    417 uassert.Equal(t, "pending", st.String())
    418}
    419
    420func TestListAndSizeStayInIDOrder(t *testing.T) {
    421 b := board3()
    422 for i := 0; i < 12; i++ {
    423 _, err := b.Submit(dave, "r", "", []*Milestone{NewMilestone("m", 1)}, int64(i))
    424 urequire.NoError(t, err)
    425 }
    426 all := b.List()
    427 urequire.Equal(t, 12, len(all))
    428 uassert.Equal(t, 12, b.Size())
    429 for i, r := range all {
    430 uassert.Equal(t, i+1, r.ID, "ids come back in numeric order past nine")
    431 }
    432}
    433
    434func longString(n int) string {
    435 s := ""
    436 for i := 0; i < n; i++ {
    437 s += "x"
    438 }
    439 return s
    440}
    441
    442// errOf and errOf2 drop the leading return value of Vote and Review so a call
    443// reads as a single assertion.
    444func errOf(_ Status, err error) error { return err }
    445func errOf2(_ Outcome, err error) error { return err }
    446
    447func TestFilingForSomeoneElseNeedsAReason(t *testing.T) {
    448 b := board3()
    449 ms := func() []*Milestone { return []*Milestone{NewMilestone("m", 100)} }
    450
    451 _, err := b.SubmitFor(dave, mallory, "", "t", "", ms(), 10)
    452 uassert.ErrorIs(t, err, ErrNeedReason, "a request for someone else has to say why")
    453
    454 // The same address is just a self request, and needs nothing extra.
    455 self, err := b.SubmitFor(dave, dave, "", "t", "", ms(), 10)
    456 urequire.NoError(t, err)
    457 uassert.False(t, self.OnBehalf())
    458
    459 _, err = b.SubmitFor(dave, address("not-an-address"), "because", "t", "", ms(), 10)
    460 uassert.ErrorIs(t, err, ErrBadAddress)
    461
    462 long := ""
    463 for i := 0; i <= MaxReason; i++ {
    464 long += "x"
    465 }
    466 _, err = b.SubmitFor(dave, mallory, long, "t", "", ms(), 10)
    467 uassert.ErrorIs(t, err, ErrBadReason)
    468
    469 r, err := b.SubmitFor(dave, mallory, "their account is empty", "t", "", ms(), 10)
    470 urequire.NoError(t, err)
    471 uassert.True(t, r.OnBehalf())
    472 uassert.Equal(t, mallory.String(), r.Payee().String())
    473 uassert.Equal(t, dave.String(), r.Applicant.String())
    474}
    475
    476// A member who is paid by a request someone else filed is the same conflict of
    477// interest as a member who filed it, with one address in between.
    478func TestBothPartiesAreBarredFromVotingOnAGrant(t *testing.T) {
    479 b := board3()
    480 r, err := b.SubmitFor(alice, bob, "bob asked me to file it", "t", "",
    481 []*Milestone{NewMilestone("m", 100)}, 10)
    482 urequire.NoError(t, err)
    483
    484 uassert.True(t, r.Excludes(alice), "the applicant")
    485 uassert.True(t, r.Excludes(bob), "the beneficiary")
    486 uassert.False(t, r.Excludes(carol))
    487 uassert.Equal(t, 1, b.Eligible(r), "three members, two of them party to it")
    488 uassert.Equal(t, 1, b.Majority(r))
    489
    490 _, err = b.Vote(alice, r.ID, true, "", 11)
    491 uassert.ErrorIs(t, err, ErrConflict, "the applicant may not vote")
    492 _, err = b.Vote(bob, r.ID, true, "", 11)
    493 uassert.ErrorIs(t, err, ErrConflict, "the payee may not vote")
    494
    495 st, err := b.Vote(carol, r.ID, true, "fine", 12)
    496 urequire.NoError(t, err)
    497 uassert.Equal(t, "approved", st.String(), "the one eligible member carries it")
    498
    499 // And the same exclusion holds on the proof review, which is where the
    500 // money actually moves.
    501 urequire.NoError(t, b.SubmitProof(alice, r.ID, 0, Proof{Kind: "text", Ref: "done", Height: 13}))
    502 _, err = b.Review(bob, r.ID, 0, true, "", 14)
    503 uassert.ErrorIs(t, err, ErrConflict, "the payee may not review their own proof")
    504}
    505
    506// The payee of a request filed for an empty account cannot transact until the
    507// first tranche lands, so the filer has to be able to prove the work; once the
    508// payee can transact, they have to be able to prove their own.
    509func TestEitherPartyMaySubmitAProof(t *testing.T) {
    510 b := board3()
    511 r, err := b.SubmitFor(dave, mallory, "empty account", "t", "",
    512 []*Milestone{NewMilestone("one", 100), NewMilestone("two", 100)}, 10)
    513 urequire.NoError(t, err)
    514 b.Vote(alice, r.ID, true, "", 11)
    515 b.Vote(bob, r.ID, true, "", 12)
    516 urequire.Equal(t, "approved", r.Status.String())
    517
    518 uassert.ErrorIs(t, b.SubmitProof(carol, r.ID, 0, Proof{Kind: "text", Ref: "x", Height: 13}),
    519 ErrNotApplicant, "a third party may not")
    520
    521 urequire.NoError(t, b.SubmitProof(dave, r.ID, 0, Proof{Kind: "text", Ref: "filer", Height: 13}))
    522 b.Review(alice, r.ID, 0, true, "", 14)
    523 b.Review(bob, r.ID, 0, true, "", 15)
    524 urequire.True(t, r.Milestones[0].Released)
    525
    526 urequire.NoError(t, b.SubmitProof(mallory, r.ID, 1, Proof{Kind: "text", Ref: "payee", Height: 16}))
    527}
    528