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.

donate.gno

4.46 Kb · 131 lines
  1package bolo
  2
  3import (
  4	"chain"
  5	"chain/banker"
  6	"chain/runtime"
  7	"math"
  8	"strconv"
  9
 10	"gno.land/p/nt/seqid/v0"
 11)
 12
 13// Donate gives to campaign id, and returns your receipt's ID. Send the gift and any
 14// tip together: tip is the part of what you send that goes to Bolo, in ugnot — 0 for
 15// none. message is optional, and public. anonymous, if true, keeps your address off
 16// Bolo's pages; the chain still shows it. In a direct campaign the gift goes straight
 17// to the beneficiary; in escrow, to the campaign's escrow, until the beneficiary
 18// withdraws it.
 19//
 20// Any user, by a direct transaction, the donor being the caller (API §3.2). The
 21// envelope is the gift plus the tip. The gift goes to the beneficiary, or to the
 22// campaign's escrow, and the tip to the tip recipient: the realm keeps nothing
 23// (INV-3), and coins found there are never credited (C4).
 24func Donate(cur realm, id string, tip int64, message string, anonymous bool) string {
 25	assertCurrent(cur)
 26	if !cur.Previous().IsUserCall() {
 27		panic(ErrNotUserCall)
 28	}
 29	runtime.AssertOriginCall()
 30	c := campaignOf(id)
 31	recordDeadline(cur, c)
 32	amount := checkGift(c, envelopeUgnot(), tip)
 33	message = checkText(message, 0, messageMax, false, ErrMessage)
 34	checkRoom(c, amount)
 35
 36	donor := cur.Previous().Address()
 37	s := c.state
 38	s.raised += amount
 39	s.donationCount++
 40	s.lastDonationAt = now()
 41	key := donor.String()
 42	var k *contribution
 43	if v := c.contributions.Get(key); v != nil {
 44		k = v.(*contribution)
 45	} else {
 46		k = &contribution{}
 47		c.contributions.Set(key, k)
 48		s.donorCount++
 49	}
 50	k.amount += amount
 51	if !anonymous && !k.public {
 52		k.public = true
 53		byDonor.Set(key+"/"+c.id.Binary(), true)
 54	}
 55	seq := s.receiptSeq.Next()
 56	c.receipts.Set(seq.Binary(), &receipt{
 57		seq: uint64(seq), donor: donor, anonymous: anonymous, amount: amount, tip: tip,
 58		message: message, at: now(), raisedAfter: s.raised, refundedBefore: k.refunded,
 59	})
 60	s.recent[s.recentNext] = uint64(seq)
 61	s.recentNext = (s.recentNext + 1) % recentSize
 62
 63	if c.mode == modeDirect {
 64		banker.NewBanker(banker.BankerTypeRealmSend, cur).SendCoins(realmAddr(), c.beneficiary, chain.NewCoins(chain.NewCoin("ugnot", amount)))
 65	} else {
 66		banker.NewBanker(banker.BankerTypeRealmSend, cur).SendCoins(realmAddr(), escrowAddr(c.id), chain.NewCoins(chain.NewCoin("ugnot", amount)))
 67	}
 68	if tip > 0 {
 69		banker.NewBanker(banker.BankerTypeRealmSend, cur).SendCoins(realmAddr(), tipRecipient, chain.NewCoins(chain.NewCoin("ugnot", tip)))
 70	}
 71	chain.Emit(evDonated,
 72		"id", idString(c.id),
 73		"seq", idString(seq),
 74		"donor", key,
 75		"amount", itoa(amount),
 76		"tip", itoa(tip),
 77		"anonymous", strconv.FormatBool(anonymous),
 78	)
 79	return receiptID(c.id, seq)
 80}
 81
 82// checkGift returns the gift in an envelope of total ugnot carrying tip, or panics
 83// (API §3.2), in this order: a negative tip, or nothing left for the gift; a
 84// campaign that isn't open; a paused Bolo; a gift under the minimum.
 85func checkGift(c *campaign, total, tip int64) int64 {
 86	if tip < 0 || total-tip <= 0 {
 87		panic(ErrBadAmount)
 88	}
 89	amount := total - tip
 90	assertOpen(c)
 91	if paused {
 92		panic(ErrPaused)
 93	}
 94	if amount < minDonation {
 95		panic(ErrBadAmount)
 96	}
 97	return amount
 98}
 99
100// checkRoom refuses a gift of amount that c can't take (API §3.2): one that would
101// push Raised past int64, or, in escrow mode with a cap set, the tracked balance —
102// never the bank's — past MaxEscrowBalance.
103func checkRoom(c *campaign, amount int64) {
104	if amount > math.MaxInt64-c.state.raised {
105		panic(ErrBadAmount)
106	}
107	if c.mode == modeEscrow && escrowCap > 0 && trackedBalance(c)+amount > escrowCap {
108		panic(ErrCapReached)
109	}
110}
111
112// receiptID is a receipt's ID: its campaign's, a slash, and its sequence number
113// (API C9).
114func receiptID(campaign, seq seqid.ID) string { return idString(campaign) + "/" + idString(seq) }
115
116// SweepStrays sends the coins someone sent to Bolo's own address by mistake to the
117// tip recipient, and returns how much: anyone can.
118//
119// It sends the ugnot at the realm's address to the tip recipient (API §3.4). The
120// realm holds nothing between transactions (INV-3), so all of it is strays; anyone
121// may call, and with nothing there it sends nothing and emits nothing (Q3).
122func SweepStrays(cur realm) int64 {
123	assertCurrent(cur)
124	amount := banker.NewReadonlyBanker().GetCoin(realmAddr(), "ugnot")
125	if amount == 0 {
126		return 0
127	}
128	banker.NewBanker(banker.BankerTypeRealmSend, cur).SendCoins(realmAddr(), tipRecipient, chain.NewCoins(chain.NewCoin("ugnot", amount)))
129	chain.Emit(evStraysSwept, "amount", itoa(amount), "to", tipRecipient.String())
130	return amount
131}