tellers.gno
4.26 Kb · 159 lines
1package grc20
2
3import (
4 "chain"
5)
6
7// CallerTeller returns a GRC20 compatible teller that, at each write call,
8// resolves the caller as rlm.Previous() — the realm that crossed into the
9// caller. rlm must be the caller's own captured cur (asserted via
10// rlm.IsCurrent() inside the Teller methods).
11func (tok *Token) CallerTeller() Teller {
12 if tok == nil {
13 panic("Token cannot be nil")
14 }
15
16 return &fnTeller{
17 accountFn: func(_ int, rlm realm) address {
18 return rlm.Previous().Address()
19 },
20 Token: tok,
21 }
22}
23
24// ReadonlyTeller is a GRC20 compatible teller that panics for any write operation.
25func (tok *Token) ReadonlyTeller() Teller {
26 if tok == nil {
27 panic("Token cannot be nil")
28 }
29
30 return &fnTeller{
31 accountFn: nil,
32 Token: tok,
33 }
34}
35
36// RealmTeller returns a GRC20 compatible teller that will store the
37// caller realm permanently. Calling anything through this teller will
38// result in allowance or balance changes for the realm that initialized the teller.
39// The initializer of this teller should usually never share the resulting Teller from
40// this method except maybe for advanced delegation flows such as a DAO treasury
41// management.
42//
43// rlm must be the caller's own captured cur (asserted via rlm.IsCurrent()).
44// The address is frozen eagerly at construction.
45func (tok *Token) RealmTeller(_ int, rlm realm) Teller {
46 if tok == nil {
47 panic("Token cannot be nil")
48 }
49 if !rlm.IsCurrent() {
50 panic(ErrSpoofedRealm)
51 }
52
53 caller := rlm.Address()
54
55 return &fnTeller{
56 accountFn: func(_ int, _ realm) address {
57 return caller
58 },
59 Token: tok,
60 }
61}
62
63// RealmSubTeller is like RealmTeller but uses the provided slug to derive a
64// subaccount.
65//
66// rlm must be the caller's own captured cur (asserted via rlm.IsCurrent()).
67// The subaccount address is frozen eagerly at construction.
68func (tok *Token) RealmSubTeller(_ int, rlm realm, slug string) Teller {
69 if tok == nil {
70 panic("Token cannot be nil")
71 }
72 if !rlm.IsCurrent() {
73 panic(ErrSpoofedRealm)
74 }
75
76 account := accountSlugAddr(rlm.Address(), slug)
77
78 return &fnTeller{
79 accountFn: func(_ int, _ realm) address {
80 return account
81 },
82 Token: tok,
83 }
84}
85
86// ImpersonateTeller returns a GRC20 compatible teller that impersonates as a
87// specified address. This allows operations to be performed as if they were
88// executed by the given address, enabling the caller to manipulate tokens on
89// behalf of that address.
90//
91// It is particularly useful in scenarios where a contract needs to perform
92// actions on behalf of a user or another account, without exposing the
93// underlying logic or requiring direct access to the user's account. The
94// returned teller will use the provided address for all operations, effectively
95// masking the original caller.
96//
97// This method should be used with caution, as it allows for potentially
98// sensitive operations to be performed under the guise of another address.
99func (ledger *PrivateLedger) ImpersonateTeller(addr address) Teller {
100 if ledger == nil {
101 panic("Ledger cannot be nil")
102 }
103
104 return &fnTeller{
105 accountFn: func(_ int, _ realm) address {
106 return addr
107 },
108 Token: ledger.token,
109 }
110}
111
112// generic tellers methods.
113//
114
115func (ft *fnTeller) Transfer(_ int, rlm realm, to address, amount int64) error {
116 if ft.accountFn == nil {
117 return ErrReadonly
118 }
119 if !rlm.IsCurrent() {
120 return ErrSpoofedRealm
121 }
122 caller := ft.accountFn(0, rlm)
123 return ft.Token.ledger.Transfer(caller, to, amount)
124}
125
126func (ft *fnTeller) Approve(_ int, rlm realm, spender address, amount int64) error {
127 if ft.accountFn == nil {
128 return ErrReadonly
129 }
130 if !rlm.IsCurrent() {
131 return ErrSpoofedRealm
132 }
133 caller := ft.accountFn(0, rlm)
134 return ft.Token.ledger.Approve(caller, spender, amount)
135}
136
137func (ft *fnTeller) TransferFrom(_ int, rlm realm, owner, to address, amount int64) error {
138 if ft.accountFn == nil {
139 return ErrReadonly
140 }
141 if !rlm.IsCurrent() {
142 return ErrSpoofedRealm
143 }
144 spender := ft.accountFn(0, rlm)
145 return ft.Token.ledger.TransferFrom(owner, spender, to, amount)
146}
147
148// helpers
149//
150
151// accountSlugAddr returns the address derived from the specified address and slug.
152func accountSlugAddr(addr address, slug string) address {
153 // XXX: use a new `std.XXX` call for this.
154 if slug == "" {
155 return addr
156 }
157 key := addr.String() + "/" + slug
158 return chain.PackageAddress(key) // temporarily using this helper
159}