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

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, _, _ := chain.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}