From 36216eea0bbbcfac6b791a7841a71490106aa60c Mon Sep 17 00:00:00 2001 From: liobrasil Date: Mon, 19 Jan 2026 18:35:53 -0400 Subject: [PATCH] docs(FLOW-16): document precision loss in cross-VM amount conversions EVM uses uint256 with 18 decimals while Cadence uses UFix64 with 8 decimals. Converting between formats truncates precision beyond 8 decimal places. This is not exploitable (truncation favors protocol) and minimum deposit mitigates dust loss. Added precision warnings to: - Cadence contract header - Cadence ufix64FromUInt256() function docstring - Solidity contract header Co-Authored-By: Claude Opus 4.5 --- cadence/contracts/FlowYieldVaultsEVM.cdc | 11 ++++++++++- solidity/src/FlowYieldVaultsRequests.sol | 5 +++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/cadence/contracts/FlowYieldVaultsEVM.cdc b/cadence/contracts/FlowYieldVaultsEVM.cdc index a8bf645..879d499 100644 --- a/cadence/contracts/FlowYieldVaultsEVM.cdc +++ b/cadence/contracts/FlowYieldVaultsEVM.cdc @@ -24,6 +24,13 @@ import "FlowEVMBridgeConfig" /// 2. For each request, calls startProcessing() to mark as PROCESSING (deducts escrow for CREATE/DEPOSIT) /// 3. Executes Cadence-side operation (create/deposit/withdraw/close YieldVault) /// 4. Calls completeProcessing() to mark as COMPLETED or FAILED (refunds escrow for CREATE/DEPOSIT failures) +/// +/// PRECISION NOTE: +/// EVM uses uint256 with 18 decimals (wei), while Cadence uses UFix64 with 8 decimals. +/// Converting between these formats truncates precision beyond 8 decimal places. +/// For example: 1.123456789012345678 FLOW (EVM) becomes 1.12345678 FLOW (Cadence). +/// This is not exploitable (users receive slightly less, not more) and the 1 FLOW +/// minimum deposit makes any dust loss negligible (~0.0000001% maximum). access(all) contract FlowYieldVaultsEVM { // ============================================ @@ -1869,9 +1876,11 @@ 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 + /// PRECISION WARNING: UFix64 has only 8 decimal places vs uint256's 18. + /// Precision beyond 8 decimals is truncated (e.g., 1.123456789... → 1.12345678). /// @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 + /// @return The converted amount in UFix64 format (truncated to 8 decimals) access(self) fun ufix64FromUInt256(_ value: UInt256, tokenAddress: EVM.EVMAddress): UFix64 { if tokenAddress.toString() == FlowYieldVaultsEVM.nativeFlowEVMAddress.toString() { return FlowEVMBridgeUtils.uint256ToUFix64(value: value, decimals: 18) diff --git a/solidity/src/FlowYieldVaultsRequests.sol b/solidity/src/FlowYieldVaultsRequests.sol index 14ef1db..ede11a6 100644 --- a/solidity/src/FlowYieldVaultsRequests.sol +++ b/solidity/src/FlowYieldVaultsRequests.sol @@ -30,6 +30,11 @@ import { * Processing uses atomic two-phase commit: * - startProcessing(): Marks request as PROCESSING, deducts user balance * - completeProcessing(): Marks as COMPLETED/FAILED, credits claimable refunds on failure + * + * PRECISION NOTE: EVM uses uint256 with 18 decimals, while Cadence uses UFix64 with 8 decimals. + * Amounts are truncated beyond 8 decimal places during cross-VM conversion. + * Example: 1.123456789012345678 FLOW → 1.12345678 FLOW (loss of ~9e-9 FLOW). + * This is not exploitable (truncation favors the protocol) and the minimum deposit mitigates dust. */ contract FlowYieldVaultsRequests is ReentrancyGuard, Ownable2Step { using SafeERC20 for IERC20;