欢迎订阅专栏:3分钟Solidity--智能合约--Web3区块链技术必学
Uniswap V4 兑换
Uniswap V4 引入了单一实例 PoolManager,将所有流动性池集中在一个智能合约中管理。
V3版本的主要区别:
- 单例架构 - 所有资金池都存在于一个合约中
- 闪存记账 - 代币转移仅在最后阶段发生,降低Gas费用
- 解锁/回调模式 - 通过回调机制进行交互
要兑换:
- 调用带有编码参数的
poolManager.unlock() - PoolManager会调用你的
unlockCallback() - 在回调函数中:执行交换、结算输入、获取输出
- 在解锁完成前,差额必须净值为零
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
// 以太坊主网上的Uniswap V4 PoolManager
address constant POOL_MANAGER = 0x000000000004444c5dc75cB358380D2e3dE08A90;
address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
/// @notice 直接在Uniswap V4上使用PoolManager进行交换的示例
/// @dev V4 使用单例PoolManager并采用unlock/unlockCallback模式
contract UniswapV4Swap is IUnlockCallback {
IPoolManager public immutable poolManager;
error NotPoolManager();
error SwapFailed();
constructor() {
poolManager = IPoolManager(POOL_MANAGER);
}
/// @notice 将输入的精确金额兑换为输出代币
/// @param key 标识池的池密钥
/// @param amountIn 输入代币的交换数量
/// @param minAmountOut 最低可接受输出量
function swapExactInput(
PoolKey calldata key,
uint128 amountIn,
uint128 minAmountOut
) external returns (uint256 amountOut) {
// Encode swap parameters to pass through unlock callback
bytes memory data = abi.encode(
SwapParams({
key: key,
amountIn: amountIn,
minAmountOut: minAmountOut,
zeroForOne: true,
sender: msg.sender
})
);
// 发起交换 - PoolManager将调用unlockCallback
bytes memory result = poolManager.unlock(data);
amountOut = abi.decode(result, (uint256));
}
/// @notice PoolManager解锁后的回调
/// @dev 这里是实际执行交换逻辑的地方
function unlockCallback(bytes calldata data)
external
override
returns (bytes memory)
{
if (msg.sender != address(poolManager)) revert NotPoolManager();
SwapParams memory params = abi.decode(data, (SwapParams));
// 执行交换
// zeroForOne: true = token0 -> token1, false = token1 -> token0
// amountSpecified: negative = exact input, positive = exact output
BalanceDelta delta = poolManager.swap(
params.key,
IPoolManager.SwapParams({
zeroForOne: params.zeroForOne,
amountSpecified: -int256(uint256(params.amountIn)),
sqrtPriceLimitX96: params.zeroForOne
? MIN_SQRT_PRICE + 1
: MAX_SQRT_PRICE - 1
}),
bytes("")
);
// 根据差值计算金额
// delta.amount0() is negative (we owe the pool)
// delta.amount1() is positive (pool owes us)
uint256 amountOut = params.zeroForOne
? uint256(int256(delta.amount1()))
: uint256(int256(delta.amount0()));
if (amountOut < params.minAmountOut) revert SwapFailed();
// 结算输入代币(支付我们所欠款项)
Currency inputCurrency = params.zeroForOne
? params.key.currency0
: params.key.currency1;
IERC20(Currency.unwrap(inputCurrency)).transferFrom(
params.sender,
address(poolManager),
params.amountIn
);
poolManager.settle(inputCurrency);
// Take the output token (receive what we're owed)
Currency outputCurrency = params.zeroForOne
? params.key.currency1
: params.key.currency0;
poolManager.take(outputCurrency, params.sender, amountOut);
return abi.encode(amountOut);
}
struct SwapParams {
PoolKey key;
uint128 amountIn;
uint128 minAmountOut;
bool zeroForOne;
address sender;
}
}
// 交换的平方根价格限制
uint160 constant MIN_SQRT_PRICE = 4295128739;
uint160 constant MAX_SQRT_PRICE =
1461446703485210103287273052203988822378723970342;
// 货币是一个地址包装器(address(0) = 原生ETH)
type Currency is address;
library CurrencyLibrary {
function unwrap(Currency currency) internal pure returns (address) {
return Currency.unwrap(currency);
}
}
using CurrencyLibrary for Currency;
struct PoolKey {
Currency currency0;
Currency currency1;
uint24 fee;
int24 tickSpacing;
address hooks;
}
/// @notice 交换操作返回的余额差额
/// @dev 负 = 你欠池子,正 = 池子欠你
type BalanceDelta is int256;
library BalanceDeltaLibrary {
function amount0(BalanceDelta delta) internal pure returns (int128) {
return int128(int256(BalanceDelta.unwrap(delta) >> 128));
}
function amount1(BalanceDelta delta) internal pure returns (int128) {
return int128(int256(BalanceDelta.unwrap(delta)));
}
}
using BalanceDeltaLibrary for BalanceDelta;
interface IPoolManager {
struct SwapParams {
bool zeroForOne;
int256 amountSpecified;
uint160 sqrtPriceLimitX96;
}
function unlock(bytes calldata data) external returns (bytes memory);
function swap(PoolKey memory key, SwapParams memory params, bytes calldata hookData)
external
returns (BalanceDelta);
function settle(Currency currency) external payable returns (uint256);
function take(Currency currency, address to, uint256 amount) external;
}
interface IUnlockCallback {
function unlockCallback(bytes calldata data) external returns (bytes memory);
}
interface IERC20 {
function transferFrom(address sender, address recipient, uint256 amount)
external
returns (bool);
function approve(address spender, uint256 amount) external returns (bool);
}