Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@ keywords:
sidebar_label: DeFi Math Utils
---

# High-Precision Fixed-Point 128 Bit Math
# High-Precision Fixed-Point 128 Bit Math

## Overview
Dealing with decimals is a notorious issue for most developers on other chains, especially when working with decentralized finance (DeFi). Blockchains are deterministic systems and floating-point arithmetic is non-deterministic across different compilers and architectures, which is why blockchains use fixed-point arithmetic via integers (scaling numbers by a fixed factor).
Dealing with decimals is a notorious issue for most developers on other chains, especially when working with decentralized finance (DeFi). Blockchains are deterministic systems and floating-point arithmetic is non-deterministic across different compilers and architectures, which is why blockchains use fixed-point arithmetic via integers (scaling numbers by a fixed factor).

The issue with this is that these fixed-point integers tend to be very imprecise when using various mathematical operations on them. The more operations you apply to these numbers, the more imprecise these numbers become. However [`DeFiActionsMathUtils`] provides a standardized library for high-precision mathematical operations in DeFi applications on Flow. The contract extends Cadence's native 8-decimal precision (`UFix64`) to 24 decimals using `UInt128` for intermediate calculations, ensuring accuracy in complex financial computations while maintaining deterministic results across the network.

Expand Down Expand Up @@ -54,15 +53,15 @@ let output = afterFee * price // More precision lost
let finalAmount = output / someRatio // Even more precision lost
```

After three-to-four sequential operations, significant cumulative rounding errors can occur, especially when dealing with large amounts. Assuming a rounding error with eight decimals (1.234567885 rounds up to 1.23456789, causing a rounding error of 0.000000005), then after 100 operations with this error and dealing with one million dollars USDF, the protocol loses $0.5 in revenue from this lack of precision. This might not seem like a lot, but if we consider the TVL of Aave, which is around 40 billion USD, then that loss results in $20,000 USD!
After three-to-four sequential operations, significant cumulative rounding errors can occur, especially when dealing with large amounts. Assuming a rounding error with eight decimals (1.234567885 rounds up to 1.23456789, causing a rounding error of 0.000000005), then after 100 operations with this error and dealing with one million dollars USDF, the protocol loses $0.5 in revenue from this lack of precision. This might not seem like a lot, but if we consider the TVL of Aave, which is around 40 billion USD, then that loss results in $20,000 USD!

## The Solution: 24-Decimal Precision

[`DeFiActionsMathUtils`] solves this with `UInt128` to represent fixed-point numbers with 24 decimal places (scaling factor of 10^24). This provides 16 additional decimal places for intermediate calculations, dramatically reducing precision loss.

:::Warning

There is still some precision loss occurring, but it is much smaller than with eight decimals.
There is still some precision loss occurring, but it is much smaller than with eight decimals.

:::

Expand Down Expand Up @@ -147,7 +146,7 @@ let protocolFee = DeFiActionsMathUtils.toUFix64RoundUp(calculatedFee)
let displayValue = DeFiActionsMathUtils.toUFix64Round(calculatedValue)
```

**RoundEven** - Select this for scenarios with many repeated calculations where you want to minimize systematic bias. Also known as "[banker's rounding]", this mode rounds ties (exactly 0.5) to the nearest even number, which statistically balances out over many operations, making it ideal for large-scale distributions or statistical calculations.
**RoundEven** - Select this for scenarios with many repeated calculations where you want to minimize systematic bias. Also known as "[banker's rounding]", this mode rounds ties (exactly 0.5) to the nearest even number, which statistically balances out over many operations, making it ideal for large-scale distributions or statistical calculations.

