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
Course Overview
About the course
Difference between Uniswap v3 and v4
Uniswap v4 PoolManager
Uniswap v4 Hooks
Uniswap v4 PositionManager
Uniswap v4 Universal Router
Uniswap v4 Singleton architecture and flash accounting
Uniswap v4 operations and lifecycle
Uniswap v4 multihopping and quoting
How to build a Uniswap v4 swap router
How to build a smart contract a liquidation bot executes
Last updated on November 6, 2025
Duration: 5min
Duration: 7min
Duration: 39min
Duration: 35min
Duration: 34min
Duration: 31min
Course Overview
About the course
Difference between Uniswap v3 and v4
Uniswap v4 PoolManager
Uniswap v4 Hooks
Uniswap v4 PositionManager
Uniswap v4 Universal Router
Uniswap v4 Singleton architecture and flash accounting
Uniswap v4 operations and lifecycle
Uniswap v4 multihopping and quoting
How to build a Uniswap v4 swap router
How to build a smart contract a liquidation bot executes
Last updated on November 6, 2025