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.

pay.gno

4.57 Kb · 144 lines
  1package sugarproxy
  2
  3import (
  4	"chain"
  5	"chain/banker"
  6	"chain/runtime/unsafe"
  7	"math/overflow"
  8	"strconv"
  9	"strings"
 10	"time"
 11
 12	"gno.land/p/nt/avl/v0"
 13)
 14
 15// Paying sharers. The hub tallies the bytes each sharer's app carried and pays them through Pay, so every payout
 16// is on chain: who was paid, how much, for how many bytes, in which period. The hub's account is an approved payer;
 17// it signs with a gno.land session key whose spending the chain caps (only calls to this realm, so much a period),
 18// so a stolen hub key can pay out at most one period's worth, and only through here, where it shows.
 19
 20const denom = "ugnot"
 21
 22// Totals is what a sharer has been paid, all told.
 23type Totals struct {
 24	Paid    int64 // ugnot
 25	Bytes   int64
 26	Payouts int
 27	Last    int64 // unix seconds
 28}
 29
 30// Payout is one call to Pay.
 31type Payout struct {
 32	Time     int64
 33	Payer    address
 34	Period   string
 35	Receipts string // the hex sha256 of the payer's receipts for the period: the confirmed downloads it paid for
 36	Count    int
 37	Ugnot    int64
 38	Bytes    int64
 39}
 40
 41var (
 42	totals  = avl.NewTree() // address -> *Totals
 43	payouts []*Payout
 44)
 45
 46// Pay pays sharers with the GNOT sent along. lines has one sharer a line: "address ugnot bytes"; the amounts must
 47// add up to what was sent, and a payout is at most 500 lines. period names what it pays for ("2026-11-01", say);
 48// receipts is the hex sha256 of the payer's receipts for it (one line per confirmed download: its tag, the file's
 49// sha256 and size, who carried how much), recorded here so the receipts can be checked against it later. Only an
 50// approved payer may call it; pausing doesn't stop it (sharers are owed for what they carried).
 51func Pay(cur realm, period, receipts, lines string) {
 52	payer := mustUser(cur)
 53	if !payerActive(payer) {
 54		panic("only an approved payer may pay")
 55	}
 56	if len(period) == 0 || len(period) > 40 || strings.ContainsAny(period, "\n\r\t") {
 57		panic("the period is 1 to 40 characters on one line")
 58	}
 59	if !hexOf(receipts, 64) {
 60		panic("receipts is the receipts' sha256: 64 lowercase hex")
 61	}
 62	sent := coinsSent(cur)
 63	type line struct {
 64		to           address
 65		ugnot, bytes int64
 66	}
 67	var all []line
 68	var sum, bytes int64
 69	for _, l := range strings.Split(strings.TrimSpace(lines), "\n") {
 70		f := strings.Fields(l)
 71		if len(f) != 3 {
 72			panic("each line is: address ugnot bytes")
 73		}
 74		to := address(f[0])
 75		u, err1 := strconv.ParseInt(f[1], 10, 64)
 76		b, err2 := strconv.ParseInt(f[2], 10, 64)
 77		if !to.IsValid() || err1 != nil || err2 != nil || u <= 0 || b < 0 {
 78			panic("a bad line: " + l)
 79		}
 80		sum = overflow.Add64p(sum, u)
 81		bytes = overflow.Add64p(bytes, b)
 82		all = append(all, line{to, u, b})
 83	}
 84	if len(all) == 0 || len(all) > 500 {
 85		panic("a payout is 1 to 500 lines")
 86	}
 87	if sum != sent {
 88		panic("the amounts add up to " + strconv.FormatInt(sum, 10) + " ugnot, but " + strconv.FormatInt(sent, 10) + " were sent")
 89	}
 90	now := time.Now().Unix()
 91	bk := banker.NewBanker(banker.BankerTypeRealmSend, cur)
 92	for _, l := range all {
 93		bk.SendCoins(cur.Address(), l.to, chain.Coins{chain.NewCoin(denom, l.ugnot)})
 94		t, _ := totals.Get(l.to.String()).(*Totals)
 95		if t == nil {
 96			t = &Totals{}
 97			totals.Set(l.to.String(), t)
 98		}
 99		t.Paid = overflow.Add64p(t.Paid, l.ugnot)
100		t.Bytes = overflow.Add64p(t.Bytes, l.bytes)
101		t.Payouts++
102		t.Last = now
103		chain.Emit("Paid", "to", l.to.String(), "ugnot", strconv.FormatInt(l.ugnot, 10), "bytes", strconv.FormatInt(l.bytes, 10), "period", period)
104	}
105	payouts = append(payouts, &Payout{Time: now, Payer: payer, Period: period, Receipts: receipts, Count: len(all), Ugnot: sum, Bytes: bytes})
106	chain.Emit("Payout", "period", period, "receipts", receipts, "sharers", strconv.Itoa(len(all)), "ugnot", strconv.FormatInt(sum, 10))
107}
108
109// PaidTo is what a sharer has been paid, all told.
110func PaidTo(a address) (ugnot, bytes int64, count int) {
111	if t, ok := totals.Get(a.String()).(*Totals); ok && t != nil {
112		return t.Paid, t.Bytes, t.Payouts
113	}
114	return 0, 0, 0
115}
116
117func payerActive(a address) bool {
118	now := time.Now().Unix()
119	for _, it := range items {
120		if it.Kind == KindPayer && it.Fields[0] == a.String() && it.State == StateApproved && now >= it.After {
121			return true
122		}
123	}
124	return false
125}
126
127// coinsSent returns the ugnot sent with a direct call, refusing other denoms. Coins sent with a run go to the run's
128// own address, not here, so a run sends none.
129func coinsSent(cur realm) int64 {
130	if !cur.Previous().IsUserCall() {
131		return 0
132	}
133	var n int64
134	for _, c := range unsafe.OriginSend() {
135		if c.Amount == 0 {
136			continue
137		}
138		if c.Denom != denom || c.Amount < 0 {
139			panic("only " + denom + " is taken")
140		}
141		n = overflow.Add64p(n, c.Amount)
142	}
143	return n
144}