```cadence
// For repeated operations where bias matters
Expand Down Expand Up @@ -304,23 +303,23 @@ access(all) fun calculateSwapOutput(
let reserveOut = DeFiActionsMathUtils.toUInt128(outputReserve)
let fee = DeFiActionsMathUtils.toUInt128(feeBasisPoints)
let basisPoints = DeFiActionsMathUtils.toUInt128(10000.0)

// Calculate: inputWithFee = inputAmount * (10000 - fee)
let feeMultiplier = DeFiActionsMathUtils.div(
basisPoints - fee,
basisPoints
)
let inputWithFee = DeFiActionsMathUtils.mul(input, feeMultiplier)

// Calculate: numerator = inputWithFee * outputReserve
let numerator = DeFiActionsMathUtils.mul(inputWithFee, reserveOut)

// Calculate: denominator = inputReserve + inputWithFee
let denominator = reserveIn + inputWithFee

// Calculate output: numerator / denominator
let output = DeFiActionsMathUtils.div(numerator, denominator)

// Return with conservative rounding (round down for user protection)
return DeFiActionsMathUtils.toUFix64RoundDown(output)
}
Expand All @@ -345,19 +344,19 @@ access(all) fun calculateCompoundInterest(
let n = DeFiActionsMathUtils.toUInt128(UFix64(periodsPerYear))
let t = DeFiActionsMathUtils.toUInt128(numberOfYears)
let one = DeFiActionsMathUtils.toUInt128(1.0)

// Calculate: rate per period = r / n
let ratePerPeriod = DeFiActionsMathUtils.div(r, n)

// Calculate: (1 + rate per period)
let onePlusRate = one + ratePerPeriod

// Calculate: number of periods = n * t
let totalPeriods = DeFiActionsMathUtils.mul(n, t)

// Note: For production, you'd need to implement a power function
// This is simplified for demonstration

// Calculate final amount with rounding
return DeFiActionsMathUtils.toUFix64Round(finalAmount)
}
Expand All @@ -379,11 +378,11 @@ access(all) fun calculateProportionalShare(
let rewards = DeFiActionsMathUtils.toUInt128(totalRewards)
let stake = DeFiActionsMathUtils.toUInt128(userStake)
let total = DeFiActionsMathUtils.toUInt128(totalStaked)

// Calculate: (userStake / totalStaked) * totalRewards
let proportion = DeFiActionsMathUtils.div(stake, total)
let userReward = DeFiActionsMathUtils.mul(proportion, rewards)

// Round down for conservative payout
return DeFiActionsMathUtils.toUFix64RoundDown(userReward)
}
Expand All @@ -405,22 +404,22 @@ access(all) fun calculatePriceImpact(
let input = DeFiActionsMathUtils.toUInt128(inputAmount)
let reserveIn = DeFiActionsMathUtils.toUInt128(inputReserve)
let reserveOut = DeFiActionsMathUtils.toUInt128(outputReserve)

// Calculate initial price: outputReserve / inputReserve
let initialPrice = DeFiActionsMathUtils.div(reserveOut, reserveIn)

// Calculate new reserves after trade
let newReserveIn = reserveIn + input
let k = DeFiActionsMathUtils.mul(reserveIn, reserveOut)
let newReserveOut = DeFiActionsMathUtils.div(k, newReserveIn)

// Calculate final price: newOutputReserve / newInputReserve
let finalPrice = DeFiActionsMathUtils.div(newReserveOut, newReserveIn)

// Calculate impact: (initialPrice - finalPrice) / initialPrice
let priceDiff = initialPrice - finalPrice
let impact = DeFiActionsMathUtils.div(priceDiff, initialPrice)

return DeFiActionsMathUtils.toUFix64Round(impact)
}
```
Expand Down Expand Up @@ -501,7 +500,7 @@ access(all) fun swap(inputAmount: UFix64) {
inputAmount > 0.0: "Amount must be positive"
inputAmount <= 1000000.0: "Amount exceeds maximum"
}

