5/5
## How Uniswap v4 Ensures Transaction Integrity with NonzeroDeltaCount Uniswap v4 introduces a powerful "flash accounting" system within its `PoolManager` contract. This system allows for complex actions like swaps, liquidity provision, and token claims to occur within a single, atomic transaction. At the heart of this system is a crucial safety mechanism: the `NonzeroDeltaCount` variable. This counter ensures that by the end of any transaction, all currency debts are settled, maintaining the pool's financial integrity. This lesson explores the role and mechanics of `NonzeroDeltaCount`, how it's modified, and why it is essential for the security and stability of the `PoolManager`. ### The Final Check: Transaction Validity in the `unlock` Function Every interaction with the `PoolManager` is wrapped within a call to the `unlock` function. This function temporarily unlocks the contract, allows the user's callback contract to perform its operations, and then re-locks it. Before it concludes, it performs a critical validation. The `unlock` function requires that all currency balances, known as deltas, are resolved before it finishes execution. If any user owes the pool tokens, or the pool owes the user tokens, the transaction is considered incomplete. This final check is enforced by inspecting `NonzeroDeltaCount`. ```solidity // file: PoolManager.sol function unlock(bytes calldata data) external override returns (bytes memory) { if (Lock.isUnlocked()) AlreadyUnlocked.selector.revertWith(); Lock.unlock(); // The caller executes all logic in this callback result = IUnlockCallback(msg.sender).unlockCallback(data); // This is the crucial check if (NonzeroDeltaCount.read() != 0) CurrencyNotSettled.selector.revertWith(); Lock.lock(); } ``` As shown in the code, the very last step before the contract is re-locked is a check to ensure `NonzeroDeltaCount` is zero. If it is not, the transaction reverts with a `CurrencyNotSettled` error, undoing every operation that occurred within the callback. This prevents the `PoolManager` from being left in an unbalanced state. ### How the Counter Works: The `_accountDelta` Function The `NonzeroDeltaCount` is modified exclusively by one internal function: `_accountDelta`. This function is called whenever a user's balance for a specific currency changes. It tracks the net balance for each address and currency within the scope of the `unlock` call. The logic for incrementing or decrementing the counter is based on whether an account's delta for a currency is transitioning _from_ or _to_ zero. ```solidity // file: PoolManager.sol /// @notice Adds a balance delta in a currency for a target address function _accountDelta(Currency currency, int128 delta, address target) internal { if (delta == 0) return; (int256 previous, int256 next) = currency.applyDelta(target, delta); if (next == 0) { NonzeroDeltaCount.decrement(); } else if (previous == 0) { NonzeroDeltaCount.increment(); } } ``` Let's break down this logic: - `currency.applyDelta(target, delta)`: This function updates the temporary balance for the `target` address and returns the balance _before_ the change (`previous`) and _after_ the change (`next`). - `if (previous == 0)`: This condition is true when an account's balance for a currency moves from `0` to any non-zero value (positive or negative). This signifies a new, unsettled balance has been created. As a result, `NonzeroDeltaCount` is incremented. - `if (next == 0)`: This condition is true when an account's balance for a currency moves from a non-zero value back to `0`. This signifies that a previously unsettled balance has been fully paid or claimed. Consequently, `NonzeroDeltaCount` is decremented. ### A Practical Example: Swapping ETH for USDC To see how `NonzeroDeltaCount` works in a real-world scenario, let's walk through a swap where a user exchanges `0.1 ETH` for `100 USDC`. **1. Initiating the Swap** The swap action creates two unsettled currency deltas. - **User owes ETH:** The user needs to pay `0.1 ETH` to the pool. This creates a negative delta for the user's ETH balance. The `_accountDelta` function is called for ETH. - `previous` ETH delta = `0` - `next` ETH delta = `-0.1 ETH` - Since `previous` was `0`, `NonzeroDeltaCount` is **incremented**. The current count is `1`. - **Pool owes User USDC:** The user is owed `100 USDC` from the pool. This creates a positive delta for the user's USDC balance. The `_accountDelta` function is called for USDC. - `previous` USDC delta = `0` - `next` USDC delta = `+100 USDC` - Since `previous` was `0`, `NonzeroDeltaCount` is **incremented again**. The current count is `2`. At this stage, two currency balances are unsettled, and the `NonzeroDeltaCount` correctly reflects this with a value of `2`. **2. Settling the Deltas** To complete the transaction successfully, the user must settle both balances within the same `unlock` callback. - **Paying the ETH:** The user sends `0.1 ETH` to the `PoolManager` contract. This calls `_accountDelta` to update the user's ETH balance. - `previous` ETH delta = `-0.1 ETH` - `next` ETH delta = `0` (because `-0.1 ETH + 0.1 ETH = 0`) - Since `next` is `0`, `NonzeroDeltaCount` is **decremented**. The current count is `1`. - **Taking the USDC:** The user calls a function to withdraw the `100 USDC` they are owed from the `PoolManager`. This calls `_accountDelta` for USDC. - `previous` USDC delta = `+100 USDC` - `next` USDC delta = `0` (because `+100 USDC - 100 USDC = 0`) - Since `next` is `0`, `NonzeroDeltaCount` is **decremented again**. The current count is `0`. **3. Final Validation** With both the ETH payment and USDC withdrawal completed, all currency deltas for the user have been resolved. The `NonzeroDeltaCount` is now back to `0`. When the execution flow returns to the `unlock` function, its final check—`if (NonzeroDeltaCount.read() != 0)`—passes, and the transaction is successfully committed to the blockchain. ### Key Takeaways - **Purpose:** `NonzeroDeltaCount` is a core safety feature in Uniswap v4 that guarantees all token debts created during a transaction are settled by its conclusion. - **Mechanism:** The counter increments when a new unsettled balance is created (delta moves from `0` to non-zero) and decrements when a balance is settled (delta moves from non-zero to `0`). - **Requirement:** The transaction will only succeed if `NonzeroDeltaCount` is `0` at the end of the `unlock` call. This forces developers to ensure users pay for all assets they take and claim all assets they are owed within a single, atomic operation.
Uniswap v4 introduces a powerful "flash accounting" system within its PoolManager
contract. This system allows for complex actions like swaps, liquidity provision, and token claims to occur within a single, atomic transaction. At the heart of this system is a crucial safety mechanism: the NonzeroDeltaCount
variable. This counter ensures that by the end of any transaction, all currency debts are settled, maintaining the pool's financial integrity.
This lesson explores the role and mechanics of NonzeroDeltaCount
, how it's modified, and why it is essential for the security and stability of the PoolManager
.
unlock
FunctionEvery interaction with the PoolManager
is wrapped within a call to the unlock
function. This function temporarily unlocks the contract, allows the user's callback contract to perform its operations, and then re-locks it. Before it concludes, it performs a critical validation.
The unlock
function requires that all currency balances, known as deltas, are resolved before it finishes execution. If any user owes the pool tokens, or the pool owes the user tokens, the transaction is considered incomplete. This final check is enforced by inspecting NonzeroDeltaCount
.
As shown in the code, the very last step before the contract is re-locked is a check to ensure NonzeroDeltaCount
is zero. If it is not, the transaction reverts with a CurrencyNotSettled
error, undoing every operation that occurred within the callback. This prevents the PoolManager
from being left in an unbalanced state.
_accountDelta
FunctionThe NonzeroDeltaCount
is modified exclusively by one internal function: _accountDelta
. This function is called whenever a user's balance for a specific currency changes. It tracks the net balance for each address and currency within the scope of the unlock
call.
The logic for incrementing or decrementing the counter is based on whether an account's delta for a currency is transitioning from or to zero.
Let's break down this logic:
currency.applyDelta(target, delta)
: This function updates the temporary balance for the target
address and returns the balance before the change (previous
) and after the change (next
).
if (previous == 0)
: This condition is true when an account's balance for a currency moves from 0
to any non-zero value (positive or negative). This signifies a new, unsettled balance has been created. As a result, NonzeroDeltaCount
is incremented.
if (next == 0)
: This condition is true when an account's balance for a currency moves from a non-zero value back to 0
. This signifies that a previously unsettled balance has been fully paid or claimed. Consequently, NonzeroDeltaCount
is decremented.
To see how NonzeroDeltaCount
works in a real-world scenario, let's walk through a swap where a user exchanges 0.1 ETH
for 100 USDC
.
1. Initiating the Swap
The swap action creates two unsettled currency deltas.
User owes ETH: The user needs to pay 0.1 ETH
to the pool. This creates a negative delta for the user's ETH balance. The _accountDelta
function is called for ETH.
previous
ETH delta = 0
next
ETH delta = -0.1 ETH
Since previous
was 0
, NonzeroDeltaCount
is incremented. The current count is 1
.
Pool owes User USDC: The user is owed 100 USDC
from the pool. This creates a positive delta for the user's USDC balance. The _accountDelta
function is called for USDC.
previous
USDC delta = 0
next
USDC delta = +100 USDC
Since previous
was 0
, NonzeroDeltaCount
is incremented again. The current count is 2
.
At this stage, two currency balances are unsettled, and the NonzeroDeltaCount
correctly reflects this with a value of 2
.
2. Settling the Deltas
To complete the transaction successfully, the user must settle both balances within the same unlock
callback.
Paying the ETH: The user sends 0.1 ETH
to the PoolManager
contract. This calls _accountDelta
to update the user's ETH balance.
previous
ETH delta = -0.1 ETH
next
ETH delta = 0
(because -0.1 ETH + 0.1 ETH = 0
)
Since next
is 0
, NonzeroDeltaCount
is decremented. The current count is 1
.
Taking the USDC: The user calls a function to withdraw the 100 USDC
they are owed from the PoolManager
. This calls _accountDelta
for USDC.
previous
USDC delta = +100 USDC
next
USDC delta = 0
(because +100 USDC - 100 USDC = 0
)
Since next
is 0
, NonzeroDeltaCount
is decremented again. The current count is 0
.
3. Final Validation
With both the ETH payment and USDC withdrawal completed, all currency deltas for the user have been resolved. The NonzeroDeltaCount
is now back to 0
. When the execution flow returns to the unlock
function, its final check—if (NonzeroDeltaCount.read() != 0)
—passes, and the transaction is successfully committed to the blockchain.
Purpose: NonzeroDeltaCount
is a core safety feature in Uniswap v4 that guarantees all token debts created during a transaction are settled by its conclusion.
Mechanism: The counter increments when a new unsettled balance is created (delta moves from 0
to non-zero) and decrements when a balance is settled (delta moves from non-zero to 0
).
Requirement: The transaction will only succeed if NonzeroDeltaCount
is 0
at the end of the unlock
call. This forces developers to ensure users pay for all assets they take and claim all assets they are owed within a single, atomic operation.
A security-focused breakdown of Uniswap v4's NonzeroDeltaCount - Explore the core safety counter at the heart of the flash accounting system, which guarantees that all token debts are settled. Learn how it tracks unsettled balances and forces a transaction to revert if all debts are not cleared.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Course Overview
About the course
Difference between Uniswap v3 and v4
Uniswap v4 PoolManager
Uniswap v4 Hooks
Uniswap v4 Singleton architecture
Uniswap v4 flash accounting
Uniswap v4 operations
Uniswap v4 lifecycle
How to build a Uniswap v4 swap router
Last updated on September 22, 2025
Duration: 5min
Duration: 7min
Duration: 39min
Duration: 35min
Course Overview
About the course
Difference between Uniswap v3 and v4
Uniswap v4 PoolManager
Uniswap v4 Hooks
Uniswap v4 Singleton architecture
Uniswap v4 flash accounting
Uniswap v4 operations
Uniswap v4 lifecycle
How to build a Uniswap v4 swap router
Last updated on September 22, 2025