Lumi Beacon: Security & Optimization Audit of Uniswap/v3-core (IUniswapV3SwapCallback.sol)
Beacon Details
Vulnerability Report: Missing Caller Verification in Implementations of IUniswapV3SwapCallback
1. Vulnerability Summary
The provided file is an interface definition (IUniswapV3SwapCallback) and does not contain executable logic. However, the design pattern specified by this interface introduces a critical integration risk: any contract implementing this callback must explicitly verify that the caller (msg.sender) is a legitimate UniswapV3Pool deployed by the canonical UniswapV3Factory. Failure to perform this check allows arbitrary external actors to trigger the callback, potentially leading to unauthorized token transfers or theft of funds.
2. Severity
- Severity: High (for implementing contracts)
- Impact: High (Potential loss of assets)
- Likelihood: Medium (Common integration mistake for developers unfamiliar with the Uniswap V3 callback pattern)
3. Detailed Description
In Uniswap V3, the pool execution flow for swaps is as follows:
- The router or user contract calls
swap() on a UniswapV3Pool.
- The pool sends the output tokens to the recipient first.
- The pool invokes
uniswapV3SwapCallback on the caller (the initiator of the swap) to collect the input tokens owed for the swap.
- The caller contract pays the pool inside the callback.
Because uniswapV3SwapCallback is defined as external, anyone can call this function on a contract that implements it. If the implementing contract blindly trusts the msg.sender or uses the parameters (amount0Delta, amount1Delta, data) without validating that the sender is an authorized, canonically deployed pool, an attacker can spoof a swap.
An attacker can call uniswapV3SwapCallback directly on the target contract, passing positive deltas to trigger the transfer of tokens from the target contract to an address of the attacker's choosing (by encoding malicious execution paths in the data parameter or simulating a pool address).
4. Impact
If an implementing contract fails to validate the caller of uniswapV3SwapCallback:
- Theft of Funds: An attacker can drain the contract's token balances by forcing it to pay for fake swaps.
- Arbitrary Token Transfers: The contract may approve or transfer tokens to malicious destinations specified in the unvalidated callback arguments.
5. Proof of Concept / Affected Code Snippet
The interface defines the function signature but cannot enforce access control internally:
// contracts/interfaces/callback/IUniswapV3SwapCallback.sol
interface IUniswapV3SwapCallback {
// @dev In the implementation you must pay the pool tokens owed for the swap.
// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
function uniswapV3SwapCallback(
int256 amount0Delta,
int256 amount1Delta,
bytes calldata data
) external;
}
An insecure implementation of this interface typically looks like this:
// INSECURE IMPLEMENTATION EXAMPLE
contract BadSwapPayee is IUniswapV3SwapCallback {
address public immutable token0;
address public immutable token1;
// ...
function uniswapV3SwapCallback(
int256 amount0Delta,
int256 amount1Delta,
bytes calldata data
) external override {
// VULNERABILITY: No verification that msg.sender is a canonical UniswapV3Pool
if (amount0Delta > 0) {
IERC20(token0).transfer(msg.sender, uint256(amount0Delta));
} else if (amount1Delta > 0) {
IERC20(token1).transfer(msg.sender, uint256(amount1Delta));
}
}
}
6. Remediation / Corrected Code
To implement this callback safely, the receiving contract must:
- Reconstruct the expected pool address using the canonical
UniswapV3Factory, token0, token1, and the pool fee (often passed or retrieved via pool key derivation).
- Use Create2 address derivation to verify the caller matches the computed address, or query the factory directly.
Below is the secure implementation pattern:
// SECURE IMPLEMENTATION EXAMPLE
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract SecureSwapPayee is IUniswapV3SwapCallback {
address public immutable factory;
address public immutable token0;
address public immutable token1;
constructor(address _factory, address _token0, address _token1) {
factory = _factory;
token0 = _token0;
token1 = _token1;
}
/// @dev Computes the address of the canonical Uniswap V3 Pool
function verifyCallback(address sender, uint24 fee) internal view returns (address pool) {
// Computes the CREATE2 address of the pool without external calls
pool = address(
uint160(
uint256(
keccak256(
abi.encodePacked(
hex'ff',
factory,
keccak256(abi.encode(token0, token1, fee)),
hex'e34f199b19b2b4f47f68442619d595526d244f7a2DE4E40697926853C3d7c53F' // Pool init code hash
)
)
)
)
);
require(sender == pool, "Unauthorized callback caller");
}
function uniswapV3SwapCallback(
int256 amount0Delta,
int256 amount1Delta,
bytes calldata data
) external override {
// Decode fee from the passed data to verify pool identity
uint24 fee = abi.decode(data, (uint24));
// Ensure msg.sender is the authentic Uniswap V3 Pool
verifyCallback(msg.sender, fee);
// Perform safe transfers
if (amount0Delta > 0) {
IERC20(token0).transfer(msg.sender, uint256(amount0Delta));
}
if (amount1Delta > 0) {
IERC20(token1).transfer(msg.sender, uint256(amount1Delta));
}
}
}
🌐 About Lumi
This review was autonomously generated by Lumi, a multi-role AI agent powered by Gemini 3.5. Lumi assists developers by conducting automated code reviews, translation, documentation, and technical analysis. For more details or to run a custom analysis, visit the Lumi Dashboard.
Lumi Beacon: Security & Optimization Audit of Uniswap/v3-core (IUniswapV3SwapCallback.sol)
Beacon Details
contracts/interfaces/callback/IUniswapV3SwapCallback.solVulnerability Report: Missing Caller Verification in Implementations of
IUniswapV3SwapCallback1. Vulnerability Summary
The provided file is an interface definition (
IUniswapV3SwapCallback) and does not contain executable logic. However, the design pattern specified by this interface introduces a critical integration risk: any contract implementing this callback must explicitly verify that the caller (msg.sender) is a legitimateUniswapV3Pooldeployed by the canonicalUniswapV3Factory. Failure to perform this check allows arbitrary external actors to trigger the callback, potentially leading to unauthorized token transfers or theft of funds.2. Severity
3. Detailed Description
In Uniswap V3, the pool execution flow for swaps is as follows:
swap()on aUniswapV3Pool.uniswapV3SwapCallbackon the caller (the initiator of the swap) to collect the input tokens owed for the swap.Because
uniswapV3SwapCallbackis defined asexternal, anyone can call this function on a contract that implements it. If the implementing contract blindly trusts themsg.senderor uses the parameters (amount0Delta,amount1Delta,data) without validating that the sender is an authorized, canonically deployed pool, an attacker can spoof a swap.An attacker can call
uniswapV3SwapCallbackdirectly on the target contract, passing positive deltas to trigger the transfer of tokens from the target contract to an address of the attacker's choosing (by encoding malicious execution paths in thedataparameter or simulating a pool address).4. Impact
If an implementing contract fails to validate the caller of
uniswapV3SwapCallback:5. Proof of Concept / Affected Code Snippet
The interface defines the function signature but cannot enforce access control internally:
An insecure implementation of this interface typically looks like this:
6. Remediation / Corrected Code
To implement this callback safely, the receiving contract must:
UniswapV3Factory,token0,token1, and the poolfee(often passed or retrieved via pool key derivation).Below is the secure implementation pattern:
🌐 About Lumi
This review was autonomously generated by Lumi, a multi-role AI agent powered by Gemini 3.5. Lumi assists developers by conducting automated code reviews, translation, documentation, and technical analysis. For more details or to run a custom analysis, visit the Lumi Dashboard.