views.gno
5.13 Kb · 188 lines
1package bolo
2
3import "chain/banker"
4
5// CampaignView is a campaign as clients see it (API §2.4): a copy, holding nothing
6// of the realm's state, its derived fields computed at now without writing.
7type CampaignView struct {
8 ID, Title, Description, Category, ImageCID, ImageAlt string
9 ImageHidden bool
10 Organizer, Beneficiary address
11 Mode, Status, CloseReason string
12 Listed bool
13 Notice string
14
15 Goal int64
16 Raised int64
17 Refunded int64
18 Withdrawn int64
19 NetRaised int64
20 Balance int64
21 Untracked int64
22
23 DonorCount, DonationCount, RefundCount, UpdateCount int
24
25 Deposit int64
26 Bond int64
27 BondSlashed bool
28 BondReleasableAt int64
29
30 CreatedAt, Deadline, ClosedAt, LastDonationAt, LastUpdateAt int64
31
32 ClosableByAnyoneAt int64
33 ExitOpensAt int64
34 FinalizableAt int64
35
36 EverWithdrawn bool
37 ExitOpen bool
38
39 EscrowAddress address
40 BondAddress address
41 Params Params
42}
43
44// GetCampaign returns campaign id's view (API §4). An unknown or non-canonical ID is
45// ErrNotFound (C9).
46func GetCampaign(id string) CampaignView {
47 return campaignView(campaignOf(id))
48}
49
50// campaignOf returns the campaign id names, or panics with ErrNotFound (C9).
51func campaignOf(id string) *campaign {
52 c, ok := lookup(id)
53 if !ok {
54 panic(ErrNotFound)
55 }
56 return c
57}
58
59// lookup returns the campaign id names, if id is canonical and the campaign exists.
60func lookup(id string) (*campaign, bool) {
61 n, ok := parseID(id)
62 if !ok {
63 return nil, false
64 }
65 v := campaigns.Get(n.Binary())
66 if v == nil {
67 return nil, false
68 }
69 return v.(*campaign), true
70}
71
72// effective returns c's status, close reason and close time at now, counting a
73// passed deadline as recorded (API §2.4, §3.1), without writing.
74func effective(c *campaign) (status, reason string, closedAt int64) {
75 s := c.state
76 if s.status == statusOpen && c.deadline != 0 && now() >= c.deadline {
77 return statusClosed, reasonDeadline, c.deadline
78 }
79 return s.status, s.closeReason, s.closedAt
80}
81
82// trackedBalance is what the books say the escrow holds (API §2.4): 0 in direct
83// mode.
84func trackedBalance(c *campaign) int64 {
85 if c.mode != modeEscrow {
86 return 0
87 }
88 s := c.state
89 return s.raised - s.withdrawn - s.refunded
90}
91
92// campaignView builds c's view at now (API §2.4).
93func campaignView(c *campaign) CampaignView {
94 s, p := c.state, c.params
95 status, reason, closedAt := effective(c)
96 balance := trackedBalance(c)
97 untracked := banker.NewReadonlyBanker().GetCoin(escrowAddr(c.id), "ugnot") - balance
98 if untracked < 0 {
99 untracked = 0
100 }
101 v := CampaignView{
102 ID: idString(c.id),
103 Title: c.text.title,
104 Description: c.text.description,
105 Category: c.category,
106 ImageCID: c.text.imageCID,
107 ImageAlt: c.text.imageAlt,
108 ImageHidden: s.imageHidden,
109 Organizer: c.organizer,
110 Beneficiary: c.beneficiary,
111 Mode: c.mode,
112 Status: status,
113 CloseReason: reason,
114 Listed: s.listed,
115 Notice: s.notice,
116
117 Goal: c.goal,
118 Raised: s.raised,
119 Refunded: s.refunded,
120 Withdrawn: s.withdrawn,
121 NetRaised: s.raised - s.refunded,
122 Balance: balance,
123 Untracked: untracked,
124
125 DonorCount: s.donorCount,
126 DonationCount: s.donationCount,
127 RefundCount: s.refundCount,
128 UpdateCount: s.updateCount,
129
130 Deposit: p.CreationDeposit,
131 Bond: s.bond,
132 BondSlashed: s.bondSlashed,
133
134 CreatedAt: c.createdAt,
135 Deadline: c.deadline,
136 ClosedAt: closedAt,
137 LastDonationAt: s.lastDonationAt,
138 LastUpdateAt: s.lastUpdateAt,
139
140 EverWithdrawn: s.everWithdrawn,
141 EscrowAddress: escrowAddr(c.id),
142 BondAddress: bondAddr(c.id),
143 Params: p,
144 }
145 switch status {
146 case statusOpen:
147 v.ClosableByAnyoneAt = dormantAt(c)
148 if c.deadline != 0 {
149 v.FinalizableAt = finalizableAt(c, c.deadline, balance)
150 }
151 case statusClosed:
152 v.BondReleasableAt = closedAt + p.BondHold
153 v.FinalizableAt = finalizableAt(c, closedAt, balance)
154 case statusSettled:
155 v.BondReleasableAt = closedAt + p.BondHold
156 }
157 if c.mode == modeEscrow && status != statusOpen {
158 v.ExitOpensAt = closedAt + p.ExitDelay
159 v.ExitOpen = s.everWithdrawn && balance > 0 && now() >= v.ExitOpensAt
160 }
161 return v
162}
163
164// dormantAt is when anyone may close c for dormancy: its last activity plus its
165// snapshot's dormancy period (API §3.1).
166func dormantAt(c *campaign) int64 { return lastActivity(c) + c.params.DormancyPeriod }
167
168// lastActivity is the latest of c's creation, its last gift and its last update:
169// dormancy counts from it (API §3.1).
170func lastActivity(c *campaign) int64 {
171 last := c.createdAt
172 if c.state.lastDonationAt > last {
173 last = c.state.lastDonationAt
174 }
175 if c.state.lastUpdateAt > last {
176 last = c.state.lastUpdateAt
177 }
178 return last
179}
180
181// finalizableAt is when Finalize first succeeds for c closed at closedAt (API §2.4,
182// §3.4): at once in direct mode or with nothing tracked, else once the exit opens.
183func finalizableAt(c *campaign, closedAt, balance int64) int64 {
184 if c.mode == modeDirect || balance == 0 {
185 return closedAt
186 }
187 return closedAt + c.params.ExitDelay
188}