1package grc20
2
3import (
4 "std"
5 "strconv"
6
7 "gno.land/p/demo/avl"
8 "gno.land/p/demo/ufmt"
9)
10
11// Banker implements a token banker with admin privileges.
12//
13// The Banker is intended to be used in two main ways:
14// 1. as a temporary object used to make the initial minting, then deleted.
15// 2. preserved in an unexported variable to support conditional administrative
16// tasks protected by the contract.
17type Banker struct {
18 name string
19 symbol string
20 decimals uint
21 totalSupply uint64
22 balances avl.Tree // std.Address(owner) -> uint64
23 allowances avl.Tree // string(owner+":"+spender) -> uint64
24 token *token // to share the same pointer
25}
26
27func NewBanker(name, symbol string, decimals uint) *Banker {
28 if name == "" {
29 panic("name should not be empty")
30 }
31 if symbol == "" {
32 panic("symbol should not be empty")
33 }
34 // XXX additional checks (length, characters, limits, etc)
35
36 b := Banker{
37 name: name,
38 symbol: symbol,
39 decimals: decimals,
40 }
41 t := &token{banker: &b}
42 b.token = t
43 return &b
44}
45
46func (b Banker) Token() Token { return b.token } // Token returns a grc20 safe-object implementation.
47func (b Banker) GetName() string { return b.name }
48func (b Banker) GetSymbol() string { return b.symbol }
49func (b Banker) GetDecimals() uint { return b.decimals }
50func (b Banker) TotalSupply() uint64 { return b.totalSupply }
51func (b Banker) KnownAccounts() int { return b.balances.Size() }
52
53func (b *Banker) Mint(address std.Address, amount uint64) error {
54 if !address.IsValid() {
55 return ErrInvalidAddress
56 }
57
58 // TODO: check for overflow
59
60 b.totalSupply += amount
61 currentBalance := b.BalanceOf(address)
62 newBalance := currentBalance + amount
63
64 b.balances.Set(string(address), newBalance)
65
66 std.Emit(
67 MintEvent,
68 "from", "",
69 "to", string(address),
70 "value", strconv.Itoa(int(amount)),
71 )
72
73 return nil
74}
75
76func (b *Banker) Burn(address std.Address, amount uint64) error {
77 if !address.IsValid() {
78 return ErrInvalidAddress
79 }
80 // TODO: check for overflow
81
82 currentBalance := b.BalanceOf(address)
83 if currentBalance < amount {
84 return ErrInsufficientBalance
85 }
86
87 b.totalSupply -= amount
88 newBalance := currentBalance - amount
89
90 b.balances.Set(string(address), newBalance)
91
92 std.Emit(
93 BurnEvent,
94 "from", string(address),
95 "to", "",
96 "value", strconv.Itoa(int(amount)),
97 )
98
99 return nil
100}
101
102func (b Banker) BalanceOf(address std.Address) uint64 {
103 balance, found := b.balances.Get(address.String())
104 if !found {
105 return 0
106 }
107 return balance.(uint64)
108}
109
110func (b *Banker) SpendAllowance(owner, spender std.Address, amount uint64) error {
111 if !owner.IsValid() {
112 return ErrInvalidAddress
113 }
114 if !spender.IsValid() {
115 return ErrInvalidAddress
116 }
117
118 currentAllowance := b.Allowance(owner, spender)
119 if currentAllowance < amount {
120 return ErrInsufficientAllowance
121 }
122
123 key := allowanceKey(owner, spender)
124 newAllowance := currentAllowance - amount
125
126 if newAllowance == 0 {
127 b.allowances.Remove(key)
128 } else {
129 b.allowances.Set(key, newAllowance)
130 }
131
132 return nil
133}
134
135func (b *Banker) Transfer(from, to std.Address, amount uint64) error {
136 if !from.IsValid() {
137 return ErrInvalidAddress
138 }
139 if !to.IsValid() {
140 return ErrInvalidAddress
141 }
142 if from == to {
143 return ErrCannotTransferToSelf
144 }
145
146 toBalance := b.BalanceOf(to)
147 fromBalance := b.BalanceOf(from)
148
149 if fromBalance < amount {
150 return ErrInsufficientBalance
151 }
152
153 newToBalance := toBalance + amount
154 newFromBalance := fromBalance - amount
155
156 b.balances.Set(string(to), newToBalance)
157 b.balances.Set(string(from), newFromBalance)
158
159 std.Emit(
160 TransferEvent,
161 "from", from.String(),
162 "to", to.String(),
163 "value", strconv.Itoa(int(amount)),
164 )
165
166 return nil
167}
168
169func (b *Banker) TransferFrom(spender, from, to std.Address, amount uint64) error {
170 if err := b.SpendAllowance(from, spender, amount); err != nil {
171 return err
172 }
173 return b.Transfer(from, to, amount)
174}
175
176func (b *Banker) Allowance(owner, spender std.Address) uint64 {
177 allowance, found := b.allowances.Get(allowanceKey(owner, spender))
178 if !found {
179 return 0
180 }
181 return allowance.(uint64)
182}
183
184func (b *Banker) Approve(owner, spender std.Address, amount uint64) error {
185 if !owner.IsValid() {
186 return ErrInvalidAddress
187 }
188 if !spender.IsValid() {
189 return ErrInvalidAddress
190 }
191
192 b.allowances.Set(allowanceKey(owner, spender), amount)
193
194 std.Emit(
195 ApprovalEvent,
196 "owner", string(owner),
197 "spender", string(spender),
198 "value", strconv.Itoa(int(amount)),
199 )
200
201 return nil
202}
203
204func (b *Banker) RenderHome() string {
205 str := ""
206 str += ufmt.Sprintf("# %s ($%s)\n\n", b.name, b.symbol)
207 str += ufmt.Sprintf("* **Decimals**: %d\n", b.decimals)
208 str += ufmt.Sprintf("* **Total supply**: %d\n", b.totalSupply)
209 str += ufmt.Sprintf("* **Known accounts**: %d\n", b.KnownAccounts())
210 return str
211}
212
213func allowanceKey(owner, spender std.Address) string {
214 return owner.String() + ":" + spender.String()
215}
banker.gno
4.77 Kb ยท 215 lines