From 40b122770fee307e0c55cc83d5059708f3321224 Mon Sep 17 00:00:00 2001 From: liobrasil Date: Mon, 19 Jan 2026 14:18:41 -0400 Subject: [PATCH 01/10] fix(FLOW-15): return per-token balances in getPendingRequestsByUserUnpacked Previously, `getPendingRequestsByUserUnpacked` only returned native FLOW balances, ignoring ERC20/WFLOW balances. This made it impossible for clients to see pending balances for non-native tokens. Changes: - Solidity: Return `balanceTokens[]`, `pendingBalances[]`, and `claimableRefundsArray[]` instead of single uint256 values - Cadence: Update `PendingRequestsInfo` struct to use dictionaries `pendingBalances: {String: UFix64}` and `claimableRefunds: {String: UFix64}` - Update ABI decoding to handle new array types - Update all tests to use new return format Co-Authored-By: Claude Opus 4.5 --- cadence/contracts/FlowYieldVaultsEVM.cdc | 41 ++++++++++------- solidity/src/FlowYieldVaultsRequests.sol | 31 ++++++++++--- solidity/test/FlowYieldVaultsRequests.t.sol | 50 +++++++++++---------- 3 files changed, 76 insertions(+), 46 deletions(-) diff --git a/cadence/contracts/FlowYieldVaultsEVM.cdc b/cadence/contracts/FlowYieldVaultsEVM.cdc index a8bf645..66eec1e 100644 --- a/cadence/contracts/FlowYieldVaultsEVM.cdc +++ b/cadence/contracts/FlowYieldVaultsEVM.cdc @@ -125,15 +125,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 } } @@ -1703,8 +1705,9 @@ access(all) contract FlowYieldVaultsEVM { Type<[String]>(), // messages Type<[String]>(), // vaultIdentifiers Type<[String]>(), // strategyIdentifiers - Type(), // pendingBalance - Type() // claimableRefund + Type<[EVM.EVMAddress]>(), // balanceTokens + Type<[UInt256]>(), // pendingBalances + Type<[UInt256]>() // claimableRefundsArray ], data: callResult.data ) @@ -1719,12 +1722,20 @@ 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 - - // Convert pending balance from wei to UFix64 - let pendingBalance = FlowEVMBridgeUtils.uint256ToUFix64(value: pendingBalanceRaw, decimals: 18) - let claimableRefund = FlowEVMBridgeUtils.uint256ToUFix64(value: claimableRefundRaw, decimals: 18) + let balanceTokens = decoded[10] as! [EVM.EVMAddress] + let pendingBalancesRaw = decoded[11] as! [UInt256] + let claimableRefundsRaw = decoded[12] as! [UInt256] + + // 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] = FlowEVMBridgeUtils.uint256ToUFix64(value: pendingBalancesRaw[j], decimals: 18) + claimableRefundsMap[tokenAddr] = FlowEVMBridgeUtils.uint256ToUFix64(value: claimableRefundsRaw[j], decimals: 18) + j = j + 1 + } // Build request array var requests: [EVMRequest] = [] @@ -1750,8 +1761,8 @@ access(all) contract FlowYieldVaultsEVM { return PendingRequestsInfo( evmAddress: evmAddressHex, pendingCount: ids.length, - pendingBalance: pendingBalance, - claimableRefund: claimableRefund, + pendingBalances: pendingBalances, + claimableRefunds: claimableRefundsMap, requests: requests ) } diff --git a/solidity/src/FlowYieldVaultsRequests.sol b/solidity/src/FlowYieldVaultsRequests.sol index 14ef1db..9963ed1 100644 --- a/solidity/src/FlowYieldVaultsRequests.sol +++ b/solidity/src/FlowYieldVaultsRequests.sol @@ -1362,8 +1362,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, WFLOW) + /// @return pendingBalances Escrowed balances for active pending requests per token + /// @return claimableRefundsArray Claimable refund amounts per token function getPendingRequestsByUserUnpacked( address user ) @@ -1380,8 +1381,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) @@ -1418,9 +1420,24 @@ contract FlowYieldVaultsRequests is ReentrancyGuard, Ownable2Step { } } - // Get balances for native FLOW - pendingBalance = pendingUserBalances[user][NATIVE_FLOW]; - claimableRefund = claimableRefunds[user][NATIVE_FLOW]; + // Get balances for all supported tokens (NATIVE_FLOW and WFLOW) + // WFLOW address could be 0 if not configured, but we include it for completeness + 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]; + } } // ============================================ diff --git a/solidity/test/FlowYieldVaultsRequests.t.sol b/solidity/test/FlowYieldVaultsRequests.t.sol index 9336d75..bbb5bfb 100644 --- a/solidity/test/FlowYieldVaultsRequests.t.sol +++ b/solidity/test/FlowYieldVaultsRequests.t.sol @@ -916,7 +916,8 @@ contract FlowYieldVaultsRequestsTest is Test { string[] memory messages, string[] memory vaultIdentifiers, string[] memory strategyIdentifiers, - uint256 pendingBalance, + address[] memory balanceTokens, + uint256[] memory pendingBalances, ) = c.getPendingRequestsByUserUnpacked(user); assertEq(ids.length, 2, "User should have 2 pending requests"); @@ -926,7 +927,8 @@ contract FlowYieldVaultsRequestsTest is Test { assertEq(requestTypes[1], uint8(FlowYieldVaultsRequests.RequestType.DEPOSIT_TO_YIELDVAULT)); assertEq(amounts[0], 1 ether); assertEq(amounts[1], 2 ether); - assertEq(pendingBalance, 3 ether, "Pending balance should be sum of amounts"); + assertEq(balanceTokens[0], NATIVE_FLOW, "First balance token should be NATIVE_FLOW"); + assertEq(pendingBalances[0], 3 ether, "Pending balance should be sum of amounts"); } function test_GetPendingRequestsByUserUnpacked_MultipleUsers() public { @@ -940,23 +942,23 @@ contract FlowYieldVaultsRequestsTest is Test { c.createYieldVault{value: 3 ether}(NATIVE_FLOW, 3 ether, VAULT_ID, STRATEGY_ID); // Check user's requests - (uint256[] memory userIds, , , , , , , , , , uint256 userBalance, ) = c.getPendingRequestsByUserUnpacked(user); + (uint256[] memory userIds, , , , , , , , , , , uint256[] memory userBalances, ) = c.getPendingRequestsByUserUnpacked(user); assertEq(userIds.length, 2, "User should have 2 requests"); - assertEq(userBalance, 4 ether, "User pending balance should be 4 ether"); + assertEq(userBalances[0], 4 ether, "User pending balance should be 4 ether"); // Check user2's requests - (uint256[] memory user2Ids, , , , , , , , , , uint256 user2Balance, ) = c.getPendingRequestsByUserUnpacked(user2); + (uint256[] memory user2Ids, , , , , , , , , , , uint256[] memory user2Balances, ) = c.getPendingRequestsByUserUnpacked(user2); assertEq(user2Ids.length, 1, "User2 should have 1 request"); - assertEq(user2Balance, 2 ether, "User2 pending balance should be 2 ether"); + assertEq(user2Balances[0], 2 ether, "User2 pending balance should be 2 ether"); } function test_GetPendingRequestsByUserUnpacked_EmptyForNewUser() public { address newUser = makeAddr("newUser"); - (uint256[] memory ids, , , , , , , , , , uint256 pendingBalance, ) = c.getPendingRequestsByUserUnpacked(newUser); + (uint256[] memory ids, , , , , , , , , , , uint256[] memory pendingBalances, ) = c.getPendingRequestsByUserUnpacked(newUser); assertEq(ids.length, 0, "New user should have no pending requests"); - assertEq(pendingBalance, 0, "New user should have 0 pending balance"); + assertEq(pendingBalances[0], 0, "New user should have 0 pending balance"); } function test_GetPendingRequestsByUserUnpacked_AfterProcessing() public { @@ -973,13 +975,13 @@ contract FlowYieldVaultsRequestsTest is Test { vm.stopPrank(); // User should now have req1 and req3 - (uint256[] memory ids, , , , uint256[] memory amounts, , , , , , uint256 pendingBalance, ) = c.getPendingRequestsByUserUnpacked(user); + (uint256[] memory ids, , , , uint256[] memory amounts, , , , , , , uint256[] memory pendingBalances, ) = c.getPendingRequestsByUserUnpacked(user); assertEq(ids.length, 2, "User should have 2 remaining requests"); // Note: Order in user array may change due to swap-and-pop optimization assertTrue(ids[0] == req1 || ids[0] == req3, "Should contain req1 or req3"); assertTrue(ids[1] == req1 || ids[1] == req3, "Should contain req1 or req3"); assertTrue(ids[0] != ids[1], "Should be different requests"); - assertEq(pendingBalance, 4 ether, "Pending balance should be 4 ether"); + assertEq(pendingBalances[0], 4 ether, "Pending balance should be 4 ether"); } function test_GetPendingRequestsByUserUnpacked_AfterCancel() public { @@ -991,13 +993,13 @@ contract FlowYieldVaultsRequestsTest is Test { c.cancelRequest(req1); vm.stopPrank(); - (uint256[] memory ids, , , , , , , , , , uint256 pendingBalance, uint256 claimableRefund) = c.getPendingRequestsByUserUnpacked(user); + (uint256[] memory ids, , , , , , , , , , , uint256[] memory pendingBalances, uint256[] memory claimableRefundsArr) = c.getPendingRequestsByUserUnpacked(user); assertEq(ids.length, 1, "User should have 1 remaining request"); assertEq(ids[0], req2, "Remaining request should be req2"); // pendingBalance = 2 ether (escrowed for req2 only) // claimableRefund = 1 ether (from cancelled req1) - assertEq(pendingBalance, 2 ether); - assertEq(claimableRefund, 1 ether); + assertEq(pendingBalances[0], 2 ether); + assertEq(claimableRefundsArr[0], 1 ether); } // ============================================ @@ -1031,16 +1033,16 @@ contract FlowYieldVaultsRequestsTest is Test { vm.stopPrank(); // Verify user1's remaining requests - (uint256[] memory u1Ids, , , , , , , , , , , ) = c.getPendingRequestsByUserUnpacked(user); + (uint256[] memory u1Ids, , , , , , , , , , , , ) = c.getPendingRequestsByUserUnpacked(user); assertEq(u1Ids.length, 2, "User1 should have 2 requests"); // Verify user2's requests unchanged - (uint256[] memory u2Ids, , , , , , , , , , , ) = c.getPendingRequestsByUserUnpacked(user2); + (uint256[] memory u2Ids, , , , , , , , , , , , ) = c.getPendingRequestsByUserUnpacked(user2); assertEq(u2Ids.length, 1, "User2 should have 1 request"); assertEq(u2Ids[0], u2r1); // Verify user3's requests unchanged - (uint256[] memory u3Ids, , , , , , , , , , , ) = c.getPendingRequestsByUserUnpacked(user3); + (uint256[] memory u3Ids, , , , , , , , , , , , ) = c.getPendingRequestsByUserUnpacked(user3); assertEq(u3Ids.length, 1, "User3 should have 1 request"); assertEq(u3Ids[0], u3r1); } @@ -1058,9 +1060,9 @@ contract FlowYieldVaultsRequestsTest is Test { c.completeProcessing(req2, true, 101, "Created"); vm.stopPrank(); - (uint256[] memory ids, , , , , , , , , , uint256 pendingBalance, ) = c.getPendingRequestsByUserUnpacked(user); + (uint256[] memory ids, , , , , , , , , , , uint256[] memory pendingBalances, ) = c.getPendingRequestsByUserUnpacked(user); assertEq(ids.length, 0, "User should have no pending requests"); - assertEq(pendingBalance, 0); + assertEq(pendingBalances[0], 0); } // ============================================ @@ -1222,7 +1224,7 @@ contract FlowYieldVaultsRequestsTest is Test { // Verify all other users still have 3 requests for (uint256 i = 0; i < 5; i++) { - (uint256[] memory ids, , , , , , , , , , , ) = c.getPendingRequestsByUserUnpacked(users[i]); + (uint256[] memory ids, , , , , , , , , , , , ) = c.getPendingRequestsByUserUnpacked(users[i]); if (i == 2) { assertEq(ids.length, 2, "User 2 should have 2 requests"); } else { @@ -1246,7 +1248,7 @@ contract FlowYieldVaultsRequestsTest is Test { assertEq(c.getPendingRequestCount(), 0); - (uint256[] memory ids, , , , , , , , , , , ) = c.getPendingRequestsByUserUnpacked(user); + (uint256[] memory ids, , , , , , , , , , , , ) = c.getPendingRequestsByUserUnpacked(user); assertEq(ids.length, 0); } @@ -1264,13 +1266,13 @@ contract FlowYieldVaultsRequestsTest is Test { vm.stopPrank(); // req1 should still be removed from pending (it's marked FAILED) - (uint256[] memory ids, , , , , , , , , , uint256 pendingBalance, uint256 claimableRefund) = c.getPendingRequestsByUserUnpacked(user); + (uint256[] memory ids, , , , , , , , , , , uint256[] memory pendingBalances, uint256[] memory claimableRefundsArr) = c.getPendingRequestsByUserUnpacked(user); assertEq(ids.length, 1, "Should have 1 pending request"); assertEq(ids[0], req2); // Escrowed balance only includes req2 - assertEq(pendingBalance, 2 ether, "Escrowed balance should be req2 amount"); + assertEq(pendingBalances[0], 2 ether, "Escrowed balance should be req2 amount"); // Refunded amount is in claimableRefunds - assertEq(claimableRefund, 1 ether, "Claimable refund should be req1 amount"); + assertEq(claimableRefundsArr[0], 1 ether, "Claimable refund should be req1 amount"); } function test_CancelMiddleRequest_UpdatesIndexCorrectly() public { @@ -1290,7 +1292,7 @@ contract FlowYieldVaultsRequestsTest is Test { assertEq(globalIds[1], req3, "Second should be req3"); // Verify user's array - (uint256[] memory userIds, , , , , , , , , , , ) = c.getPendingRequestsByUserUnpacked(user); + (uint256[] memory userIds, , , , , , , , , , , , ) = c.getPendingRequestsByUserUnpacked(user); assertEq(userIds.length, 2); } } From 4bb6f457d898d7dfc6a904a9178649557e0238c6 Mon Sep 17 00:00:00 2001 From: liobrasil Date: Mon, 19 Jan 2026 16:12:08 -0400 Subject: [PATCH 02/10] docs(FLOW-15): update docs and ABI artifacts --- FLOW_YIELD_VAULTS_EVM_BRIDGE_DESIGN.md | 3 +- FRONTEND_INTEGRATION.md | 17 +++-- .../artifacts/FlowYieldVaultsRequests.json | 68 +++++++++++++++++-- .../artifacts/FlowYieldVaultsRequests.json | 68 +++++++++++++++++-- 4 files changed, 138 insertions(+), 18 deletions(-) diff --git a/FLOW_YIELD_VAULTS_EVM_BRIDGE_DESIGN.md b/FLOW_YIELD_VAULTS_EVM_BRIDGE_DESIGN.md index 4c57ab7..449a824 100644 --- a/FLOW_YIELD_VAULTS_EVM_BRIDGE_DESIGN.md +++ b/FLOW_YIELD_VAULTS_EVM_BRIDGE_DESIGN.md @@ -508,7 +508,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 diff --git a/FRONTEND_INTEGRATION.md b/FRONTEND_INTEGRATION.md index 860bcc5..a49aeef 100644 --- a/FRONTEND_INTEGRATION.md +++ b/FRONTEND_INTEGRATION.md @@ -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) diff --git a/deployments/artifacts/FlowYieldVaultsRequests.json b/deployments/artifacts/FlowYieldVaultsRequests.json index 1c49944..9cee511 100644 --- a/deployments/artifacts/FlowYieldVaultsRequests.json +++ b/deployments/artifacts/FlowYieldVaultsRequests.json @@ -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" @@ -1214,6 +1219,25 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "yieldVaultTokens", + "inputs": [ + { + "name": "", + "type": "uint64", + "internalType": "uint64" + } + ], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "yieldVaultsByUser", @@ -2032,5 +2056,37 @@ "type": "error", "name": "TransferFailed", "inputs": [] + }, + { + "type": "error", + "name": "YieldVaultTokenMismatch", + "inputs": [ + { + "name": "yieldVaultId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "expected", + "type": "address", + "internalType": "address" + }, + { + "name": "provided", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "YieldVaultTokenNotSet", + "inputs": [ + { + "name": "yieldVaultId", + "type": "uint64", + "internalType": "uint64" + } + ] } ] diff --git a/solidity/deployments/artifacts/FlowYieldVaultsRequests.json b/solidity/deployments/artifacts/FlowYieldVaultsRequests.json index 1c49944..9cee511 100644 --- a/solidity/deployments/artifacts/FlowYieldVaultsRequests.json +++ b/solidity/deployments/artifacts/FlowYieldVaultsRequests.json @@ -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" @@ -1214,6 +1219,25 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "yieldVaultTokens", + "inputs": [ + { + "name": "", + "type": "uint64", + "internalType": "uint64" + } + ], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "yieldVaultsByUser", @@ -2032,5 +2056,37 @@ "type": "error", "name": "TransferFailed", "inputs": [] + }, + { + "type": "error", + "name": "YieldVaultTokenMismatch", + "inputs": [ + { + "name": "yieldVaultId", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "expected", + "type": "address", + "internalType": "address" + }, + { + "name": "provided", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "YieldVaultTokenNotSet", + "inputs": [ + { + "name": "yieldVaultId", + "type": "uint64", + "internalType": "uint64" + } + ] } ] From a10871428ef5a2f9ecad4cbb11949eb7b45dc4a5 Mon Sep 17 00:00:00 2001 From: liobrasil Date: Tue, 17 Mar 2026 09:56:32 -0400 Subject: [PATCH 03/10] fix(FLOW-15): harden per-token balance decoding --- cadence/contracts/FlowYieldVaultsEVM.cdc | 16 ++++++++++++++-- solidity/src/FlowYieldVaultsRequests.sol | 5 ++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/cadence/contracts/FlowYieldVaultsEVM.cdc b/cadence/contracts/FlowYieldVaultsEVM.cdc index 0d48442..bfa0458 100644 --- a/cadence/contracts/FlowYieldVaultsEVM.cdc +++ b/cadence/contracts/FlowYieldVaultsEVM.cdc @@ -1705,14 +1705,26 @@ access(all) contract FlowYieldVaultsEVM { let pendingBalancesRaw = decoded[11] as! [UInt256] let claimableRefundsRaw = decoded[12] as! [UInt256] + 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] = FlowEVMBridgeUtils.uint256ToUFix64(value: pendingBalancesRaw[j], decimals: 18) - claimableRefundsMap[tokenAddr] = FlowEVMBridgeUtils.uint256ToUFix64(value: claimableRefundsRaw[j], decimals: 18) + pendingBalances[tokenAddr] = FlowYieldVaultsEVM.ufix64FromUInt256( + pendingBalancesRaw[j], + tokenAddress: balanceTokens[j] + ) + claimableRefundsMap[tokenAddr] = FlowYieldVaultsEVM.ufix64FromUInt256( + claimableRefundsRaw[j], + tokenAddress: balanceTokens[j] + ) j = j + 1 } diff --git a/solidity/src/FlowYieldVaultsRequests.sol b/solidity/src/FlowYieldVaultsRequests.sol index c76f598..fd9a8ca 100644 --- a/solidity/src/FlowYieldVaultsRequests.sol +++ b/solidity/src/FlowYieldVaultsRequests.sol @@ -1274,7 +1274,7 @@ contract FlowYieldVaultsRequests is ReentrancyGuard, Ownable2Step { /// @return messages Messages /// @return vaultIdentifiers Vault identifiers /// @return strategyIdentifiers Strategy identifiers - /// @return balanceTokens Token addresses for balance arrays (NATIVE_FLOW, WFLOW) + /// @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( @@ -1332,8 +1332,7 @@ contract FlowYieldVaultsRequests is ReentrancyGuard, Ownable2Step { } } - // Get balances for all supported tokens (NATIVE_FLOW and WFLOW) - // WFLOW address could be 0 if not configured, but we include it for completeness + // Get balances for NATIVE_FLOW and, when configured, WFLOW uint256 tokenCount = WFLOW != address(0) ? 2 : 1; balanceTokens = new address[](tokenCount); pendingBalances = new uint256[](tokenCount); From 4d1a4a2b7d319345a99a32328424d94d710324cc Mon Sep 17 00:00:00 2001 From: liobrasil Date: Tue, 17 Mar 2026 09:58:13 -0400 Subject: [PATCH 04/10] test(FLOW-15): cover WFLOW balance arrays --- solidity/test/FlowYieldVaultsRequests.t.sol | 70 ++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/solidity/test/FlowYieldVaultsRequests.t.sol b/solidity/test/FlowYieldVaultsRequests.t.sol index c138a18..e16a660 100644 --- a/solidity/test/FlowYieldVaultsRequests.t.sol +++ b/solidity/test/FlowYieldVaultsRequests.t.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.20; import "forge-std/Test.sol"; +import {ERC20Mock} from "@openzeppelin/contracts/mocks/token/ERC20Mock.sol"; import "../src/FlowYieldVaultsRequests.sol"; contract FlowYieldVaultsRequestsTestHelper is FlowYieldVaultsRequests { @@ -20,11 +21,12 @@ contract FlowYieldVaultsRequestsTestHelper is FlowYieldVaultsRequests { contract FlowYieldVaultsRequestsTest is Test { FlowYieldVaultsRequestsTestHelper public c; + ERC20Mock public wflow; address user = makeAddr("user"); address user2 = makeAddr("user2"); address coa = makeAddr("coa"); address constant NATIVE_FLOW = 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF; - address constant WFLOW = 0xd3bF53DAC106A0290B0483EcBC89d40FcC961f3e; + address WFLOW; // Events for testing (from OpenZeppelin Ownable2Step) event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner); @@ -44,6 +46,10 @@ contract FlowYieldVaultsRequestsTest is Test { function setUp() public { vm.deal(user, 100 ether); vm.deal(user2, 100 ether); + wflow = new ERC20Mock(); + WFLOW = address(wflow); + wflow.mint(user, 100 ether); + wflow.mint(user2, 100 ether); c = new FlowYieldVaultsRequestsTestHelper(coa, WFLOW); c.testRegisterYieldVaultId(42, user, NATIVE_FLOW); } @@ -1003,6 +1009,68 @@ contract FlowYieldVaultsRequestsTest is Test { assertEq(pendingBalances[0], 3 ether, "Pending balance should be sum of amounts"); } + function test_GetPendingRequestsByUserUnpacked_WFLOWBalances() public { + vm.startPrank(user); + wflow.approve(address(c), 2 ether); + + uint256 reqId = c.createYieldVault(WFLOW, 2 ether, VAULT_ID, STRATEGY_ID); + + ( + uint256[] memory ids, + , + , + , + , + , + , + , + , + , + address[] memory balanceTokens, + uint256[] memory pendingBalances, + uint256[] memory claimableRefundsArr + ) = c.getPendingRequestsByUserUnpacked(user); + + assertEq(ids.length, 1, "User should have 1 pending request"); + assertEq(ids[0], reqId, "Pending request should be the WFLOW create request"); + assertEq(balanceTokens.length, 2, "Should return NATIVE_FLOW and WFLOW balances"); + assertEq(balanceTokens[0], NATIVE_FLOW, "First balance token should be NATIVE_FLOW"); + assertEq(balanceTokens[1], WFLOW, "Second balance token should be WFLOW"); + assertEq(pendingBalances[0], 0, "NATIVE_FLOW pending balance should be 0"); + assertEq(pendingBalances[1], 2 ether, "WFLOW pending balance should match request amount"); + assertEq(claimableRefundsArr[0], 0, "NATIVE_FLOW refund balance should be 0"); + assertEq(claimableRefundsArr[1], 0, "WFLOW refund balance should be 0 before cancellation"); + assertEq(c.getUserPendingBalance(user, WFLOW), pendingBalances[1], "Direct WFLOW getter should match unpacked view"); + assertEq(c.getClaimableRefund(user, WFLOW), claimableRefundsArr[1], "Direct WFLOW refund getter should match unpacked view"); + + c.cancelRequest(reqId); + + ( + uint256[] memory idsAfterCancel, + , + , + , + , + , + , + , + , + , + address[] memory balanceTokensAfterCancel, + uint256[] memory pendingBalancesAfterCancel, + uint256[] memory claimableRefundsAfterCancel + ) = c.getPendingRequestsByUserUnpacked(user); + + assertEq(idsAfterCancel.length, 0, "Cancelled WFLOW request should no longer be pending"); + assertEq(balanceTokensAfterCancel.length, 2, "Token balance slots should remain stable"); + assertEq(balanceTokensAfterCancel[1], WFLOW, "Second balance token should remain WFLOW"); + assertEq(pendingBalancesAfterCancel[1], 0, "WFLOW pending balance should be cleared after cancellation"); + assertEq(claimableRefundsAfterCancel[1], 2 ether, "WFLOW refund balance should match cancelled amount"); + assertEq(c.getUserPendingBalance(user, WFLOW), 0, "Direct WFLOW pending getter should be cleared after cancellation"); + assertEq(c.getClaimableRefund(user, WFLOW), 2 ether, "Direct WFLOW refund getter should match cancelled amount"); + vm.stopPrank(); + } + function test_GetPendingRequestsByUserUnpacked_MultipleUsers() public { vm.prank(user); c.createYieldVault{value: 1 ether}(NATIVE_FLOW, 1 ether, VAULT_ID, STRATEGY_ID); From 38648bb345a89e4fb7d434d46757cbc47ec97519 Mon Sep 17 00:00:00 2001 From: liobrasil Date: Mon, 30 Mar 2026 19:56:42 -0400 Subject: [PATCH 05/10] Track configured tokens in pending balance view --- solidity/src/FlowYieldVaultsRequests.sol | 68 ++++++++++------- solidity/test/FlowYieldVaultsRequests.t.sol | 82 +++++++++++++++++++++ 2 files changed, 122 insertions(+), 28 deletions(-) diff --git a/solidity/src/FlowYieldVaultsRequests.sol b/solidity/src/FlowYieldVaultsRequests.sol index fd9a8ca..016db37 100644 --- a/solidity/src/FlowYieldVaultsRequests.sol +++ b/solidity/src/FlowYieldVaultsRequests.sol @@ -131,6 +131,12 @@ contract FlowYieldVaultsRequests is ReentrancyGuard, Ownable2Step { /// @notice Token configurations indexed by token address mapping(address => TokenConfig) public allowedTokens; + /// @notice Token addresses surfaced in per-user balance views + address[] private _trackedTokens; + + /// @notice O(1) lookup for tracked-token membership + mapping(address => bool) private _isTrackedToken; + /// @notice Maximum number of pending requests allowed per user (0 = unlimited) uint256 public maxPendingRequestsPerUser; @@ -494,19 +500,11 @@ contract FlowYieldVaultsRequests is ReentrancyGuard, Ownable2Step { _requestIdCounter = 1; maxPendingRequestsPerUser = 10; - allowedTokens[NATIVE_FLOW] = TokenConfig({ - isSupported: true, - minimumBalance: 1 ether, - isNative: true - }); + _setTokenConfig(NATIVE_FLOW, true, 1 ether, true); // WFLOW is treated as ERC20 on EVM side, but unwraps to native FlowToken on Cadence if (wflowAddress != address(0)) { - allowedTokens[WFLOW] = TokenConfig({ - isSupported: true, - minimumBalance: 1 ether, - isNative: false - }); + _setTokenConfig(WFLOW, true, 1 ether, false); } } @@ -627,11 +625,7 @@ contract FlowYieldVaultsRequests is ReentrancyGuard, Ownable2Step { uint256 minimumBalance, bool isNative ) external onlyOwner { - allowedTokens[tokenAddress] = TokenConfig({ - isSupported: isSupported, - minimumBalance: minimumBalance, - isNative: isNative - }); + _setTokenConfig(tokenAddress, isSupported, minimumBalance, isNative); emit TokenConfigured( tokenAddress, @@ -1274,7 +1268,7 @@ contract FlowYieldVaultsRequests is ReentrancyGuard, Ownable2Step { /// @return messages Messages /// @return vaultIdentifiers Vault identifiers /// @return strategyIdentifiers Strategy identifiers - /// @return balanceTokens Token addresses for balance arrays (NATIVE_FLOW and, when configured, WFLOW) + /// @return balanceTokens Token addresses for balance arrays /// @return pendingBalances Escrowed balances for active pending requests per token /// @return claimableRefundsArray Claimable refund amounts per token function getPendingRequestsByUserUnpacked( @@ -1332,22 +1326,21 @@ contract FlowYieldVaultsRequests is ReentrancyGuard, Ownable2Step { } } - // Get balances for NATIVE_FLOW and, when configured, WFLOW - uint256 tokenCount = WFLOW != address(0) ? 2 : 1; + // Return balances for the full tracked token set so previously configured + // tokens remain visible even if later disabled. + uint256 tokenCount = _trackedTokens.length; 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]; + for (uint256 i = 0; i < tokenCount; ) { + address token = _trackedTokens[i]; + balanceTokens[i] = token; + pendingBalances[i] = pendingUserBalances[user][token]; + claimableRefundsArray[i] = claimableRefunds[user][token]; + unchecked { + ++i; + } } } @@ -1450,6 +1443,25 @@ contract FlowYieldVaultsRequests is ReentrancyGuard, Ownable2Step { } } + /// @dev Stores token configuration and records tokens for future balance queries. + function _setTokenConfig( + address tokenAddress, + bool isSupported, + uint256 minimumBalance, + bool isNative + ) internal { + allowedTokens[tokenAddress] = TokenConfig({ + isSupported: isSupported, + minimumBalance: minimumBalance, + isNative: isNative + }); + + if (!_isTrackedToken[tokenAddress]) { + _isTrackedToken[tokenAddress] = true; + _trackedTokens.push(tokenAddress); + } + } + /** * @dev Internal implementation for starting request processing. * Transitions request to PROCESSING status and handles fund transfers. diff --git a/solidity/test/FlowYieldVaultsRequests.t.sol b/solidity/test/FlowYieldVaultsRequests.t.sol index e16a660..2cbce80 100644 --- a/solidity/test/FlowYieldVaultsRequests.t.sol +++ b/solidity/test/FlowYieldVaultsRequests.t.sol @@ -22,11 +22,13 @@ contract FlowYieldVaultsRequestsTestHelper is FlowYieldVaultsRequests { contract FlowYieldVaultsRequestsTest is Test { FlowYieldVaultsRequestsTestHelper public c; ERC20Mock public wflow; + ERC20Mock public tokenB; address user = makeAddr("user"); address user2 = makeAddr("user2"); address coa = makeAddr("coa"); address constant NATIVE_FLOW = 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF; address WFLOW; + address TOKEN_B; // Events for testing (from OpenZeppelin Ownable2Step) event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner); @@ -47,9 +49,13 @@ contract FlowYieldVaultsRequestsTest is Test { vm.deal(user, 100 ether); vm.deal(user2, 100 ether); wflow = new ERC20Mock(); + tokenB = new ERC20Mock(); WFLOW = address(wflow); + TOKEN_B = address(tokenB); wflow.mint(user, 100 ether); wflow.mint(user2, 100 ether); + tokenB.mint(user, 100 ether); + tokenB.mint(user2, 100 ether); c = new FlowYieldVaultsRequestsTestHelper(coa, WFLOW); c.testRegisterYieldVaultId(42, user, NATIVE_FLOW); } @@ -1071,6 +1077,82 @@ contract FlowYieldVaultsRequestsTest is Test { vm.stopPrank(); } + function test_GetPendingRequestsByUserUnpacked_IncludesTrackedERC20Balances() public { + vm.prank(c.owner()); + c.setTokenConfig(TOKEN_B, true, 0.5 ether, false); + + vm.startPrank(user); + tokenB.approve(address(c), 3 ether); + uint256 reqId = c.createYieldVault(TOKEN_B, 3 ether, VAULT_ID, STRATEGY_ID); + vm.stopPrank(); + + ( + uint256[] memory ids, + , + , + , + , + , + , + , + , + , + address[] memory balanceTokens, + uint256[] memory pendingBalances, + uint256[] memory claimableRefundsArr + ) = c.getPendingRequestsByUserUnpacked(user); + + assertEq(ids.length, 1, "User should have 1 pending request"); + assertEq(ids[0], reqId, "Pending request should be the TokenB create request"); + assertEq(balanceTokens.length, 3, "Should return all tracked token balances"); + assertEq(balanceTokens[0], NATIVE_FLOW, "First balance token should be NATIVE_FLOW"); + assertEq(balanceTokens[1], WFLOW, "Second balance token should be WFLOW"); + assertEq(balanceTokens[2], TOKEN_B, "Third balance token should be TokenB"); + assertEq(pendingBalances[0], 0, "NATIVE_FLOW pending balance should be 0"); + assertEq(pendingBalances[1], 0, "WFLOW pending balance should be 0"); + assertEq(pendingBalances[2], 3 ether, "TokenB pending balance should match request amount"); + assertEq(claimableRefundsArr[2], 0, "TokenB refund balance should be 0 before cancellation"); + assertEq(c.getUserPendingBalance(user, TOKEN_B), pendingBalances[2], "Direct TokenB getter should match unpacked view"); + assertEq(c.getClaimableRefund(user, TOKEN_B), claimableRefundsArr[2], "Direct TokenB refund getter should match unpacked view"); + } + + function test_GetPendingRequestsByUserUnpacked_KeepsTrackedTokenVisibleAfterDisable() public { + vm.prank(c.owner()); + c.setTokenConfig(TOKEN_B, true, 0.5 ether, false); + + vm.startPrank(user); + tokenB.approve(address(c), 2 ether); + uint256 reqId = c.createYieldVault(TOKEN_B, 2 ether, VAULT_ID, STRATEGY_ID); + c.cancelRequest(reqId); + vm.stopPrank(); + + vm.prank(c.owner()); + c.setTokenConfig(TOKEN_B, false, 0, false); + + ( + uint256[] memory ids, + , + , + , + , + , + , + , + , + , + address[] memory balanceTokens, + uint256[] memory pendingBalances, + uint256[] memory claimableRefundsArr + ) = c.getPendingRequestsByUserUnpacked(user); + + assertEq(ids.length, 0, "Cancelled TokenB request should no longer be pending"); + assertEq(balanceTokens.length, 3, "Tracked token slots should remain stable after disable"); + assertEq(balanceTokens[2], TOKEN_B, "Disabled tracked token should remain visible"); + assertEq(pendingBalances[2], 0, "Disabled tracked token should have no pending balance after cancellation"); + assertEq(claimableRefundsArr[2], 2 ether, "Disabled tracked token refund should remain visible"); + assertEq(c.getClaimableRefund(user, TOKEN_B), 2 ether, "Direct TokenB refund getter should match unpacked view"); + } + function test_GetPendingRequestsByUserUnpacked_MultipleUsers() public { vm.prank(user); c.createYieldVault{value: 1 ether}(NATIVE_FLOW, 1 ether, VAULT_ID, STRATEGY_ID); From d9d714775f7a4e80ecdb3ec0ab64b6d56732aa8b Mon Sep 17 00:00:00 2001 From: liobrasil Date: Mon, 30 Mar 2026 20:43:46 -0400 Subject: [PATCH 06/10] Add multi-token Cadence pending request query test --- cadence/tests/evm_bridge_lifecycle_test.cdc | 65 +++++++++ cadence/tests/test_helpers.cdc | 47 ++++++ .../test/PendingRequestsByUserQueryMock.sol | 134 ++++++++++++++++++ 3 files changed, 246 insertions(+) create mode 100644 solidity/src/test/PendingRequestsByUserQueryMock.sol diff --git a/cadence/tests/evm_bridge_lifecycle_test.cdc b/cadence/tests/evm_bridge_lifecycle_test.cdc index 238b525..b07744d 100644 --- a/cadence/tests/evm_bridge_lifecycle_test.cdc +++ b/cadence/tests/evm_bridge_lifecycle_test.cdc @@ -255,6 +255,71 @@ fun testProcessResultStructure() { Test.assertEqual("Insufficient COA balance", failureResult.message) } +access(all) +fun testGetPendingRequestsForEVMAddressDecodesBalances() { + let tokenBAddress = deployERC20DecimalsOnlyMock(admin, decimals: 6) + let tokenCAddress = deployERC20DecimalsOnlyMock(admin, decimals: 8) + let mockAddress = deployPendingRequestsByUserQueryMock(admin, tokenBAddress: tokenBAddress, tokenCAddress: tokenCAddress) + let tokenBKey = EVM.addressFromString(tokenBAddress).toString() + let tokenCKey = EVM.addressFromString(tokenCAddress).toString() + let setAddrResult = updateRequestsAddress(admin, mockAddress) + Test.expect(setAddrResult, Test.beSucceeded()) + + let result = _executeScript( + "../scripts/get_pending_requests_for_evm_address.cdc", + [userEVMAddr1.toString()] + ) + Test.assertEqual(Test.ResultStatus.succeeded, result.status) + + let pendingInfo = result.returnValue as! FlowYieldVaultsEVM.PendingRequestsInfo + let nativeFlowKey = nativeFlowAddr.toString() + let pendingNative = pendingInfo.pendingBalances[nativeFlowKey] + ?? panic("Missing NATIVE_FLOW pending balance entry") + let refundNative = pendingInfo.claimableRefunds[nativeFlowKey] + ?? panic("Missing NATIVE_FLOW refund balance entry") + let pendingTokenB = pendingInfo.pendingBalances[tokenBKey] + ?? panic("Missing tokenB pending balance entry") + let refundTokenB = pendingInfo.claimableRefunds[tokenBKey] + ?? panic("Missing tokenB refund balance entry") + let pendingTokenC = pendingInfo.pendingBalances[tokenCKey] + ?? panic("Missing tokenC pending balance entry") + let refundTokenC = pendingInfo.claimableRefunds[tokenCKey] + ?? panic("Missing tokenC refund balance entry") + + Test.assertEqual(userEVMAddr1.toString(), pendingInfo.evmAddress) + Test.assertEqual(3, pendingInfo.pendingCount) + Test.assertEqual(3.0, pendingNative) + Test.assertEqual(0.0, refundNative) + Test.assertEqual(1.234567, pendingTokenB) + Test.assertEqual(0.5, refundTokenB) + Test.assertEqual(7.654321, pendingTokenC) + Test.assertEqual(0.0, refundTokenC) + Test.assertEqual(3, pendingInfo.requests.length) + + Test.assertEqual(11 as UInt256, pendingInfo.requests[0].id) + Test.assertEqual(12 as UInt256, pendingInfo.requests[1].id) + Test.assertEqual(13 as UInt256, pendingInfo.requests[2].id) + Test.assertEqual(1000000000000000000 as UInt256, pendingInfo.requests[0].amount) + Test.assertEqual(1234567 as UInt256, pendingInfo.requests[1].amount) + Test.assertEqual(765432100 as UInt256, pendingInfo.requests[2].amount) + Test.assertEqual(42 as UInt64?, pendingInfo.requests[1].yieldVaultId) + Test.assertEqual(43 as UInt64?, pendingInfo.requests[2].yieldVaultId) + Test.assertEqual(tokenBKey, pendingInfo.requests[1].tokenAddress.toString()) + Test.assertEqual(tokenCKey, pendingInfo.requests[2].tokenAddress.toString()) + Test.assertEqual( + FlowYieldVaultsEVM.RequestType.CREATE_YIELDVAULT.rawValue, + pendingInfo.requests[0].requestType + ) + Test.assertEqual( + FlowYieldVaultsEVM.RequestType.DEPOSIT_TO_YIELDVAULT.rawValue, + pendingInfo.requests[1].requestType + ) + Test.assertEqual( + FlowYieldVaultsEVM.RequestType.DEPOSIT_TO_YIELDVAULT.rawValue, + pendingInfo.requests[2].requestType + ) +} + access(all) fun testVaultAndStrategyIdentifiers() { // Test that vault and strategy identifiers are preserved correctly diff --git a/cadence/tests/test_helpers.cdc b/cadence/tests/test_helpers.cdc index ff17511..fd734d8 100644 --- a/cadence/tests/test_helpers.cdc +++ b/cadence/tests/test_helpers.cdc @@ -20,6 +20,16 @@ access(all) let nativeFlowAddr = EVM.addressFromString("0xFFfFfFffFFfffFFfFFfFFF access(all) let mockVaultIdentifier = "A.0ae53cb6e3f42a79.FlowToken.Vault" access(all) let mockStrategyIdentifier = "A.045a1763c93006ca.MockStrategies.TracerStrategy" +/* --- Embedded EVM mock bytecode --- */ + +// Regenerate with the repo's Foundry defaults from solidity/foundry.toml: +// `cd solidity && forge inspect src/test/PendingRequestsByUserQueryMock.sol:ERC20DecimalsOnlyMock bytecode` +access(all) let erc20DecimalsOnlyMockBytecode = "60a03461006257601f61011338819003918201601f19168301916001600160401b0383118484101761006657808492602094604052833981010312610062575160ff81168103610062576080526040516098908161007b823960805181603a0152f35b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60808060405260043610156011575f80fd5b5f90813560e01c63313ce567146025575f80fd5b34605e5781600319360112605e5760209060ff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b5080fdfea26469706673582212203c866ad60ad80831e09284cb4b396975c6e952f53d1755f246b0f36dcd9c903664736f6c63430008140033" + +// Regenerate with the repo's Foundry defaults from solidity/foundry.toml: +// `cd solidity && forge inspect src/test/PendingRequestsByUserQueryMock.sol:PendingRequestsByUserQueryMock bytecode` +access(all) let pendingRequestsByUserQueryMockBytecode = "60c03461008857601f6109c838819003918201601f19168301916001600160401b0383118484101761008c57808492604094855283398101031261008857610052602061004b836100a0565b92016100a0565b9060805260a05260405161091390816100b58239608051818181610404015261067d015260a051818181610441015261075b0152f35b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b51906001600160a01b03821682036100885756fe60806040526004361015610011575f80fd5b5f803560e01c636b67542814610025575f80fd5b346101875760209081600319360112610187576004356001600160a01b038116810361018357610054906103a2565b9b9c94969d939792989199909a604051809e6101a080835282016100779161018a565b9086818303910152610088916101bd565b8d810360408f0152610099916101bd565b8c810360608e01526100aa916101f3565b8b810360808d01526100bb9161018a565b908a820360a08c015280808d5193848152019c01925b828110610165575050505093610134610152946101256101619895610116610143966101088f8f9e9c8f60c081840391015261018a565b8d810360e08f01529061022f565b908b82036101008d015261022f565b908982036101208b015261022f565b908782036101408901526101f3565b9085820361016087015261018a565b9083820361018085015261018a565b0390f35b835167ffffffffffffffff168d529b81019b928101926001016100d1565b5080fd5b80fd5b9081518082526020808093019301915f5b8281106101a9575050505090565b83518552938101939281019260010161019b565b9081518082526020808093019301915f5b8281106101dc575050505090565b835160ff16855293810193928101926001016101ce565b9081518082526020808093019301915f5b828110610212575050505090565b83516001600160a01b031685529381019392810192600101610204565b90808083519081815260208080809301948460051b01019501935f9384915b84831061025f575050505050505090565b9091929394959684601f198084840301855289518051908185528a5b8281106102a757505080840183018a9052601f011690910181019781019695949360010192019061024e565b81810185015186820186015289940161027b565b604051906020820182811067ffffffffffffffff8211176102db57604052565b634e487b7160e01b5f52604160045260245ffd5b604051906080820182811067ffffffffffffffff8211176102db57604052565b604051906060820182811067ffffffffffffffff8211176102db57604052565b80511561033c5760200190565b634e487b7160e01b5f52603260045260245ffd5b80516001101561033c5760400190565b80516002101561033c5760600190565b6103786102ef565b906003825281905f5b60608082101561039b579060209182828701015201610381565b5050909150565b906103ab6102ef565b90600382526060366020840137816103c16102ef565b60038152606036602083013780916103d76102ef565b906003825260603660208401376011829760018060a01b036103f88461032f565b5261040283610350565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316905261043783610360565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116909152160361084e575050506104766102ef565b60038152916060366020850137829461048d6102ef565b6003815290606036602084013781956104a46102ef565b60038152606036602083013780966104ba6102ef565b6003815292606036602086013783976104d16102ef565b6003815295606036602089013786986104e86102ef565b600381529760603660208b013780899a6105006102ef565b600381526040366020830137809b610516610370565b9b8c610520610370565b9c8d9561052b610370565b9d8e6105368261032f565b600b90525f988995866105488661032f565b52866105538761032f565b526001600160a01b036105658961032f565b5261056f8c61032f565b670de0b6b3a764000090526105838a61032f565b6064905261058f6102bb565b87815261059b8261032f565b526105a58161032f565b506105ae61030f565b602281527f412e306165353363623665336634326137392e466c6f77546f6b656e2e5661756020820152611b1d60f21b60408201526105ec8361032f565b526105f68261032f565b506105ff61030f565b603081527f412e303435613137363363393330303663612e4d6f636b53747261746567696560208201526f732e547261636572537472617465677960801b604082015261064b8461032f565b526106558361032f565b5061065f84610350565b600c905261066c85610350565b600190528661067a87610350565b527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166106ae89610350565b526212d6876106bd819d610350565b526106c789610350565b602a90526106d48a610350565b606590526106e06102bb565b8781526106ec82610350565b526106f690610350565b506106ff6102bb565b86815261070b82610350565b5261071590610350565b5061071e6102bb565b85815261072a82610350565b5261073490610350565b5061073e90610360565b600d905261074b90610360565b6001905261075890610360565b527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169061078d90610360565b52632d9f912461079d8196610360565b526107a790610360565b602b90526107b490610360565b606690526107c06102bb565b8181526107cc8b610360565b526107d68a610360565b506107df6102bb565b8181526107eb8a610360565b526107f589610360565b506107fe6102bb565b90815261080a88610360565b5261081487610360565b5061081e8561032f565b6729a2241af62c0000905261083285610350565b5261083c84610360565b5261084682610350565b6207a1209052565b9250929450925061085d6102bb565b925f9283855261086b6102bb565b958487526108776102bb565b958587526108836102bb565b9580875261088f6102bb565b9581875261089b6102bb565b958287526108a76102bb565b958387526108b36102bb565b958487526108bf6102bb565b958587526108cb6102bb565b9586529c9b9a9998979695949392919056fea26469706673582212201da31499d382804da1e8c81851caccf7d38977f5ea928a251455d3a923454c5964736f6c63430008140033" + /* --- Setup helpers --- */ // Deploys all required contracts for FlowYieldVaultsEVM @@ -263,6 +273,43 @@ fun setupCOA(_ signer: Test.TestAccount): Test.TransactionResult { ) } +access(all) +fun deployEVMContractWithArgs(_ signer: Test.TestAccount, _ bytecode: String, _ args: [AnyStruct]): String { + let argsBytecode = EVM.encodeABI(args) + let bytecodeWithArgs = String.encodeHex(bytecode.decodeHex().concat(argsBytecode)) + let result = _executeTransaction( + "../transactions/deploy_evm_contract.cdc", + [bytecodeWithArgs, UInt64(15_000_000)], + signer + ) + Test.expect(result, Test.beSucceeded()) + + let txnEvents = Test.eventsOfType(Type()) + Test.assert(txnEvents.length > 0, message: "Expected an EVM.TransactionExecuted event for mock deployment") + + let deployEvent = txnEvents[txnEvents.length - 1] as? EVM.TransactionExecuted + ?? panic("Latest event is not an EVM.TransactionExecuted deployment event") + Test.assert(deployEvent.contractAddress.length > 0, message: "Deployment event missing contract address") + + return deployEvent.contractAddress +} + +access(all) +fun deployERC20DecimalsOnlyMock(_ signer: Test.TestAccount, decimals: UInt8): String { + return deployEVMContractWithArgs(signer, erc20DecimalsOnlyMockBytecode, [decimals]) +} + +access(all) +fun deployPendingRequestsByUserQueryMock( + _ signer: Test.TestAccount, + tokenBAddress: String, + tokenCAddress: String +): String { + let tokenB = EVM.addressFromString(tokenBAddress) + let tokenC = EVM.addressFromString(tokenCAddress) + return deployEVMContractWithArgs(signer, pendingRequestsByUserQueryMockBytecode, [tokenB, tokenC]) +} + /* --- FlowYieldVaultsEVM specific script helpers --- */ access(all) diff --git a/solidity/src/test/PendingRequestsByUserQueryMock.sol b/solidity/src/test/PendingRequestsByUserQueryMock.sol new file mode 100644 index 0000000..5ed0f90 --- /dev/null +++ b/solidity/src/test/PendingRequestsByUserQueryMock.sol @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +/// @notice Tiny ERC20-like mock exposing only decimals(). +contract ERC20DecimalsOnlyMock { + uint8 internal immutable tokenDecimals; + + constructor(uint8 tokenDecimals_) { + tokenDecimals = tokenDecimals_; + } + + function decimals() external view returns (uint8) { + return tokenDecimals; + } +} + +/// @notice Test-only EVM mock for Cadence query coverage. +/// @dev Returns a stable getPendingRequestsByUserUnpacked payload for one user. +contract PendingRequestsByUserQueryMock { + address internal constant TARGET_USER = 0x0000000000000000000000000000000000000011; + address internal constant NATIVE_FLOW = 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF; + address internal immutable tokenB; + address internal immutable tokenC; + + constructor(address tokenB_, address tokenC_) { + tokenB = tokenB_; + tokenC = tokenC_; + } + + function getPendingRequestsByUserUnpacked( + address user + ) + external + view + returns ( + uint256[] memory ids, + uint8[] memory requestTypes, + uint8[] memory statuses, + address[] memory tokenAddresses, + uint256[] memory amounts, + uint64[] memory yieldVaultIds, + uint256[] memory timestamps, + string[] memory messages, + string[] memory vaultIdentifiers, + string[] memory strategyIdentifiers, + address[] memory balanceTokens, + uint256[] memory pendingBalances, + uint256[] memory claimableRefundsArray + ) + { + balanceTokens = new address[](3); + pendingBalances = new uint256[](3); + claimableRefundsArray = new uint256[](3); + balanceTokens[0] = NATIVE_FLOW; + balanceTokens[1] = tokenB; + balanceTokens[2] = tokenC; + + if (user != TARGET_USER) { + ids = new uint256[](0); + requestTypes = new uint8[](0); + statuses = new uint8[](0); + tokenAddresses = new address[](0); + amounts = new uint256[](0); + yieldVaultIds = new uint64[](0); + timestamps = new uint256[](0); + messages = new string[](0); + vaultIdentifiers = new string[](0); + strategyIdentifiers = new string[](0); + return ( + ids, + requestTypes, + statuses, + tokenAddresses, + amounts, + yieldVaultIds, + timestamps, + messages, + vaultIdentifiers, + strategyIdentifiers, + balanceTokens, + pendingBalances, + claimableRefundsArray + ); + } + + ids = new uint256[](3); + requestTypes = new uint8[](3); + statuses = new uint8[](3); + tokenAddresses = new address[](3); + amounts = new uint256[](3); + yieldVaultIds = new uint64[](3); + timestamps = new uint256[](3); + messages = new string[](3); + vaultIdentifiers = new string[](3); + strategyIdentifiers = new string[](3); + + ids[0] = 11; + requestTypes[0] = 0; + statuses[0] = 0; + tokenAddresses[0] = NATIVE_FLOW; + amounts[0] = 1 ether; + timestamps[0] = 100; + messages[0] = ""; + vaultIdentifiers[0] = "A.0ae53cb6e3f42a79.FlowToken.Vault"; + strategyIdentifiers[0] = "A.045a1763c93006ca.MockStrategies.TracerStrategy"; + + ids[1] = 12; + requestTypes[1] = 1; + statuses[1] = 0; + tokenAddresses[1] = tokenB; + amounts[1] = 1_234_567; + yieldVaultIds[1] = 42; + timestamps[1] = 101; + messages[1] = ""; + vaultIdentifiers[1] = ""; + strategyIdentifiers[1] = ""; + + ids[2] = 13; + requestTypes[2] = 1; + statuses[2] = 0; + tokenAddresses[2] = tokenC; + amounts[2] = 765_432_100; + yieldVaultIds[2] = 43; + timestamps[2] = 102; + messages[2] = ""; + vaultIdentifiers[2] = ""; + strategyIdentifiers[2] = ""; + + pendingBalances[0] = 3 ether; + pendingBalances[1] = 1_234_567; + pendingBalances[2] = 765_432_100; + claimableRefundsArray[1] = 500_000; + } +} From 06cf13f6ef283c04c951c5a0b6f581a11a5e619d Mon Sep 17 00:00:00 2001 From: liobrasil Date: Mon, 30 Mar 2026 20:50:29 -0400 Subject: [PATCH 07/10] Clarify query mock ABI intent --- solidity/src/test/PendingRequestsByUserQueryMock.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/solidity/src/test/PendingRequestsByUserQueryMock.sol b/solidity/src/test/PendingRequestsByUserQueryMock.sol index 5ed0f90..41db6c6 100644 --- a/solidity/src/test/PendingRequestsByUserQueryMock.sol +++ b/solidity/src/test/PendingRequestsByUserQueryMock.sol @@ -15,7 +15,8 @@ contract ERC20DecimalsOnlyMock { } /// @notice Test-only EVM mock for Cadence query coverage. -/// @dev Returns a stable getPendingRequestsByUserUnpacked payload for one user. +/// @dev Mirrors the production getPendingRequestsByUserUnpacked ABI exactly, +/// but returns synthetic fixed data instead of reading real request state. contract PendingRequestsByUserQueryMock { address internal constant TARGET_USER = 0x0000000000000000000000000000000000000011; address internal constant NATIVE_FLOW = 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF; From 21ad0bd426348bd45a0ce38998bff812fac941a1 Mon Sep 17 00:00:00 2001 From: liobrasil Date: Mon, 30 Mar 2026 21:11:47 -0400 Subject: [PATCH 08/10] Fix tracked token query regressions --- cadence/contracts/FlowYieldVaultsEVM.cdc | 31 ++++++++++++------ cadence/tests/evm_bridge_lifecycle_test.cdc | 16 +++------- cadence/tests/test_helpers.cdc | 2 +- .../artifacts/FlowYieldVaultsRequests.json | 23 +++++++++++++ solidity/src/FlowYieldVaultsRequests.sol | 2 +- .../test/PendingRequestsByUserQueryMock.sol | 32 ++++++------------- solidity/test/FlowYieldVaultsRequests.t.sol | 30 +++++++++++++++++ 7 files changed, 90 insertions(+), 46 deletions(-) diff --git a/cadence/contracts/FlowYieldVaultsEVM.cdc b/cadence/contracts/FlowYieldVaultsEVM.cdc index bfa0458..c12395c 100644 --- a/cadence/contracts/FlowYieldVaultsEVM.cdc +++ b/cadence/contracts/FlowYieldVaultsEVM.cdc @@ -1717,14 +1717,20 @@ access(all) contract FlowYieldVaultsEVM { 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] - ) + let rawPending = pendingBalancesRaw[j] + let rawRefund = claimableRefundsRaw[j] + pendingBalances[tokenAddr] = rawPending == 0 + ? 0.0 + : FlowYieldVaultsEVM.ufix64FromUInt256( + rawPending, + tokenAddress: balanceTokens[j] + ) + claimableRefundsMap[tokenAddr] = rawRefund == 0 + ? 0.0 + : FlowYieldVaultsEVM.ufix64FromUInt256( + rawRefund, + tokenAddress: balanceTokens[j] + ) j = j + 1 } @@ -1951,7 +1957,10 @@ access(all) contract FlowYieldVaultsEVM { /// @notice Converts a UInt256 amount from EVM to UFix64 for Cadence /// @dev For native FLOW: Uses 18 decimals (attoflow to FLOW conversion) - /// For ERC20: Uses FlowEVMBridgeUtils to look up token decimals + /// For ERC20: Uses FlowEVMBridgeUtils to look up token decimals. + /// Cadence UFix64 preserves 8 decimal places, so tokens with more than 8 + /// decimals are truncated toward zero when entering Cadence. Any remainder + /// smaller than 0.00000001 token is lost and cannot be recovered later. /// @param value The amount in wei/smallest unit (UInt256) /// @param tokenAddress The token address to determine decimal conversion /// @return The converted amount in UFix64 format @@ -1964,7 +1973,9 @@ access(all) contract FlowYieldVaultsEVM { /// @notice Converts a UFix64 amount from Cadence to UInt256 for EVM /// @dev For native FLOW: Uses 18 decimals (FLOW to attoflow conversion) - /// For ERC20: Uses FlowEVMBridgeUtils to look up token decimals + /// For ERC20: Uses FlowEVMBridgeUtils to look up token decimals. + /// This reconstructs an EVM amount from the already-quantized UFix64 value, + /// so previously truncated sub-1e-8 dust does not reappear on the return path. /// @param value The amount in UFix64 format /// @param tokenAddress The token address to determine decimal conversion /// @return The converted amount in wei/smallest unit (UInt256) diff --git a/cadence/tests/evm_bridge_lifecycle_test.cdc b/cadence/tests/evm_bridge_lifecycle_test.cdc index b07744d..2fa840a 100644 --- a/cadence/tests/evm_bridge_lifecycle_test.cdc +++ b/cadence/tests/evm_bridge_lifecycle_test.cdc @@ -258,7 +258,7 @@ fun testProcessResultStructure() { access(all) fun testGetPendingRequestsForEVMAddressDecodesBalances() { let tokenBAddress = deployERC20DecimalsOnlyMock(admin, decimals: 6) - let tokenCAddress = deployERC20DecimalsOnlyMock(admin, decimals: 8) + let tokenCAddress = "0x00000000000000000000000000000000000000cc" let mockAddress = deployPendingRequestsByUserQueryMock(admin, tokenBAddress: tokenBAddress, tokenCAddress: tokenCAddress) let tokenBKey = EVM.addressFromString(tokenBAddress).toString() let tokenCKey = EVM.addressFromString(tokenCAddress).toString() @@ -287,25 +287,21 @@ fun testGetPendingRequestsForEVMAddressDecodesBalances() { ?? panic("Missing tokenC refund balance entry") Test.assertEqual(userEVMAddr1.toString(), pendingInfo.evmAddress) - Test.assertEqual(3, pendingInfo.pendingCount) + Test.assertEqual(2, pendingInfo.pendingCount) Test.assertEqual(3.0, pendingNative) Test.assertEqual(0.0, refundNative) Test.assertEqual(1.234567, pendingTokenB) Test.assertEqual(0.5, refundTokenB) - Test.assertEqual(7.654321, pendingTokenC) + Test.assertEqual(0.0, pendingTokenC) Test.assertEqual(0.0, refundTokenC) - Test.assertEqual(3, pendingInfo.requests.length) + Test.assertEqual(2, pendingInfo.requests.length) Test.assertEqual(11 as UInt256, pendingInfo.requests[0].id) Test.assertEqual(12 as UInt256, pendingInfo.requests[1].id) - Test.assertEqual(13 as UInt256, pendingInfo.requests[2].id) Test.assertEqual(1000000000000000000 as UInt256, pendingInfo.requests[0].amount) Test.assertEqual(1234567 as UInt256, pendingInfo.requests[1].amount) - Test.assertEqual(765432100 as UInt256, pendingInfo.requests[2].amount) Test.assertEqual(42 as UInt64?, pendingInfo.requests[1].yieldVaultId) - Test.assertEqual(43 as UInt64?, pendingInfo.requests[2].yieldVaultId) Test.assertEqual(tokenBKey, pendingInfo.requests[1].tokenAddress.toString()) - Test.assertEqual(tokenCKey, pendingInfo.requests[2].tokenAddress.toString()) Test.assertEqual( FlowYieldVaultsEVM.RequestType.CREATE_YIELDVAULT.rawValue, pendingInfo.requests[0].requestType @@ -314,10 +310,6 @@ fun testGetPendingRequestsForEVMAddressDecodesBalances() { FlowYieldVaultsEVM.RequestType.DEPOSIT_TO_YIELDVAULT.rawValue, pendingInfo.requests[1].requestType ) - Test.assertEqual( - FlowYieldVaultsEVM.RequestType.DEPOSIT_TO_YIELDVAULT.rawValue, - pendingInfo.requests[2].requestType - ) } access(all) diff --git a/cadence/tests/test_helpers.cdc b/cadence/tests/test_helpers.cdc index fd734d8..6fb14e7 100644 --- a/cadence/tests/test_helpers.cdc +++ b/cadence/tests/test_helpers.cdc @@ -28,7 +28,7 @@ access(all) let erc20DecimalsOnlyMockBytecode = "60a03461006257601f6101133881900 // Regenerate with the repo's Foundry defaults from solidity/foundry.toml: // `cd solidity && forge inspect src/test/PendingRequestsByUserQueryMock.sol:PendingRequestsByUserQueryMock bytecode` -access(all) let pendingRequestsByUserQueryMockBytecode = "60c03461008857601f6109c838819003918201601f19168301916001600160401b0383118484101761008c57808492604094855283398101031261008857610052602061004b836100a0565b92016100a0565b9060805260a05260405161091390816100b58239608051818181610404015261067d015260a051818181610441015261075b0152f35b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b51906001600160a01b03821682036100885756fe60806040526004361015610011575f80fd5b5f803560e01c636b67542814610025575f80fd5b346101875760209081600319360112610187576004356001600160a01b038116810361018357610054906103a2565b9b9c94969d939792989199909a604051809e6101a080835282016100779161018a565b9086818303910152610088916101bd565b8d810360408f0152610099916101bd565b8c810360608e01526100aa916101f3565b8b810360808d01526100bb9161018a565b908a820360a08c015280808d5193848152019c01925b828110610165575050505093610134610152946101256101619895610116610143966101088f8f9e9c8f60c081840391015261018a565b8d810360e08f01529061022f565b908b82036101008d015261022f565b908982036101208b015261022f565b908782036101408901526101f3565b9085820361016087015261018a565b9083820361018085015261018a565b0390f35b835167ffffffffffffffff168d529b81019b928101926001016100d1565b5080fd5b80fd5b9081518082526020808093019301915f5b8281106101a9575050505090565b83518552938101939281019260010161019b565b9081518082526020808093019301915f5b8281106101dc575050505090565b835160ff16855293810193928101926001016101ce565b9081518082526020808093019301915f5b828110610212575050505090565b83516001600160a01b031685529381019392810192600101610204565b90808083519081815260208080809301948460051b01019501935f9384915b84831061025f575050505050505090565b9091929394959684601f198084840301855289518051908185528a5b8281106102a757505080840183018a9052601f011690910181019781019695949360010192019061024e565b81810185015186820186015289940161027b565b604051906020820182811067ffffffffffffffff8211176102db57604052565b634e487b7160e01b5f52604160045260245ffd5b604051906080820182811067ffffffffffffffff8211176102db57604052565b604051906060820182811067ffffffffffffffff8211176102db57604052565b80511561033c5760200190565b634e487b7160e01b5f52603260045260245ffd5b80516001101561033c5760400190565b80516002101561033c5760600190565b6103786102ef565b906003825281905f5b60608082101561039b579060209182828701015201610381565b5050909150565b906103ab6102ef565b90600382526060366020840137816103c16102ef565b60038152606036602083013780916103d76102ef565b906003825260603660208401376011829760018060a01b036103f88461032f565b5261040283610350565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316905261043783610360565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116909152160361084e575050506104766102ef565b60038152916060366020850137829461048d6102ef565b6003815290606036602084013781956104a46102ef565b60038152606036602083013780966104ba6102ef565b6003815292606036602086013783976104d16102ef565b6003815295606036602089013786986104e86102ef565b600381529760603660208b013780899a6105006102ef565b600381526040366020830137809b610516610370565b9b8c610520610370565b9c8d9561052b610370565b9d8e6105368261032f565b600b90525f988995866105488661032f565b52866105538761032f565b526001600160a01b036105658961032f565b5261056f8c61032f565b670de0b6b3a764000090526105838a61032f565b6064905261058f6102bb565b87815261059b8261032f565b526105a58161032f565b506105ae61030f565b602281527f412e306165353363623665336634326137392e466c6f77546f6b656e2e5661756020820152611b1d60f21b60408201526105ec8361032f565b526105f68261032f565b506105ff61030f565b603081527f412e303435613137363363393330303663612e4d6f636b53747261746567696560208201526f732e547261636572537472617465677960801b604082015261064b8461032f565b526106558361032f565b5061065f84610350565b600c905261066c85610350565b600190528661067a87610350565b527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166106ae89610350565b526212d6876106bd819d610350565b526106c789610350565b602a90526106d48a610350565b606590526106e06102bb565b8781526106ec82610350565b526106f690610350565b506106ff6102bb565b86815261070b82610350565b5261071590610350565b5061071e6102bb565b85815261072a82610350565b5261073490610350565b5061073e90610360565b600d905261074b90610360565b6001905261075890610360565b527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169061078d90610360565b52632d9f912461079d8196610360565b526107a790610360565b602b90526107b490610360565b606690526107c06102bb565b8181526107cc8b610360565b526107d68a610360565b506107df6102bb565b8181526107eb8a610360565b526107f589610360565b506107fe6102bb565b90815261080a88610360565b5261081487610360565b5061081e8561032f565b6729a2241af62c0000905261083285610350565b5261083c84610360565b5261084682610350565b6207a1209052565b9250929450925061085d6102bb565b925f9283855261086b6102bb565b958487526108776102bb565b958587526108836102bb565b9580875261088f6102bb565b9581875261089b6102bb565b958287526108a76102bb565b958387526108b36102bb565b958487526108bf6102bb565b958587526108cb6102bb565b9586529c9b9a9998979695949392919056fea26469706673582212201da31499d382804da1e8c81851caccf7d38977f5ea928a251455d3a923454c5964736f6c63430008140033" +access(all) let pendingRequestsByUserQueryMockBytecode = "60c03461008157601f6108c838819003918201601f19168301916001600160401b0383118484101761008557808492604094855283398101031261008157610052602061004b83610099565b9201610099565b9060805260a05260405161081a90816100ae82396080518181816103f0015261066d015260a0518161042e0152f35b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b51906001600160a01b03821682036100815756fe60806040526004361015610011575f80fd5b5f803560e01c636b67542814610025575f80fd5b346101875760209081600319360112610187576004356001600160a01b0381168103610183576100549061038c565b9b9c94969d939792989199909a604051809e6101a080835282016100779161018a565b9086818303910152610088916101bd565b8d810360408f0152610099916101bd565b8c810360608e01526100aa916101f3565b8b810360808d01526100bb9161018a565b908a820360a08c015280808d5193848152019c01925b828110610165575050505093610134610152946101256101619895610116610143966101088f8f9e9c8f60c081840391015261018a565b8d810360e08f01529061022f565b908b82036101008d015261022f565b908982036101208b015261022f565b908782036101408901526101f3565b9085820361016087015261018a565b9083820361018085015261018a565b0390f35b835167ffffffffffffffff168d529b81019b928101926001016100d1565b5080fd5b80fd5b9081518082526020808093019301915f5b8281106101a9575050505090565b83518552938101939281019260010161019b565b9081518082526020808093019301915f5b8281106101dc575050505090565b835160ff16855293810193928101926001016101ce565b9081518082526020808093019301915f5b828110610212575050505090565b83516001600160a01b031685529381019392810192600101610204565b9080825180825260208092019180808360051b8601019501935f9384915b84831061025e575050505050505090565b9091929394959684601f198084840301855289518051908185528a5b8281106102a657505080840183018a9052601f011690910181019781019695949360010192019061024d565b81810185015186820186015289940161027a565b604051906020820182811067ffffffffffffffff8211176102da57604052565b634e487b7160e01b5f52604160045260245ffd5b604051906060820182811067ffffffffffffffff8211176102da57604052565b604051906080820182811067ffffffffffffffff8211176102da57604052565b80511561033b5760200190565b634e487b7160e01b5f52603260045260245ffd5b80516001101561033b5760400190565b6103676102ee565b9060028252815f5b6040811061037b575050565b80606060208093850101520161036f565b9061039561030e565b90600382526060366020840137816103ab61030e565b60038152606036602083013780916103c161030e565b9060038252606036602084013790958691906001600160a01b036103e48361032e565b526103ee8261034f565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316905281516002101561033b576001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811660608401521660101901610755575050506104686102ee565b60028152916040366020850137829461047f6102ee565b6002815290604036602084013781956104966102ee565b60028152604036602083013780966104ac6102ee565b6002815292604036602086013783976104c36102ee565b6002815295604036602089013786986104da6102ee565b600281529760403660208b013788996104f16102ee565b600281526040366020830137809a61050761035f565b9a8b61051161035f565b9b8c9561051c61035f565b9c6105268161032e565b600b90525f978894856105388561032e565b52856105438661032e565b526001600160a01b036105558861032e565b5261055f8b61032e565b670de0b6b3a764000090526105738961032e565b6064905261057f6102ba565b86815261058b8261032e565b526105959061032e565b5061059e6102ee565b602281527f412e306165353363623665336634326137392e466c6f77546f6b656e2e5661756020820152611b1d60f21b60408201526105dc8261032e565b526105e69061032e565b508d6105f06102ee565b603081527f412e303435613137363363393330303663612e4d6f636b53747261746567696560208201526f732e547261636572537472617465677960801b604082015261063c8261032e565b526106469061032e565b506106509061034f565b600c905261065d9061034f565b6001905261066a9061034f565b527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169061069f9061034f565b526212d6876106ae819561034f565b526106b89061034f565b602a90526106c59061034f565b606590526106d16102ba565b8181526106dd8a61034f565b526106e78961034f565b506106f06102ba565b8181526106fc8961034f565b526107068861034f565b5061070f6102ba565b90815261071b8761034f565b526107258661034f565b5061072f8461032e565b6729a2241af62c000090526107438461034f565b5261074d8261034f565b6207a1209052565b925092945092506107646102ba565b925f928385526107726102ba565b9584875261077e6102ba565b9585875261078a6102ba565b958087526107966102ba565b958187526107a26102ba565b958287526107ae6102ba565b958387526107ba6102ba565b958487526107c66102ba565b958587526107d26102ba565b9586529c9b9a9998979695949392919056fea2646970667358221220cfa80be468db850f056dd10c0c0cfcd65bac57fec1fbf9c969b09350055e9c6364736f6c63430008140033" /* --- Setup helpers --- */ diff --git a/deployments/artifacts/FlowYieldVaultsRequests.json b/deployments/artifacts/FlowYieldVaultsRequests.json index 9f8244b..32fc49f 100644 --- a/deployments/artifacts/FlowYieldVaultsRequests.json +++ b/deployments/artifacts/FlowYieldVaultsRequests.json @@ -19,6 +19,19 @@ "type": "receive", "stateMutability": "payable" }, + { + "type": "function", + "name": "DEFAULT_MINIMUM_BALANCE", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "NATIVE_FLOW", @@ -1952,6 +1965,11 @@ "name": "CannotAllowlistZeroAddress", "inputs": [] }, + { + "type": "error", + "name": "CannotBlocklistZeroAddress", + "inputs": [] + }, { "type": "error", "name": "CannotRegisterSentinelYieldVaultId", @@ -2003,6 +2021,11 @@ "name": "InvalidCOAAddress", "inputs": [] }, + { + "type": "error", + "name": "InvalidMinimumBalance", + "inputs": [] + }, { "type": "error", "name": "InvalidRequestState", diff --git a/solidity/src/FlowYieldVaultsRequests.sol b/solidity/src/FlowYieldVaultsRequests.sol index 3a6ceea..b114136 100644 --- a/solidity/src/FlowYieldVaultsRequests.sol +++ b/solidity/src/FlowYieldVaultsRequests.sol @@ -1469,7 +1469,7 @@ contract FlowYieldVaultsRequests is ReentrancyGuard, Ownable2Step { isNative: isNative }); - if (!_isTrackedToken[tokenAddress]) { + if (isSupported && !_isTrackedToken[tokenAddress]) { _isTrackedToken[tokenAddress] = true; _trackedTokens.push(tokenAddress); } diff --git a/solidity/src/test/PendingRequestsByUserQueryMock.sol b/solidity/src/test/PendingRequestsByUserQueryMock.sol index 41db6c6..ecddfd3 100644 --- a/solidity/src/test/PendingRequestsByUserQueryMock.sol +++ b/solidity/src/test/PendingRequestsByUserQueryMock.sol @@ -84,16 +84,16 @@ contract PendingRequestsByUserQueryMock { ); } - ids = new uint256[](3); - requestTypes = new uint8[](3); - statuses = new uint8[](3); - tokenAddresses = new address[](3); - amounts = new uint256[](3); - yieldVaultIds = new uint64[](3); - timestamps = new uint256[](3); - messages = new string[](3); - vaultIdentifiers = new string[](3); - strategyIdentifiers = new string[](3); + ids = new uint256[](2); + requestTypes = new uint8[](2); + statuses = new uint8[](2); + tokenAddresses = new address[](2); + amounts = new uint256[](2); + yieldVaultIds = new uint64[](2); + timestamps = new uint256[](2); + messages = new string[](2); + vaultIdentifiers = new string[](2); + strategyIdentifiers = new string[](2); ids[0] = 11; requestTypes[0] = 0; @@ -116,20 +116,8 @@ contract PendingRequestsByUserQueryMock { vaultIdentifiers[1] = ""; strategyIdentifiers[1] = ""; - ids[2] = 13; - requestTypes[2] = 1; - statuses[2] = 0; - tokenAddresses[2] = tokenC; - amounts[2] = 765_432_100; - yieldVaultIds[2] = 43; - timestamps[2] = 102; - messages[2] = ""; - vaultIdentifiers[2] = ""; - strategyIdentifiers[2] = ""; - pendingBalances[0] = 3 ether; pendingBalances[1] = 1_234_567; - pendingBalances[2] = 765_432_100; claimableRefundsArray[1] = 500_000; } } diff --git a/solidity/test/FlowYieldVaultsRequests.t.sol b/solidity/test/FlowYieldVaultsRequests.t.sol index 6a70b99..ce65e29 100644 --- a/solidity/test/FlowYieldVaultsRequests.t.sol +++ b/solidity/test/FlowYieldVaultsRequests.t.sol @@ -1175,6 +1175,36 @@ contract FlowYieldVaultsRequestsTest is Test { assertEq(c.getClaimableRefund(user, TOKEN_B), 2 ether, "Direct TokenB refund getter should match unpacked view"); } + function test_GetPendingRequestsByUserUnpacked_DoesNotTrackNeverSupportedToken() public { + vm.prank(c.owner()); + c.setTokenConfig(TOKEN_B, false, 0, false); + + ( + uint256[] memory ids, + , + , + , + , + , + , + , + , + , + address[] memory balanceTokens, + uint256[] memory pendingBalances, + uint256[] memory claimableRefundsArr + ) = c.getPendingRequestsByUserUnpacked(user); + + assertEq(ids.length, 0, "User should have no pending requests"); + assertEq(balanceTokens.length, 2, "Never-supported token should not be tracked"); + assertEq(balanceTokens[0], NATIVE_FLOW, "First balance token should be NATIVE_FLOW"); + assertEq(balanceTokens[1], WFLOW, "Second balance token should be WFLOW"); + assertEq(pendingBalances[0], 0, "NATIVE_FLOW pending balance should remain 0"); + assertEq(pendingBalances[1], 0, "WFLOW pending balance should remain 0"); + assertEq(claimableRefundsArr[0], 0, "NATIVE_FLOW refund balance should remain 0"); + assertEq(claimableRefundsArr[1], 0, "WFLOW refund balance should remain 0"); + } + function test_GetPendingRequestsByUserUnpacked_MultipleUsers() public { vm.prank(user); c.createYieldVault{value: 1 ether}(NATIVE_FLOW, 1 ether, VAULT_ID, STRATEGY_ID); From 6efc9aa6fdeb3a47cc6341f20f2ae2a07cbec212 Mon Sep 17 00:00:00 2001 From: liobrasil Date: Mon, 30 Mar 2026 21:29:11 -0400 Subject: [PATCH 09/10] Fix pending request sentinel mock --- cadence/tests/evm_bridge_lifecycle_test.cdc | 1 + cadence/tests/test_helpers.cdc | 2 +- solidity/src/test/PendingRequestsByUserQueryMock.sol | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cadence/tests/evm_bridge_lifecycle_test.cdc b/cadence/tests/evm_bridge_lifecycle_test.cdc index 2fa840a..6babcf9 100644 --- a/cadence/tests/evm_bridge_lifecycle_test.cdc +++ b/cadence/tests/evm_bridge_lifecycle_test.cdc @@ -300,6 +300,7 @@ fun testGetPendingRequestsForEVMAddressDecodesBalances() { Test.assertEqual(12 as UInt256, pendingInfo.requests[1].id) Test.assertEqual(1000000000000000000 as UInt256, pendingInfo.requests[0].amount) Test.assertEqual(1234567 as UInt256, pendingInfo.requests[1].amount) + Test.assertEqual(nil, pendingInfo.requests[0].yieldVaultId) Test.assertEqual(42 as UInt64?, pendingInfo.requests[1].yieldVaultId) Test.assertEqual(tokenBKey, pendingInfo.requests[1].tokenAddress.toString()) Test.assertEqual( diff --git a/cadence/tests/test_helpers.cdc b/cadence/tests/test_helpers.cdc index 6fb14e7..89329de 100644 --- a/cadence/tests/test_helpers.cdc +++ b/cadence/tests/test_helpers.cdc @@ -28,7 +28,7 @@ access(all) let erc20DecimalsOnlyMockBytecode = "60a03461006257601f6101133881900 // Regenerate with the repo's Foundry defaults from solidity/foundry.toml: // `cd solidity && forge inspect src/test/PendingRequestsByUserQueryMock.sol:PendingRequestsByUserQueryMock bytecode` -access(all) let pendingRequestsByUserQueryMockBytecode = "60c03461008157601f6108c838819003918201601f19168301916001600160401b0383118484101761008557808492604094855283398101031261008157610052602061004b83610099565b9201610099565b9060805260a05260405161081a90816100ae82396080518181816103f0015261066d015260a0518161042e0152f35b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b51906001600160a01b03821682036100815756fe60806040526004361015610011575f80fd5b5f803560e01c636b67542814610025575f80fd5b346101875760209081600319360112610187576004356001600160a01b0381168103610183576100549061038c565b9b9c94969d939792989199909a604051809e6101a080835282016100779161018a565b9086818303910152610088916101bd565b8d810360408f0152610099916101bd565b8c810360608e01526100aa916101f3565b8b810360808d01526100bb9161018a565b908a820360a08c015280808d5193848152019c01925b828110610165575050505093610134610152946101256101619895610116610143966101088f8f9e9c8f60c081840391015261018a565b8d810360e08f01529061022f565b908b82036101008d015261022f565b908982036101208b015261022f565b908782036101408901526101f3565b9085820361016087015261018a565b9083820361018085015261018a565b0390f35b835167ffffffffffffffff168d529b81019b928101926001016100d1565b5080fd5b80fd5b9081518082526020808093019301915f5b8281106101a9575050505090565b83518552938101939281019260010161019b565b9081518082526020808093019301915f5b8281106101dc575050505090565b835160ff16855293810193928101926001016101ce565b9081518082526020808093019301915f5b828110610212575050505090565b83516001600160a01b031685529381019392810192600101610204565b9080825180825260208092019180808360051b8601019501935f9384915b84831061025e575050505050505090565b9091929394959684601f198084840301855289518051908185528a5b8281106102a657505080840183018a9052601f011690910181019781019695949360010192019061024d565b81810185015186820186015289940161027a565b604051906020820182811067ffffffffffffffff8211176102da57604052565b634e487b7160e01b5f52604160045260245ffd5b604051906060820182811067ffffffffffffffff8211176102da57604052565b604051906080820182811067ffffffffffffffff8211176102da57604052565b80511561033b5760200190565b634e487b7160e01b5f52603260045260245ffd5b80516001101561033b5760400190565b6103676102ee565b9060028252815f5b6040811061037b575050565b80606060208093850101520161036f565b9061039561030e565b90600382526060366020840137816103ab61030e565b60038152606036602083013780916103c161030e565b9060038252606036602084013790958691906001600160a01b036103e48361032e565b526103ee8261034f565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316905281516002101561033b576001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811660608401521660101901610755575050506104686102ee565b60028152916040366020850137829461047f6102ee565b6002815290604036602084013781956104966102ee565b60028152604036602083013780966104ac6102ee565b6002815292604036602086013783976104c36102ee565b6002815295604036602089013786986104da6102ee565b600281529760403660208b013788996104f16102ee565b600281526040366020830137809a61050761035f565b9a8b61051161035f565b9b8c9561051c61035f565b9c6105268161032e565b600b90525f978894856105388561032e565b52856105438661032e565b526001600160a01b036105558861032e565b5261055f8b61032e565b670de0b6b3a764000090526105738961032e565b6064905261057f6102ba565b86815261058b8261032e565b526105959061032e565b5061059e6102ee565b602281527f412e306165353363623665336634326137392e466c6f77546f6b656e2e5661756020820152611b1d60f21b60408201526105dc8261032e565b526105e69061032e565b508d6105f06102ee565b603081527f412e303435613137363363393330303663612e4d6f636b53747261746567696560208201526f732e547261636572537472617465677960801b604082015261063c8261032e565b526106469061032e565b506106509061034f565b600c905261065d9061034f565b6001905261066a9061034f565b527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169061069f9061034f565b526212d6876106ae819561034f565b526106b89061034f565b602a90526106c59061034f565b606590526106d16102ba565b8181526106dd8a61034f565b526106e78961034f565b506106f06102ba565b8181526106fc8961034f565b526107068861034f565b5061070f6102ba565b90815261071b8761034f565b526107258661034f565b5061072f8461032e565b6729a2241af62c000090526107438461034f565b5261074d8261034f565b6207a1209052565b925092945092506107646102ba565b925f928385526107726102ba565b9584875261077e6102ba565b9585875261078a6102ba565b958087526107966102ba565b958187526107a26102ba565b958287526107ae6102ba565b958387526107ba6102ba565b958487526107c66102ba565b958587526107d26102ba565b9586529c9b9a9998979695949392919056fea2646970667358221220cfa80be468db850f056dd10c0c0cfcd65bac57fec1fbf9c969b09350055e9c6364736f6c63430008140033" +access(all) let pendingRequestsByUserQueryMockBytecode = "60c03461008157601f6108dc38819003918201601f19168301916001600160401b0383118484101761008557808492604094855283398101031261008157610052602061004b83610099565b9201610099565b9060805260a05260405161082e90816100ae82396080518181816103f00152610681015260a0518161042e0152f35b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b51906001600160a01b03821682036100815756fe60806040526004361015610011575f80fd5b5f803560e01c636b67542814610025575f80fd5b346101875760209081600319360112610187576004356001600160a01b0381168103610183576100549061038c565b9b9c94969d939792989199909a604051809e6101a080835282016100779161018a565b9086818303910152610088916101bd565b8d810360408f0152610099916101bd565b8c810360608e01526100aa916101f3565b8b810360808d01526100bb9161018a565b908a820360a08c015280808d5193848152019c01925b828110610165575050505093610134610152946101256101619895610116610143966101088f8f9e9c8f60c081840391015261018a565b8d810360e08f01529061022f565b908b82036101008d015261022f565b908982036101208b015261022f565b908782036101408901526101f3565b9085820361016087015261018a565b9083820361018085015261018a565b0390f35b835167ffffffffffffffff168d529b81019b928101926001016100d1565b5080fd5b80fd5b9081518082526020808093019301915f5b8281106101a9575050505090565b83518552938101939281019260010161019b565b9081518082526020808093019301915f5b8281106101dc575050505090565b835160ff16855293810193928101926001016101ce565b9081518082526020808093019301915f5b828110610212575050505090565b83516001600160a01b031685529381019392810192600101610204565b9080825180825260208092019180808360051b8601019501935f9384915b84831061025e575050505050505090565b9091929394959684601f198084840301855289518051908185528a5b8281106102a657505080840183018a9052601f011690910181019781019695949360010192019061024d565b81810185015186820186015289940161027a565b604051906020820182811067ffffffffffffffff8211176102da57604052565b634e487b7160e01b5f52604160045260245ffd5b604051906060820182811067ffffffffffffffff8211176102da57604052565b604051906080820182811067ffffffffffffffff8211176102da57604052565b80511561033b5760200190565b634e487b7160e01b5f52603260045260245ffd5b80516001101561033b5760400190565b6103676102ee565b9060028252815f5b6040811061037b575050565b80606060208093850101520161036f565b9061039561030e565b90600382526060366020840137816103ab61030e565b60038152606036602083013780916103c161030e565b9060038252606036602084013790958691906001600160a01b036103e48361032e565b526103ee8261034f565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316905281516002101561033b576001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811660608401521660101901610769575050506104686102ee565b60028152916040366020850137829461047f6102ee565b6002815290604036602084013781956104966102ee565b60028152604036602083013780966104ac6102ee565b6002815292604036602086013783976104c36102ee565b6002815295604036602089013786986104da6102ee565b600281529760403660208b013788996104f16102ee565b600281526040366020830137809a61050761035f565b9a8b61051161035f565b9b8c9561051c61035f565b9c6105268161032e565b600b90525f978894856105388561032e565b52856105438661032e565b526001600160a01b036105558861032e565b5261055f8b61032e565b670de0b6b3a764000090526105738861032e565b67ffffffffffffffff90526105878961032e565b606490526105936102ba565b86815261059f8261032e565b526105a99061032e565b506105b26102ee565b602281527f412e306165353363623665336634326137392e466c6f77546f6b656e2e5661756020820152611b1d60f21b60408201526105f08261032e565b526105fa9061032e565b508d6106046102ee565b603081527f412e303435613137363363393330303663612e4d6f636b53747261746567696560208201526f732e547261636572537472617465677960801b60408201526106508261032e565b5261065a9061032e565b506106649061034f565b600c90526106719061034f565b6001905261067e9061034f565b527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906106b39061034f565b526212d6876106c2819561034f565b526106cc9061034f565b602a90526106d99061034f565b606590526106e56102ba565b8181526106f18a61034f565b526106fb8961034f565b506107046102ba565b8181526107108961034f565b5261071a8861034f565b506107236102ba565b90815261072f8761034f565b526107398661034f565b506107438461032e565b6729a2241af62c000090526107578461034f565b526107618261034f565b6207a1209052565b925092945092506107786102ba565b925f928385526107866102ba565b958487526107926102ba565b9585875261079e6102ba565b958087526107aa6102ba565b958187526107b66102ba565b958287526107c26102ba565b958387526107ce6102ba565b958487526107da6102ba565b958587526107e66102ba565b9586529c9b9a9998979695949392919056fea2646970667358221220f90ed4a66eaa118955dbeba6ec133f3d37c612ed4d77722dcf4cd4ba51d5b4a464736f6c63430008140033" /* --- Setup helpers --- */ diff --git a/solidity/src/test/PendingRequestsByUserQueryMock.sol b/solidity/src/test/PendingRequestsByUserQueryMock.sol index ecddfd3..432c426 100644 --- a/solidity/src/test/PendingRequestsByUserQueryMock.sol +++ b/solidity/src/test/PendingRequestsByUserQueryMock.sol @@ -100,6 +100,7 @@ contract PendingRequestsByUserQueryMock { statuses[0] = 0; tokenAddresses[0] = NATIVE_FLOW; amounts[0] = 1 ether; + yieldVaultIds[0] = type(uint64).max; timestamps[0] = 100; messages[0] = ""; vaultIdentifiers[0] = "A.0ae53cb6e3f42a79.FlowToken.Vault"; From 5922881a2d0b371e335637aa0c3380afd463b0de Mon Sep 17 00:00:00 2001 From: liobrasil Date: Tue, 7 Apr 2026 07:48:29 -0400 Subject: [PATCH 10/10] fix: sync duplicate ABI artifact export --- scripts/export-artifacts.sh | 14 +++++++---- .../artifacts/FlowYieldVaultsRequests.json | 23 +++++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/scripts/export-artifacts.sh b/scripts/export-artifacts.sh index ce9107d..3f49544 100755 --- a/scripts/export-artifacts.sh +++ b/scripts/export-artifacts.sh @@ -60,7 +60,7 @@ fi echo -e "${GREEN}Exporting contract artifacts...${NC}" # Create directories if they don't exist -mkdir -p deployments/artifacts +mkdir -p deployments/artifacts solidity/deployments/artifacts # Build contracts echo -e "${YELLOW}Building contracts...${NC}" @@ -68,9 +68,14 @@ cd solidity && forge build && cd .. # Extract ABI echo -e "${YELLOW}Extracting FlowYieldVaultsRequests ABI...${NC}" -jq '.abi' solidity/out/FlowYieldVaultsRequests.sol/FlowYieldVaultsRequests.json > deployments/artifacts/FlowYieldVaultsRequests.json +CANONICAL_ABI_PATH="deployments/artifacts/FlowYieldVaultsRequests.json" +SOLIDITY_ABI_PATH="solidity/deployments/artifacts/FlowYieldVaultsRequests.json" -echo -e "${GREEN}✓ ABI exported to deployments/artifacts/FlowYieldVaultsRequests.json${NC}" +jq '.abi' solidity/out/FlowYieldVaultsRequests.sol/FlowYieldVaultsRequests.json > "$CANONICAL_ABI_PATH" +cp "$CANONICAL_ABI_PATH" "$SOLIDITY_ABI_PATH" + +echo -e "${GREEN}✓ ABI exported to ${CANONICAL_ABI_PATH}${NC}" +echo -e "${GREEN}✓ ABI synced to ${SOLIDITY_ABI_PATH}${NC}" # Update addresses if network is specified if [[ -n "$NETWORK" ]]; then @@ -131,7 +136,8 @@ echo "" echo -e "${GREEN}Done!${NC}" echo "" echo "Exported files:" -echo " - deployments/artifacts/FlowYieldVaultsRequests.json (ABI)" +echo " - ${CANONICAL_ABI_PATH} (ABI)" +echo " - ${SOLIDITY_ABI_PATH} (ABI copy)" if [[ -n "$NETWORK" ]]; then echo " - deployments/contract-addresses.json (updated for ${NETWORK})" fi diff --git a/solidity/deployments/artifacts/FlowYieldVaultsRequests.json b/solidity/deployments/artifacts/FlowYieldVaultsRequests.json index 9f8244b..32fc49f 100644 --- a/solidity/deployments/artifacts/FlowYieldVaultsRequests.json +++ b/solidity/deployments/artifacts/FlowYieldVaultsRequests.json @@ -19,6 +19,19 @@ "type": "receive", "stateMutability": "payable" }, + { + "type": "function", + "name": "DEFAULT_MINIMUM_BALANCE", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "NATIVE_FLOW", @@ -1952,6 +1965,11 @@ "name": "CannotAllowlistZeroAddress", "inputs": [] }, + { + "type": "error", + "name": "CannotBlocklistZeroAddress", + "inputs": [] + }, { "type": "error", "name": "CannotRegisterSentinelYieldVaultId", @@ -2003,6 +2021,11 @@ "name": "InvalidCOAAddress", "inputs": [] }, + { + "type": "error", + "name": "InvalidMinimumBalance", + "inputs": [] + }, { "type": "error", "name": "InvalidRequestState",