多跳交换
以下示例是 v3 上可用的两种多跳交换样式的实现。以下示例不是可用于生产的代码,并且以简单的方式实现,以用于学习目的。
声明将用于编译合约的 solidity 版本,并 abicoder v2 允许在 calldata 中对任意嵌套数组和结构进行编码和解码,这是我们在执行交换时使用的功能。
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;
pragma abicoder v2;
创建一个名为的合约 SwapExamples,并声明一个 swapRouter 类型的不可变公共变量 ISwapRouter。这使我们能够调用 ISwapRouter 接口中的函数。
contract SwapExamples {
// For the scope of these swap examples,
// we will detail the design considerations when using `exactInput`, `exactInputSingle`, `exactOutput`, and `exactOutputSingle`.
// It should be noted that for the sake of these examples we pass in the swap router as a constructor argument instead of inheriting it.
// More advanced example contracts will detail how to inherit the swap router safely.
// This example swaps DAI/WETH9 for single path swaps and DAI/USDC/WETH9 for multi path swaps.
ISwapRouter public immutable swapRouter;
为示例硬编码代币合约地址和池费用等级。在生产中,您可能会为此使用输入参数并将输入传递到内存变量中,从而允许合约根据每笔交易更改与之交互的池和代币,但为了概念简单起见,我们在这里对其进行硬编码。
address public constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
address public constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
// For this example, we will set the pool fee to 0.3%.
uint24 public constant poolFee = 3000;
constructor(ISwapRouter _swapRouter) {
swapRouter = _swapRouter;
}
精确输入多跳
精确输入多跳交换将在给定输入令牌上交换固定数量,以获得给定输出的最大可能数量,并且可以包含任意数量的中间交换。
输入
- path:路径是(tokenAddress- fee- tokenAddress)的序列,这些变量是计算我们交换序列中每个池合约地址所需的变量。多跳交换路由器代码将使用这些变量自动找到正确的池,并在我们的序列中的每个池中执行所需的交换。
- recipient:出站资产的目标地址。
- deadline:交易将被撤销的 unix 时间,以防止长时间延迟和其中价格大幅波动的可能性增加。
- amountIn:入站资产的金额
- amountOutMin:出站资产的最小金额,低于此金额将导致交易撤销。为了便于说明,我们将其设置为 0,在生产中,需要使用 SDK 来引用预期价格,或者使用链上价格预言机来获得更高级的防操纵系统。
调用
/// @notice swapExactInputMultihop swaps a fixed amount of DAI for a maximum possible amount of WETH9 through an intermediary pool.
/// For this example, we will swap DAI to USDC, then USDC to WETH9 to achieve our desired output.
/// @dev The calling address must approve this contract to spend at least `amountIn` worth of its DAI for this function to succeed.
/// @param amountIn The amount of DAI to be swapped.
/// @return amountOut The amount of WETH9 received after the swap.
function swapExactInputMultihop(uint256 amountIn) external returns (uint256 amountOut) {
// Transfer `amountIn` of DAI to this contract.
TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountIn);
// Approve the router to spend DAI.
TransferHelper.safeApprove(DAI, address(swapRouter), amountIn);
// Multiple pool swaps are encoded through bytes called a `path`. A path is a sequence of token addresses and poolFees that define the pools used in the swaps.
// The format for pool encoding is (tokenIn, fee, tokenOut/tokenIn, fee, tokenOut) where tokenIn/tokenOut parameter is the shared token across the pools.
// Since we are swapping DAI to USDC and then USDC to WETH9 the path encoding is (DAI, 0.3%, USDC, 0.3%, WETH9).
ISwapRouter.ExactInputParams memory params =
ISwapRouter.ExactInputParams({
path: abi.encodePacked(DAI, poolFee, USDC, poolFee, WETH9),
recipient: msg.sender,
deadline: block.timestamp,
amountIn: amountIn,
amountOutMinimum: 0
});
// Executes the swap.
amountOut = swapRouter.exactInput(params);
}
精确输出多跳
精确输出交换将用可变数量的输入代币交换固定数量的出站代币。这是多跳交换中不太常见的技术。交换的代码大致相同,除了一个显着的区别,即 Path 是反向编码的,因为精确输出交换是按相反顺序执行的,以传递交易链所需的变量
输入
- path:路径是一系列以反向顺序编码 tokenAddress Fee tokenAddress 的,这些是计算我们交换序列中每个池合约地址所需的变量。多跳交换路由器代码将使用这些变量自动找到正确的池,并在我们的序列中的每个池中执行所需的交换。
- recipient:出站资产的目标地址。
- deadline:交易将被撤销的 unix 时间,以防止长时间延迟和其中价格大幅波动的可能性增加。
- amountOut:所需的 WETH9 数量。
- amountInMaximum:愿意与指定数量的 WETH9 交换的 DAI 的最大数量。
调用
/// @notice swapExactOutputMultihop swaps a minimum possible amount of DAI for a fixed amount of WXOC through an intermediary pool.
/// For this example, we want to swap DAI for WETH9 through a USDC pool but we specify the desired amountOut of WETH9. Notice how the path encoding is slightly different in for exact output swaps.
/// @dev The calling address must approve this contract to spend its DAI for this function to succeed. As the amount of input DAI is variable,
/// the calling address will need to approve for a slightly higher amount, anticipating some variance.
/// @param amountOut The desired amount of WETH9.
/// @param amountInMaximum The maximum amount of DAI willing to be swapped for the specified amountOut of WETH9.
/// @return amountIn The amountIn of DAI actually spent to receive the desired amountOut.
function swapExactOutputMultihop(uint256 amountOut, uint256 amountInMaximum) external returns (uint256 amountIn) {
// Transfer the specified `amountInMaximum` to this contract.
TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountInMaximum);
// Approve the router to spend `amountInMaximum`.
TransferHelper.safeApprove(DAI, address(swapRouter), amountInMaximum);
// The parameter path is encoded as (tokenOut, fee, tokenIn/tokenOut, fee, tokenIn)
// The tokenIn/tokenOut field is the shared token between the two pools used in the multiple pool swap. In this case USDC is the "shared" token.
// For an exactOutput swap, the first swap that occurs is the swap which returns the eventual desired token.
// In this case, our desired output token is WETH9 so that swap happens first, and is encoded in the path accordingly.
ISwapRouter.ExactOutputParams memory params =
ISwapRouter.ExactOutputParams({
path: abi.encodePacked(WETH9, poolFee, USDC, poolFee, DAI),
recipient: msg.sender,
deadline: block.timestamp,
amountOut: amountOut,
amountInMaximum: amountInMaximum
});
// Executes the swap, returning the amountIn actually spent.
amountIn = swapRouter.exactOutput(params);
// If the swap did not require the full amountInMaximum to achieve the exact amountOut then we refund msg.sender and approve the router to spend 0.
if (amountIn < amountInMaximum) {
TransferHelper.safeApprove(DAI, address(swapRouter), 0);
TransferHelper.safeTransferFrom(DAI, address(this), msg.sender, amountInMaximum - amountIn);
}
}
完整
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;
pragma abicoder v2;
contract SwapExamples {
// For the scope of these swap examples,
// we will detail the design considerations when using
// `exactInput`, `exactInputSingle`, `exactOutput`, and `exactOutputSingle`.
// It should be noted that for the sake of these examples, we purposefully pass in the swap router instead of inherit the swap router for simplicity.
// More advanced example contracts will detail how to inherit the swap router safely.
ISwapRouter public immutable swapRouter;
// This example swaps DAI/WETH9 for single path swaps and DAI/USDC/WETH9 for multi path swaps.
address public constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
address public constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
// For this example, we will set the pool fee to 0.3%.
uint24 public constant poolFee = 3000;
constructor(ISwapRouter _swapRouter) {
swapRouter = _swapRouter;
}
/// @notice swapInputMultiplePools swaps a fixed amount of DAI for a maximum possible amount of WETH9 through an intermediary pool.
/// For this example, we will swap DAI to USDC, then USDC to WETH9 to achieve our desired output.
/// @dev The calling address must approve this contract to spend at least `amountIn` worth of its DAI for this function to succeed.
/// @param amountIn The amount of DAI to be swapped.
/// @return amountOut The amount of WETH9 received after the swap.
function swapExactInputMultihop(uint256 amountIn) external returns (uint256 amountOut) {
// Transfer `amountIn` of DAI to this contract.
TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountIn);
// Approve the router to spend DAI.
TransferHelper.safeApprove(DAI, address(swapRouter), amountIn);
// Multiple pool swaps are encoded through bytes called a `path`. A path is a sequence of token addresses and poolFees that define the pools used in the swaps.
// The format for pool encoding is (tokenIn, fee, tokenOut/tokenIn, fee, tokenOut) where tokenIn/tokenOut parameter is the shared token across the pools.
// Since we are swapping DAI to USDC and then USDC to WETH9 the path encoding is (DAI, 0.3%, USDC, 0.3%, WETH9).
ISwapRouter.ExactInputParams memory params =
ISwapRouter.ExactInputParams({
path: abi.encodePacked(DAI, poolFee, USDC, poolFee, WETH9),
recipient: msg.sender,
deadline: block.timestamp,
amountIn: amountIn,
amountOutMinimum: 0
});
// Executes the swap.
amountOut = swapRouter.exactInput(params);
}
/// @notice swapExactOutputMultihop swaps a minimum possible amount of DAI for a fixed amount of WXOC through an intermediary pool.
/// For this example, we want to swap DAI for WETH9 through a USDC pool but we specify the desired amountOut of WETH9. Notice how the path encoding is slightly different in for exact output swaps.
/// @dev The calling address must approve this contract to spend its DAI for this function to succeed. As the amount of input DAI is variable,
/// the calling address will need to approve for a slightly higher amount, anticipating some variance.
/// @param amountOut The desired amount of WETH9.
/// @param amountInMaximum The maximum amount of DAI willing to be swapped for the specified amountOut of WETH9.
/// @return amountIn The amountIn of DAI actually spent to receive the desired amountOut.
function swapExactOutputMultihop(uint256 amountOut, uint256 amountInMaximum) external returns (uint256 amountIn) {
// Transfer the specified `amountInMaximum` to this contract.
TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountInMaximum);
// Approve the router to spend `amountInMaximum`.
TransferHelper.safeApprove(DAI, address(swapRouter), amountInMaximum);
// The parameter path is encoded as (tokenOut, fee, tokenIn/tokenOut, fee, tokenIn)
// The tokenIn/tokenOut field is the shared token between the two pools used in the multiple pool swap. In this case USDC is the "shared" token.
// For an exactOutput swap, the first swap that occurs is the swap which returns the eventual desired token.
// In this case, our desired output token is WETH9 so that swap happpens first, and is encoded in the path accordingly.
ISwapRouter.ExactOutputParams memory params =
ISwapRouter.ExactOutputParams({
path: abi.encodePacked(WETH9, poolFee, USDC, poolFee, DAI),
recipient: msg.sender,
deadline: block.timestamp,
amountOut: amountOut,
amountInMaximum: amountInMaximum
});
// Executes the swap, returning the amountIn actually spent.
amountIn = swapRouter.exactOutput(params);
// If the swap did not require the full amountInMaximum to achieve the exact amountOut then we refund msg.sender and approve the router to spend 0.
if (amountIn < amountInMaximum) {
TransferHelper.safeApprove(DAI, address(swapRouter), 0);
TransferHelper.safeTransferFrom(DAI, address(this), msg.sender, amountInMaximum - amountIn);
}
}
}