Smart Contract

TestHelpers

A.a092c4aab33daeda.TestHelpers

Valid From

138,375,759

Deployed

1w ago
Feb 15, 2026, 11:01:02 PM UTC

Dependents

1 imports
1import FungibleToken from 0xf233dcee88fe0abe
2import DeFiActions from 0x92195d814edf9cb0
3
4/// TestHelpers - Production-Safe Vault Connector for Testing
5/// 
6/// This contract provides secure mock implementations of DeFiActions interfaces
7/// for testing purposes. It follows the same security pattern as FlowYieldVaultsConnector.
8///
9/// SECURITY MODEL:
10/// - Vault capabilities are stored in a Resource (VaultManager), NOT in structs
11/// - Resources cannot be copied, protecting the underlying vault capabilities
12/// - The Connector struct only holds a capability to the VaultManager resource
13/// - Only the VaultManager owner (account) can create Connectors
14/// - Public interface only exposes safe operations (deposit, balance check)
15/// - Withdraw operations require entitled capability that can be revoked
16/// - All operations emit events for transparency and monitoring
17///
18/// USAGE:
19/// 1. Call createVaultManagerAndConnector() with your account to set up the manager
20/// 2. Use the returned VaultConnector with PrizeSavings
21/// 3. Fund the manager's underlying vault for testing
22///
23/// NOTE: While this is production-safe, it should primarily be used for testing.
24/// For production yield sources, use proper DeFi integrations like FlowYieldVaultsConnector.
25///
26access(all) contract TestHelpers {
27    
28    // Entitlement for withdraw operations - only entitled references can withdraw
29    access(all) entitlement Withdraw
30    
31    // Storage paths
32    access(all) let VaultManagerStoragePath: StoragePath
33    access(all) let VaultManagerPublicPath: PublicPath
34    
35    // Events for transparency and monitoring
36    access(all) event VaultManagerCreated(ownerAddress: Address, vaultType: String)
37    access(all) event ConnectorCreated(managerAddress: Address, vaultType: String)
38    access(all) event DepositedToManager(managerAddress: Address, amount: UFix64, vaultType: String)
39    access(all) event WithdrawnFromManager(managerAddress: Address, amount: UFix64, vaultType: String)
40    
41    /// Public interface - exposes only safe operations
42    /// Anyone can deposit funds and check balance, but cannot withdraw
43    access(all) resource interface VaultManagerPublic {
44        access(all) view fun getBalance(): UFix64
45        access(all) view fun getVaultType(): Type
46        access(all) fun deposit(from: @{FungibleToken.Vault})
47    }
48    
49    /// VaultManager Resource
50    /// 
51    /// SECURITY: This resource holds the sensitive vault capabilities.
52    /// Resources cannot be copied, so the capabilities are protected.
53    /// Only entitled references can call withdrawFunds.
54    ///
55    access(all) resource VaultManager: VaultManagerPublic {
56        /// Private: Vault provider capability - can withdraw funds
57        access(self) let providerCap: Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Provider, FungibleToken.Balance}>
58        
59        /// Private: Vault receiver capability - can receive deposits
60        access(self) let receiverCap: Capability<&{FungibleToken.Receiver}>
61        
62        /// Public: Vault type for type checking
63        access(all) let vaultType: Type
64        
65        /// Public: Owner address for monitoring
66        access(all) let ownerAddress: Address
67        
68        init(
69            providerCap: Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Provider, FungibleToken.Balance}>,
70            receiverCap: Capability<&{FungibleToken.Receiver}>,
71            vaultType: Type,
72            ownerAddress: Address
73        ) {
74            pre {
75                providerCap.check(): "Invalid provider capability"
76                receiverCap.check(): "Invalid receiver capability"
77            }
78            self.providerCap = providerCap
79            self.receiverCap = receiverCap
80            self.vaultType = vaultType
81            self.ownerAddress = ownerAddress
82        }
83        
84        /// Get current balance - SAFE: read-only operation
85        access(all) view fun getBalance(): UFix64 {
86            if let provider = self.providerCap.borrow() {
87                return provider.balance
88            }
89            return 0.0
90        }
91        
92        /// Get vault type - SAFE: read-only operation
93        access(all) view fun getVaultType(): Type {
94            return self.vaultType
95        }
96        
97        /// Deposit funds - SAFE: adding funds doesn't risk existing funds
98        access(all) fun deposit(from: @{FungibleToken.Vault}) {
99            pre {
100                from.getType() == self.vaultType: "Vault type mismatch: expected ".concat(self.vaultType.identifier)
101            }
102            let amount = from.balance
103            let receiver = self.receiverCap.borrow() ?? panic("Cannot borrow receiver capability")
104            receiver.deposit(from: <- from)
105            emit DepositedToManager(
106                managerAddress: self.ownerAddress,
107                amount: amount,
108                vaultType: self.vaultType.identifier
109            )
110        }
111        
112        /// Withdraw funds - PROTECTED: requires Withdraw entitlement
113        /// Only callable through an entitled reference, which can only be issued
114        /// by the account that stores this resource.
115        access(Withdraw) fun withdrawFunds(maxAmount: UFix64): @{FungibleToken.Vault} {
116            let provider = self.providerCap.borrow() 
117                ?? panic("Cannot borrow provider capability")
118            let available = provider.balance
119            let toWithdraw = maxAmount < available ? maxAmount : available
120            
121            if toWithdraw == 0.0 {
122                // Return empty vault of correct type
123                return <- provider.withdraw(amount: 0.0)
124            }
125            
126            let vault <- provider.withdraw(amount: toWithdraw)
127            emit WithdrawnFromManager(
128                managerAddress: self.ownerAddress,
129                amount: toWithdraw,
130                vaultType: self.vaultType.identifier
131            )
132            return <- vault
133        }
134    }
135    
136    /// VaultConnector Struct
137    /// 
138    /// Implements DeFiActions.Sink and DeFiActions.Source for use with PrizeSavings.
139    /// 
140    /// SECURITY:
141    /// - Only holds a capability to VaultManager, NOT the raw vault capability
142    /// - Deposit uses public capability (safe - anyone can deposit)
143    /// - Withdraw uses entitled capability (protected - requires Withdraw entitlement)
144    /// - The entitled capability can only be issued by the VaultManager owner
145    ///
146    access(all) struct VaultConnector: DeFiActions.Sink, DeFiActions.Source {
147        /// Address of the account that owns the VaultManager resource
148        access(all) let managerAddress: Address
149        
150        /// Entitled capability to VaultManager for withdraw operations
151        /// This can only be issued by the account that stores the VaultManager
152        access(self) let withdrawCap: Capability<auth(Withdraw) &VaultManager>
153        
154        /// Vault type for interface compliance
155        access(all) let vaultType: Type
156        
157        /// DeFiActions unique ID
158        access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
159        
160        init(
161            managerAddress: Address,
162            withdrawCap: Capability<auth(Withdraw) &VaultManager>,
163            vaultType: Type
164        ) {
165            pre {
166                withdrawCap.check(): "Invalid withdraw capability"
167            }
168            self.managerAddress = managerAddress
169            self.withdrawCap = withdrawCap
170            self.vaultType = vaultType
171            self.uniqueID = nil
172        }
173        
174        // ============================================================
175        // DeFiActions.Sink Implementation
176        // ============================================================
177        
178        /// Deposit funds into the managed vault
179        /// Uses PUBLIC capability - safe operation
180        access(all) fun depositCapacity(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) {
181            let managerAccount = getAccount(self.managerAddress)
182            let managerRef = managerAccount.capabilities.borrow<&{VaultManagerPublic}>(
183                TestHelpers.VaultManagerPublicPath
184            ) ?? panic("Cannot borrow VaultManager from ".concat(self.managerAddress.toString()))
185            
186            let amount = from.balance
187            managerRef.deposit(from: <- from.withdraw(amount: amount))
188        }
189        
190        access(all) view fun getSinkType(): Type {
191            return self.vaultType
192        }
193        
194        access(all) view fun minimumCapacity(): UFix64 {
195            return 0.0
196        }
197        
198        // ============================================================
199        // DeFiActions.Source Implementation
200        // ============================================================
201        
202        /// Get available balance
203        /// Uses PUBLIC capability - safe read-only operation
204        access(all) view fun minimumAvailable(): UFix64 {
205            let managerAccount = getAccount(self.managerAddress)
206            if let managerRef = managerAccount.capabilities.borrow<&{VaultManagerPublic}>(
207                TestHelpers.VaultManagerPublicPath
208            ) {
209                return managerRef.getBalance()
210            }
211            return 0.0
212        }
213        
214        /// Withdraw funds from the managed vault
215        /// Uses ENTITLED capability - protected operation
216        access(FungibleToken.Withdraw) fun withdrawAvailable(maxAmount: UFix64): @{FungibleToken.Vault} {
217            let managerRef = self.withdrawCap.borrow() 
218                ?? panic("Cannot borrow VaultManager with Withdraw entitlement. Capability may have been revoked.")
219            return <- managerRef.withdrawFunds(maxAmount: maxAmount)
220        }
221        
222        access(all) view fun getSourceType(): Type {
223            return self.vaultType
224        }
225        
226        // ============================================================
227        // DeFiActions Component Interface
228        // ============================================================
229        
230        access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
231            return DeFiActions.ComponentInfo(
232                type: self.getType(),
233                id: self.uniqueID?.id,
234                innerComponents: []
235            )
236        }
237        
238        access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
239            return self.uniqueID
240        }
241        
242        access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
243            self.uniqueID = id
244        }
245    }
246    
247    // ============================================================
248    // Factory Functions
249    // ============================================================
250    
251    /// Create a VaultManager resource and store it in the caller's account.
252    /// Returns a VaultConnector that can be used with PrizeSavings.
253    ///
254    /// SECURITY: This function requires auth(Storage, Capabilities) which means
255    /// only the account owner can call it through a transaction they sign.
256    ///
257    /// @param account: The account to store the VaultManager in (must be signer)
258    /// @param providerCap: Capability to withdraw from the vault (will be protected)
259    /// @param receiverCap: Capability to deposit into the vault
260    /// @param vaultType: Type of the fungible token vault
261    /// @return VaultConnector struct for use with PrizeSavings
262    ///
263    access(all) fun createVaultManagerAndConnector(
264        account: auth(Storage, Capabilities) &Account,
265        providerCap: Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Provider, FungibleToken.Balance}>,
266        receiverCap: Capability<&{FungibleToken.Receiver}>,
267        vaultType: Type
268    ): VaultConnector {
269        pre {
270            providerCap.check(): "Invalid provider capability"
271            receiverCap.check(): "Invalid receiver capability"
272            account.storage.borrow<&VaultManager>(from: self.VaultManagerStoragePath) == nil: 
273                "VaultManager already exists at this storage path"
274        }
275        
276        // Create and store the VaultManager resource
277        let manager <- create VaultManager(
278            providerCap: providerCap,
279            receiverCap: receiverCap,
280            vaultType: vaultType,
281            ownerAddress: account.address
282        )
283        
284        account.storage.save(<-manager, to: self.VaultManagerStoragePath)
285        
286        // Issue and publish PUBLIC capability (deposit and balance only)
287        let publicCap = account.capabilities.storage.issue<&{VaultManagerPublic}>(
288            self.VaultManagerStoragePath
289        )
290        account.capabilities.publish(publicCap, at: self.VaultManagerPublicPath)
291        
292        // Issue ENTITLED capability for withdrawals (NOT published publicly)
293        // This capability is stored in the returned connector
294        let withdrawCap = account.capabilities.storage.issue<auth(Withdraw) &VaultManager>(
295            self.VaultManagerStoragePath
296        )
297        
298        emit VaultManagerCreated(
299            ownerAddress: account.address, 
300            vaultType: vaultType.identifier
301        )
302        emit ConnectorCreated(
303            managerAddress: account.address, 
304            vaultType: vaultType.identifier
305        )
306        
307        return VaultConnector(
308            managerAddress: account.address,
309            withdrawCap: withdrawCap,
310            vaultType: vaultType
311        )
312    }
313    
314    /// Create an additional connector for an existing VaultManager.
315    /// Useful if you need to use the same vault for multiple pools.
316    ///
317    /// SECURITY: Requires auth(Capabilities) - only account owner can create connectors
318    ///
319    access(all) fun createAdditionalConnector(
320        account: auth(Capabilities) &Account
321    ): VaultConnector {
322        // Verify the account has a VaultManager by borrowing public capability
323        let publicCap = account.capabilities.get<&{VaultManagerPublic}>(self.VaultManagerPublicPath)
324        let managerRef = publicCap.borrow() 
325            ?? panic("No VaultManager found. Call createVaultManagerAndConnector first.")
326        
327        let vaultType = managerRef.getVaultType()
328        
329        // Issue a new entitled capability for this connector
330        let withdrawCap = account.capabilities.storage.issue<auth(Withdraw) &VaultManager>(
331            self.VaultManagerStoragePath
332        )
333        
334        emit ConnectorCreated(
335            managerAddress: account.address, 
336            vaultType: vaultType.identifier
337        )
338        
339        return VaultConnector(
340            managerAddress: account.address,
341            withdrawCap: withdrawCap,
342            vaultType: vaultType
343        )
344    }
345    
346    // ============================================================
347    // Contract Initialization
348    // ============================================================
349    
350    init() {
351        // Use contract address in path identifier for uniqueness
352        let identifier = "testHelpersVaultManager_".concat(self.account.address.toString())
353        self.VaultManagerStoragePath = StoragePath(identifier: identifier)!
354        self.VaultManagerPublicPath = PublicPath(identifier: identifier)!
355    }
356}
357