Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ out/

# flow
*.pkey
!emulator-account.pkey
*.pem
imports
coverage.lcov
Expand Down
7 changes: 4 additions & 3 deletions cadence/contracts/connectors/evm/ERC4626SwapConnectors.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import "ERC4626SinkConnectors"
import "SwapConnectors"
import "EVMTokenConnectors"
import "ERC4626Utils"
import "EVMAmountUtils"

/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
/// THIS CONTRACT IS IN BETA AND IS NOT FINALIZED - INTERFACES MAY CHANGE AND/OR PENDING CHANGES MAY REQUIRE REDEPLOYMENT
Expand Down Expand Up @@ -133,14 +134,14 @@ access(all) contract ERC4626SwapConnectors {
)
}

let ufixRequired = FlowEVMBridgeUtils.convertERC20AmountToCadenceAmount(uintRequired, erc20Address: self.assetEVMAddress)
let ufixRequired = EVMAmountUtils.toCadenceInForToken(uintRequired, erc20Address: self.assetEVMAddress)

// Cap input to maxCapacity and recalculate output if needed
if ufixRequired > maxCapacity {
// Required assets exceed capacity - cap at maxCapacity and calculate achievable shares
let uintMaxCapacity = FlowEVMBridgeUtils.convertCadenceAmountToERC20Amount(maxCapacity, erc20Address: self.assetEVMAddress)
if let uintActualShares = ERC4626Utils.previewDeposit(vault: self.vault, assets: uintMaxCapacity) {
let ufixActualShares = FlowEVMBridgeUtils.convertERC20AmountToCadenceAmount(uintActualShares, erc20Address: self.vault)
let ufixActualShares = EVMAmountUtils.toCadenceOutForToken(uintActualShares, erc20Address: self.vault)
return SwapConnectors.BasicQuote(
inType: self.asset,
outType: self.vaultType,
Expand Down Expand Up @@ -188,7 +189,7 @@ access(all) contract ERC4626SwapConnectors {


if let uintShares = ERC4626Utils.previewDeposit(vault: self.vault, assets: uintForProvided) {
let ufixShares = FlowEVMBridgeUtils.convertERC20AmountToCadenceAmount(uintShares, erc20Address: self.vault)
let ufixShares = EVMAmountUtils.toCadenceOutForToken(uintShares, erc20Address: self.vault)
return SwapConnectors.BasicQuote(
inType: self.asset,
outType: self.vaultType,
Expand Down
74 changes: 74 additions & 0 deletions cadence/contracts/connectors/evm/EVMAmountUtils.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import "FlowEVMBridgeUtils"
import "EVM"

/// EVMAmountUtils
///
/// Shared utility contract for precision-safe EVM ↔ Cadence UFix64 amount conversions.
///
/// EVM tokens can have up to 18 decimal places, while Cadence UFix64 only supports 8.
/// Converting naively truncates lower-order digits, which can cause rounding errors in
/// DeFi operations. These helpers apply directional rounding:
///
/// - `toCadenceOut` (round **down**): safe for **output** amounts — user receives at most this much
/// - `toCadenceIn` (round **up**): safe for **input** amounts — user must provide at least this much
///
access(all) contract EVMAmountUtils {

/// Convert an ERC20 `UInt256` amount into a Cadence `UFix64` **by rounding down** to the
/// maximum `UFix64` precision (8 decimal places).
///
/// - For `decimals <= 8`, the value is exactly representable, so this is a direct conversion.
/// - For `decimals > 8`, this floors the ERC20 amount to the nearest multiple of
/// `quantum = 10^(decimals - 8)` so the result round-trips safely:
/// `ufix64ToUInt256(result) <= amt`.
access(all) fun toCadenceOut(_ amt: UInt256, decimals: UInt8): UFix64 {
if decimals <= 8 {
return FlowEVMBridgeUtils.uint256ToUFix64(value: amt, decimals: decimals)
}

let quantumExp: UInt8 = decimals - 8
let quantum: UInt256 = FlowEVMBridgeUtils.pow(base: 10, exponent: quantumExp)
let remainder: UInt256 = amt % quantum
let floored: UInt256 = amt - remainder

return FlowEVMBridgeUtils.uint256ToUFix64(value: floored, decimals: decimals)
}

/// Convert an ERC20 `UInt256` amount into a Cadence `UFix64` **by rounding up** to the
/// smallest representable value at `UFix64` precision (8 decimal places).
///
/// - For `decimals <= 8`, the value is exactly representable, so this is a direct conversion.
/// - For `decimals > 8`, this ceils the ERC20 amount to the next multiple of
/// `quantum = 10^(decimals - 8)` (unless already exact), ensuring:
/// `ufix64ToUInt256(result) >= amt`, and the increase is `< quantum`.
access(all) fun toCadenceIn(_ amt: UInt256, decimals: UInt8): UFix64 {
if decimals <= 8 {
return FlowEVMBridgeUtils.uint256ToUFix64(value: amt, decimals: decimals)
}

let quantumExp: UInt8 = decimals - 8
let quantum: UInt256 = FlowEVMBridgeUtils.pow(base: 10, exponent: quantumExp)

let remainder: UInt256 = amt % quantum
var padded: UInt256 = amt
if remainder != 0 {
padded = amt + (quantum - remainder)
}

return FlowEVMBridgeUtils.uint256ToUFix64(value: padded, decimals: decimals)
}

/// Convenience: resolve token decimals and round down for output amounts
access(all) fun toCadenceOutForToken(_ amt: UInt256, erc20Address: EVM.EVMAddress): UFix64 {
let decimals = FlowEVMBridgeUtils.getTokenDecimals(evmContractAddress: erc20Address)
return self.toCadenceOut(amt, decimals: decimals)
}

/// Convenience: resolve token decimals and round up for input amounts
access(all) fun toCadenceInForToken(_ amt: UInt256, erc20Address: EVM.EVMAddress): UFix64 {
let decimals = FlowEVMBridgeUtils.getTokenDecimals(evmContractAddress: erc20Address)
return self.toCadenceIn(amt, decimals: decimals)
}

init() {}
}
51 changes: 3 additions & 48 deletions cadence/contracts/connectors/evm/UniswapV3SwapConnectors.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import "FlowEVMBridgeConfig"
import "DeFiActions"
import "SwapConnectors"
import "EVMAbiHelpers"
import "EVMAmountUtils"

/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
/// THIS CONTRACT IS IN BETA AND IS NOT FINALIZED - INTERFACES MAY CHANGE AND/OR PENDING CHANGES MAY REQUIRE REDEPLOYMENT
Expand All @@ -20,50 +21,6 @@ import "EVMAbiHelpers"
///
access(all) contract UniswapV3SwapConnectors {

/// Convert an ERC20 `UInt256` amount into a Cadence `UFix64` **by rounding down** to the
/// maximum `UFix64` precision (8 decimal places).
///
/// - For `decimals <= 8`, the value is exactly representable, so this is a direct conversion.
/// - For `decimals > 8`, this floors the ERC20 amount to the nearest multiple of
/// `quantum = 10^(decimals - 8)` so the result round-trips safely:
/// `ufix64ToUInt256(result) <= amt`.
access(all) fun toCadenceOutWithDecimals(_ amt: UInt256, decimals: UInt8): UFix64 {
if decimals <= 8 {
return FlowEVMBridgeUtils.uint256ToUFix64(value: amt, decimals: decimals)
}

let quantumExp: UInt8 = decimals - 8
let quantum: UInt256 = FlowEVMBridgeUtils.pow(base: 10, exponent: quantumExp)
let remainder: UInt256 = amt % quantum
let floored: UInt256 = amt - remainder

return FlowEVMBridgeUtils.uint256ToUFix64(value: floored, decimals: decimals)
}

/// Convert an ERC20 `UInt256` amount into a Cadence `UFix64` **by rounding up** to the
/// smallest representable value at `UFix64` precision (8 decimal places).
///
/// - For `decimals <= 8`, the value is exactly representable, so this is a direct conversion.
/// - For `decimals > 8`, this ceils the ERC20 amount to the next multiple of
/// `quantum = 10^(decimals - 8)` (unless already exact), ensuring:
/// `ufix64ToUInt256(result) >= amt`, and the increase is `< quantum`.
access(all) fun toCadenceInWithDecimals(_ amt: UInt256, decimals: UInt8): UFix64 {
if decimals <= 8 {
return FlowEVMBridgeUtils.uint256ToUFix64(value: amt, decimals: decimals)
}

let quantumExp: UInt8 = decimals - 8
let quantum: UInt256 = FlowEVMBridgeUtils.pow(base: 10, exponent: quantumExp)

let remainder: UInt256 = amt % quantum
var padded: UInt256 = amt
if remainder != 0 {
padded = amt + (quantum - remainder)
}

return FlowEVMBridgeUtils.uint256ToUFix64(value: padded, decimals: decimals)
}

/// ExactInputSingleParams facilitates the ABI encoding/decoding of the
/// Solidity tuple expected in `ISwapRouter.exactInput` function.
access(all) struct ExactInputSingleParams {
Expand Down Expand Up @@ -606,15 +563,13 @@ access(all) contract UniswapV3SwapConnectors {

/// OUT amounts: round down to UFix64 precision
access(self) fun _toCadenceOut(_ amt: UInt256, erc20Address: EVM.EVMAddress): UFix64 {
let decimals = FlowEVMBridgeUtils.getTokenDecimals(evmContractAddress: erc20Address)
return UniswapV3SwapConnectors.toCadenceOutWithDecimals(amt, decimals: decimals)
return EVMAmountUtils.toCadenceOutForToken(amt, erc20Address: erc20Address)
}

/// IN amounts: round up to the next UFix64 such that the ERC20 conversion
/// (via ufix64ToUInt256) is >= the original UInt256 amount.
access(self) fun _toCadenceIn(_ amt: UInt256, erc20Address: EVM.EVMAddress): UFix64 {
let decimals = FlowEVMBridgeUtils.getTokenDecimals(evmContractAddress: erc20Address)
return UniswapV3SwapConnectors.toCadenceInWithDecimals(amt, decimals: decimals)
return EVMAmountUtils.toCadenceInForToken(amt, erc20Address: erc20Address)
}
access(self) fun getPoolToken0(_ pool: EVM.EVMAddress): EVM.EVMAddress {
// token0() selector = 0x0dfe1681
Expand Down
Loading