erc1155.gno
3.95 Kb · 156 lines
1// Package erc1155 is an idiomatic gno.land port of the Solidity ERC-1155
2// multi-token standard. A single realm tracks many distinct token ids, each
3// with its own fungible balances. Balances are keyed by (owner, id) in an
4// avl.Tree so Render can iterate deterministically.
5package erc1155
6
7import (
8 "strconv"
9 "strings"
10
11 "chain"
12 "chain/runtime/unsafe"
13
14 "gno.land/p/nt/avl/v0"
15)
16
17// balances maps "<owner>:<id>" -> uint64 amount.
18var balances avl.Tree
19
20// supply maps "<id>" -> uint64 total minted for that id.
21var supply avl.Tree
22
23func balKey(owner address, id int) string {
24 return owner.String() + ":" + strconv.Itoa(id)
25}
26
27func supKey(id int) string {
28 return strconv.Itoa(id)
29}
30
31func getBalance(owner address, id int) uint64 {
32 k := balKey(owner, id)
33 if !balances.Has(k) {
34 return 0
35 }
36 return balances.Get(k).(uint64)
37}
38
39func setBalance(owner address, id int, amount uint64) {
40 k := balKey(owner, id)
41 if amount == 0 {
42 balances.Remove(k)
43 return
44 }
45 balances.Set(k, amount)
46}
47
48func getSupply(id int) uint64 {
49 k := supKey(id)
50 if !supply.Has(k) {
51 return 0
52 }
53 return supply.Get(k).(uint64)
54}
55
56// Mint creates `amount` units of token `id` and credits them to `to`.
57func Mint(cur realm, to address, id int, amount uint64) {
58 if !to.IsValid() {
59 panic("erc1155: mint to invalid address")
60 }
61 if amount == 0 {
62 panic("erc1155: mint amount must be > 0")
63 }
64 setBalance(to, id, getBalance(to, id)+amount)
65 supply.Set(supKey(id), getSupply(id)+amount)
66
67 operator := unsafe.PreviousRealm().Address()
68 chain.Emit("Mint",
69 "operator", operator.String(),
70 "to", to.String(),
71 "id", strconv.Itoa(id),
72 "amount", strconv.FormatUint(amount, 10),
73 )
74}
75
76// TransferSingle moves `amount` of token `id` from the caller to `to`.
77func TransferSingle(cur realm, to address, id int, amount uint64) {
78 if !to.IsValid() {
79 panic("erc1155: transfer to invalid address")
80 }
81 if amount == 0 {
82 panic("erc1155: transfer amount must be > 0")
83 }
84 from := unsafe.PreviousRealm().Address()
85 fromBal := getBalance(from, id)
86 if fromBal < amount {
87 panic("erc1155: insufficient balance")
88 }
89 setBalance(from, id, fromBal-amount)
90 setBalance(to, id, getBalance(to, id)+amount)
91
92 chain.Emit("TransferSingle",
93 "from", from.String(),
94 "to", to.String(),
95 "id", strconv.Itoa(id),
96 "amount", strconv.FormatUint(amount, 10),
97 )
98}
99
100// BalanceOf returns the amount of token `id` held by `owner`.
101func BalanceOf(owner address, id int) uint64 {
102 return getBalance(owner, id)
103}
104
105// TotalSupply returns the total amount minted of token `id`.
106func TotalSupply(id int) uint64 {
107 return getSupply(id)
108}
109
110// Render prints a balances table and a per-id supply table.
111func Render(path string) string {
112 var b strings.Builder
113 b.WriteString("# ERC-1155 Multi-Token\n\n")
114 b.WriteString("A single realm holding many fungible token ids. ")
115 b.WriteString("Balances are keyed by `(owner, id)`.\n\n")
116
117 b.WriteString("## Balances\n\n")
118 if balances.Size() == 0 {
119 b.WriteString("_No balances yet. Call `Mint` to create tokens._\n\n")
120 } else {
121 b.WriteString("| Owner | ID | Amount |\n")
122 b.WriteString("|---|---|---|\n")
123 balances.Iterate("", "", func(key string, value interface{}) bool {
124 owner, id := splitBalKey(key)
125 b.WriteString("| `" + owner + "` | " + id + " | " +
126 strconv.FormatUint(value.(uint64), 10) + " |\n")
127 return false
128 })
129 b.WriteString("\n")
130 }
131
132 b.WriteString("## Supply per ID\n\n")
133 if supply.Size() == 0 {
134 b.WriteString("_No tokens minted._\n")
135 } else {
136 b.WriteString("| ID | Total Supply |\n")
137 b.WriteString("|---|---|\n")
138 supply.Iterate("", "", func(key string, value interface{}) bool {
139 b.WriteString("| " + key + " | " +
140 strconv.FormatUint(value.(uint64), 10) + " |\n")
141 return false
142 })
143 }
144
145 return b.String()
146}
147
148// splitBalKey splits a "<owner>:<id>" balance key. The owner (a bech32
149// address) contains no ':', so the id is the substring after the last ':'.
150func splitBalKey(key string) (owner, id string) {
151 i := strings.LastIndex(key, ":")
152 if i < 0 {
153 return key, ""
154 }
155 return key[:i], key[i+1:]
156}