let inputHP = DeFiActionsMathUtils.toUInt128(inputAmount)
// ... perform calculations
}
Expand Down Expand Up @@ -533,4 +532,4 @@ The simple **convert → calculate → convert back** pattern, combined with str
[banker's rounding]: https://learn.microsoft.com/en-us/openspecs/microsoft_general_purpose_programming_languages/ms-vbal/98152b5a-4d86-4acb-b875-66cb1f49433e
[View the DeFiActionsMathUtils source code]: https://github.com/onflow/FlowActions/blob/main/cadence/contracts/utils/DeFiActionsMathUtils.cdc
[Flow DeFi Actions Documentation]: https://developers.flow.com/blockchain-development-tutorials/forte/flow-actions
[Cadence Fixed-Point Numbers]: https://cadence-lang.org/docs/language/values-and-types/fixed-point-nums-ints
[Cadence Fixed-Point Numbers]: https://cadence-lang.org/docs/language/values-and-types/fixed-point-nums-ints
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,6 @@ keywords:

# Composing Workflows with Flow Actions

:::warning

We are reviewing and finalizing Flow Actions in [FLIP 339]. The specific implementation may change as a part of this process.

We will update these tutorials, but you may need to refactor your code if the implementation changes.

:::

## Overview

Flow Actions are designed to be **composable**, which means you can chain them together like LEGO blocks to build complex strategies. Each primitive has a standardized interface that works consistently across all protocols and eliminates the need to learn multiple APIs. This composability allows atomic execution of multi-step workflows within single transactions, ensuring either complete success or safe failure. When developers combine these primitives, they create sophisticated decentralized finance (DeFi) strategies like automated yield farming, cross-protocol arbitrage, and portfolio rebalancing. The [5 Flow Actions Primitives] are:

- **Source** → Provides tokens on demand by withdrawing from vaults or claiming rewards. Sources respect minimum balance constraints and return empty vaults gracefully when nothing is available.
Expand Down Expand Up @@ -321,7 +311,6 @@ This advanced workflow demonstrates the power of combining VaultSource with Zapp

![vault source zapper](./imgs/vaultsource-zapper.png)


**How it works:**

1. VaultSource withdraws tokens from vault while respecting minimum balance.
Expand Down Expand Up @@ -389,7 +378,7 @@ let autoBalancer <- FlowActions.createAutoBalancer(
lowerThreshold: 0.8,
upperThreshold: 1.2,
source: rebalanceSource,
sink: rebalanceSink,
sink: rebalanceSink,
oracle: priceOracle,
uniqueID: nil
)
Expand Down Expand Up @@ -419,9 +408,9 @@ This advanced compounding strategy maximizes yield by automatically claiming sta
5. Compound interest effect increases overall position size and future rewards.

```cadence
// Restake rewards workflow
// Restake rewards workflow
let rewardsSource = IncrementFiStakingConnectors.PoolRewardsSource(
poolID: 1,
poolID: 1,
staker: userAddress,
vaultType: Type<@FlowToken.Vault>(),
overflowSinks: {},
Expand All @@ -436,13 +425,13 @@ let zapper = IncrementFiPoolLiquidityConnectors.Zapper(
)

let swapSource = SwapConnectors.SwapSource(
swapper: zapper,
source: rewardsSource,
swapper: zapper,
source: rewardsSource,
uniqueID: nil
)

let poolSink = IncrementFiStakingConnectors.PoolSink(
staker: userAddress,
staker: userAddress,
poolID: 1,
uniqueID: nil
)
Expand All @@ -459,7 +448,6 @@ poolSink.depositCapacity(from: lpTokens)
- **Set-and-Forget**: Automated compounding without manual intervention required.
- **Optimal Conversion**: Zapper ensures efficient reward token to LP token conversion.


## Safety Best Practices

### Always Check Capacity
Expand Down Expand Up @@ -528,7 +516,7 @@ Tests individual connectors in isolation to verify they respect their constraint
// Test individual components
test("VaultSource should maintain minimum balance") {
let source = VaultSource(min: 100.0, withdrawVault: vaultCap, uniqueID: nil)

// Test minimum balance enforcement
let available = source.minimumAvailable()
assert(available >= 100.0, message: "Should maintain minimum balance")
Expand All @@ -547,7 +535,7 @@ test("Reward harvesting workflow should complete successfully") {
swapper: swapper,
sink: sink
)

let result = workflow.execute()
assert(result.success, message: "Workflow should complete successfully")
}
Expand All @@ -564,14 +552,14 @@ test("Strategy should handle price volatility") {
priceOracle: mockPriceOracle,
swapper: mockSwapper
)

// Simulate price changes
mockPriceOracle.setPrice(1.0)
let result1 = strategy.execute()

mockPriceOracle.setPrice(2.0)
let result2 = strategy.execute()

assert(result1 != result2, message: "Strategy should adapt to price changes")
}
```
Expand All @@ -591,5 +579,6 @@ In this tutorial, you learned how to combine Flow Actions primitives to create s
Composability is the core strength of Flow Actions. These examples demonstrate how Flow Actions primitives can be combined to create powerful, automated workflows that integrate multiple protocols seamlessly. The framework's standardized interfaces enable developers to chain operations together like LEGO blocks, focusing on strategy implementation rather than protocol-specific integration details.

<!-- Relative links, will not render on page -->

[FLIP 339]: https://github.com/onflow/flips/pull/339/files
[5 Flow Actions Primitives]: intro-to-flow-actions.md
[5 Flow Actions Primitives]: intro-to-flow-actions.md
Loading