create.gno
4.42 Kb · 128 lines
1package bolo
2
3import (
4 "chain"
5 "chain/banker"
6 "chain/runtime"
7
8 "gno.land/p/nt/avl/v0"
9 "gno.land/p/nt/seqid/v0"
10)
11
12// maxDeadlineDays bounds how far ahead a deadline may be, so no timer computed from
13// it can overflow (API §3.1).
14const maxDeadlineDays = 3650
15
16// CreateCampaign opens a campaign, which you organize, and returns its ID. Send the
17// creation deposit with it — Bolo's "Start a campaign" link attaches it — and it comes
18// back when the campaign completes. title and description are public, and the
19// description may hold links, never an image. category is one of Bolo's categories.
20// imageCID and imageAlt are a photo on IPFS and what it shows, or both empty.
21// beneficiary is the address the money goes to, which can never change. goal is in
22// ugnot, 0 for none. mode is "escrow" or "direct". deadline is a Unix time, 0 for
23// none.
24//
25// It opens a campaign for its caller, the organizer (API §3.1). The envelope is
26// exactly the creation deposit, which goes to the campaign's bond address: the realm
27// keeps nothing (INV-3).
28func CreateCampaign(cur realm, title, description, category, imageCID, imageAlt string, beneficiary address, goal int64, mode string, deadline int64) string {
29 assertCurrent(cur)
30 if !cur.Previous().IsUserCall() {
31 panic(ErrNotUserCall)
32 }
33 runtime.AssertOriginCall()
34 if envelopeUgnot() != params.CreationDeposit {
35 panic(ErrDepositMismatch)
36 }
37 if paused {
38 panic(ErrPaused)
39 }
40 if sunset {
41 panic(ErrSunset)
42 }
43 text := checkCampaignText(title, description, imageCID, imageAlt)
44 checkCampaignTerms(category, mode, goal, deadline)
45 checkBeneficiary(beneficiary, nextID+1)
46
47 id := nextID.Next()
48 c := &campaign{
49 id: id,
50 category: category,
51 organizer: cur.Previous().Address(),
52 beneficiary: beneficiary,
53 mode: mode,
54 goal: goal,
55 deadline: deadline,
56 createdAt: now(),
57 params: params,
58
59 text: text,
60 state: &campaignState{status: statusOpen, listed: true},
61
62 contributions: avl.NewTree(),
63 receipts: avl.NewTree(),
64 withdrawals: avl.NewTree(),
65 refunds: avl.NewTree(),
66 updates: avl.NewTree(),
67 }
68 key := id.Binary()
69 campaigns.Set(key, c)
70 stats.open++
71 derived.Set(escrowAddr(id).String(), true)
72 derived.Set(bondAddr(id).String(), true)
73 addActive(c)
74 byOrganizer.Set(c.organizer.String()+"/"+key, true)
75 byBeneficiary.Set(beneficiary.String()+"/"+key, true)
76
77 banker.NewBanker(banker.BankerTypeRealmSend, cur).SendCoins(realmAddr(), bondAddr(id), chain.NewCoins(chain.NewCoin("ugnot", c.params.CreationDeposit)))
78 chain.Emit(evCampaignCreated,
79 "id", idString(id),
80 "organizer", c.organizer.String(),
81 "beneficiary", beneficiary.String(),
82 "mode", mode,
83 "category", category,
84 "goal", itoa(goal),
85 "deadline", itoa(deadline),
86 )
87 return idString(id)
88}
89
90// checkCampaignText checks a campaign's text (API §2.3) and returns it normalized:
91// the title, the story, and the photo with its description.
92func checkCampaignText(title, description, imageCID, imageAlt string) *campaignText {
93 title = checkText(title, titleMin, titleMax, false, ErrTitle)
94 description = checkStory(description, 0, descriptionMax, ErrDescription)
95 imageCID, imageAlt = checkImage(imageCID, imageAlt)
96 return &campaignText{title: title, description: description, imageCID: imageCID, imageAlt: imageAlt}
97}
98
99// checkCampaignTerms refuses a category not listed, a mode that isn't one, a
100// negative goal, and a deadline neither 0 nor in the future, at most maxDeadlineDays
101// ahead (API §3.1).
102func checkCampaignTerms(category, mode string, goal, deadline int64) {
103 if !categories.Has(category) {
104 panic(ErrCategory)
105 }
106 if mode != modeDirect && mode != modeEscrow {
107 panic(ErrMode)
108 }
109 if goal < 0 {
110 panic(ErrGoal)
111 }
112 if deadline != 0 && (deadline <= now() || deadline > now()+maxDeadlineDays*secondsPerDay) {
113 panic(ErrDeadline)
114 }
115}
116
117// checkBeneficiary refuses a malformed address (C12) and every address a campaign
118// must never pay (API §1): the realm's, slashed, the storage-deposit address, the
119// registry and its storage-deposit address, every escrow and bond address handed
120// out — and those of id, the campaign about to be created, which paying itself
121// would trap every gift.
122func checkBeneficiary(b address, id seqid.ID) {
123 checkAddress(b, ErrBeneficiary)
124 if b == realmAddr() || b == slashedAddr() || b == depositAddr() || b == registryAddr() || b == registryDepositAddr() ||
125 derived.Has(b.String()) || b == escrowAddr(id) || b == bondAddr(id) {
126 panic(ErrBeneficiary)
127 }
128}