Skip to content

Commit 3393329

Browse files
authored
Merge pull request #189 from onflow/loic/pkm-385-add-nav-based-balance-to-yield-vault-strategies
Add NAV-based balance using convertToAssets instead of AMM quote
2 parents f9daf70 + aafc519 commit 3393329

File tree

3 files changed

+84
-1
lines changed

3 files changed

+84
-1
lines changed

cadence/contracts/FlowYieldVaults.cdc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,11 @@ access(all) contract FlowYieldVaults {
139139
/// Returns the balance of the given token available for withdrawal. Note that this may be an estimate due to
140140
/// the lack of guarantees inherent to DeFiActions Sources
141141
access(all) fun availableBalance(ofToken: Type): UFix64
142+
/// Returns the NAV-based balance of the given token. Defaults to availableBalance(); strategies backed by
143+
/// ERC-4626 vaults should override to return convertToAssets(shares) instead of an AMM quote.
144+
access(all) fun navBalance(ofToken: Type): UFix64 {
145+
return self.availableBalance(ofToken: ofToken)
146+
}
142147
/// Deposits up to the balance of the referenced Vault into this Strategy
143148
access(all) fun deposit(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) {
144149
pre {
@@ -311,6 +316,10 @@ access(all) contract FlowYieldVaults {
311316
access(all) fun getYieldVaultBalance(): UFix64 {
312317
return self._borrowStrategy().availableBalance(ofToken: self.vaultType)
313318
}
319+
/// Returns the NAV-based balance of the YieldVault's position via convertToAssets on the underlying ERC-4626 vault
320+
access(all) fun getNAVBalance(): UFix64 {
321+
return self._borrowStrategy().navBalance(ofToken: self.vaultType)
322+
}
314323
/// Burner.Burnable conformance - emits the BurnedYieldVault event when burned
315324
access(contract) fun burnCallback() {
316325
emit BurnedYieldVault(

cadence/contracts/PMStrategiesV1.cdc

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import "FlowYieldVaults"
1616
import "FlowYieldVaultsAutoBalancers"
1717
// vm bridge
1818
import "FlowEVMBridgeConfig"
19+
import "FlowEVMBridgeUtils"
20+
import "EVMAmountUtils"
1921
// live oracles
2022
import "ERC4626PriceOracles"
2123

@@ -73,6 +75,15 @@ access(all) contract PMStrategiesV1 {
7375
access(all) fun availableBalance(ofToken: Type): UFix64 {
7476
return ofToken == self.source.getSourceType() ? self.source.minimumAvailable() : 0.0
7577
}
78+
/// Returns the NAV-based balance by calling convertToAssets on the ERC-4626 vault
79+
access(all) fun navBalance(ofToken: Type): UFix64 {
80+
return PMStrategiesV1._navBalanceFor(
81+
strategyType: self.getType(),
82+
collateralType: self.sink.getSinkType(),
83+
ofToken: ofToken,
84+
id: self.id()!
85+
)
86+
}
7687
/// Deposits up to the inner Sink's capacity from the provided authorized Vault reference
7788
access(all) fun deposit(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) {
7889
self.sink.depositCapacity(from: from)
@@ -148,6 +159,15 @@ access(all) contract PMStrategiesV1 {
148159
access(all) fun availableBalance(ofToken: Type): UFix64 {
149160
return ofToken == self.source.getSourceType() ? self.source.minimumAvailable() : 0.0
150161
}
162+
/// Returns the NAV-based balance by calling convertToAssets on the ERC-4626 vault
163+
access(all) fun navBalance(ofToken: Type): UFix64 {
164+
return PMStrategiesV1._navBalanceFor(
165+
strategyType: self.getType(),
166+
collateralType: self.sink.getSinkType(),
167+
ofToken: ofToken,
168+
id: self.id()!
169+
)
170+
}
151171
/// Deposits up to the inner Sink's capacity from the provided authorized Vault reference
152172
access(all) fun deposit(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) {
153173
self.sink.depositCapacity(from: from)
@@ -223,6 +243,15 @@ access(all) contract PMStrategiesV1 {
223243
access(all) fun availableBalance(ofToken: Type): UFix64 {
224244
return ofToken == self.source.getSourceType() ? self.source.minimumAvailable() : 0.0
225245
}
246+
/// Returns the NAV-based balance by calling convertToAssets on the ERC-4626 vault
247+
access(all) fun navBalance(ofToken: Type): UFix64 {
248+
return PMStrategiesV1._navBalanceFor(
249+
strategyType: self.getType(),
250+
collateralType: self.sink.getSinkType(),
251+
ofToken: ofToken,
252+
id: self.id()!
253+
)
254+
}
226255
/// Deposits up to the inner Sink's capacity from the provided authorized Vault reference
227256
access(all) fun deposit(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) {
228257
self.sink.depositCapacity(from: from)
@@ -549,6 +578,51 @@ access(all) contract PMStrategiesV1 {
549578
}
550579
}
551580

581+
/// Looks up the EVM vault address for a given strategy + collateral pair from the on-chain StrategyComposerIssuer config
582+
access(contract) fun _getYieldTokenEVMAddress(forStrategy: Type, collateralType: Type): EVM.EVMAddress? {
583+
let issuer = self.account.storage.borrow<&StrategyComposerIssuer>(from: self.IssuerStoragePath)
584+
if issuer == nil { return nil }
585+
if let composerConfig = issuer!.configs[Type<@ERC4626VaultStrategyComposer>()] {
586+
if let strategyConfig = composerConfig[forStrategy] {
587+
if let collateralConfig = strategyConfig[collateralType] {
588+
// Dictionary access through references yields &EVM.EVMAddress, not EVM.EVMAddress;
589+
// cast to reference, then reconstruct via addressFromString
590+
if let addrRef = collateralConfig["yieldTokenEVMAddress"] as? &EVM.EVMAddress {
591+
return EVM.addressFromString("0x\(addrRef.toString())")
592+
}
593+
}
594+
}
595+
}
596+
return nil
597+
}
598+
599+
/// Shared NAV balance computation: reads Cadence-side share balance from AutoBalancer,
600+
/// converts to underlying asset value via ERC-4626 convertToAssets
601+
access(contract) fun _navBalanceFor(strategyType: Type, collateralType: Type, ofToken: Type, id: UInt64): UFix64 {
602+
if ofToken != collateralType { return 0.0 }
603+
604+
let ab = FlowYieldVaultsAutoBalancers.borrowAutoBalancer(id: id)
605+
if ab == nil { return 0.0 }
606+
let sharesBalance = ab!.vaultBalance()
607+
if sharesBalance == 0.0 { return 0.0 }
608+
609+
let vaultAddr = self._getYieldTokenEVMAddress(forStrategy: strategyType, collateralType: collateralType)
610+
?? panic("No EVM vault address configured for \(strategyType.identifier)")
611+
612+
let sharesWei = FlowEVMBridgeUtils.ufix64ToUInt256(
613+
value: sharesBalance,
614+
decimals: FlowEVMBridgeUtils.getTokenDecimals(evmContractAddress: vaultAddr)
615+
)
616+
617+
let navWei = ERC4626Utils.convertToAssets(vault: vaultAddr, shares: sharesWei)
618+
?? panic("convertToAssets failed for vault ".concat(vaultAddr.toString()))
619+
620+
let assetAddr = ERC4626Utils.underlyingAssetEVMAddress(vault: vaultAddr)
621+
?? panic("No underlying asset EVM address found for vault \(vaultAddr.toString())")
622+
623+
return EVMAmountUtils.toCadenceOutForToken(navWei, erc20Address: assetAddr)
624+
}
625+
552626
/// Returns the COA capability for this account
553627
/// TODO: this is temporary until we have a better way to pass user's COAs to inner connectors
554628
access(self)

0 commit comments

Comments
 (0)