Skip to content
Open
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
3 changes: 2 additions & 1 deletion FLOW_YIELD_VAULTS_EVM_BRIDGE_DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,8 @@ function getPendingRequestIds() returns (uint256[] memory);
// Get pending requests in unpacked arrays (pagination)
function getPendingRequestsUnpacked(uint256 startIndex, uint256 count) returns (...);

// Get pending requests for a user in unpacked arrays (includes native FLOW balances)
// Get pending requests for a user in unpacked arrays (includes NATIVE_FLOW / WFLOW escrow+refund balances)
// Returns (..., address[] memory balanceTokens, uint256[] memory pendingBalances, uint256[] memory claimableRefundsArray)
function getPendingRequestsByUserUnpacked(address user) returns (...);

// Get single request by ID
Expand Down
17 changes: 12 additions & 5 deletions FRONTEND_INTEGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,12 +179,19 @@ const [
messages,
vaultIdentifiers,
strategyIdentifiers,
pendingBalance,
claimableRefund,
balanceTokens,
pendingBalances,
claimableRefunds,
] = await contract.getPendingRequestsByUserUnpacked(userAddress);
// pendingBalance = escrowed funds for active pending requests (native FLOW only)
// claimableRefund = funds available to claim via claimRefund() (native FLOW only)
// Use getUserPendingBalance/getClaimableRefund for a specific token

