Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}