-
Notifications
You must be signed in to change notification settings - Fork 1
fix(FLOW-15): Return per-token balances in getPendingRequestsByUserUnpacked #36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
40b1227
4bb6f45
2234eb2
a108714
4d1a4a2
38648bb
f44f132
d9d7147
06cf13f
21ad0bd
6efc9aa
e2dbbdb
4e051fd
5922881
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
| } | ||
| } | ||
|
|
@@ -1701,8 +1703,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 | ||
| ) | ||
|
|
@@ -1717,12 +1720,38 @@ 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() | ||
| 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] | ||
| ) | ||
|
Comment on lines
+1737
to
+1752
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. New failure mode:
Before this PR the Cadence query decoded a single scalar for native FLOW and made no ERC20 The zero-shortcut (
Comment on lines
+1741
to
+1752
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The zero-guard correctly skips the Before this PR, The SchedulerHandler is unaffected (it uses Worth adding a defensive try/catch around the conversion — fall back to // pseudo-pattern
let converted: UFix64 = rawPending == 0 ? 0.0 : (
FlowYieldVaultsEVM._safeUfix64FromUInt256(rawPending, tokenAddress: balanceTokens[j]) ?? 0.0
)Or at minimum add a comment noting this invariant so future token additions are made with it in mind. |
||
| j = j + 1 | ||
| } | ||
|
Comment on lines
+1723
to
+1754
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new ABI-decode path has no Cadence-side test coverage. This block is the most complex change in the PR: it decodes three new return values ( All four Cadence test suites pass trivially because none of them call
liobrasil marked this conversation as resolved.
|
||
|
|
||
| // Build request array | ||
| var requests: [EVMRequest] = [] | ||
|
|
@@ -1748,8 +1777,8 @@ access(all) contract FlowYieldVaultsEVM { | |
| return PendingRequestsInfo( | ||
| evmAddress: evmAddressHex, | ||
| pendingCount: ids.length, | ||
| pendingBalance: pendingBalance, | ||
| claimableRefund: claimableRefund, | ||
| pendingBalances: pendingBalances, | ||
| claimableRefunds: claimableRefundsMap, | ||
| requests: requests | ||
| ) | ||
| } | ||
|
|
@@ -1947,7 +1976,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 | ||
|
|
@@ -1960,7 +1992,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) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -255,6 +255,64 @@ fun testProcessResultStructure() { | |
| Test.assertEqual("Insufficient COA balance", failureResult.message) | ||
| } | ||
|
|
||
| access(all) | ||
| fun testGetPendingRequestsForEVMAddressDecodesBalances() { | ||
| let tokenBAddress = deployERC20DecimalsOnlyMock(admin, decimals: 6) | ||
| let tokenCAddress = "0x00000000000000000000000000000000000000cc" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test gap:
This means the risky path—"tracked token with a non-zero amount but an unresponsive ERC20 contract"—has no test coverage at all. Consider adding a second variant of the mock that sets a non-zero balance for |
||
| 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(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(0.0, pendingTokenC) | ||
| Test.assertEqual(0.0, refundTokenC) | ||
| 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(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( | ||
| FlowYieldVaultsEVM.RequestType.CREATE_YIELDVAULT.rawValue, | ||
| pendingInfo.requests[0].requestType | ||
| ) | ||
| Test.assertEqual( | ||
| FlowYieldVaultsEVM.RequestType.DEPOSIT_TO_YIELDVAULT.rawValue, | ||
| pendingInfo.requests[1].requestType | ||
| ) | ||
| } | ||
|
|
||
| access(all) | ||
| fun testVaultAndStrategyIdentifiers() { | ||
| // Test that vault and strategy identifiers are preserved correctly | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the token address is basically
EVM.EVMAddress, can we use it as key instead?If not, I would suggest to mention in the comments something like the token address string is the hex format of
EVM.EVMAddress.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did some checks and
{EVM.EVMAddress: UFix64}is not viable in Cadence.EVM.EVMAddressis a struct, and Cadence does not allow struct types as dictionary keys.