From 1665d24ddfbba8b89367fc99df98ae1d6bf1b46a Mon Sep 17 00:00:00 2001 From: chumeston Date: Thu, 8 Jan 2026 09:12:01 -0600 Subject: [PATCH] feat(contracts): add CrossVMConnectors for unified cross-VM balance sourcing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add CrossVMConnectors.cdc implementing DeFiActions.Source interface - Enables withdrawals from combined Cadence vault + EVM COA balance - Withdrawal priority: Cadence vault → COA native FLOW → COA ERC-20 via bridge - Add tests covering all withdrawal scenarios - Add test transaction for unified balance withdrawals --- .../connectors/CrossVMConnectors.cdc | 245 ++++++++++++++++++ cadence/tests/CrossVMConnectors_test.cdc | 243 +++++++++++++++++ .../withdraw_via_unified_source.cdc | 96 +++++++ flow.json | 7 + 4 files changed, 591 insertions(+) create mode 100644 cadence/contracts/connectors/CrossVMConnectors.cdc create mode 100644 cadence/tests/CrossVMConnectors_test.cdc create mode 100644 cadence/tests/transactions/cross-vm-connectors/withdraw_via_unified_source.cdc diff --git a/cadence/contracts/connectors/CrossVMConnectors.cdc b/cadence/contracts/connectors/CrossVMConnectors.cdc new file mode 100644 index 00000000..f3affc69 --- /dev/null +++ b/cadence/contracts/connectors/CrossVMConnectors.cdc @@ -0,0 +1,245 @@ +import "FungibleToken" +import "FlowToken" +import "EVM" +import "FlowEVMBridge" +import "FlowEVMBridgeConfig" +import "FlowEVMBridgeUtils" +import "ScopedFTProviders" + +import "DeFiActions" +import "DeFiActionsUtils" + +/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +/// THIS CONTRACT IS IN BETA AND IS NOT FINALIZED - INTERFACES MAY CHANGE AND/OR PENDING CHANGES MAY REQUIRE REDEPLOYMENT +/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +/// +/// CrossVMConnectors +/// +/// This contract defines DeFi Actions Source connector implementations for unified cross-VM balance operations. These +/// connectors can be used alone or in conjunction with other DeFi Actions connectors to create complex DeFi workflows +/// that span both Cadence vaults and EVM Cadence Owned Accounts (COA). +/// +access(all) contract CrossVMConnectors { + + /// UnifiedBalanceSource + /// + /// A DeFiActions.Source connector that provides unified balance sourcing across Cadence and EVM. + /// + /// Withdrawal Priority: + /// 1. Cadence vault balance (no fees) + /// 2. COA native FLOW balance - for FlowToken only (no bridge fees) + /// 3. COA ERC-20 balance via bridge (incurs bridge fees) + /// + /// This ordering minimizes bridge fees by preferring Cadence and native FLOW withdrawals. + /// + /// Usage: + /// ```cadence + /// let source = CrossVMConnectors.UnifiedBalanceSource( + /// vaultType: Type<@FlowToken.Vault>(), + /// cadenceVault: vaultCap, + /// coa: coaCap, + /// feeProvider: feeProviderCap, + /// availableCadenceBalance: signer.availableBalance, + /// uniqueID: DeFiActions.createUniqueIdentifier() + /// ) + /// let vault <- source.withdrawAvailable(maxAmount: 100.0) + /// ``` + /// + access(all) struct UnifiedBalanceSource: DeFiActions.Source { + /// The FungibleToken vault type this source provides (e.g., Type<@FlowToken.Vault>()) + access(all) let vaultType: Type + /// Whether this source handles FlowToken (enables native FLOW optimization) + access(all) let isFlowToken: Bool + /// The EVM contract address of the bridged token + access(all) let evmAddress: EVM.EVMAddress + /// Available Cadence balance at initialization. For FlowToken, pass signer.availableBalance to account for + /// storage reservation. For other tokens, pass vault.balance. + access(all) let availableCadenceBalance: UFix64 + /// Capability to withdraw from the Cadence vault + access(self) let cadenceVault: Capability + /// Capability to the COA for native withdrawals and bridging + access(self) let coa: Capability + /// Capability to provide FLOW for bridge fees + access(self) let feeProvider: Capability + /// Optional identifier for DeFiActions tracing + access(contract) var uniqueID: DeFiActions.UniqueIdentifier? + + /// Creates a new UnifiedBalanceSource + /// + /// @param vaultType: The FungibleToken vault type to withdraw + /// @param cadenceVault: Capability to the user's Cadence vault (must be valid) + /// @param coa: Capability to the user's COA (must be valid) + /// @param feeProvider: Capability for bridge fee payment (must be valid) + /// @param availableCadenceBalance: Pre-computed available balance from Cadence. For FlowToken, use + /// signer.availableBalance to account for storage reservation. For other tokens, use vault.balance. + /// @param uniqueID: Optional identifier for Flow Actions tracing + /// + init( + vaultType: Type, + cadenceVault: Capability, + coa: Capability, + feeProvider: Capability, + availableCadenceBalance: UFix64, + uniqueID: DeFiActions.UniqueIdentifier? + ) { + pre { + cadenceVault.check(): + "Provided invalid Cadence vault Capability" + coa.check(): + "Provided invalid COA Capability" + feeProvider.check(): + "Provided invalid fee provider Capability" + DeFiActionsUtils.definingContractIsFungibleToken(vaultType): + "The contract defining Vault \(vaultType.identifier) does not conform to FungibleToken contract interface" + } + let evmAddr = FlowEVMBridge.getAssociatedEVMAddress(with: vaultType) + ?? panic("Token type \(vaultType.identifier) is not bridgeable - ensure the token is onboarded to the VM bridge") + + self.vaultType = vaultType + self.isFlowToken = vaultType == Type<@FlowToken.Vault>() + self.evmAddress = evmAddr + self.availableCadenceBalance = availableCadenceBalance + self.cadenceVault = cadenceVault + self.coa = coa + self.feeProvider = feeProvider + self.uniqueID = uniqueID + } + + /// Returns a ComponentInfo struct containing information about this UnifiedBalanceSource and its inner DFA + /// components + /// + /// @return a ComponentInfo struct containing information about this component and a list of ComponentInfo for + /// each inner component in the stack. + /// + access(all) fun getComponentInfo(): DeFiActions.ComponentInfo { + return DeFiActions.ComponentInfo( + type: self.getType(), + id: self.id(), + innerComponents: [] + ) + } + + /// Returns the Vault type provided by this Source + /// + /// @return the type of the Vault this Source provides + /// + access(all) view fun getSourceType(): Type { + return self.vaultType + } + + /// Returns a copy of the struct's UniqueIdentifier, used in extending a stack to identify another connector in + /// a DeFiActions stack. See DeFiActions.align() for more information. + /// + /// @return a copy of the struct's UniqueIdentifier + /// + access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? { + return self.uniqueID + } + + /// Sets the UniqueIdentifier of this component to the provided UniqueIdentifier, used in extending a stack to + /// identify another connector in a DeFiActions stack. See DeFiActions.align() for more information. + /// + /// @param id: the UniqueIdentifier to set for this component + /// + access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) { + self.uniqueID = id + } + + /// Returns an estimate of how much of the associated Vault can be provided by this Source + /// + /// @return the total available balance across Cadence vault and COA + /// + access(all) fun minimumAvailable(): UFix64 { + return self._getCadenceBalance() + self._getCOABalance() + } + + /// Withdraws the lesser of maxAmount or minimumAvailable(). If none is available, an empty Vault is returned. + /// Withdrawal priority: Cadence vault → COA native FLOW (for FlowToken) → COA ERC-20 via bridge. + /// + /// @param maxAmount: the maximum amount to withdraw + /// + /// @return a Vault containing the withdrawn funds (may be less than maxAmount if insufficient balance) + /// + access(FungibleToken.Withdraw) fun withdrawAvailable(maxAmount: UFix64): @{FungibleToken.Vault} { + let available = self.minimumAvailable() + if available == 0.0 || maxAmount == 0.0 { + return <-DeFiActionsUtils.getEmptyVault(self.vaultType) + } + + let withdrawAmount = available <= maxAmount ? available : maxAmount + let cadenceBalance = self._getCadenceBalance() + + // Calculate amounts from each source + let amountFromCadence = cadenceBalance < withdrawAmount ? cadenceBalance : withdrawAmount + var amountFromCOA = withdrawAmount - amountFromCadence + + // Withdraw from Cadence vault + let vault = self.cadenceVault.borrow()! + let result <- vault.withdraw(amount: amountFromCadence) + + // Bridge from COA if Cadence balance was insufficient + if amountFromCOA > 0.0 { + let coaRef = self.coa.borrow()! + var remaining = amountFromCOA + + // For FlowToken: withdraw native FLOW first (no bridge fees) + if self.isFlowToken { + let nativeFlowBalance = coaRef.balance().inFLOW() + if nativeFlowBalance > 0.0 { + let nativeWithdraw = nativeFlowBalance < remaining ? nativeFlowBalance : remaining + let withdrawBal = EVM.Balance(attoflow: 0) + withdrawBal.setFLOW(flow: nativeWithdraw) + result.deposit(from: <-coaRef.withdraw(balance: withdrawBal)) + remaining = remaining - nativeWithdraw + } + } + + // Bridge ERC-20 if still needed + if remaining > 0.0 { + let scopedProvider <- ScopedFTProviders.createScopedFTProvider( + provider: self.feeProvider, + filters: [ScopedFTProviders.AllowanceFilter(FlowEVMBridgeUtils.calculateBridgeFee(bytes: 400_000))], + expiration: getCurrentBlock().timestamp + 1.0 + ) + + let bridged <- coaRef.withdrawTokens( + type: self.vaultType, + amount: FlowEVMBridgeUtils.convertCadenceAmountToERC20Amount(remaining, erc20Address: self.evmAddress), + feeProvider: &scopedProvider as auth(FungibleToken.Withdraw) &{FungibleToken.Provider} + ) + result.deposit(from: <-bridged) + destroy scopedProvider + } + } + + return <-result + } + + /// Returns the available Cadence balance (passed at initialization) + access(self) fun _getCadenceBalance(): UFix64 { + return self.availableCadenceBalance + } + + /// Returns the total COA balance (native FLOW for FlowToken + ERC-20) + access(self) fun _getCOABalance(): UFix64 { + if let coaRef = self.coa.borrow() { + var balance: UFix64 = 0.0 + + // Add native FLOW balance for FlowToken + if self.isFlowToken { + balance = balance + coaRef.balance().inFLOW() + } + + // Add ERC-20 balance + let erc20Balance = FlowEVMBridgeUtils.convertERC20AmountToCadenceAmount( + FlowEVMBridgeUtils.balanceOf(owner: coaRef.address(), evmContractAddress: self.evmAddress), + erc20Address: self.evmAddress + ) + balance = balance + erc20Balance + + return balance + } + return 0.0 + } + } +} diff --git a/cadence/tests/CrossVMConnectors_test.cdc b/cadence/tests/CrossVMConnectors_test.cdc new file mode 100644 index 00000000..104c8149 --- /dev/null +++ b/cadence/tests/CrossVMConnectors_test.cdc @@ -0,0 +1,243 @@ +import Test +import BlockchainHelpers +import "test_helpers.cdc" + +import "FungibleToken" +import "FlowToken" +import "TokenA" +import "EVM" +import "DeFiActions" + +access(all) let serviceAccount = Test.serviceAccount() +access(all) let bridgeAccount = Test.getAccount(0x0000000000000007) +access(all) let tokenAAccount = Test.getAccount(0x0000000000000010) +access(all) var tokenAERCAddress = "" +access(all) var wflowHex = "" + +access(all) fun setup() { + log("================== Setting up CrossVMConnectors test ==================") + wflowHex = getEVMAddressAssociated(withType: Type<@FlowToken.Vault>().identifier)! + + var err = Test.deployContract( + name: "TestTokenMinter", + path: "./contracts/TestTokenMinter.cdc", + arguments: [], + ) + Test.expect(err, Test.beNil()) + err = Test.deployContract( + name: "TokenA", + path: "./contracts/TokenA.cdc", + arguments: [], + ) + Test.expect(err, Test.beNil()) + + // fund the tokenA account to pay for VM Bridge onboarding + transferFlow(signer: serviceAccount, recipient: tokenAAccount.address, amount: 100.0) + + // onboard TokenA to the bridge + let onboardResult = _executeTransaction( + "./transactions/bridge/onboard_by_type_identifier.cdc", + [Type<@TokenA.Vault>().identifier], + tokenAAccount + ) + Test.expect(onboardResult, Test.beSucceeded()) + + // get the EVM address associated with the TokenA type + tokenAERCAddress = getEVMAddressAssociated(withType: Type<@TokenA.Vault>().identifier)! + + err = Test.deployContract( + name: "DeFiActionsUtils", + path: "../contracts/utils/DeFiActionsUtils.cdc", + arguments: [], + ) + Test.expect(err, Test.beNil()) + err = Test.deployContract( + name: "DeFiActions", + path: "../contracts/interfaces/DeFiActions.cdc", + arguments: [], + ) + Test.expect(err, Test.beNil()) + err = Test.deployContract( + name: "FungibleTokenConnectors", + path: "../contracts/connectors/FungibleTokenConnectors.cdc", + arguments: [], + ) + Test.expect(err, Test.beNil()) + err = Test.deployContract( + name: "CrossVMConnectors", + path: "../contracts/connectors/CrossVMConnectors.cdc", + arguments: [], + ) + Test.expect(err, Test.beNil()) +} + +/// Test: Withdraw FLOW from Cadence vault only (no COA balance) +access(all) fun testUnifiedSourceWithdrawFromCadenceOnlySucceeds() { + // create a user account and fund it with FLOW + let user = Test.createAccount() + let flowBalance = 100.0 + transferFlow(signer: serviceAccount, recipient: user.address, amount: flowBalance) + + // create a COA for the user (required for UnifiedBalanceSource) + createCOA(user, fundingAmount: 0.0) + + // get initial Cadence balance + var cadenceBalance = getBalance(address: user.address, vaultPublicPath: /public/flowTokenReceiver)! + Test.assertEqual(flowBalance, cadenceBalance) + + // withdraw 50 FLOW via UnifiedBalanceSource - should come from Cadence only + let withdrawAmount = 50.0 + let withdrawResult = _executeTransaction( + "./transactions/cross-vm-connectors/withdraw_via_unified_source.cdc", + [withdrawAmount, Type<@FlowToken.Vault>().identifier, nil], + user + ) + Test.expect(withdrawResult, Test.beSucceeded()) + + // verify the withdrawal succeeded - balance should remain the same since we withdraw and deposit to same vault + cadenceBalance = getBalance(address: user.address, vaultPublicPath: /public/flowTokenReceiver)! + // Note: withdrawing and depositing to same vault results in same balance + Test.assert(cadenceBalance > 0.0, message: "Cadence balance should be positive") +} + +/// Test: Withdraw FLOW from COA native balance (when Cadence vault is empty) +access(all) fun testUnifiedSourceWithdrawFromCOANativeSucceeds() { + // create a user account and fund it + let user = Test.createAccount() + let flowBalance = 100.0 + transferFlow(signer: serviceAccount, recipient: user.address, amount: flowBalance) + + // create a COA for the user and fund it with native FLOW + let coaFunding = 50.0 + createCOA(user, fundingAmount: coaFunding) + + // get the COA address + let coaAddressHex = getCOAAddressHex(atFlowAddress: user.address) + + // verify COA has native FLOW balance + let evmBalance = getEVMFlowBalance(coaAddressHex) + Test.assertEqual(coaFunding, evmBalance) + + // withdraw FLOW via UnifiedBalanceSource + let withdrawAmount = 25.0 + let withdrawResult = _executeTransaction( + "./transactions/cross-vm-connectors/withdraw_via_unified_source.cdc", + [withdrawAmount, Type<@FlowToken.Vault>().identifier, nil], + user + ) + Test.expect(withdrawResult, Test.beSucceeded()) + + // verify the COA still has some balance + let evmBalanceAfter = getEVMFlowBalance(coaAddressHex) + Test.assert(evmBalanceAfter >= 0.0, message: "COA balance should be non-negative") +} + +/// Test: Withdraw FLOW from combined Cadence + COA native balance +access(all) fun testUnifiedSourceWithdrawFromCombinedBalanceSucceeds() { + // create a user account and fund it + let user = Test.createAccount() + let flowBalance = 100.0 + transferFlow(signer: serviceAccount, recipient: user.address, amount: flowBalance) + + // create a COA for the user and fund it with native FLOW + let coaFunding = 50.0 + createCOA(user, fundingAmount: coaFunding) + + // get the COA address + let coaAddressHex = getCOAAddressHex(atFlowAddress: user.address) + + // get initial balances + let cadenceBalanceBefore = getBalance(address: user.address, vaultPublicPath: /public/flowTokenReceiver)! + let evmBalanceBefore = getEVMFlowBalance(coaAddressHex) + + // combined balance should be positive + let combinedBalance = cadenceBalanceBefore + evmBalanceBefore + Test.assert(combinedBalance > 0.0, message: "Combined balance should be positive") + + // withdraw a small amount via UnifiedBalanceSource + let withdrawAmount = 10.0 + let withdrawResult = _executeTransaction( + "./transactions/cross-vm-connectors/withdraw_via_unified_source.cdc", + [withdrawAmount, Type<@FlowToken.Vault>().identifier, nil], + user + ) + Test.expect(withdrawResult, Test.beSucceeded()) +} + +/// Test: Withdraw TokenA from Cadence vault +access(all) fun testUnifiedSourceWithdrawTokenAFromCadenceSucceeds() { + // create a user account and fund it + let user = Test.createAccount() + let flowBalance = 10.0 + let tokenABalance = 100.0 + + transferFlow(signer: serviceAccount, recipient: user.address, amount: flowBalance) + setupGenericVault(signer: user, vaultIdentifier: Type<@TokenA.Vault>().identifier) + mintTestTokens(signer: tokenAAccount, recipient: user.address, amount: tokenABalance, minterStoragePath: TokenA.AdminStoragePath, receiverPublicPath: TokenA.ReceiverPublicPath) + + // create a COA for the user (required for UnifiedBalanceSource) + createCOA(user, fundingAmount: 0.0) + + // get initial TokenA balance + var cadenceBalance = getBalance(address: user.address, vaultPublicPath: TokenA.ReceiverPublicPath)! + Test.assertEqual(tokenABalance, cadenceBalance) + + // withdraw 50 TokenA via UnifiedBalanceSource - should come from Cadence only + let withdrawAmount = 50.0 + let withdrawResult = _executeTransaction( + "./transactions/cross-vm-connectors/withdraw_via_unified_source.cdc", + [withdrawAmount, Type<@TokenA.Vault>().identifier, nil], + user + ) + Test.expect(withdrawResult, Test.beSucceeded()) + + // verify the withdrawal succeeded - balance should be same since we withdraw/deposit to same vault + cadenceBalance = getBalance(address: user.address, vaultPublicPath: TokenA.ReceiverPublicPath)! + Test.assertEqual(tokenABalance, cadenceBalance) +} + +/// Test: UnifiedBalanceSource returns correct minimumAvailable +access(all) fun testUnifiedSourceMinimumAvailableReturnsCorrectValue() { + // create a user account and fund it + let user = Test.createAccount() + let flowBalance = 100.0 + transferFlow(signer: serviceAccount, recipient: user.address, amount: flowBalance) + + // create a COA for the user and fund it + let coaFunding = 50.0 + createCOA(user, fundingAmount: coaFunding) + + // execute a transaction that just checks the source is created correctly + let withdrawResult = _executeTransaction( + "./transactions/cross-vm-connectors/withdraw_via_unified_source.cdc", + [0.0, Type<@FlowToken.Vault>().identifier, nil], + user + ) + Test.expect(withdrawResult, Test.beSucceeded()) +} + +/// Test: Withdraw zero amount returns empty vault +access(all) fun testUnifiedSourceWithdrawZeroReturnsEmptyVault() { + // create a user account and fund it + let user = Test.createAccount() + let flowBalance = 100.0 + transferFlow(signer: serviceAccount, recipient: user.address, amount: flowBalance) + + // create a COA for the user + createCOA(user, fundingAmount: 0.0) + + // get initial balance + let balanceBefore = getBalance(address: user.address, vaultPublicPath: /public/flowTokenReceiver)! + + // withdraw 0 FLOW via UnifiedBalanceSource + let withdrawResult = _executeTransaction( + "./transactions/cross-vm-connectors/withdraw_via_unified_source.cdc", + [0.0, Type<@FlowToken.Vault>().identifier, nil], + user + ) + Test.expect(withdrawResult, Test.beSucceeded()) + + // balance should remain unchanged + let balanceAfter = getBalance(address: user.address, vaultPublicPath: /public/flowTokenReceiver)! + Test.assertEqual(balanceBefore, balanceAfter) +} diff --git a/cadence/tests/transactions/cross-vm-connectors/withdraw_via_unified_source.cdc b/cadence/tests/transactions/cross-vm-connectors/withdraw_via_unified_source.cdc new file mode 100644 index 00000000..118cb485 --- /dev/null +++ b/cadence/tests/transactions/cross-vm-connectors/withdraw_via_unified_source.cdc @@ -0,0 +1,96 @@ +import "FungibleToken" +import "FungibleTokenMetadataViews" +import "FlowToken" +import "EVM" +import "FlowEVMBridgeConfig" +import "DeFiActions" +import "CrossVMConnectors" + +/// Withdraws the given amount from the signer's unified balance (Cadence vault + COA) via a +/// CrossVMConnectors.UnifiedBalanceSource connector +/// +/// @param amount: The maximum amount to withdraw +/// @param withdrawVaultIdentifier: The type identifier of the vault to withdraw from +/// @param to: The address to deposit withdrawn funds; if nil, the signer's vault will receive the funds +/// +transaction(amount: UFix64, withdrawVaultIdentifier: String, to: Address?) { + /// the type of the withdraw token + let withdrawVaultType: Type + /// the receiver of the withdrawn funds + let receiver: &{FungibleToken.Vault} + /// the balance of the recipient before the withdrawal + let receiverBeforeBal: UFix64 + /// the Source to withdraw the funds from + let source: {DeFiActions.Source} + /// the available amount to withdraw from the Source + let available: UFix64 + + prepare(signer: auth(BorrowValue, IssueStorageCapabilityController) &Account) { + // get the withdraw token type + self.withdrawVaultType = CompositeType(withdrawVaultIdentifier) + ?? panic("Invalid withdraw token identifier: \(withdrawVaultIdentifier)") + + // get the FTVaultData for the withdraw token type + let vaultData = getAccount(self.withdrawVaultType.address!).contracts.borrow<&{FungibleToken}>( + name: self.withdrawVaultType.contractName! + )!.resolveContractView( + resourceType: self.withdrawVaultType, + viewType: Type() + ) as? FungibleTokenMetadataViews.FTVaultData + ?? panic("Could not resolve FTVaultData for \(self.withdrawVaultType.identifier)") + + // get the receiver Vault + if to == nil { + self.receiver = signer.capabilities.borrow<&{FungibleToken.Vault}>(vaultData.receiverPath) + ?? panic("Could not find vault in recipient's capabilities at path \(vaultData.receiverPath)") + } else { + self.receiver = getAccount(to!).capabilities.borrow<&{FungibleToken.Vault}>(vaultData.receiverPath) + ?? panic("Could not find vault in recipient's capabilities at path \(vaultData.receiverPath)") + } + // get the balance before the withdrawal + self.receiverBeforeBal = self.receiver.balance + + // issue capability on the signer's Cadence vault + let cadenceVaultCap = signer.capabilities.storage.issue( + vaultData.storagePath + ) + + // issue capability on the signer's COA + let storagePath = /storage/evm + let coaCap = signer.capabilities.storage.issue(storagePath) + + // issue capability for fee provision + let feeProviderCap = signer.capabilities.storage.issue( + /storage/flowTokenVault + ) + + // Calculate available Cadence balance + // For FlowToken, use availableBalance to account for storage reservation + // For other tokens, use vault balance + let availableCadenceBalance: UFix64 = self.withdrawVaultType == Type<@FlowToken.Vault>() + ? signer.availableBalance + : (signer.storage.borrow<&{FungibleToken.Vault}>(from: vaultData.storagePath)?.balance ?? 0.0) + + // create the UnifiedBalanceSource + self.source = CrossVMConnectors.UnifiedBalanceSource( + vaultType: self.withdrawVaultType, + cadenceVault: cadenceVaultCap, + coa: coaCap, + feeProvider: feeProviderCap, + availableCadenceBalance: availableCadenceBalance, + uniqueID: nil + ) + self.available = self.source.minimumAvailable() + } + + pre { + // check that the Source provides the withdraw token type + self.source.getSourceType() == self.withdrawVaultType: + "Source must provide \(self.withdrawVaultType.identifier) but found \(self.source.getSourceType().identifier)" + } + + execute { + let withdrawal <- self.source.withdrawAvailable(maxAmount: amount) + self.receiver.deposit(from: <-withdrawal) + } +} diff --git a/flow.json b/flow.json index 235cd4bf..0f2656ab 100644 --- a/flow.json +++ b/flow.json @@ -6,6 +6,12 @@ "testing": "0000000000000007" } }, + "CrossVMConnectors": { + "source": "cadence/contracts/connectors/CrossVMConnectors.cdc", + "aliases": { + "testing": "0000000000000009" + } + }, "DeFiActions": { "source": "cadence/contracts/interfaces/DeFiActions.cdc", "aliases": { @@ -778,6 +784,7 @@ "DeFiActionsUtils", "SwapConnectors", "FungibleTokenConnectors", + "CrossVMConnectors", "EVMNativeFLOWConnectors", "EVMTokenConnectors", "ERC4626Utils",