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