finalize.gno
4.49 Kb · 123 lines
1package bolo
2
3import (
4 "chain"
5 "chain/banker"
6)
7
8// Finalize completes closed campaign id: anyone can, once the campaign's page offers
9// it. It returns the organizer's deposit, and sends the beneficiary the coins someone
10// sent to the escrow outside Bolo. What donors can still reclaim stays theirs.
11//
12// It settles a closed campaign, and forwards what no donor is owed (API §3.4): anyone
13// may call, with no identity. At its first call it settles the campaign;
14// every call forwards the coins sent to the escrow outside Bolo to the beneficiary,
15// and everything at the bond address but a bond still on hold to the organizer — the
16// deposit, and the bond once its hold has ended. It never moves the tracked balance:
17// that stays the donors' (INV-16).
18func Finalize(cur realm, id string) {
19 assertCurrent(cur)
20 c := campaignOf(id)
21 recordDeadline(cur, c)
22 f := bookFinalize(c)
23 if f.toBeneficiary > 0 {
24 banker.NewBanker(banker.BankerTypeRealmSend, escrowSub(cur, c)).SendCoins(escrowAddr(c.id), c.beneficiary, chain.NewCoins(chain.NewCoin("ugnot", f.toBeneficiary)))
25 }
26 if f.toOrganizer > 0 {
27 banker.NewBanker(banker.BankerTypeRealmSend, bondSub(cur, c)).SendCoins(bondAddr(c.id), c.organizer, chain.NewCoins(chain.NewCoin("ugnot", f.toOrganizer)))
28 }
29 if f.settled {
30 chain.Emit(evSettled, "id", idString(c.id), "toOrganizer", itoa(f.toOrganizer), "toBeneficiary", itoa(f.toBeneficiary))
31 }
32 if f.released > 0 {
33 chain.Emit(evBondReleased, "id", idString(c.id), "amount", itoa(f.released), "to", c.organizer.String())
34 }
35}
36
37// settlement is what one Finalize call moves, and announces.
38type settlement struct {
39 toBeneficiary int64 // the untracked coins at the escrow
40 toOrganizer int64 // everything at the bond address but a bond still on hold
41 released int64 // the bond this call releases, or 0
42 settled bool // this call settles the campaign
43}
44
45// bookFinalize checks a Finalize call on c and enters it in the books, before any coin
46// moves (API §3.4): the first settles c, if checkFinalizable allows; every one works
47// out what to forward, and releases a bond whose hold has ended.
48func bookFinalize(c *campaign) settlement {
49 s := c.state
50 settled := false
51 if s.status != statusSettled {
52 checkFinalizable(c)
53 s.status = statusSettled
54 stats.settled++
55 settled = true
56 }
57 f := pending(c)
58 f.settled = settled
59 if f.released > 0 {
60 s.bondReleased = true
61 }
62 return f
63}
64
65// checkFinalizable refuses c's first settlement (API §3.4), with ErrNotFinalizable,
66// unless it is closed and its FinalizableAt has come: at once in direct mode or with
67// no tracked balance, else once its exit window opens. The view, CanFinalize and this
68// share one rule, finalizableAt.
69func checkFinalizable(c *campaign) {
70 s := c.state
71 if s.status != statusClosed {
72 panic(ErrNotFinalizable)
73 }
74 if now() < finalizableAt(c, s.closedAt, trackedBalance(c)) {
75 panic(ErrNotFinalizable)
76 }
77}
78
79// pending works out what a Finalize call would move from c now, without writing: the
80// escrow's coins above its books, to the beneficiary; the bond address's coins but a
81// bond still on hold — not slashed, not released, its hold not ended — to the
82// organizer, the bond itself included once its hold has ended. Books above the bank,
83// at either address, are ErrBooksMismatch (INV-1, INV-7).
84func pending(c *campaign) settlement {
85 s := c.state
86 var f settlement
87 f.toBeneficiary = banker.NewReadonlyBanker().GetCoin(escrowAddr(c.id), "ugnot") - trackedBalance(c)
88 held := int64(0)
89 if s.bond > 0 && !s.bondSlashed && !s.bondReleased {
90 if now() >= s.closedAt+c.params.BondHold {
91 f.released = s.bond
92 } else {
93 held = s.bond
94 }
95 }
96 f.toOrganizer = banker.NewReadonlyBanker().GetCoin(bondAddr(c.id), "ugnot") - held
97 if f.toBeneficiary < 0 || f.toOrganizer < 0 {
98 panic(ErrBooksMismatch)
99 }
100 return f
101}
102
103// CanFinalize says what Finalize(id) would do now (API §4), a passed deadline counted
104// as recorded: true with "settle" for the first settlement, or "forward" on a settled
105// campaign with something to move; false with "open", "not_yet" while a tracked balance
106// waits for its exit window, or "settled" with nothing to move.
107func CanFinalize(id string) (bool, string) {
108 c := campaignOf(id)
109 status, _, closedAt := effective(c)
110 switch status {
111 case statusOpen:
112 return false, statusOpen
113 case statusClosed:
114 if now() < finalizableAt(c, closedAt, trackedBalance(c)) {
115 return false, "not_yet"
116 }
117 return true, "settle"
118 }
119 if f := pending(c); f.toBeneficiary > 0 || f.toOrganizer > 0 {
120 return true, "forward"
121 }
122 return false, statusSettled
123}