BeforeSwapDelta
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
int128
values 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
beforeSwap
hook returns aBeforeSwapDelta
value. - The
getSpecifiedDelta()
of thisBeforeSwapDelta
is used to adjust the originalparams.amountSpecified
. - This adjustment results in the final
amountToSwap
that 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.amountSpecified
would be 100- The hook calculates the fee as 1 token and returns a
beforeSwapDelta
with a specified delta of -1 amountToSwap
is 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
) external 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:
amountIn
remains 100 (the originalparams.amountSpecified
)delta
represents 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
afterSwap
hook receives thebeforeSwapDelta
as a parameter. - The unspecified delta (
beforeSwapDelta.getUnspecifiedDelta()
) is particularly important in theafterSwap
context. - 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
beforeSwap
hook are properly considered when finalizing the swap. - The
afterSwap
hook 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
int128
values 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
beforeSwap
hook to pass information to theafterSwap
hook, enabling more complex and stateful hook logic. - Hook Fees Implementation:
BeforeSwapDelta
offers flexible options for implementing hook fees:
- Fees can be charged on either the specified or unspecified token.
- Fees can be implemented in either the
beforeSwap
orafterSwap
hook. - 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
toBeforeSwapDelta
function uses bitwise operations (shl
,or
,and
,sub
) to pack twoint128
values into a singleint256
. - The
getSpecifiedDelta
function uses thesar
(shift arithmetic right) operation to extract the upper 128 bits. - The
getUnspecifiedDelta
function uses thesignextend
operation 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
beforeSwap
orafterSwap
, charging fees on the unspecified token inafterSwap
is 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
BeforeSwapDelta
are 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
beforeSwap
andafterSwap
hooks to maintain the integrity of the swap process. - Gas Efficiency: When implementing fees, consider the gas costs of your calculations. The compact nature of
BeforeSwapDelta
can 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:
BalanceDelta
represents amount0 and amount1.BeforeSwapDelta
represents 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 (
getSpecifiedDelta
andgetUnspecifiedDelta
) to extract delta values. - Ensure that the signs of the delta values are correct from the hook's perspective.
- Use
SafeCast
when 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
) external 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.amountSpecified
toint128
, which is required forBeforeSwapDelta
. - It sets the
unspecifiedAmount
to 0, which means this hook isn't modifying the counterpart token in the swap. - It creates a
BeforeSwapDelta
using 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
) external 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.zeroForOne
to determine the direction of the swap (token0 to token1 or vice versa). - Amount Conversion: It converts
params.amountSpecified
to a positiveint128
. This is necessary becauseBeforeSwapDelta
works withint128
values. - 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
BeforeSwapDelta
is 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:
amountIn
is set to the originalparams.amountSpecified
. This allows the pool to account for the full amount the user is putting in or expecting out.- The
delta
value contains our adjusted amounts. 0
is 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.