From ce17d4c6b1dab8765012e3515384d11188955cfb Mon Sep 17 00:00:00 2001 From: vishal <1117327+vishalchangrani@users.noreply.github.com> Date: Sun, 8 Mar 2026 20:28:05 -0400 Subject: [PATCH 1/5] docs: add FlowYieldVaults v1 supported features reference Adds docs/v1_supported_features.md covering: supported strategies (TracerStrategy, mUSDCStrategy, mUSDFStrategy, simple ERC4626 strategies), FLOW-only deposit token, YieldToken output, 10-minute auto-rebalancing via FlowTransactionScheduler, health factors, deposit limits, fee model, and closed-beta access control. Open items flagged for team review. Co-Authored-By: Claude Sonnet 4.6 --- docs/v1_supported_features.md | 153 ++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 docs/v1_supported_features.md diff --git a/docs/v1_supported_features.md b/docs/v1_supported_features.md new file mode 100644 index 00000000..9528bfef --- /dev/null +++ b/docs/v1_supported_features.md @@ -0,0 +1,153 @@ +# FlowYieldVaults v1 — Supported Features + +> **Status:** Draft — items marked ⚠️ require confirmation before finalization. + +--- + +## 1. Strategies Supported + +### TracerStrategy +The flagship v1 strategy. Deposits FLOW as collateral into a FlowALP lending position, borrows MOET against it, and swaps into a yield-bearing token for ongoing yield. + +- **Collateral**: FLOW +- **Borrow token**: MOET (via FlowALP) +- **Yield token**: YieldToken (e.g. tauUSDF or mUSDC, depending on configuration) +- **Integrated protocols**: FlowALP v1, Uniswap V3 (EVM), ERC4626 vaults + +### mUSDCStrategy +ERC4626 vault integration strategy. Swaps into mUSDC and deposits into a Morpho-compatible vault. + +- **Collateral**: FLOW +- **Yield source**: mUSDC (ERC4626 vault) +- **Integrated protocols**: Uniswap V3, ERC4626 + +### mUSDFStrategy *(v1.1)* +Advanced strategy targeting USDF yield via Morpho Finance. + +- **Collateral**: FLOW +- **Yield source**: mUSDDF (Morpho Finance) +- **Integrated protocols**: Uniswap V3, Morpho Finance, ERC4626 + +### Simple ERC4626 Strategies *(PMStrategiesV1)* + +| Strategy | Yield Source | +|----------|-------------| +| `syWFLOWvStrategy` | Swap-based Yield FLOW | +| `tauUSDFvStrategy` | Tau Labs USDF vault | +| `FUSDEVStrategy` | Flow USD Expeditionary Vault | + +--- + +## 2. Supported Input (Deposit) Token + +| Token | Notes | +|-------|-------| +| **FLOW** | Only supported deposit token in v1 | + +> ⚠️ Multi-collateral support (WETH, WBTC, etc.) is not in v1 scope — confirm if any bridged assets are accepted directly. + +--- + +## 3. Output / Receipt Token + +- **YieldToken** (`@YieldToken.Vault`) — issued to users representing their share of the yield-bearing position. +- Each YieldVault also tracks a **position ID** (UInt64) tied to the underlying FlowALP position. + +--- + +## 4. Rebalancing + +| Parameter | Value | +|-----------|-------| +| Rebalancing frequency | **10 minutes** (600 seconds) | +| Scheduling mechanism | Flow native `FlowTransactionScheduler` (FLIP 330) | +| Lower rebalance threshold | **0.95** (5% below target triggers recollateralization) | +| Upper rebalance threshold | **1.05** (5% above target triggers rebalancing) | +| Force rebalance option | Available (bypasses threshold checks) | +| Fee margin multiplier | 1.2× (20% buffer on estimated scheduling fees) | +| Supervisor recovery batch | Up to 50 stuck vaults per recovery run | + +The AutoBalancer self-schedules each subsequent execution at creation and after each run. A Supervisor contract handles recovery for vaults that fall out of the schedule. + +--- + +## 5. Health Factors (FlowALP Position) + +| Parameter | Value | +|-----------|-------| +| Target health | 1.30 | +| Minimum health (liquidation threshold) | 1.10 | +| Liquidation target health factor | 1.05 | + +--- + +## 6. Position & Deposit Limits + +| Parameter | Value | Notes | +|-----------|-------|-------| +| Minimum deposit | ⚠️ TBD | No contract-enforced minimum found | +| Maximum deposit capacity | 1,000,000 FLOW (default) | Governance-configurable cap | +| Deposit rate limit | 1,000,000 FLOW (default) | Per-block rate limiting via FlowALP | + +--- + +## 7. Fees + +| Fee Type | Value | Notes | +|----------|-------|-------| +| Scheduling / rebalancing fee | Paid in FLOW from AutoBalancer fee source | Min fallback: governance-set | +| Protocol / interest fee | Dynamic (utilization-based via FlowALP) | | +| Insurance reserve | 0.1% of credit balance | Taken before distributing credit interest | +| Management / performance fee | ⚠️ TBD | Confirm if any protocol-level fee applies | +| Withdrawal fee | ⚠️ TBD | Not observed in contracts — confirm | + +--- + +## 8. Access Control (Closed Beta) + +| Feature | Details | +|---------|---------| +| YieldVault creation | Requires a `BetaBadge` capability | +| Badge grant / revoke | Admin-only via `FlowYieldVaultsClosedBeta` contract | +| Rebalancing | Any account can trigger; Supervisor handles recovery | +| Governance params | Admin / Configure entitlements only | + +--- + +## 9. Contracts & Mainnet Addresses + +All core contracts are deployed to **`0xb1d63873c3cc9f79`**. + +| Contract | Address | +|----------|---------| +| `FlowYieldVaults` | `0xb1d63873c3cc9f79` | +| `FlowYieldVaultsStrategies` | `0xb1d63873c3cc9f79` | +| `FlowYieldVaultsAutoBalancers` | `0xb1d63873c3cc9f79` | +| `FlowYieldVaultsClosedBeta` | `0xb1d63873c3cc9f79` | +| `FlowYieldVaultsSchedulerV1` | `0xb1d63873c3cc9f79` | +| `FlowYieldVaultsSchedulerRegistry` | `0xb1d63873c3cc9f79` | +| FlowALP (lending) | `0x6b00ff876c299c61` | +| DeFiActions platform | `0x6d888f175c158410` | +| EVM Bridge | `0x1e4aa0b87d10b141` | + +### Key EVM Asset Addresses (Mainnet) + +| Token | EVM Address | +|-------|-------------| +| USDC | `0xF1815bd50389c46847f0Bda824eC8da914045D14` | +| wETH | `0x2F6F07CDcf3588944Bf4C42aC74ff24bF56e7590` | +| cbBTC | `0xA0197b2044D28b08Be34d98b23c9312158Ea9A18` | +| tauUSDF (ERC4626) | `0xc52E820d2D6207D18667a97e2c6Ac22eB26E803c` | + +--- + +## 10. Open Items + +| # | Item | Owner | +|---|------|-------| +| 1 | Minimum deposit value (is there a floor?) | | +| 2 | Management / performance fee — does one exist? | | +| 3 | Withdrawal fee — confirm none | | +| 4 | Are any bridged assets (WETH, WBTC) accepted as direct deposits in v1? | | +| 5 | Confirm deposit capacity cap of 1,000,000 FLOW for v1 launch | | +| 6 | mUSDFStrategy and simple ERC4626 strategies — in v1 or later? | | From 9efa3b1092a8e073825fd9a560e73e57cad1a6c0 Mon Sep 17 00:00:00 2001 From: vishal <1117327+vishalchangrani@users.noreply.github.com> Date: Sun, 8 Mar 2026 20:29:34 -0400 Subject: [PATCH 2/5] docs: replace TBD with ??? for unknown values Co-Authored-By: Claude Sonnet 4.6 --- docs/v1_supported_features.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/v1_supported_features.md b/docs/v1_supported_features.md index 9528bfef..a42ebcd2 100644 --- a/docs/v1_supported_features.md +++ b/docs/v1_supported_features.md @@ -85,7 +85,7 @@ The AutoBalancer self-schedules each subsequent execution at creation and after | Parameter | Value | Notes | |-----------|-------|-------| -| Minimum deposit | ⚠️ TBD | No contract-enforced minimum found | +| Minimum deposit | ⚠️ ??? | No contract-enforced minimum found | | Maximum deposit capacity | 1,000,000 FLOW (default) | Governance-configurable cap | | Deposit rate limit | 1,000,000 FLOW (default) | Per-block rate limiting via FlowALP | @@ -98,8 +98,8 @@ The AutoBalancer self-schedules each subsequent execution at creation and after | Scheduling / rebalancing fee | Paid in FLOW from AutoBalancer fee source | Min fallback: governance-set | | Protocol / interest fee | Dynamic (utilization-based via FlowALP) | | | Insurance reserve | 0.1% of credit balance | Taken before distributing credit interest | -| Management / performance fee | ⚠️ TBD | Confirm if any protocol-level fee applies | -| Withdrawal fee | ⚠️ TBD | Not observed in contracts — confirm | +| Management / performance fee | ⚠️ ??? | Confirm if any protocol-level fee applies | +| Withdrawal fee | ⚠️ ??? | Not observed in contracts — confirm | --- From 01ec89e09deabfe9498841337a098f4edb9f9d62 Mon Sep 17 00:00:00 2001 From: vishal <1117327+vishalchangrani@users.noreply.github.com> Date: Wed, 11 Mar 2026 15:00:54 -0400 Subject: [PATCH 3/5] diag: add diag_verify_pools script to verify all UniV3 pool health on mainnet Checks all 8 Uniswap V3 pools required by FlowYieldVaultsStrategiesV2: - exists, initialized, hasLiquidity flags via factory.getPool + slot0 + liquidity() - Human-readable token reserves (balance) alongside raw wei values Co-Authored-By: Claude Sonnet 4.6 --- cadence/scripts/diag_verify_pools.cdc | 189 ++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 cadence/scripts/diag_verify_pools.cdc diff --git a/cadence/scripts/diag_verify_pools.cdc b/cadence/scripts/diag_verify_pools.cdc new file mode 100644 index 00000000..a7d3e6f2 --- /dev/null +++ b/cadence/scripts/diag_verify_pools.cdc @@ -0,0 +1,189 @@ +import "EVM" +import "FlowEVMBridgeUtils" + +/// Verifies all Uniswap V3 pools required by FlowYieldVaultsStrategiesV2 on Flow EVM mainnet. +/// +/// For each pool reports: +/// poolAddress – address from factory.getPool(); zero = pool never deployed +/// exists – true if pool address is non-zero +/// initialized – true if slot0.sqrtPriceX96 != 0 (initial price has been set) +/// hasLiquidity – true if current in-range liquidity > 0 +/// _balance – human-readable reserve of tokenA (token units, 8 dp precision) +/// _balance – human-readable reserve of tokenB (token units, 8 dp precision) +/// _balance_wei – raw ERC20 reserve of tokenA in smallest unit +/// _balance_wei – raw ERC20 reserve of tokenB in smallest unit +/// +/// Interpretation: +/// exists=false → pool contract was never created; must be deployed by an LP +/// initialized=false → pool exists but no price was ever set; add initial liquidity first +/// hasLiquidity=false → pool is initialised but all LP positions are currently out of range +/// balance=0.0 → no reserves in pool; needs liquidity seeded before swaps will work +/// +/// Run: +/// flow scripts execute cadence/scripts/diag_verify_pools.cdc --network mainnet +/// +/// Pools checked (all Uniswap V3, factory 0xca6d7Bb03334bBf135902e1d919a5feccb461632): +/// +/// ┌─ User-requested pools ─────────────────────────────────────────────────────────┐ +/// │ 1. MOET / PYUSD0 fee=100 pre-swap: PYUSD0 collateral → MOET for FlowALP │ +/// │ 2. FUSDEV / PYUSD0 fee=100 FUSDEVStrategy: yield token ↔ stablecoin │ +/// │ 3. syWFLOWv / WFLOW fee=100 syWFLOWvStrategy: yield token ↔ WFLOW │ +/// └────────────────────────────────────────────────────────────────────────────────┘ +/// ┌─ Multi-hop path pools (also required) ─────────────────────────────────────────┐ +/// │ 4. PYUSD0 / WFLOW fee=3000 FUSDEVStrategy FLOW collateral path │ +/// │ 5. PYUSD0 / WETH fee=3000 FUSDEVStrategy WETH collateral path │ +/// │ 6. PYUSD0 / WBTC fee=3000 FUSDEVStrategy WBTC collateral path │ +/// │ 7. WFLOW / WETH fee=3000 syWFLOWvStrategy WETH/WBTC collateral first hop │ +/// │ 8. WETH / WBTC fee=3000 syWFLOWvStrategy WBTC collateral second hop │ +/// └────────────────────────────────────────────────────────────────────────────────┘ +access(all) fun main(): {String: {String: AnyStruct}} { + + let factory = EVM.addressFromString("0xca6d7Bb03334bBf135902e1d919a5feccb461632") + + // ── Token EVM addresses ──────────────────────────────────────────────────── + let moet = EVM.addressFromString("0x213979bb8a9a86966999b3aa797c1fcf3b967ae2") + let pyusd0 = EVM.addressFromString("0x99aF3EeA856556646C98c8B9b2548Fe815240750") + let fusdev = EVM.addressFromString("0xd069d989e2F44B70c65347d1853C0c67e10a9F8D") + let wflow = EVM.addressFromString("0xd3bF53DAC106A0290B0483EcBC89d40FcC961f3e") + let sywflowv = EVM.addressFromString("0xCBf9a7753F9D2d0e8141ebB36d99f87AcEf98597") + let weth = EVM.addressFromString("0x2F6F07CDcf3588944Bf4C42aC74ff24bF56e7590") + let wbtc = EVM.addressFromString("0x717DAE2BaF7656BE9a9B01deE31d571a9d4c9579") + + // ── EVM call helper ──────────────────────────────────────────────────────── + fun call(_ to: EVM.EVMAddress, _ data: [UInt8]): EVM.Result { + return EVM.dryCall( + from: factory, to: to, data: data, + gasLimit: 1_000_000, value: EVM.Balance(attoflow: 0) + ) + } + + // ── Wei → token units (truncated to UFix64's 8 decimal places) ──────────── + fun toHuman(_ wei: UInt256, _ decimals: UInt8): UFix64 { + if decimals <= 8 { + return FlowEVMBridgeUtils.uint256ToUFix64(value: wei, decimals: decimals) + } + // Floor to 8-decimal boundary before converting to avoid precision loss + let quantum = FlowEVMBridgeUtils.pow(base: 10, exponent: decimals - 8) + return FlowEVMBridgeUtils.uint256ToUFix64(value: wei - (wei % quantum), decimals: decimals) + } + + // ── Single-pool checker ──────────────────────────────────────────────────── + fun checkPool( + label: String, + tokenA: EVM.EVMAddress, tokenAName: String, + tokenB: EVM.EVMAddress, tokenBName: String, + fee: UInt256 + ): {String: AnyStruct} { + var out: {String: AnyStruct} = {"label": label} + + // 1. Resolve pool address from factory + let poolRes = call(factory, EVM.encodeABIWithSignature( + "getPool(address,address,uint24)", [tokenA, tokenB, fee] + )) + if poolRes.status != EVM.Status.successful { + out["error"] = "getPool failed: ".concat(poolRes.errorMessage) + return out + } + let poolAddr = EVM.decodeABI(types: [Type()], data: poolRes.data)[0] as! EVM.EVMAddress + let poolStr = poolAddr.toString() + out["poolAddress"] = poolStr + + let exists = poolStr != "0000000000000000000000000000000000000000" + out["exists"] = exists + if !exists { + out["initialized"] = false + out["hasLiquidity"] = false + return out + } + + // 2. slot0() → sqrtPriceX96 (uint160 ABI-padded to 32 bytes, decoded as UInt256) + // non-zero means the pool was initialised with a starting price + let slot0Res = call(poolAddr, EVM.encodeABIWithSignature("slot0()", [])) + if slot0Res.status != EVM.Status.successful { + out["error"] = "slot0 call failed: ".concat(slot0Res.errorMessage) + return out + } + let sqrtPriceX96 = EVM.decodeABI(types: [Type()], data: slot0Res.data)[0] as! UInt256 + out["sqrtPriceX96"] = sqrtPriceX96 + out["initialized"] = sqrtPriceX96 != UInt256(0) + + // 3. liquidity() → current in-range liquidity (uint128, decoded as UInt256) + let liqRes = call(poolAddr, EVM.encodeABIWithSignature("liquidity()", [])) + if liqRes.status == EVM.Status.successful { + let liq = EVM.decodeABI(types: [Type()], data: liqRes.data)[0] as! UInt256 + out["liquidity"] = liq + out["hasLiquidity"] = liq > UInt256(0) + } + + // 4. ERC20 decimals + balanceOf(pool) — actual reserves held in the pool contract + let decA = FlowEVMBridgeUtils.getTokenDecimals(evmContractAddress: tokenA) + let decB = FlowEVMBridgeUtils.getTokenDecimals(evmContractAddress: tokenB) + + let balARes = call(tokenA, EVM.encodeABIWithSignature("balanceOf(address)", [poolAddr])) + if balARes.status == EVM.Status.successful { + let wei = EVM.decodeABI(types: [Type()], data: balARes.data)[0] as! UInt256 + out[tokenAName.concat("_balance")] = toHuman(wei, decA) + out[tokenAName.concat("_balance_wei")] = wei + } + let balBRes = call(tokenB, EVM.encodeABIWithSignature("balanceOf(address)", [poolAddr])) + if balBRes.status == EVM.Status.successful { + let wei = EVM.decodeABI(types: [Type()], data: balBRes.data)[0] as! UInt256 + out[tokenBName.concat("_balance")] = toHuman(wei, decB) + out[tokenBName.concat("_balance_wei")] = wei + } + + return out + } + + // ── Run all checks ───────────────────────────────────────────────────────── + return { + "1_moet_pyusd0_fee100": checkPool( + label: "MOET / PYUSD0 fee=100 [pre-swap: PYUSD0 collateral → MOET for FlowALP]", + tokenA: moet, tokenAName: "MOET", + tokenB: pyusd0, tokenBName: "PYUSD0", + fee: UInt256(100) + ), + "2_fusdev_pyusd0_fee100": checkPool( + label: "FUSDEV / PYUSD0 fee=100 [FUSDEVStrategy: yield token ↔ stablecoin]", + tokenA: fusdev, tokenAName: "FUSDEV", + tokenB: pyusd0, tokenBName: "PYUSD0", + fee: UInt256(100) + ), + "3_sywflowv_wflow_fee100": checkPool( + label: "syWFLOWv / WFLOW fee=100 [syWFLOWvStrategy: yield token ↔ WFLOW]", + tokenA: sywflowv, tokenAName: "syWFLOWv", + tokenB: wflow, tokenBName: "WFLOW", + fee: UInt256(100) + ), + "4_pyusd0_wflow_fee3000": checkPool( + label: "PYUSD0 / WFLOW fee=3000 [FUSDEVStrategy: FLOW collateral exit path]", + tokenA: pyusd0, tokenAName: "PYUSD0", + tokenB: wflow, tokenBName: "WFLOW", + fee: UInt256(3000) + ), + "5_pyusd0_weth_fee3000": checkPool( + label: "PYUSD0 / WETH fee=3000 [FUSDEVStrategy: WETH collateral exit path]", + tokenA: pyusd0, tokenAName: "PYUSD0", + tokenB: weth, tokenBName: "WETH", + fee: UInt256(3000) + ), + "6_pyusd0_wbtc_fee3000": checkPool( + label: "PYUSD0 / WBTC fee=3000 [FUSDEVStrategy: WBTC collateral exit path]", + tokenA: pyusd0, tokenAName: "PYUSD0", + tokenB: wbtc, tokenBName: "WBTC", + fee: UInt256(3000) + ), + "7_wflow_weth_fee3000": checkPool( + label: "WFLOW / WETH fee=3000 [syWFLOWvStrategy: WETH & WBTC collateral first hop]", + tokenA: wflow, tokenAName: "WFLOW", + tokenB: weth, tokenBName: "WETH", + fee: UInt256(3000) + ), + "8_weth_wbtc_fee3000": checkPool( + label: "WETH / WBTC fee=3000 [syWFLOWvStrategy: WBTC collateral second hop]", + tokenA: weth, tokenAName: "WETH", + tokenB: wbtc, tokenBName: "WBTC", + fee: UInt256(3000) + ) + } +} From b8d67255349645ad238cef3b13c13e9e5a74e174 Mon Sep 17 00:00:00 2001 From: vishal <1117327+vishalchangrani@users.noreply.github.com> Date: Wed, 11 Mar 2026 16:10:20 -0400 Subject: [PATCH 4/5] diag: add diag_evm_token_balances script to check token balances at any EVM address Checks balances of all 7 tokens used by FlowYieldVaultsStrategiesV2 (MOET, PYUSD0, FUSDEV, WFLOW, syWFLOWv, WETH, WBTC) at a given EVM address. Useful for inspecting COAs, strategy contracts, or pool reserves. Co-Authored-By: Claude Sonnet 4.6 --- cadence/scripts/diag_evm_token_balances.cdc | 71 +++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 cadence/scripts/diag_evm_token_balances.cdc diff --git a/cadence/scripts/diag_evm_token_balances.cdc b/cadence/scripts/diag_evm_token_balances.cdc new file mode 100644 index 00000000..00bfd1ae --- /dev/null +++ b/cadence/scripts/diag_evm_token_balances.cdc @@ -0,0 +1,71 @@ +import "EVM" +import "FlowEVMBridgeUtils" + +/// Returns the ERC20 balances of all tokens relevant to FlowYieldVaultsStrategiesV2 +/// for a given EVM address (e.g. a user's COA, the strategy contract, or a pool address). +/// +/// For each token reports: +/// balance – human-readable amount (token units, 8 dp precision) +/// balance_wei – raw amount in the token's smallest unit +/// decimals – the token's ERC20 decimal count +/// +/// Run: +/// flow scripts execute cadence/scripts/diag_evm_token_balances.cdc \ +/// --args-json '[{"type":"String","value":"0xYOUR_EVM_ADDRESS"}]' \ +/// --network mainnet +access(all) fun main(evmAddressHex: String): {String: {String: AnyStruct}} { + + let target = EVM.addressFromString(evmAddressHex) + let caller = EVM.addressFromString("0xca6d7Bb03334bBf135902e1d919a5feccb461632") // factory, used as from + + // ── Token EVM addresses ──────────────────────────────────────────────────── + let tokens: {String: EVM.EVMAddress} = { + "MOET": EVM.addressFromString("0x213979bb8a9a86966999b3aa797c1fcf3b967ae2"), + "PYUSD0": EVM.addressFromString("0x99aF3EeA856556646C98c8B9b2548Fe815240750"), + "FUSDEV": EVM.addressFromString("0xd069d989e2F44B70c65347d1853C0c67e10a9F8D"), + "WFLOW": EVM.addressFromString("0xd3bF53DAC106A0290B0483EcBC89d40FcC961f3e"), + "syWFLOWv": EVM.addressFromString("0xCBf9a7753F9D2d0e8141ebB36d99f87AcEf98597"), + "WETH": EVM.addressFromString("0x2F6F07CDcf3588944Bf4C42aC74ff24bF56e7590"), + "WBTC": EVM.addressFromString("0x717DAE2BaF7656BE9a9B01deE31d571a9d4c9579") + } + + // ── Helpers ──────────────────────────────────────────────────────────────── + fun call(_ to: EVM.EVMAddress, _ data: [UInt8]): EVM.Result { + return EVM.dryCall( + from: caller, to: to, data: data, + gasLimit: 100_000, value: EVM.Balance(attoflow: 0) + ) + } + + fun toHuman(_ wei: UInt256, _ decimals: UInt8): UFix64 { + if decimals <= 8 { + return FlowEVMBridgeUtils.uint256ToUFix64(value: wei, decimals: decimals) + } + let quantum = FlowEVMBridgeUtils.pow(base: 10, exponent: decimals - 8) + return FlowEVMBridgeUtils.uint256ToUFix64(value: wei - (wei % quantum), decimals: decimals) + } + + // ── Query each token ─────────────────────────────────────────────────────── + var result: {String: {String: AnyStruct}} = {} + + for name in tokens.keys { + let tokenAddr = tokens[name]! + var entry: {String: AnyStruct} = {} + + let decimals = FlowEVMBridgeUtils.getTokenDecimals(evmContractAddress: tokenAddr) + entry["decimals"] = decimals + + let balRes = call(tokenAddr, EVM.encodeABIWithSignature("balanceOf(address)", [target])) + if balRes.status == EVM.Status.successful { + let wei = EVM.decodeABI(types: [Type()], data: balRes.data)[0] as! UInt256 + entry["balance"] = toHuman(wei, decimals) + entry["balance_wei"] = wei + } else { + entry["error"] = "balanceOf call failed: ".concat(balRes.errorMessage) + } + + result[name] = entry + } + + return result +} From ff9898caae546b420d823b8d42a12482e5c0bb37 Mon Sep 17 00:00:00 2001 From: vishal <1117327+vishalchangrani@users.noreply.github.com> Date: Wed, 11 Mar 2026 20:44:24 -0400 Subject: [PATCH 5/5] diag: add diag_vault_prices script to report ERC4626 vault share prices in USD Reports the redemption price of syWFLOWv and FUSDEV vaults in both their underlying token and USD via Band Protocol oracle. Co-Authored-By: Claude Sonnet 4.6 --- cadence/scripts/diag_vault_prices.cdc | 94 +++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 cadence/scripts/diag_vault_prices.cdc diff --git a/cadence/scripts/diag_vault_prices.cdc b/cadence/scripts/diag_vault_prices.cdc new file mode 100644 index 00000000..bb86c15d --- /dev/null +++ b/cadence/scripts/diag_vault_prices.cdc @@ -0,0 +1,94 @@ +import "EVM" +import "FlowToken" +import "ERC4626Utils" +import "FlowEVMBridgeUtils" +import "BandOracle" + +/// Reports the current redemption price of the syWFLOWv and FUSDEV ERC4626 vaults, +/// both in their underlying token and in USD (via Band Protocol oracle). +/// +/// syWFLOWv price (USD) = (syWFLOWv / WFLOW) × FLOW/USD (Band symbol: "FLOW") +/// FUSDEV price (USD) = (FUSDEV / PYUSD0) × PYUSD/USD (Band symbol: "PYUSD") +/// +/// Note: syWFLOWv does not implement convertToAssets(); share price is derived from +/// totalAssets / totalSupply instead. +/// +/// Note: BandOracle.getReferenceData requires a FLOW fee. This script works only when +/// BandOracle.getFee() == 0.0. If the fee is non-zero the assertion will panic. +/// +/// Run: flow scripts execute cadence/scripts/diag_vault_prices.cdc --network mainnet +access(all) fun main(): {String: {String: AnyStruct}} { + let syWFLOWv = EVM.addressFromString("0xCBf9a7753F9D2d0e8141ebB36d99f87AcEf98597") + let fusdev = EVM.addressFromString("0xd069d989e2F44B70c65347d1853C0c67e10a9F8D") + + // ── Band Oracle USD prices ───────────────────────────────────────────────── + fun bandPrice(_ symbol: String): UFix64 { + let fee = BandOracle.getFee() + assert(fee == 0.0, message: "BandOracle fee is non-zero (".concat(fee.toString()).concat(" FLOW). Use a transaction to pay the fee.")) + let payment <- FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>()) + let data = BandOracle.getReferenceData(baseSymbol: symbol, quoteSymbol: "USD", payment: <-payment) + return data.fixedPointRate + } + + // ── Wei → human-readable token units (8 dp) ─────────────────────────────── + fun toHuman(_ wei: UInt256, _ decimals: UInt8): UFix64 { + if decimals <= 8 { + return FlowEVMBridgeUtils.uint256ToUFix64(value: wei, decimals: decimals) + } + let quantum = FlowEVMBridgeUtils.pow(base: 10, exponent: decimals - 8) + return FlowEVMBridgeUtils.uint256ToUFix64(value: wei - (wei % quantum), decimals: decimals) + } + + // ── Per-vault price computation ──────────────────────────────────────────── + fun vaultPrice( + _ vault: EVM.EVMAddress, + _ name: String, + _ underlyingName: String, + _ bandSymbol: String + ): {String: AnyStruct} { + let shareDec = FlowEVMBridgeUtils.getTokenDecimals(evmContractAddress: vault) + + // Resolve underlying asset address + decimals via ERC4626 asset() + let underlyingRes = EVM.dryCall( + from: vault, to: vault, + data: EVM.encodeABIWithSignature("asset()", []), + gasLimit: 100_000, value: EVM.Balance(attoflow: 0) + ) + let underlying = underlyingRes.status == EVM.Status.successful + ? (EVM.decodeABI(types: [Type()], data: underlyingRes.data)[0] as! EVM.EVMAddress) + : vault + let assetDec = FlowEVMBridgeUtils.getTokenDecimals(evmContractAddress: underlying) + + let totalAssetsWei = ERC4626Utils.totalAssets(vault: vault) ?? UInt256(0) + let totalSharesWei = ERC4626Utils.totalShares(vault: vault) ?? UInt256(0) + + let assetsHuman = toHuman(totalAssetsWei, assetDec) + let sharesHuman = toHuman(totalSharesWei, shareDec) + + // price per share in underlying token + let priceInUnderlying = sharesHuman > 0.0 ? assetsHuman / sharesHuman : 0.0 + + // USD price of the underlying from Band Oracle, then multiply through + let underlyingUSD = bandPrice(bandSymbol) + let priceUSD = priceInUnderlying * underlyingUSD + + return { + "price_usd": priceUSD, + "price_in_underlying": priceInUnderlying, + "underlying_usd": underlyingUSD, + "interpretation": "1 ".concat(name).concat(" = $").concat(priceUSD.toString()), + "totalAssets": assetsHuman, + "totalAssets_wei": totalAssetsWei, + "totalShares": sharesHuman, + "totalShares_wei": totalSharesWei, + "underlying_address": underlying.toString(), + "asset_decimals": assetDec, + "share_decimals": shareDec + } + } + + return { + "syWFLOWv": vaultPrice(syWFLOWv, "syWFLOWv", "WFLOW", "FLOW"), + "FUSDEV": vaultPrice(fusdev, "FUSDEV", "PYUSD0", "PYUSD") + } +}