Smart Contract
REVVVaultAccess
A.d01e482eb680ec9f.REVVVaultAccess
1import FungibleToken from 0xf233dcee88fe0abe
2import REVV from 0xd01e482eb680ec9f
3
4// The REVVVaultAccess contract's role is to allow the REVV contract account owner ('REVV owner')
5// to grant other accounts ('other account') access to withdraw REVV from the REVV owner's REVV vault
6// while imposing the conditions that:
7// [+] there is a max withdrawal limit per other account and,
8// [+] access to the REVV owner vault can be revoked by the REVV owner.
9//
10// The other account can in this way independently withdraw REVV from the REVV owner's vault,
11// without the need for multi-sig transactions, or REVV owner sending a transfer transaction.
12//
13// The immediate use case is for the TeleportCustody operator account to be able to withdraw REVV
14// from the REVV owner REVV Vault when they need to top up the TeleportCustody contract, without having to ask
15// the REVV owner for a transfer.
16//
17// The VaultProxy and VaultGuard are based on the FUSD contract's MinterProxy and Minter design.
18//
19// How to use the contract:
20// * The REVV vault owner creates a VaultGuard resource with a max amount and an address for the other account that will withdraw the REVV.
21// * The other account creates and saves a VaultProxy resource
22// * The REVV owner sets the VaultGuard capability on the VaultProxy
23// * The other account can now withdraw REVV
24// * The REVV owner can revoke access at any time by unlinking the VaultGuard capability
25//
26access(all) contract REVVVaultAccess {
27
28 access(all) entitlement SetVaultGuard
29
30 // The storage path for the Proxy Vault
31 access(all) let VaultProxyStoragePath: StoragePath
32
33 // The public path for the Proxy Vault
34 access(all) let VaultProxyPublicPath: PublicPath
35
36 // The storage path for the REVV contract Admin
37 access(all) let AdminStoragePath: StoragePath
38
39 // The storage Path for the proxyToGuardMap
40 access(all) view fun getProxyToGuardMapStoragePath(): StoragePath {
41 return /storage/proxyToGuardMap
42 }
43
44 // The amount of REVV authorized for all Vault Guards
45 access(all) var totalAuthorizedAmount: UFix64
46
47 // UNUSED - replaced by VaultGuardStoragePaths - but can't be removed
48 // Dictionary to store a (VaultProxy address) -> (VaultGuard paths) map
49 // The registry helps answer which guards match which proxy.
50 //
51 access(contract) let proxyToGuardMap: { Address : VaultGuardPaths }
52
53 // Dictionary to store a (VaultGuard paths) -> (VaultProxy Address) map
54 // The registry helps answer which proxy owners match which guard
55 //
56 access(contract) let guardToProxyMap: { StoragePath : Address }
57
58 access(all) fun initialize(adminRef: &Admin) {
59 pre {
60 adminRef != nil : "adminRef is nil"
61 }
62 REVVVaultAccess.setProxyToGuardMap({})
63 }
64
65 // UNUSED - replaced by VaultGuardStoragePaths
66 // Struct used to store paths for a Vault
67 // Should be saved in a dictionary with VaultProxy address as key
68 //
69 access(all) struct VaultGuardPaths {
70 access(all) let storagePath: StoragePath
71 access(all) let privatePath: PrivatePath
72 init(storagePath: StoragePath, privatePath: PrivatePath) {
73 self.storagePath = storagePath
74 self.privatePath = privatePath
75 }
76 }
77
78 // replaces above struct and stored in account storage as a dictionary with VaultProxy address as key
79 access(all) struct VaultGuardStoragePaths {
80 access(all) let storagePath: StoragePath
81 access(all) let providerStoragePath: StoragePath
82 init(storagePath: StoragePath, providerStoragePath: StoragePath) {
83 self.storagePath = storagePath
84 self.providerStoragePath = providerStoragePath
85 }
86 }
87
88 // VaultGuard
89 //
90 // The VaultGuard's role is to be the source of a revokable link to the account's REVV vault.
91 //
92 access(all) resource VaultGuard {
93
94 // max is the largest total amount that can be withdrawn using the VaultGuard
95 //
96 access(all) let max: UFix64
97
98 // total keeps track of how much has been withdrawn via the VaultGuard
99 //
100 access(all) var total: UFix64
101
102 // A reference to the vault that holds REVV tokens *with withdraw entitlement*
103 //
104 access(self) let vaultCapability: Capability<auth(FungibleToken.Withdraw) &REVV.Vault>
105
106 // withdraws REVV tokens from the VaultGuard's internal vault reference
107 // Will fail if vault reference is nil / revoked, or amount + previously withdrawn exceeds max
108 //
109 access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @REVV.Vault {
110 pre {
111 (amount + self.total) <= self.max : "total of amount + previously withdrawn exceeds max withdrawal."
112 }
113 self.total = self.total + amount
114 //let vaultRef: auth(FungibleToken.Withdraw) &REVV.Vault = self.vaultCapability.borrow() as! auth(FungibleToken.Withdraw) &REVV.Vault
115 let vaultRef: auth(FungibleToken.Withdraw) &REVV.Vault = self.vaultCapability.borrow() ?? panic("Could not borrow REVV.Vault capability")
116 return <- vaultRef.withdraw(amount: amount)
117 }
118
119 // constructor - takes a REVV vault reference, and a max withdrawal amount
120 //
121 init(vaultCapability: Capability<auth(FungibleToken.Withdraw) &REVV.Vault>, max: UFix64) {
122 pre {
123 vaultCapability != nil : "vaultCapability is nil in REVV.VaultGuard constructor"
124 }
125 self.vaultCapability = vaultCapability
126 self.max = max
127 self.total = 0.0
128 }
129 }
130
131 // createVaultGuard
132 //
133 // @param adminRef - a reference to a REVVVaultAccess.Admin. Only accessible to contract account
134 // @param vaultProxyAddress - the account address where the VaultProxy will be stored
135 // @param maxAmount - the max amount of REVV which VaultGuard will allow the VaultProxy to transfer
136 // @param guardStoragePath - the storage path in the REVVVaultAccess contract owner account
137 // @param guardPrivatePath - the private path linked to the guardStoragePath /// j00lz ****NO LONGER USED****
138
139 //
140 access(all) fun createVaultGuard(adminRef: &Admin, vaultProxyAddress: Address, maxAmount: UFix64, guardStoragePath: StoragePath, guardPrivatePath: PrivatePath, guardWithdrawPath: StoragePath) {
141 pre {
142 adminRef != nil : "adminRef is nil"
143 self.totalAuthorizedAmount + maxAmount <= REVV.MAX_SUPPLY : "Requested max amount + previously authorized amount exceeds max supply"
144 //self.guardToProxyMap.containsKey(guardStoragePath) == false : "VaultGuard StoragePath already registered"
145 }
146 let proxyToGuardMap: {Address: REVVVaultAccess.VaultGuardStoragePaths} = REVVVaultAccess.getProxyToGuardMap()
147 assert(proxyToGuardMap[vaultProxyAddress] == nil, message: "VaultProxy Address already registered")
148
149 self.totalAuthorizedAmount = self.totalAuthorizedAmount + maxAmount
150
151 // get authorized REVV vault capability
152 let vaultWithdrawCap: Capability<auth(FungibleToken.Withdraw) &REVV.Vault> = self.account.storage.load<Capability<auth(FungibleToken.Withdraw) &REVV.Vault>>(from: REVV.getProviderPath())
153 ?? panic("Could not load REVV.Vault provider (FungibleToken.Withdraw entitled) capability")
154
155 // create a VaultGuard and save it in storage
156 let guardVault: @REVVVaultAccess.VaultGuard <- create VaultGuard(vaultCapability: vaultWithdrawCap, max: maxAmount)
157 self.account.storage.save(<- guardVault, to: guardStoragePath)
158
159 // issue capability to the VaultGuard **and save to storage**
160 let vaultGuardCap: Capability<&REVVVaultAccess.VaultGuard> = self.account.capabilities.storage.issue<&VaultGuard>(guardStoragePath)
161 self.account.storage.save(vaultGuardCap, to: guardWithdrawPath)
162
163 // let pathObject: REVVVaultAccess.VaultGuardPaths = VaultGuardPaths(storagePath: guardStoragePath, privatePath: guardPrivatePath)
164
165 // self.proxyToGuardMap.insert(key: vaultProxyAddress, pathObject)
166 self.guardToProxyMap.insert(key: guardStoragePath, vaultProxyAddress)
167
168 // New VaultGuardStoragePaths object
169 let newVaultGuardStoragePaths: REVVVaultAccess.VaultGuardStoragePaths = VaultGuardStoragePaths(storagePath: guardStoragePath, providerStoragePath: guardWithdrawPath)
170 proxyToGuardMap[vaultProxyAddress] = newVaultGuardStoragePaths
171 REVVVaultAccess.setProxyToGuardMap(proxyToGuardMap)
172 }
173
174 // OLD API replaced with new function below
175 // getVaultGuardPaths returns storage path and private path for a VaultGuard
176 // @param account address of VaultProxy using the VaultGuard
177 //
178 access(all) fun getVaultGuardPaths(vaultProxyAddress: Address): VaultGuardPaths? {
179 return self.proxyToGuardMap[vaultProxyAddress]
180 }
181
182 // NEW API. function replaced as we can't change the return type of the function above
183 // getVaultGuardStoragePaths returns storage path and provider storage path for a VaultGuard
184 // @param account address of VaultProxy using the VaultGuard
185 //
186 access(all) fun getVaultGuardStoragePaths(vaultProxyAddress: Address): VaultGuardStoragePaths? {
187 let proxyToGuardMap: {Address: REVVVaultAccess.VaultGuardStoragePaths} =
188 self.account.storage.load<{Address: VaultGuardStoragePaths}>(from: REVVVaultAccess.getProxyToGuardMapStoragePath())
189 ?? panic("Could not load proxyToGuardMap")
190 return proxyToGuardMap[vaultProxyAddress]
191 }
192
193 // getVaultProxyAddress returns an address of a VaultProxy
194 // @param the storage path of the VaultGuard used by the VaultProxy
195 //
196 access(all) fun getVaultProxyAddress(guardStoragePath: StoragePath): Address? {
197 return self.guardToProxyMap[guardStoragePath]
198 }
199
200
201 // returns all VaultProxy addresses
202 //
203 access(all) fun getAllVaultProxyAddresses(): [Address] {
204 return REVVVaultAccess.getProxyToGuardMap().keys
205 }
206
207 // returns all storage paths for VaultGuards
208 //
209 access(all) fun getAllVaultGuardStoragePaths() : [StoragePath] {
210 return self.guardToProxyMap.keys
211 }
212
213 // returns max authorized amount of withdrawal for an account address
214 //
215 access(all) fun getMaxAmountForAccount(vaultProxyAddress: Address): UFix64 {
216 // let paths: REVVVaultAccess.VaultGuardPaths = self.proxyToGuardMap[vaultProxyAddress]!
217 let paths: {Address: REVVVaultAccess.VaultGuardStoragePaths} = REVVVaultAccess.getProxyToGuardMap()
218 let capability: Capability<&REVVVaultAccess.VaultGuard> = self.account.capabilities.get<&REVVVaultAccess.VaultGuard>(REVV.RevvReceiverPublicPath)
219 let vaultRef: &REVVVaultAccess.VaultGuard = capability.borrow()!
220 return vaultRef.max
221 }
222
223 // returns total withdrawn amount for an account address
224 //
225 access(all) fun getTotalAmountForAccount(vaultProxyAddress: Address): UFix64 {
226 let paths: {Address: REVVVaultAccess.VaultGuardStoragePaths} = REVVVaultAccess.getProxyToGuardMap()
227 let capability: Capability<&REVVVaultAccess.VaultGuard> = self.account.capabilities.get<&REVVVaultAccess.VaultGuard>(REVV.RevvReceiverPublicPath)
228 let vaultRef: &REVVVaultAccess.VaultGuard = capability.borrow()!
229 return vaultRef.total
230 }
231
232 // revokes withdrawal capability for an account
233 //
234 access(all) fun revokeVaultGuard(adminRef: &Admin, vaultProxyAddress: Address){
235 pre {
236 adminRef != nil : "adminRef is nil"
237 }
238 let paths: REVVVaultAccess.VaultGuardStoragePaths = self.getVaultGuardStoragePaths(vaultProxyAddress: vaultProxyAddress)!
239
240 //remove from maps
241 self.proxyToGuardMap.remove(key: vaultProxyAddress)
242 self.guardToProxyMap.remove(key: paths.storagePath)
243
244 // remove from new map in storage
245 let proxyToGuardMap: {Address: REVVVaultAccess.VaultGuardStoragePaths} = REVVVaultAccess.getProxyToGuardMap()
246 proxyToGuardMap.remove(key: vaultProxyAddress)
247
248 // save updated map to storage
249 REVVVaultAccess.setProxyToGuardMap(proxyToGuardMap)
250
251 //delete guards
252 let guardVault: @REVVVaultAccess.VaultGuard <- self.account.storage.load<@REVVVaultAccess.VaultGuard>(from: paths.storagePath)!
253 self.totalAuthorizedAmount = self.totalAuthorizedAmount - guardVault.max
254 destroy guardVault
255 }
256
257 // interface which allows setting of VaultGuard capability
258 //
259 access(all) resource interface VaultProxyPublic {
260 access(SetVaultGuard) fun setCapability(cap: Capability<auth(FungibleToken.Withdraw) &REVVVaultAccess.VaultGuard>)
261 }
262
263 // VaultProxy is a resource to allow designated other accounts to retrieve REVV from the REVV contract's REVV vault.
264 // Any account can call createVaultProxy() to create a VaultProxy, but only if REVV account calls setCapability
265 // on the VaultProxy, can REVV be withdrawn
266 //
267 access(all) resource VaultProxy: VaultProxyPublic {
268 access(self) var vaultGuardCap: Capability<auth(FungibleToken.Withdraw) &REVVVaultAccess.VaultGuard>?
269
270 // withdraw REVV. ***MUST** be kept private / non-publicly accessible after setCapability has been called
271 // Will fail unless REVV contract account has set a capability using setCapability
272 //
273 access(all) fun withdraw(amount: UFix64): @{FungibleToken.Vault} {
274 pre {
275 self.vaultGuardCap!.check() == true : "Can't withdraw. vaultGuardCap.check() failed"
276 }
277 let cap: auth(FungibleToken.Withdraw) &REVVVaultAccess.VaultGuard?= self.vaultGuardCap!.borrow()
278 return <- cap!.withdraw(amount: amount)
279 }
280
281 // set a REVV.VaultGuard capability, to allow withdrawal.
282 // Only the REVV contract account can create a VaultGuard so the method can be publicly accessible.
283 //
284 access(SetVaultGuard) fun setCapability(cap: Capability<auth(FungibleToken.Withdraw) &REVVVaultAccess.VaultGuard>) {
285 pre {
286 cap.check() == true : "Capability<&REVV.VaultGuard> failed check()"
287 cap != nil : "Setting Capability<&REVV.VaultGuard> that is nil"
288 }
289 self.vaultGuardCap = cap
290 }
291
292 init() {
293 self.vaultGuardCap = nil
294 }
295
296 }
297
298 // Anyone can create a VaultProxy but it's useless until the Vault Guard capability is set on it.
299 // Only the REVVVaultAccess owner can create and set a VaultGuard capability
300 //
301 access(all) fun createVaultProxy(): @REVVVaultAccess.VaultProxy {
302 return <- create VaultProxy()
303 }
304
305 // Admin resource
306 //
307 access(all) resource Admin { }
308
309 // Helper Functions for loading/saving the proxyToGuardMap
310 access(contract) fun getProxyToGuardMap(): {Address: REVVVaultAccess.VaultGuardStoragePaths} {
311 let proxyToGuardMap: {Address: REVVVaultAccess.VaultGuardStoragePaths} =
312 self.account.storage.load<{Address: VaultGuardStoragePaths}>(from: REVVVaultAccess.getProxyToGuardMapStoragePath())
313 ?? panic("Could not load proxyToGuardMap")
314 return proxyToGuardMap
315 }
316
317 access(contract) fun setProxyToGuardMap(_ proxyToGuardMap: {Address: REVVVaultAccess.VaultGuardStoragePaths}) {
318 self.account.storage.save(proxyToGuardMap, to: REVVVaultAccess.getProxyToGuardMapStoragePath())
319 }
320
321 init() {
322
323 self.totalAuthorizedAmount = 0.0
324
325 self.proxyToGuardMap = {}
326
327 self.guardToProxyMap = {}
328
329 REVVVaultAccess.setProxyToGuardMap({})
330
331 self.VaultProxyStoragePath = /storage/revvVaultProxy
332
333 self.VaultProxyPublicPath = /public/revvVaultProxy
334
335 self.AdminStoragePath = /storage/revvVaultAccessAdmin
336
337 // create an Admin and save it in storage
338 //
339 let admin: @REVVVaultAccess.Admin <- create Admin()
340 self.account.storage.save(<- admin, to: self.AdminStoragePath)
341 }
342}