Skip to content

Conversation

@chumeston
Copy link
Collaborator

No description provided.

…ourcing

- 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
Copy link

@loic1 loic1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work. left a couple comments. the main ones are around caps vs refs and fee provider handling, which feel like important design choices to get right for a standard. also a couple feature suggestions. Let me know what you think!

/// 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<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reasoning behind using entitled capabilities instead of entitled references for the Cadence vault and COA?

My concern is that capabilities can be stored, which makes this a persistent access delegation primitive rather than a transaction-scoped helper. For our use case, we only need tx-scoped access. References provide that with security by default since they can't accidentally create persistent withdrawal rights. Using capabilities also adds a bit of ceremony in transactions (issuing/managing caps) that references would avoid

That said, I can see persistent delegation being useful for other ecosystem use cases down the road. Maybe that could be a separate source type if/when needed? (not a blocker for now, tho)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! Using entitled capabilities aligns with the broader defi actions connector architecture throughout the rest of the codebase. I was following along with what was already in place.

  1. VaultSink and Source use capabilities, not references.
  2. The actual usage in our withdraw via unified source has the capabilities being issued in the prepare block but the UnifiedBalanceSource itself is never stored (used in tx and then discarded).
  3. References being safer by default is valid, but, capabilities provide flexibility for future use cases like persistent delegation.

Would it be worth adding documentation or preconditions that explicitly state these connectors should not be stored? Or are we okay with maintaining consistency with the existing defi actions patterns as is?

My vote is to keep capabilities for consistency unless there's a strong reason to diverge from the connector arch.

/// Capability to the COA for native withdrawals and bridging
access(self) let coa: Capability<auth(EVM.Withdraw, EVM.Bridge) &EVM.CadenceOwnedAccount>
/// Capability to provide FLOW for bridge fees
access(self) let feeProvider: Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Storing an entitled capability for fee provider means the struct holds unlimited withdrawal rights. If we accept a pre-scoped reference instead (auth(FungibleToken.Withdraw) &ScopedFTProviders.ScopedFTProvider), we'd bound the fee budget at the source and limit the surface if something goes wrong. it'd also align with the tx-scoped reference pattern above. thoughts?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a good change that improves security for sure. The tx can calculate a conservative fee estimate upfront. Would you want this applied to both cadenceVault and coa as well or just feeProvider?

/// 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 {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be great to emit a withdrawal summary event showing where funds were sourced from. something like:

access(all) event Withdrawn(
  vaultType: String,
  cadenceAmount: UFix64,
  coaNativeAmount: UFix64,
  coaBridgedAmount: UFix64,
  bridgeFee: UFix64,
  uniqueID: UInt64?
)

This would give visibility into the actual source breakdown (Cadence vs COA native vs bridged) and fees paid. useful for debugging, auditing, etc

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion! This could help with observability, but I want to point out a couple things:

  1. The Source interface already emits a DefiActions.Withdrawn event automatically via the postcondition. Your suggestion would add a supplementary detailed event specific to UnifiedBalanceSource.
  2. The actual bridge fee paid is determined internally by the bridge operation and we would only provide an allowance to the scoped provider. To emit the actual fee paid we'd need to track the feeProvider before/after the bridge operation or emit the allowance amount (which I think would be the upper bound, not the actual fee?)
  3. For precise fee tracking we would have something like
// Before bridging
let feeProviderBefore = self.feeProvider.borrow()?.balance ?? 0.0

// ... bridge operation ...

// After bridging
let feeProviderAfter = self.feeProvider.borrow()?.balance ?? 0.0
let actualFeePaid = feeProviderBefore - feeProviderAfter

and then we could emit an event like you suggested with the breakdown. Is this worth adding tracking overhead for better observability?

/// 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 {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would also be useful to expose a utility function for querying unified balance without needing to create a Source (e.g., for scripts):

access(all) fun getUnifiedBalance(account: &Account, vaultType: Type): UFix64

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea this would be useful for scripts and offchain queries. A few considerations on the implementation:

  1. Remember above we were discussing capabilities. Scripts need public access. Our existing UnifiedBalanceSource relies on the entitled capabilities which aren't available in script context. A utility function would need to work with public references.
  2. The current pattern we have that we migrated from PM requires the caller to pre-compute availableCadenceBalance. A utility function would have to handle both cases.
  3. The function would need to locate the users COA. We seem to be storing it consistently at storage/evm but should the utility function accept a custom path or assume we can rely on the standard one?

Worth implementing or would a simpler signature returning UFix64 total be sufficient?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants