Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions cadence/contracts/FlowYieldVaults.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@ access(all) contract FlowYieldVaults {
/// Returns the balance of the given token available for withdrawal. Note that this may be an estimate due to
/// the lack of guarantees inherent to DeFiActions Sources
access(all) fun availableBalance(ofToken: Type): UFix64
/// Returns the NAV-based balance of the given token. Defaults to availableBalance(); strategies backed by
/// ERC-4626 vaults should override to return convertToAssets(shares) instead of an AMM quote.
access(all) fun navBalance(ofToken: Type): UFix64 {
Copy link
Contributor

Choose a reason for hiding this comment

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

navBalance should mean mark-to-market position value, not “safe-to-withdraw now”.

Right now the base Strategy.navBalance() default delegates to availableBalance(). For strategies that don’t override it, this makes navBalance health/withdrawability-constrained (as it depends on FlowALP health considerations), which is a different concept from NAV and can understate value.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@liobrasil Good catch. My intention was that this comment would clarify what navBalance represents: Returns the NAV-based balance of the given token. Defaults to availableBalance(); strategies backed by ERC-4626 vaults should override to return convertToAssets(shares) instead of an AMM quote.

Does updating the method definition to access(all) fun navBalance(ofToken: Type): UFix64? { return nil } address your concern? If not, do you have any suggestions? The contract was already updated to mainnet to unblock enter strategy transaction failures in peak money.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

cc @nialexsan for vis

Copy link
Contributor

Choose a reason for hiding this comment

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

return self.availableBalance(ofToken: ofToken)
}
/// Deposits up to the balance of the referenced Vault into this Strategy
access(all) fun deposit(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) {
pre {
Expand Down Expand Up @@ -311,6 +316,10 @@ access(all) contract FlowYieldVaults {
access(all) fun getYieldVaultBalance(): UFix64 {
return self._borrowStrategy().availableBalance(ofToken: self.vaultType)
}
/// Returns the NAV-based balance of the YieldVault's position via convertToAssets on the underlying ERC-4626 vault
access(all) fun getNAVBalance(): UFix64 {
return self._borrowStrategy().navBalance(ofToken: self.vaultType)
}
/// Burner.Burnable conformance - emits the BurnedYieldVault event when burned
access(contract) fun burnCallback() {
emit BurnedYieldVault(
Expand Down
74 changes: 74 additions & 0 deletions cadence/contracts/PMStrategiesV1.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import "FlowYieldVaults"
import "FlowYieldVaultsAutoBalancers"
// vm bridge
import "FlowEVMBridgeConfig"
import "FlowEVMBridgeUtils"
import "EVMAmountUtils"
// live oracles
import "ERC4626PriceOracles"

Expand Down Expand Up @@ -73,6 +75,15 @@ access(all) contract PMStrategiesV1 {
access(all) fun availableBalance(ofToken: Type): UFix64 {
return ofToken == self.source.getSourceType() ? self.source.minimumAvailable() : 0.0
}
/// Returns the NAV-based balance by calling convertToAssets on the ERC-4626 vault
access(all) fun navBalance(ofToken: Type): UFix64 {
return PMStrategiesV1._navBalanceFor(
strategyType: self.getType(),
collateralType: self.sink.getSinkType(),
ofToken: ofToken,
id: self.id()!
)
}
/// Deposits up to the inner Sink's capacity from the provided authorized Vault reference
access(all) fun deposit(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) {
self.sink.depositCapacity(from: from)
Expand Down Expand Up @@ -148,6 +159,15 @@ access(all) contract PMStrategiesV1 {
access(all) fun availableBalance(ofToken: Type): UFix64 {
return ofToken == self.source.getSourceType() ? self.source.minimumAvailable() : 0.0
}
/// Returns the NAV-based balance by calling convertToAssets on the ERC-4626 vault
access(all) fun navBalance(ofToken: Type): UFix64 {
return PMStrategiesV1._navBalanceFor(
strategyType: self.getType(),
collateralType: self.sink.getSinkType(),
ofToken: ofToken,
id: self.id()!
)
}
/// Deposits up to the inner Sink's capacity from the provided authorized Vault reference
access(all) fun deposit(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) {
self.sink.depositCapacity(from: from)
Expand Down Expand Up @@ -223,6 +243,15 @@ access(all) contract PMStrategiesV1 {
access(all) fun availableBalance(ofToken: Type): UFix64 {
return ofToken == self.source.getSourceType() ? self.source.minimumAvailable() : 0.0
}
/// Returns the NAV-based balance by calling convertToAssets on the ERC-4626 vault
access(all) fun navBalance(ofToken: Type): UFix64 {
return PMStrategiesV1._navBalanceFor(
strategyType: self.getType(),
collateralType: self.sink.getSinkType(),
ofToken: ofToken,
id: self.id()!
)
}
/// Deposits up to the inner Sink's capacity from the provided authorized Vault reference
access(all) fun deposit(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) {
self.sink.depositCapacity(from: from)
Expand Down Expand Up @@ -549,6 +578,51 @@ access(all) contract PMStrategiesV1 {
}
}

/// Looks up the EVM vault address for a given strategy + collateral pair from the on-chain StrategyComposerIssuer config
access(contract) fun _getYieldTokenEVMAddress(forStrategy: Type, collateralType: Type): EVM.EVMAddress? {
let issuer = self.account.storage.borrow<&StrategyComposerIssuer>(from: self.IssuerStoragePath)
if issuer == nil { return nil }
if let composerConfig = issuer!.configs[Type<@ERC4626VaultStrategyComposer>()] {
if let strategyConfig = composerConfig[forStrategy] {
if let collateralConfig = strategyConfig[collateralType] {
// Dictionary access through references yields &EVM.EVMAddress, not EVM.EVMAddress;
// cast to reference, then reconstruct via addressFromString
if let addrRef = collateralConfig["yieldTokenEVMAddress"] as? &EVM.EVMAddress {
return EVM.addressFromString("0x\(addrRef.toString())")
}
}
}
}
return nil
}

/// Shared NAV balance computation: reads Cadence-side share balance from AutoBalancer,
/// converts to underlying asset value via ERC-4626 convertToAssets
access(contract) fun _navBalanceFor(strategyType: Type, collateralType: Type, ofToken: Type, id: UInt64): UFix64 {
if ofToken != collateralType { return 0.0 }

let ab = FlowYieldVaultsAutoBalancers.borrowAutoBalancer(id: id)
if ab == nil { return 0.0 }
let sharesBalance = ab!.vaultBalance()
if sharesBalance == 0.0 { return 0.0 }

let vaultAddr = self._getYieldTokenEVMAddress(forStrategy: strategyType, collateralType: collateralType)
?? panic("No EVM vault address configured for \(strategyType.identifier)")

let sharesWei = FlowEVMBridgeUtils.ufix64ToUInt256(
value: sharesBalance,
decimals: FlowEVMBridgeUtils.getTokenDecimals(evmContractAddress: vaultAddr)
)

let navWei = ERC4626Utils.convertToAssets(vault: vaultAddr, shares: sharesWei)
?? panic("convertToAssets failed for vault ".concat(vaultAddr.toString()))

let assetAddr = ERC4626Utils.underlyingAssetEVMAddress(vault: vaultAddr)
?? panic("No underlying asset EVM address found for vault \(vaultAddr.toString())")

return EVMAmountUtils.toCadenceOutForToken(navWei, erc20Address: assetAddr)
}

/// Returns the COA capability for this account
/// TODO: this is temporary until we have a better way to pass user's COAs to inner connectors
access(self)
Expand Down