// `balanceTokens` will contain `[NATIVE_FLOW, WFLOW]` when WFLOW is configured (otherwise `[NATIVE_FLOW]`).
// The balance arrays are aligned by index.
const pendingByToken = Object.fromEntries(
balanceTokens.map((token, i) => [token, pendingBalances[i]])
);
const claimableByToken = Object.fromEntries(
balanceTokens.map((token, i) => [token, claimableRefunds[i]])
);
```

#### Get All Pending Requests (Paginated, Admin)
Expand Down
51 changes: 37 additions & 14 deletions cadence/contracts/FlowYieldVaultsEVM.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,17 @@ access(all) contract FlowYieldVaultsEVM {
access(all) struct PendingRequestsInfo {
access(all) let evmAddress: String
access(all) let pendingCount: Int
access(all) let pendingBalance: UFix64
access(all) let claimableRefund: UFix64
/// @notice Pending balances per token address (maps token address string -> UFix64 balance)
access(all) let pendingBalances: {String: UFix64}
/// @notice Claimable refunds per token address (maps token address string -> UFix64 balance)
access(all) let claimableRefunds: {String: UFix64}
access(all) let requests: [EVMRequest]

init(evmAddress: String, pendingCount: Int, pendingBalance: UFix64, claimableRefund: UFix64, requests: [EVMRequest]) {
init(evmAddress: String, pendingCount: Int, pendingBalances: {String: UFix64}, claimableRefunds: {String: UFix64}, requests: [EVMRequest]) {
self.evmAddress = evmAddress
self.pendingCount = pendingCount
self.pendingBalance = pendingBalance
self.claimableRefund = claimableRefund
self.pendingBalances = pendingBalances
self.claimableRefunds = claimableRefunds
self.requests = requests
}
}
Expand Down Expand Up @@ -1682,8 +1684,9 @@ access(all) contract FlowYieldVaultsEVM {
Type<[String]>(), // messages
Type<[String]>(), // vaultIdentifiers
Type<[String]>(), // strategyIdentifiers
Type<UInt256>(), // pendingBalance
Type<UInt256>() // claimableRefund
Type<[EVM.EVMAddress]>(), // balanceTokens
Type<[UInt256]>(), // pendingBalances
Type<[UInt256]>() // claimableRefundsArray
],
data: callResult.data
)
Expand All @@ -1698,12 +1701,32 @@ access(all) contract FlowYieldVaultsEVM {
let messages = decoded[7] as! [String]
let vaultIdentifiers = decoded[8] as! [String]
let strategyIdentifiers = decoded[9] as! [String]
let pendingBalanceRaw = decoded[10] as! UInt256
let claimableRefundRaw = decoded[11] as! UInt256
let balanceTokens = decoded[10] as! [EVM.EVMAddress]
let pendingBalancesRaw = decoded[11] as! [UInt256]
let claimableRefundsRaw = decoded[12] as! [UInt256]

// Convert pending balance from wei to UFix64
let pendingBalance = FlowEVMBridgeUtils.uint256ToUFix64(value: pendingBalanceRaw, decimals: 18)
let claimableRefund = FlowEVMBridgeUtils.uint256ToUFix64(value: claimableRefundRaw, decimals: 18)
assert(
balanceTokens.length == pendingBalancesRaw.length
&& balanceTokens.length == claimableRefundsRaw.length,
message: "Balance array length mismatch in ABI decode"
)

// Build per-token balance dictionaries
var pendingBalances: {String: UFix64} = {}
var claimableRefundsMap: {String: UFix64} = {}
var j = 0
while j < balanceTokens.length {
let tokenAddr = balanceTokens[j].toString()
pendingBalances[tokenAddr] = FlowYieldVaultsEVM.ufix64FromUInt256(
pendingBalancesRaw[j],
tokenAddress: balanceTokens[j]
)
claimableRefundsMap[tokenAddr] = FlowYieldVaultsEVM.ufix64FromUInt256(
claimableRefundsRaw[j],
tokenAddress: balanceTokens[j]
)
j = j + 1
}

// Build request array
var requests: [EVMRequest] = []
Expand All @@ -1729,8 +1752,8 @@ access(all) contract FlowYieldVaultsEVM {
return PendingRequestsInfo(
evmAddress: evmAddressHex,
pendingCount: ids.length,
pendingBalance: pendingBalance,
claimableRefund: claimableRefund,
pendingBalances: pendingBalances,
claimableRefunds: claimableRefundsMap,
requests: requests
)
}
Expand Down
17 changes: 11 additions & 6 deletions deployments/artifacts/FlowYieldVaultsRequests.json
Original file line number Diff line number Diff line change
Expand Up @@ -532,14 +532,19 @@
"internalType": "string[]"
},
{
"name": "pendingBalance",
"type": "uint256",
"internalType": "uint256"
"name": "balanceTokens",
"type": "address[]",
"internalType": "address[]"
},
{
"name": "claimableRefund",
"type": "uint256",
"internalType": "uint256"
"name": "pendingBalances",
"type": "uint256[]",
"internalType": "uint256[]"
},
{
"name": "claimableRefundsArray",
"type": "uint256[]",
"internalType": "uint256[]"
}
],
"stateMutability": "view"
Expand Down
17 changes: 11 additions & 6 deletions solidity/deployments/artifacts/FlowYieldVaultsRequests.json
Original file line number Diff line number Diff line change
Expand Up @@ -532,14 +532,19 @@
"internalType": "string[]"
},
{
"name": "pendingBalance",
"type": "uint256",
"internalType": "uint256"
"name": "balanceTokens",
"type": "address[]",
"internalType": "address[]"
},
{
"name": "claimableRefund",
"type": "uint256",
"internalType": "uint256"
"name": "pendingBalances",
"type": "uint256[]",
"internalType": "uint256[]"
},
{
"name": "claimableRefundsArray",
"type": "uint256[]",
"internalType": "uint256[]"
}
],
"stateMutability": "view"
Expand Down
30 changes: 23 additions & 7 deletions solidity/src/FlowYieldVaultsRequests.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1274,8 +1274,9 @@ contract FlowYieldVaultsRequests is ReentrancyGuard, Ownable2Step {
/// @return messages Messages
/// @return vaultIdentifiers Vault identifiers
/// @return strategyIdentifiers Strategy identifiers
/// @return pendingBalance Escrowed balance for active pending requests (native FLOW only)
/// @return claimableRefund Claimable refund amount (native FLOW only)
/// @return balanceTokens Token addresses for balance arrays (NATIVE_FLOW and, when configured, WFLOW)
/// @return pendingBalances Escrowed balances for active pending requests per token
/// @return claimableRefundsArray Claimable refund amounts per token
function getPendingRequestsByUserUnpacked(
address user
)
Expand All @@ -1292,8 +1293,9 @@ contract FlowYieldVaultsRequests is ReentrancyGuard, Ownable2Step {
string[] memory messages,
string[] memory vaultIdentifiers,
string[] memory strategyIdentifiers,
uint256 pendingBalance,
uint256 claimableRefund
address[] memory balanceTokens,
uint256[] memory pendingBalances,
uint256[] memory claimableRefundsArray
)
{
// Use the user's pending request IDs directly (O(1) lookup)
Expand Down Expand Up @@ -1330,9 +1332,23 @@ contract FlowYieldVaultsRequests is ReentrancyGuard, Ownable2Step {
}
}

// Get balances for native FLOW
pendingBalance = pendingUserBalances[user][NATIVE_FLOW];
claimableRefund = claimableRefunds[user][NATIVE_FLOW];
// Get balances for NATIVE_FLOW and, when configured, WFLOW
uint256 tokenCount = WFLOW != address(0) ? 2 : 1;
balanceTokens = new address[](tokenCount);
pendingBalances = new uint256[](tokenCount);
claimableRefundsArray = new uint256[](tokenCount);

// Native FLOW balances
balanceTokens[0] = NATIVE_FLOW;
pendingBalances[0] = pendingUserBalances[user][NATIVE_FLOW];
claimableRefundsArray[0] = claimableRefunds[user][NATIVE_FLOW];

// WFLOW balances (if configured)
if (WFLOW != address(0)) {
balanceTokens[1] = WFLOW;
pendingBalances[1] = pendingUserBalances[user][WFLOW];
claimableRefundsArray[1] = claimableRefunds[user][WFLOW];
Copy link

Choose a reason for hiding this comment

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

The WFLOW balance path (index [1]) has no test coverage — the core feature of this fix is untested

Every updated test assertion in this PR checks pendingBalances[0] / claimableRefundsArr[0] (NATIVE_FLOW). No test exercises:

  • balanceTokens[1] == WFLOW when WFLOW is configured
  • pendingBalances[1] reflecting an active WFLOW CREATE/DEPOSIT request
  • claimableRefundsArr[1] reflecting a cancelled/dropped WFLOW request

A test along these lines is needed:

  1. Approve WFLOW, call createYieldVault(WFLOW, 2 ether, ...) — verify pendingBalances[1] == 2 ether.
  2. Cancel the request — verify pendingBalances[1] == 0 and claimableRefundsArr[1] == 2 ether.

Without this test the regression this PR aims to fix (WFLOW balances not returned) could silently re-appear.

}
}

// ============================================
Expand Down
Loading
Loading