5/5
## How to Settle Debts in PoolManager: The Sync and Settle Workflow When interacting with smart contracts that manage multiple assets, a common task is repaying, or "settling," a debt of a specific token. In the `PoolManager` contract, this process is handled by a pair of functions, `sync` and `settle`, which must be used together in a specific pattern. This lesson breaks down the "sync before settle" workflow, revealing how the system uses transient storage (EIP-1153) to efficiently and securely calculate the amount of tokens paid within a single transaction. ### The Core Logic: Calculating Payment in the `_settle` Function The heart of the repayment mechanism is the internal `_settle` function. Its primary job is to determine how much of a given currency was paid by a user and update their balance accordingly. For ERC20 tokens, the function calculates the `paid` amount by comparing the contract's token balance at two different points in time: a balance snapshotted *before* the user sent tokens, and the balance *after* the tokens were received. The difference between these two values is the amount paid. Let's examine the code: ```solidity // file: PoolManager.sol function _settle(address recipient) internal returns (uint256 paid) { Currency currency = CurrencyReserves.getSyncedCurrency(); // if not previously synced, or the syncedCurrency slot has been reset if (currency.isAddressZero()) { paid = msg.value; // Logic for native currency (ETH) } else { if (msg.value > 0) NonzeroNativeValue.selector.revertWith(); // Reserves are guaranteed to be set because currency and reserves are set together uint256 reservesBefore = CurrencyReserves.getSyncedReserves(); uint256 reservesNow = currency.balanceOfSelf(); paid = reservesNow - reservesBefore; CurrencyReserves.resetCurrency(); } _accountDelta(currency, paid.toInt128(), recipient); } ``` The key logic for ERC20 tokens resides in the `else` block: 1. `reservesBefore` is retrieved by calling `CurrencyReserves.getSyncedReserves()`. 2. `reservesNow` is the contract's current token balance. 3. `paid` is calculated as `reservesNow - reservesBefore`. This raises a critical question: How does the function get the `reservesBefore` value? This value must have been stored somewhere earlier in the transaction. To find the answer, we must look for where `CurrencyReserves` is written to, which leads us to the `sync` function. ### Setting the Stage: The `sync` Function The `sync` function is the necessary prerequisite to settling an ERC20 debt. Its purpose is to take a snapshot of the `PoolManager`'s current balance for a specific token and store it temporarily for the `settle` function to use later. ```solidity // file: PoolManager.sol function sync(Currency currency) external { // address(0) is used for the native currency if (currency.isAddressZero()) { // The reserves balance is not used for native settling CurrencyReserves.resetCurrency(); } else { uint256 balance = currency.balanceOfSelf(); CurrencyReserves.syncCurrencyAndReserves(currency, balance); } } ``` When `sync` is called with an ERC20 token address, it fetches the contract's current balance of that token and passes both the currency and its balance to `CurrencyReserves.syncCurrencyAndReserves()`. This is the exact moment the `reservesBefore` snapshot is created. ### The Bridge Between Functions: `CurrencyReserves` and Transient Storage The `CurrencyReserves` library is the crucial link that enables communication between the `sync` and `settle` function calls within the same transaction. It achieves this by using **transient storage**, a feature introduced in EIP-1153. Transient storage is a form of data storage that persists only for the duration of a single transaction. It is significantly cheaper than regular contract storage (`SSTORE`) because the data is automatically discarded when the transaction completes. It is accessed via low-level EVM opcodes, `TSTORE` (to store) and `TLOAD` (to load), which are used within assembly blocks. The `CurrencyReserves.sol` library is a simple wrapper around these opcodes. ```solidity // file: libraries/CurrencyReserves.sol library CurrencyReserves { // ... slot constants defined ... function syncCurrencyAndReserves(Currency currency, uint256 value) internal { assembly ("memory-safe") { // stores currency address in a transient slot tstore(CURRENCY_SLOT, and(currency, 0xffffff...)) // stores the balance in another transient slot tstore(RESERVES_OF_SLOT, value) } } function getSyncedReserves() internal view returns (uint256 value) { assembly ("memory-safe") { value := tload(RESERVES_OF_SLOT) } } // Also contains getSyncedCurrency(), which uses tload(CURRENCY_SLOT) } ``` - **`syncCurrencyAndReserves`**: Uses `tstore` to save the currency address and its balance (`reservesBefore`) into transient storage slots. - **`getSyncedReserves`**: Uses `tload` to retrieve the balance from the transient storage slot where it was previously saved. This lightweight mechanism allows `sync` to "pass a message" to `settle` without incurring the high gas cost of writing to permanent state. ### The Complete "Sync Before Settle" Workflow To correctly settle an ERC20 token debt with the `PoolManager`, an integrator must follow these steps in order within a single transaction: 1. **Call `sync(currency)`:** First, call `sync` with the address of the ERC20 token you intend to repay. This action records the token's identity and its current balance (`reservesBefore`) in transient storage. 2. **Transfer Tokens:** Immediately after the `sync` call, transfer the ERC20 tokens to the `PoolManager` contract address. This action increases the contract's balance of that token, establishing the `reservesNow` value. 3. **Call `settle()`:** Finally, call `settle`. The `settle` function will now execute its logic: * It retrieves the currency and the `reservesBefore` value from transient storage. * It reads the contract's new, higher token balance (`reservesNow`). * It calculates the `paid` amount by subtracting `reservesBefore` from `reservesNow`. * It updates the user's debt and cleans up the transient storage slots for the next transaction. This "sync before settle" pattern is mandatory. Without first calling `sync`, the `settle` function would have no record of which currency is being paid or what its balance was before the transfer, making a correct calculation impossible.
When interacting with smart contracts that manage multiple assets, a common task is repaying, or "settling," a debt of a specific token. In the PoolManager contract, this process is handled by a pair of functions, sync and settle, which must be used together in a specific pattern. This lesson breaks down the "sync before settle" workflow, revealing how the system uses transient storage (EIP-1153) to efficiently and securely calculate the amount of tokens paid within a single transaction.
_settle FunctionThe heart of the repayment mechanism is the internal _settle function. Its primary job is to determine how much of a given currency was paid by a user and update their balance accordingly.
For ERC20 tokens, the function calculates the paid amount by comparing the contract's token balance at two different points in time: a balance snapshotted before the user sent tokens, and the balance after the tokens were received. The difference between these two values is the amount paid.
Let's examine the code:
The key logic for ERC20 tokens resides in the else block:
reservesBefore is retrieved by calling CurrencyReserves.getSyncedReserves().
reservesNow is the contract's current token balance.
paid is calculated as reservesNow - reservesBefore.
This raises a critical question: How does the function get the reservesBefore value? This value must have been stored somewhere earlier in the transaction. To find the answer, we must look for where CurrencyReserves is written to, which leads us to the sync function.
sync FunctionThe sync function is the necessary prerequisite to settling an ERC20 debt. Its purpose is to take a snapshot of the PoolManager's current balance for a specific token and store it temporarily for the settle function to use later.
When sync is called with an ERC20 token address, it fetches the contract's current balance of that token and passes both the currency and its balance to CurrencyReserves.syncCurrencyAndReserves(). This is the exact moment the reservesBefore snapshot is created.
CurrencyReserves and Transient StorageThe CurrencyReserves library is the crucial link that enables communication between the sync and settle function calls within the same transaction. It achieves this by using transient storage, a feature introduced in EIP-1153.
Transient storage is a form of data storage that persists only for the duration of a single transaction. It is significantly cheaper than regular contract storage (SSTORE) because the data is automatically discarded when the transaction completes. It is accessed via low-level EVM opcodes, TSTORE (to store) and TLOAD (to load), which are used within assembly blocks.
The CurrencyReserves.sol library is a simple wrapper around these opcodes.
syncCurrencyAndReserves: Uses tstore to save the currency address and its balance (reservesBefore) into transient storage slots.
getSyncedReserves: Uses tload to retrieve the balance from the transient storage slot where it was previously saved.
This lightweight mechanism allows sync to "pass a message" to settle without incurring the high gas cost of writing to permanent state.
To correctly settle an ERC20 token debt with the PoolManager, an integrator must follow these steps in order within a single transaction:
Call sync(currency): First, call sync with the address of the ERC20 token you intend to repay. This action records the token's identity and its current balance (reservesBefore) in transient storage.
Transfer Tokens: Immediately after the sync call, transfer the ERC20 tokens to the PoolManager contract address. This action increases the contract's balance of that token, establishing the reservesNow value.
Call settle(): Finally, call settle. The settle function will now execute its logic:
It retrieves the currency and the reservesBefore value from transient storage.
It reads the contract's new, higher token balance (reservesNow).
It calculates the paid amount by subtracting reservesBefore from reservesNow.
It updates the user's debt and cleans up the transient storage slots for the next transaction.
This "sync before settle" pattern is mandatory. Without first calling sync, the settle function would have no record of which currency is being paid or what its balance was before the transfer, making a correct calculation impossible.
A technical breakdown of the sync and settle workflow - Explore the mandatory two-step process for repaying token debts in the PoolManager. Learn how `sync` and `settle` use transient storage to calculate payment amounts within a single transaction.
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