-
Notifications
You must be signed in to change notification settings - Fork 13
/
FungibleToken.cdc
241 lines (207 loc) · 10.1 KB
/
FungibleToken.cdc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
/**
# The Flow Fungible Token standard
## `FungibleToken` contract
The Fungible Token standard is no longer an interface
that all fungible token contracts would have to conform to.
If a users wants to deploy a new token contract, their contract
does not need to implement the FungibleToken interface, but their tokens
do need to implement the interfaces defined in this contract.
## `Vault` resource interface
Each fungible token resource type needs to implement the `Vault` resource interface.
## `Provider`, `Receiver`, and `Balance` resource interfaces
These interfaces declare pre-conditions and post-conditions that restrict
the execution of the functions in the Vault.
They are separate because it gives the user the ability to share
a reference to their Vault that only exposes the fields functions
in one or more of the interfaces.
It also gives users the ability to make custom resources that implement
these interfaces to do various things with the tokens.
For example, a faucet can be implemented by conforming
to the Provider interface.
*/
import "ViewResolver"
import "Burner"
/// FungibleToken
///
/// Fungible Token implementations should implement the fungible token
/// interface.
access(all) contract interface FungibleToken: ViewResolver {
// An entitlement for allowing the withdrawal of tokens from a Vault
access(all) entitlement Withdraw
/// The event that is emitted when tokens are withdrawn from a Vault
access(all) event Withdrawn(type: String, amount: UFix64, from: Address?, fromUUID: UInt64, withdrawnUUID: UInt64, balanceAfter: UFix64)
/// The event that is emitted when tokens are deposited to a Vault
access(all) event Deposited(type: String, amount: UFix64, to: Address?, toUUID: UInt64, depositedUUID: UInt64, balanceAfter: UFix64)
/// Event that is emitted when the global burn method is called with a non-zero balance
access(all) event Burned(type: String, amount: UFix64, fromUUID: UInt64)
/// Balance
///
/// The interface that provides standard functions\
/// for getting balance information
///
access(all) resource interface Balance {
access(all) var balance: UFix64
}
/// Provider
///
/// The interface that enforces the requirements for withdrawing
/// tokens from the implementing type.
///
/// It does not enforce requirements on `balance` here,
/// because it leaves open the possibility of creating custom providers
/// that do not necessarily need their own balance.
///
access(all) resource interface Provider {
/// Function to ask a provider if a specific amount of tokens
/// is available to be withdrawn
/// This could be useful to avoid panicing when calling withdraw
/// when the balance is unknown
/// Additionally, if the provider is pulling from multiple vaults
/// it only needs to check some of the vaults until the desired amount
/// is reached, potentially helping with performance.
///
access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool
/// withdraw subtracts tokens from the implementing resource
/// and returns a Vault with the removed tokens.
///
/// The function's access level is `access(Withdraw)`
/// So in order to access it, one would either need the object itself
/// or an entitled reference with `Withdraw`.
///
access(Withdraw) fun withdraw(amount: UFix64): @{Vault} {
post {
// `result` refers to the return value
result.balance == amount:
"Withdrawal amount must be the same as the balance of the withdrawn Vault"
}
}
}
/// Receiver
///
/// The interface that enforces the requirements for depositing
/// tokens into the implementing type.
///
/// We do not include a condition that checks the balance because
/// we want to give users the ability to make custom receivers that
/// can do custom things with the tokens, like split them up and
/// send them to different places.
///
access(all) resource interface Receiver {
/// deposit takes a Vault and deposits it into the implementing resource type
///
access(all) fun deposit(from: @{Vault})
/// getSupportedVaultTypes returns a dictionary of Vault types
/// and whether the type is currently supported by this Receiver
access(all) view fun getSupportedVaultTypes(): {Type: Bool}
/// Returns whether or not the given type is accepted by the Receiver
/// A vault that can accept any type should just return true by default
access(all) view fun isSupportedVaultType(type: Type): Bool
}
/// Vault
///
/// Ideally, this interface would also conform to Receiver, Balance, Transferor, Provider, and Resolver
/// but that is not supported yet
///
access(all) resource interface Vault: Receiver, Provider, Balance, ViewResolver.Resolver, Burner.Burnable {
/// Field that tracks the balance of a vault
access(all) var balance: UFix64
/// Called when a fungible token is burned via the `Burner.burn()` method
/// Implementations can do any bookkeeping or emit any events
/// that should be emitted when a vault is destroyed.
/// Many implementations will want to update the token's total supply
/// to reflect that the tokens have been burned and removed from the supply.
/// Implementations also need to set the balance to zero before the end of the function
/// This is to prevent vault owners from spamming fake Burned events.
access(contract) fun burnCallback() {
pre {
emit Burned(type: self.getType().identifier, amount: self.balance, fromUUID: self.uuid)
}
post {
self.balance == 0.0: "The balance must be set to zero during the burnCallback method so that it cannot be spammed"
}
self.balance = 0.0
}
/// getSupportedVaultTypes returns a dictionary of vault types and whether this receiver accepts the indexed type
/// The default implementation is included here because vaults are expected
/// to only accepted their own type, so they have no need to provide an implementation
/// for this function
access(all) view fun getSupportedVaultTypes(): {Type: Bool} {
// Below check is implemented to make sure that run-time type would
// only get returned when the parent resource conforms with `FungibleToken.Vault`.
if self.getType().isSubtype(of: Type<@{FungibleToken.Vault}>()) {
return {self.getType(): true}
} else {
// Return an empty dictionary as the default value for resource who don't
// implement `FungibleToken.Vault`, such as `FungibleTokenSwitchboard`, `TokenForwarder` etc.
return {}
}
}
/// Checks if the given type is supported by this Vault
access(all) view fun isSupportedVaultType(type: Type): Bool {
return self.getSupportedVaultTypes()[type] ?? false
}
/// withdraw subtracts `amount` from the Vault's balance
/// and returns a new Vault with the subtracted balance
///
access(Withdraw) fun withdraw(amount: UFix64): @{Vault} {
pre {
self.balance >= amount:
"Amount withdrawn must be less than or equal than the balance of the Vault"
}
post {
result.getType() == self.getType(): "Must return the same vault type as self"
// use the special function `before` to get the value of the `balance` field
// at the beginning of the function execution
//
self.balance == before(self.balance) - amount:
"New Vault balance must be the difference of the previous balance and the withdrawn Vault balance"
emit Withdrawn(
type: result.getType().identifier,
amount: amount,
from: self.owner?.address,
fromUUID: self.uuid,
withdrawnUUID: result.uuid,
balanceAfter: self.balance
)
}
}
/// deposit takes a Vault and adds its balance to the balance of this Vault
///
access(all) fun deposit(from: @{FungibleToken.Vault}) {
// Assert that the concrete type of the deposited vault is the same
// as the vault that is accepting the deposit
pre {
from.isInstance(self.getType()):
"Cannot deposit an incompatible token type"
}
post {
emit Deposited(
type: before(from.getType().identifier),
amount: before(from.balance),
to: self.owner?.address,
toUUID: self.uuid,
depositedUUID: before(from.uuid),
balanceAfter: self.balance
)
self.balance == before(self.balance) + before(from.balance):
"New Vault balance must be the sum of the previous balance and the deposited Vault"
}
}
/// createEmptyVault allows any user to create a new Vault that has a zero balance
///
access(all) fun createEmptyVault(): @{Vault} {
post {
result.balance == 0.0: "The newly created Vault must have zero balance"
result.getType() == self.getType(): "The newly created Vault must have the same type as the creating vault"
}
}
}
/// createEmptyVault allows any user to create a new Vault that has a zero balance
///
access(all) fun createEmptyVault(vaultType: Type): @{FungibleToken.Vault} {
post {
result.getType() == vaultType: "The returned vault does not match the desired type"
result.balance == 0.0: "The newly created Vault must have zero balance"
}
}
}