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

erc20.gno

5.31 Kb · 190 lines
  1// Package erc20 implements MiniToken, a minimal fungible token modeled on the
  2// Solidity ERC-20 idea, ported to gno.land (gno 0.9 / sapphire).
  3//
  4// State is package-level and persistent. All state-mutating exported functions
  5// are crossing functions (they take `cur realm` as the first parameter) per the
  6// gno 0.9 interrealm convention, and identify the caller via
  7// cur.Previous().Address() after checking cur.IsCurrent().
  8package erc20
  9
 10import (
 11	"errors"
 12	"sort"
 13	"strconv"
 14	"strings"
 15)
 16
 17const (
 18	name   = "MiniToken"
 19	symbol = "MINI"
 20)
 21
 22var (
 23	// balances maps holder address -> token balance.
 24	balances = map[address]uint64{}
 25	// allowances[owner][spender] = amount the spender may pull from owner.
 26	allowances = map[address]map[address]uint64{}
 27	// totalSupply is the sum of all balances.
 28	totalSupply uint64
 29)
 30
 31var (
 32	errZeroAddress  = errors.New("erc20: zero address")
 33	errInsufficient = errors.New("erc20: insufficient balance")
 34	errAllowanceLow = errors.New("erc20: insufficient allowance")
 35	errOverflow     = errors.New("erc20: supply overflow")
 36	errSpoofedRealm = errors.New("erc20: spoofed realm")
 37	errNotUser      = errors.New("erc20: caller is not a user account")
 38)
 39
 40// caller returns the immediate caller's address for a crossing function.
 41// It enforces the gno 0.9 authentication primitive: cur must be the live
 42// crossing frame before its Previous() is trusted.
 43func caller(cur realm) address {
 44	if !cur.IsCurrent() {
 45		panic(errSpoofedRealm)
 46	}
 47	prev := cur.Previous()
 48	if !prev.IsUser() {
 49		panic(errNotUser)
 50	}
 51	return prev.Address()
 52}
 53
 54// Mint creates `amount` new tokens and credits them to `to`, increasing the
 55// total supply. Crossing function.
 56func Mint(cur realm, to address, amount uint64) {
 57	_ = caller(cur) // authenticate the crossing frame; anyone may mint in this demo
 58	if !to.IsValid() {
 59		panic(errZeroAddress)
 60	}
 61	if amount == 0 {
 62		return
 63	}
 64	if totalSupply+amount < totalSupply {
 65		panic(errOverflow)
 66	}
 67	totalSupply += amount
 68	balances[to] += amount
 69}
 70
 71// Transfer moves `amount` tokens from the caller to `to`. Crossing function.
 72func Transfer(cur realm, to address, amount uint64) {
 73	from := caller(cur)
 74	if !to.IsValid() {
 75		panic(errZeroAddress)
 76	}
 77	if err := transfer(from, to, amount); err != nil {
 78		panic(err)
 79	}
 80}
 81
 82// Approve authorizes `spender` to pull up to `amount` tokens from the caller.
 83// Crossing function.
 84func Approve(cur realm, spender address, amount uint64) {
 85	owner := caller(cur)
 86	if !spender.IsValid() {
 87		panic(errZeroAddress)
 88	}
 89	m := allowances[owner]
 90	if m == nil {
 91		m = map[address]uint64{}
 92		allowances[owner] = m
 93	}
 94	m[spender] = amount
 95}
 96
 97// TransferFrom moves `amount` tokens from `from` to `to`, spending the caller's
 98// allowance granted by `from`. Crossing function.
 99func TransferFrom(cur realm, from, to address, amount uint64) {
100	spender := caller(cur)
101	if !from.IsValid() || !to.IsValid() {
102		panic(errZeroAddress)
103	}
104	allowed := allowances[from][spender]
105	if allowed < amount {
106		panic(errAllowanceLow)
107	}
108	if err := transfer(from, to, amount); err != nil {
109		panic(err)
110	}
111	allowances[from][spender] = allowed - amount
112}
113
114// transfer is the internal, non-crossing balance mover. Pure logic on package
115// state; returns an error rather than panicking so callers control reverts.
116func transfer(from, to address, amount uint64) error {
117	if amount == 0 {
118		return nil
119	}
120	if balances[from] < amount {
121		return errInsufficient
122	}
123	balances[from] -= amount
124	balances[to] += amount
125	return nil
126}
127
128// BalanceOf returns the token balance of `holder`. Read-only, non-crossing.
129func BalanceOf(holder address) uint64 {
130	return balances[holder]
131}
132
133// Allowance returns how much `spender` may still pull from `owner`.
134func Allowance(owner, spender address) uint64 {
135	return allowances[owner][spender]
136}
137
138// TotalSupply returns the total number of tokens in existence.
139func TotalSupply() uint64 {
140	return totalSupply
141}
142
143// Name returns the token name.
144func Name() string { return name }
145
146// Symbol returns the token symbol.
147func Symbol() string { return symbol }
148
149// Render produces the gnoweb Markdown view of the token: metadata plus a
150// deterministically-ordered table of non-zero balances. Render is NOT a
151// crossing function (no `cur realm` parameter).
152func Render(path string) string {
153	var b strings.Builder
154
155	b.WriteString("# " + name + " (" + symbol + ")\n\n")
156	b.WriteString("A minimal ERC-20-style fungible token on gno.land.\n\n")
157	b.WriteString("- **Name:** " + name + "\n")
158	b.WriteString("- **Symbol:** " + symbol + "\n")
159	b.WriteString("- **Total supply:** " + strconv.FormatUint(totalSupply, 10) + "\n\n")
160
161	// Collect and sort holder addresses for deterministic output. Map iteration
162	// order is not a stable contract, so we sort the string keys (gno's sort
163	// package has no generic Slice; Strings is the deterministic primitive).
164	keys := make([]string, 0, len(balances))
165	byKey := make(map[string]uint64, len(balances))
166	for addr, bal := range balances {
167		if bal == 0 {
168			continue
169		}
170		s := addr.String()
171		keys = append(keys, s)
172		byKey[s] = bal
173	}
174	sort.Strings(keys)
175
176	b.WriteString("## Balances\n\n")
177	if len(keys) == 0 {
178		b.WriteString("_No holders yet — call `Mint` to create tokens._\n")
179		return b.String()
180	}
181
182	b.WriteString("| Address | Amount |\n")
183	b.WriteString("| --- | ---: |\n")
184	for _, k := range keys {
185		b.WriteString("| `" + k + "` | " +
186			strconv.FormatUint(byKey[k], 10) + " |\n")
187	}
188
189	return b.String()
190}