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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

tellers.gno

5.32 Kb · 206 lines
  1package grc721
  2
  3import (
  4	"chain"
  5)
  6
  7// CallerTeller resolves the acting account at each write as rlm.Previous() —
  8// the realm that crossed into the realm holding the teller.
  9//
 10// SECURITY: this accessor hangs off *PrivateLedger, not *Token, and that is
 11// load-bearing. A frame-relative teller acts as whoever crossed into its
 12// holder, so it is only meaningful inside the token's own realm, where the
 13// wrappers act for a caller who knowingly invoked the collection. Anywhere
 14// else it is a confused deputy: a realm a user merely calls could move that
 15// user's tokens. The *Token pointer is published — exported vars, the
 16// collection facade, grc721reg — while the ledger is not, since NewToken hands
 17// it to the creating realm and nowhere else. So a foreign realm cannot mint
 18// one.
 19//
 20// Construction privacy alone is not enough: a realm may legally build a teller
 21// and then export the VALUE. The write methods therefore also verify that the
 22// invoking realm is the token's own (see guardHome), which leaves a leaked
 23// teller inert everywhere but home.
 24func (led *PrivateLedger) CallerTeller() Teller {
 25	if led == nil {
 26		panic("Ledger cannot be nil")
 27	}
 28
 29	return &fnTeller{
 30		accountFn: func(_ int, rlm realm) address {
 31			return rlm.Previous().Address()
 32		},
 33		homeGuard: true,
 34		Token:     led.token,
 35	}
 36}
 37
 38// Write methods return ErrReadonly.
 39func (tok *Token) ReadonlyTeller() Teller {
 40	if tok == nil {
 41		panic("Token cannot be nil")
 42	}
 43
 44	return &fnTeller{
 45		accountFn: nil,
 46		Token:     tok,
 47	}
 48}
 49
 50// Permanently acts as the calling realm (verified via IsCurrent).
 51func (tok *Token) RealmTeller(_ int, rlm realm) Teller {
 52	if tok == nil {
 53		panic("Token cannot be nil")
 54	}
 55
 56	if !rlm.IsCurrent() {
 57		panic(ErrSpoofedRealm)
 58	}
 59
 60	caller := rlm.Address()
 61
 62	return &fnTeller{
 63		accountFn: func(_ int, _ realm) address {
 64			return caller
 65		},
 66		Token: tok,
 67	}
 68}
 69
 70// Like RealmTeller but acts as a sub-account derived from slug.
 71func (tok *Token) RealmSubTeller(_ int, rlm realm, slug string) Teller {
 72	if tok == nil {
 73		panic("Token cannot be nil")
 74	}
 75
 76	if !rlm.IsCurrent() {
 77		panic(ErrSpoofedRealm)
 78	}
 79
 80	account := accountSlugAddr(rlm.Address(), slug)
 81
 82	return &fnTeller{
 83		accountFn: func(_ int, _ realm) address {
 84			return account
 85		},
 86		Token: tok,
 87	}
 88}
 89
 90// Admin-grade: issuer-only (holds the PrivateLedger). Panics on invalid addr to close the empty-sentinel hole.
 91func (led *PrivateLedger) ImpersonateTeller(addr address) Teller {
 92	if led == nil {
 93		panic("Ledger cannot be nil")
 94	}
 95
 96	if !addr.IsValid() {
 97		panic(ErrInvalidAddress)
 98	}
 99
100	return &fnTeller{
101		accountFn: func(_ int, _ realm) address {
102			return addr
103		},
104		Token: led.token,
105	}
106}
107
108// guardHome confines a frame-relative teller to the token's own realm.
109// Construction privacy stops a foreign realm from minting one; this stops a
110// minted one from travelling, which is what happens when a realm legally builds
111// a teller and then exports the value.
112//
113// The check is on the invoking realm's path alone — deliberately NOT on whether
114// the resolved actor is an end user. Keying on the actor only blocks the case
115// where the debited party is the signing user, and leaves two doors open: a
116// realm can be charged by a realm it calls, and TransferFrom resolves the
117// *spender* from the frame, so a realm reached from an honest hub spends that
118// hub's approvals against any owner who granted one. Both are the same defect
119// as the original one level up — frame-relative resolution means whoever you
120// call can act as you — and neither is reachable once the teller only works at
121// home.
122//
123// The host is compared after stripping any "#subpath" synthesized by realm.Sub,
124// so the token's own sub-realms are not falsely rejected.
125//
126// A foreign realm that needs to move a user's tokens uses the ordinary route:
127// the owner Approves it, and it spends as itself through RealmTeller, which is
128// eagerly bound to its own address and approval-gated.
129//
130// The leading int keeps this a plain method.
131func (ft *fnTeller) guardHome(_ int, rlm realm) error {
132	if !ft.homeGuard {
133		return nil
134	}
135
136	host, _, _ := splitPkgSubPath(rlm.PkgPath())
137	if host != ft.Token.origRealm {
138		return ErrForeignCallerTeller
139	}
140
141	return nil
142}
143
144func (ft *fnTeller) Approve(_ int, rlm realm, to address, tid TokenID) error {
145	if ft.accountFn == nil {
146		return ErrReadonly
147	}
148
149	if !rlm.IsCurrent() {
150		return ErrSpoofedRealm
151	}
152
153	if err := ft.guardHome(0, rlm); err != nil {
154		return err
155	}
156
157	caller := ft.accountFn(0, rlm)
158
159	return ft.Token.ledger.Approve(caller, to, tid)
160}
161
162func (ft *fnTeller) SetApprovalForAll(_ int, rlm realm, operator address, approved bool) error {
163	if ft.accountFn == nil {
164		return ErrReadonly
165	}
166
167	if !rlm.IsCurrent() {
168		return ErrSpoofedRealm
169	}
170
171	if err := ft.guardHome(0, rlm); err != nil {
172		return err
173	}
174
175	caller := ft.accountFn(0, rlm)
176
177	return ft.Token.ledger.SetApprovalForAll(caller, operator, approved)
178}
179
180func (ft *fnTeller) TransferFrom(_ int, rlm realm, from, to address, tid TokenID) error {
181	if ft.accountFn == nil {
182		return ErrReadonly
183	}
184
185	if !rlm.IsCurrent() {
186		return ErrSpoofedRealm
187	}
188
189	if err := ft.guardHome(0, rlm); err != nil {
190		return err
191	}
192
193	spender := ft.accountFn(0, rlm)
194
195	return ft.Token.ledger.TransferFrom(spender, from, to, tid)
196}
197
198func accountSlugAddr(addr address, slug string) address {
199	if slug == "" {
200		return addr
201	}
202
203	key := addr.String() + "/" + slug
204
205	return chain.PackageAddress(key)
206}