grc1155.gno
3.19 Kb · 134 lines
1// Package grc1155 is an edition ledger. One id has a balance per address.
2// The realm that calls New owns every later Mint and Move. A caller address
3// is not an argument: the home check is the realm value itself.
4package grc1155
5
6import (
7 "chain"
8 "math/overflow"
9 "strconv"
10
11 "gno.land/p/nt/avl/v0"
12)
13
14// Ledger is stored by the creating realm. Do not export it from that realm.
15type Ledger struct {
16 home string
17 bal avl.Tree // id|address -> int64
18 supply avl.Tree // id -> int64
19}
20
21// New binds the ledger to rlm. The realm is not the first argument, so this
22// stays a normal call and home stays the creating realm. rlm must be current.
23func New(_ int, rlm realm) *Ledger {
24 if !rlm.IsCurrent() {
25 panic("grc1155: spoofed realm")
26 }
27 home := rlm.PkgPath()
28 if home == "" {
29 panic("grc1155: home")
30 }
31 return &Ledger{home: home}
32}
33
34// Mint adds amount of id to to. from is empty in the event.
35func (led *Ledger) Mint(_ int, rlm realm, to address, id string, amount int64) {
36 led.guard(0, rlm)
37 if !to.IsValid() {
38 panic("grc1155: address")
39 }
40 if !validID(id) || amount <= 0 {
41 panic("grc1155: amount")
42 }
43 next := overflow.Add64p(led.Supply(id), amount)
44 led.supply.Set(id, next)
45 key := balKey(id, to)
46 led.bal.Set(key, overflow.Add64p(led.Balance(to, id), amount))
47 emit(address(""), to, id, amount)
48}
49
50// Move sends amount of id from from to to. Both addresses must already be valid.
51func (led *Ledger) Move(_ int, rlm realm, from, to address, id string, amount int64) {
52 led.guard(0, rlm)
53 if !from.IsValid() || !to.IsValid() || from == to {
54 panic("grc1155: address")
55 }
56 if !validID(id) || amount <= 0 {
57 panic("grc1155: amount")
58 }
59 if led.supply.Get(id) == nil {
60 panic("grc1155: unknown id")
61 }
62 have := led.Balance(from, id)
63 if have < amount {
64 panic("grc1155: balance")
65 }
66 left := overflow.Sub64p(have, amount)
67 if left == 0 {
68 led.bal.Remove(balKey(id, from))
69 } else {
70 led.bal.Set(balKey(id, from), left)
71 }
72 led.bal.Set(balKey(id, to), overflow.Add64p(led.Balance(to, id), amount))
73 emit(from, to, id, amount)
74}
75
76// Balance is the free or escrowed amount of id held by addr. Unknown pairs are 0.
77func (led *Ledger) Balance(addr address, id string) int64 {
78 if led == nil || !addr.IsValid() || !validID(id) {
79 return 0
80 }
81 v := led.bal.Get(balKey(id, addr))
82 if v == nil {
83 return 0
84 }
85 return v.(int64)
86}
87
88// Supply is the minted amount of id. An unknown id is 0.
89func (led *Ledger) Supply(id string) int64 {
90 if led == nil || !validID(id) {
91 return 0
92 }
93 v := led.supply.Get(id)
94 if v == nil {
95 return 0
96 }
97 return v.(int64)
98}
99
100func (led *Ledger) guard(_ int, rlm realm) {
101 if led == nil || !rlm.IsCurrent() || rlm.PkgPath() != led.home {
102 panic("grc1155: home")
103 }
104}
105
106func balKey(id string, addr address) string {
107 return id + "|" + addr.String()
108}
109
110func validID(id string) bool {
111 if len(id) < 1 || len(id) > 16 {
112 return false
113 }
114 if id[0] < 'a' || id[0] > 'z' {
115 return false
116 }
117 for i := 0; i < len(id); i++ {
118 c := id[i]
119 if (c < 'a' || c > 'z') && (c < '0' || c > '9') {
120 return false
121 }
122 }
123 return true
124}
125
126func emit(from, to address, id string, amount int64) {
127 chain.Emit(
128 "EditionTransfer",
129 "from", from.String(),
130 "to", to.String(),
131 "id", id,
132 "amount", strconv.FormatInt(amount, 10),
133 )
134}