BeforeSwapDelta Guide
BeforeSwapDelta is a custom type used in Uniswap V4 hook contracts to represent balance changes during swap operations. It is specifically designed to handle the return value of the beforeSwap hook and to be compatible with the afterSwap hook.
Before explaining BeforeSwapDelta in detail, it is worth noting that in the context of Uniswap V4 swaps:
- The specified token is the one for which the user specifies an exact input or output amount.
- The unspecified token is the counterpart in the swap, whose amount is determined by the pool's pricing mechanism.
Purpose
The main purpose of BeforeSwapDelta is to efficiently encode and decode balance changes for both specified and unspecified tokens in a single 256-bit value. This compact representation allows for gas-efficient operations and seamless integration with Uniswap V4's hook system.
BeforeSwapDelta is essential for:
- Allowing hooks to modify swap parameters or override default swap behavior
- Allowing hooks to take fees from swaps
- Providing fine-grained control over balance adjustments resulting from swaps
- Optimizing gas usage by packing two
int128values into a singleint256
To summarise, BeforeSwapDelta is used to ensure that the net balance change for each token is zero after the hook's functionality is executed. This is important for maintaining the integrity of the pool's balances and ensuring that the hooks do not introduce any unexpected or unauthorized balance changes.
Type Definition
type BeforeSwapDelta is int256;
The BeforeSwapDelta type is an alias for int256, where:
- The upper 128 bits represent the delta in specified tokens
- The lower 128 bits represent the delta in unspecified tokens
Using Directives
using BeforeSwapDeltaLibrary for BeforeSwapDelta global;
using SafeCast for int256;
These using directives enable library functions to be used directly on BeforeSwapDelta values and provide safe casting operations for int256 values.
Functions
toBeforeSwapDelta
function toBeforeSwapDelta(int128 deltaSpecified, int128 deltaUnspecified) pure returns (BeforeSwapDelta beforeSwapDelta);
Creates a BeforeSwapDelta value from two int128 values representing deltaSpecified and deltaUnspecified.
| Param Name | Type | Description |
|---|---|---|
| deltaSpecified | int128 | The balance change for the specified token |
| deltaUnspecified | int128 | The balance change for the unspecified token |
Returns the created BeforeSwapDelta value.
This function uses bitwise operations in assembly for gas-efficient packing of the two int128 values:
assembly ("memory-safe") {
beforeSwapDelta := or(shl(128, deltaSpecified), and(sub(shl(128, 1), 1), deltaUnspecified))
}
Library Functions
ZERO_DELTA
BeforeSwapDelta public constant ZERO_DELTA = BeforeSwapDelta.wrap(0);
A constant representing a zero delta (no balance changes). It should be used as a default return value. It is most commonly used for hooks that are not implementing custom accounting.
getSpecifiedDelta
function getSpecifiedDelta(BeforeSwapDelta delta) internal pure returns (int128 deltaSpecified);
Extracts the specified token delta from a BeforeSwapDelta value.
| Param Name | Type | Description |
|---|---|---|
| delta | BeforeSwapDelta | The BeforeSwapDelta value |
Returns the extracted specified token delta as an int128.
getUnspecifiedDelta
function getUnspecifiedDelta(BeforeSwapDelta delta) internal pure returns (int128 deltaUnspecified);
Extracts the unspecified token delta from a BeforeSwapDelta value.
| Param Name | Type | Description |
|---|---|---|
| delta | BeforeSwapDelta | The BeforeSwapDelta |
Returns the extracted unspecified token delta as an int128.
Usage in Hooks
When a hook is called during a swap operation, it can perform custom logic and interact with the pool's balances. The beforeSwap hook returns a BeforeSwapDelta value to indicate any balance changes the hook introduces. For example, hooks taking fees should return the value it took as a BeforeSwapDelta.
Usage in PoolManager.sol
BeforeSwapDelta plays a crucial role in Uniswap V4's PoolManager contract, particularly in the swap process. Here's an overview of how it's used:
Calling the beforeSwap Hook
In the swap function of the PoolManager contract, the beforeSwap hook is called:
function swap(PoolKey memory key, IPoolManager.SwapParams memory params, bytes calldata hookData)
// ...
returns (BalanceDelta swapDelta)
{
// ... (other code)
BeforeSwapDelta beforeSwapDelta;
{
int256 amountToSwap;
uint24 lpFeeOverride;
(amountToSwap, beforeSwapDelta, lpFeeOverride) = key.hooks.beforeSwap(key, params, hookData);
// ... (swap execution)
}
// ... (other code)
}
The beforeSwap hook returns a BeforeSwapDelta value along with other parameters.
Interaction between beforeSwapDelta and amountToSwap
The beforeSwapDelta returned by the hook is used in conjunction with params.amountSpecified to determine the final amountToSwap. This allows hooks to modify the swap amount based on their custom logic. Here's a more detailed explanation of how this works:
- The
beforeSwaphook returns aBeforeSwapDeltavalue. - The
getSpecifiedDelta()of thisBeforeSwapDeltais used to adjust the originalparams.amountSpecified. - This adjustment results in the final
amountToSwapthat will be used for the actual swap operation.
Here's a simplified representation of this calculation:
int256 amountToSwap = params.amountSpecified + beforeSwapDelta.getSpecifiedDelta();
In this example, the amountToSwap is calculated by adding the specified delta from beforeSwapDelta to the original amountSpecified. This calculation allows hooks to increase or decrease the swap amount, effectively implementing features like fees, rebates, or other custom logic.
Detailed Example:
Let's say a user wants to swap 100 tokens, but a hook implements a 1% fee:
params.amountSpecifiedwould be 100- The hook calculates the fee as 1 token and returns a
beforeSwapDeltawith a specified delta of -1 amountToSwapis then calculated as 100 + (-1) = 99
This way, the actual amount swapped (99) reflects the fee taken by the hook, while still allowing the pool to execute the swap based on the original 100 token input from the user.
Here's how the beforeSwap hook might handle this:
function _beforeSwap(
address,
PoolKey calldata,
IPoolManager.SwapParams calldata params,
bytes calldata
) internal override returns (int256 amountIn, BeforeSwapDelta delta, uint24) {
int128 specifiedAmount = params.amountSpecified.toInt128();
int128 fee = specifiedAmount / 100; // 1% fee
int128 adjustedAmount = specifiedAmount - fee;
delta = BeforeSwapDelta.from(-fee, 0); // Fee taken from specified token
amountIn = params.amountSpecified; // Original amount
return (amountIn, delta, 0);
}
After this hook executes:
amountInremains 100 (the originalparams.amountSpecified)deltarepresents a change of -1 in the specified token (the fee)
Then, in the PoolManager:
int256 amountToSwap = params.amountSpecified + beforeSwapDelta.getSpecifiedDelta();
// This effectively calculates: 100 + (-1) = 99
As a result:
- The pool sees the full input amount of 100 tokens.
- The actual amount swapped is 99 tokens.
- The 1 token difference becomes the hook's fee.
This mechanism allows hooks to influence the swap amount while maintaining transparency about the full input amount, enabling complex custom logic within the Uniswap V4 framework.
Relation to afterSwap
While beforeSwapDelta is primarily used in the beforeSwap hook, it also plays a role in the afterSwap process. Specifically:
- The
afterSwaphook receives thebeforeSwapDeltaas a parameter. - The unspecified delta (
beforeSwapDelta.getUnspecifiedDelta()) is particularly important in theafterSwapcontext. - This unspecified delta is accounted for in
afterSwap's calculations, allowing for consistent balance tracking across the entire swap process.
This mechanism ensures that:
- Changes made by the
beforeSwaphook are properly considered when finalizing the swap. - The
afterSwaphook can make informed decisions based on the full context of the swap, including any modifications made inbeforeSwap. - Complex swap logic can be implemented across multiple hook points while maintaining consistency.
For example, if a fee was taken on the specified token in beforeSwap, the afterSwap hook can use this information to ensure the overall balance changes are correct, potentially adjusting the unspecified token amount accordingly.
Developers implementing custom hooks should be aware of this relationship and ensure their beforeSwap and afterSwap implementations work together coherently, especially when implementing features like fees or rebates that affect token balances.
Key Purposes of BeforeSwapDelta
The BeforeSwapDelta serves several important purposes in the Uniswap V4 swap process:
- Customization of Swap Behavior: It allows hooks to modify the swap parameters or even completely override the default swap behavior.
- Balance Adjustment: The delta values can be used to adjust the final balance changes resulting from the swap, giving hooks fine-grained control over the swap's outcome.
- Gas Optimization: By packing two
int128values into a singleint256, it reduces the number of stack variables and can lead to gas savings. - Cross-Hook Communication: It provides a way for the
beforeSwaphook to pass information to theafterSwaphook, enabling more complex and stateful hook logic. - Hook Fees Implementation:
BeforeSwapDeltaoffers flexible options for implementing hook fees:
- Fees can be charged on either the specified or unspecified token.
- Fees can be implemented in either the
beforeSwaporafterSwaphook. - For
beforeSwap:
- Adjust the specified amount to account for the fee.
- Useful for scenarios where the fee needs to be known before the swap execution.
- For
afterSwap:
- Generally considered best practice to charge fees on the unspecified token.
- Allows for more accurate fee calculation based on the actual swap outcome.
- As a result, developers can implement various fee structures, such as:
- Fixed fee amounts
- Percentage-based fees
- Tiered fee structures based on swap volume or other criteria
Example of a simple percentage-based fee in beforeSwap:
int128 fee = specifiedAmount * FEE_PERCENTAGE / 100;
int128 adjustedAmount = specifiedAmount - fee;
delta = BeforeSwapDelta.from(-adjustedAmount, 0);
This flexibility in fee implementation allows developers to create sophisticated economic models within their Uniswap V4 hooks, tailoring the behavior to specific use cases while maintaining the efficiency and standardization provided by the BeforeSwapDelta structure.
Perspective
It's important to note that the BeforeSwapDelta is from the perspective of the hook itself, not the user. For example, if a user swaps 1 USDC for 1 USDT:
- User gives 1 USDC: balance0OfUser decreases
- Hook gets 1 USDC: balance0OfHook increases
This perspective is key to correctly interpreting and manipulating the delta values within hook implementations.
Implementation Details
The BeforeSwapDelta type and its associated functions use low-level assembly code for efficient bit manipulation and gas optimization:
- The
toBeforeSwapDeltafunction uses bitwise operations (shl,or,and,sub) to pack twoint128values into a singleint256. - The
getSpecifiedDeltafunction uses thesar(shift arithmetic right) operation to extract the upper 128 bits. - The
getUnspecifiedDeltafunction uses thesignextendoperation to extract and sign-extend the lower 128 bits.
The toBeforeSwapDelta function combines the specified and unspecified deltas into a single int256 value using bitwise operations. The getSpecifiedDelta and getUnspecifiedDelta functions extract the respective deltas using bit shifting and sign extension.
By leveraging this compact representation and efficient arithmetic operations, Uniswap V4 can perform complex balance calculations and updates in a gas-optimized manner, reducing the overall cost of executing pool-related operations.
Implementation Considerations
When working with BeforeSwapDelta, especially for implementing hook fees, consider the following:
- Fee Timing: While fees can be implemented in either
beforeSwaporafterSwap, charging fees on the unspecified token inafterSwapis often considered best practice. This approach can provide more accurate fee calculations based on the final swap amounts. - Fee Direction: Remember that the deltas in
BeforeSwapDeltaare from the perspective of the hook. A positive delta means the hook is receiving tokens, while a negative delta means the hook is paying out tokens. - Consistency: Ensure that your fee implementation is consistent across both
beforeSwapandafterSwaphooks to maintain the integrity of the swap process. - Gas Efficiency: When implementing fees, consider the gas costs of your calculations. The compact nature of
BeforeSwapDeltacan help in optimizing gas usage, but complex fee structures might increase gas costs.
Comparison with BalanceDelta
BeforeSwapDelta shares a similar structure with BalanceDelta, both packing two int128 values into a single int256. However, there are key differences:
BalanceDeltarepresents amount0 and amount1.BeforeSwapDeltarepresents specified and unspecified amounts, which may not directly correspond to token0 and token1, depending on the swap direction.
Best Practices
When working with BeforeSwapDelta, consider the following best practices:
- Always use the provided library functions (
getSpecifiedDeltaandgetUnspecifiedDelta) to extract delta values. - Ensure that the signs of the delta values are correct from the hook's perspective.
- Use
SafeCastwhen converting between different integer types to prevent overflow/underflow errors.
Error Handling and Edge Cases
- Overflow/Underflow: Ensure that the input int128 values do not exceed their range when packing into BeforeSwapDelta.
- Zero Values: ZERO_DELTA represents no balance changes. Be cautious when interpreting zero values in specific contexts.
- Sign Mismatch: Ensure that the signs of the delta values correctly represent the intended balance changes from the hook's perspective.
Example Usage in a Hook
Basic Example
Here's a simple example of how BeforeSwapDelta might be used in a beforeSwap hook:
function _beforeSwap(
address,
PoolKey calldata,
IPoolManager.SwapParams calldata params,
bytes calldata
) internal override returns (int256 amountIn, BeforeSwapDelta delta, uint24) {
// Convert the specified amount to int128
int128 specifiedAmount = params.amountSpecified.toInt128();
// In this example, we're not modifying the unspecified amount
int128 unspecifiedAmount = 0; // Calculated based on your custom logic
// Create the BeforeSwapDelta
delta = toBeforeSwapDelta(specifiedAmount, unspecifiedAmount);
// Return the original amount as amountIn
amountIn = params.amountSpecified;
// Return 0 for lpFeeOverride as we're not changing the LP fee
return (amountIn, delta, 0);
}
Let's break down what this basic hook is doing:
- It converts the
params.amountSpecifiedtoint128, which is required forBeforeSwapDelta. - It sets the
unspecifiedAmountto 0, which means this hook isn't modifying the counterpart token in the swap. - It creates a
BeforeSwapDeltausing these amounts. - It returns the original
amountIn, the createddelta, and 0 forlpFeeOverride.
This basic example doesn't modify the swap parameters or introduce any fees. It demonstrates the minimal structure of a beforeSwap hook using BeforeSwapDelta.
Advanced Example: Implementing a Fee
For a more practical use case, here's an example that implements a simple fee mechanism:
function _beforeSwap(
address,
PoolKey calldata key,
IPoolManager.SwapParams calldata params,
bytes calldata
) internal override returns (int256 amountIn, BeforeSwapDelta delta, uint24) {
// Determine if this is a swap for token0 or token1
bool zeroForOne = params.zeroForOne;
// Convert the specified amount to int128, ensuring it's positive
int128 specifiedAmount = params.amountSpecified.abs().toInt128();
// Calculate a 0.1% fee
int128 fee = specifiedAmount / 1000;
// Adjust the specified amount based on swap direction
int128 adjustedSpecifiedAmount;
if (params.exactInput) {
// For exact input, reduce the amount by the fee
adjustedSpecifiedAmount = specifiedAmount - fee;
} else {
// For exact output, increase the amount by the fee
adjustedSpecifiedAmount = specifiedAmount + fee;
}
// Create the BeforeSwapDelta
delta = zeroForOne
? BeforeSwapDelta.from(-adjustedSpecifiedAmount, 0)
: BeforeSwapDelta.from(0, -adjustedSpecifiedAmount);
// Return the original amount as amountIn
amountIn = params.amountSpecified;
// Return 0 for lpFeeOverride as we're not changing the LP fee
return (amountIn, delta, 0);
}
Let's break down what this hook is doing:
- Swap Direction Determination:
The hook checks
params.zeroForOneto determine the direction of the swap (token0 to token1 or vice versa). - Amount Conversion: It converts
params.amountSpecifiedto a positiveint128. This is necessary becauseBeforeSwapDeltaworks withint128values. - Fee Calculation: A 0.1% fee is calculated based on the specified amount.
- Amount Adjustment: Depending on whether the swap is exact input or exact output, the specified amount is adjusted:
- For exact input, the fee is subtracted (user provides less to the pool).
- For exact output, the fee is added (user needs to provide more to the pool).
- BeforeSwapDelta Creation: The
BeforeSwapDeltais created using the adjusted amount. The negative sign indicates that the pool will receive these tokens from the user. The amount is placed in either the first or second parameter ofBeforeSwapDelta.from()depending on the swap direction. - Return Values:
amountInis set to the originalparams.amountSpecified. This allows the pool to account for the full amount the user is putting in or expecting out.- The
deltavalue contains our adjusted amounts. 0is returned forlpFeeOverride, meaning we're not changing the default LP fee.
What This Accomplishes:
- This hook implements a 0.1% fee on the swaps.
- It handles both exact input and exact output swaps correctly.
- It accounts for the swap direction (token0 to token1 or vice versa).
- The fee is taken from the input amount for exact input swaps, or added to the input amount for exact output swaps.
- The pool will see the full input/output amount, but will only swap the adjusted amount (after accounting for the fee).
- The difference between the original amount and the adjusted amount effectively becomes the hook's fee.
Considerations:
- This example assumes the fee is always taken in the input token. In practice, you might want to design more sophisticated fee structures.
- The hook doesn't handle storage of collected fees. In a real implementation, you'd need to account for and possibly transfer these fees.
- Always ensure that your hook's logic is consistent with the overall pool behavior and doesn't introduce unexpected side effects.
- This implementation doesn't change the LP fee (lpFeeOverride is 0). In some cases, you might want to adjust this as well.
As you can see from this example, by using BeforeSwapDelta, hooks can implement custom logic such as fees, rebates, or other modifications to the swap parameters, allowing for highly flexible and customizable pool behavior in Uniswap V4.