Smart Contract

REVVVaultAccess

A.d01e482eb680ec9f.REVVVaultAccess

Deployed

1d ago
Feb 26, 2026, 10:51:09 PM UTC

Dependents

0 imports
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}