Skip to content

Commit

Permalink
SwapRouter 合约 (#128)
Browse files Browse the repository at this point in the history
* feat: swapRouter 合约基础提交

* feat: 重构swap router逻辑

* feat: 完成 SwapRouter 中 exactInput & exactOutput 函数开发

* feat: 根据 Pool 合约 swap 方法,重新调整 exactInput & exactOutpu 中数量更新逻辑

* feat: add quoteExactInput and quoteExactOutput

* fix: swap callback check

* test: 初始化测试用例文件

* test: update swap test case

* chore: init swapRouter readme

---------

Co-authored-by: tingzhao.ytz <yutingzhao1991@gmail.com>
  • Loading branch information
MarioJames and yutingzhao1991 authored Oct 24, 2024
1 parent d4c9aad commit 0876aac
Show file tree
Hide file tree
Showing 6 changed files with 340 additions and 51 deletions.
7 changes: 7 additions & 0 deletions P109_SwapRouter/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
本节作者:[@mocha.wiz](https://x.com/mocha_wizard) [@愚指导](https://x.com/yudao1024)

这一讲我们将引导大家完成 `SwapRouter.sol` 合约的开发。

---

TODO
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@

**第 P108 讲:PositionManager 合约开发**[教程](./P108_PositionManager/readme.md) | [代码](./demo-contract/contracts/wtfswap/PositionManager.sol)

**第 P109 讲:SwapRouter 合约开发**
**第 P109 讲:SwapRouter 合约开发**[教程](./P109_SwapRouter/readme.md) | [代码](./demo-contract/contracts/wtfswap/SwapRouter.sol)

**第 P201 讲:初始化前端代码和技术分析**[教程](./P201_InitFrontend/readme.md) | [代码](./P201_InitFrontend/code/)

Expand Down
2 changes: 1 addition & 1 deletion demo-contract/contracts/wtfswap/Factory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ contract Factory is IFactory {

(token0, token1) = sortToken(tokenA, tokenB);

return pools[tokenA][tokenB][index];
return pools[token0][token1][index];
}

function createPool(
Expand Down
231 changes: 192 additions & 39 deletions demo-contract/contracts/wtfswap/SwapRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
pragma solidity ^0.8.24;
pragma abicoder v2;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import "./interfaces/ISwapRouter.sol";
import "./interfaces/IPool.sol";
import "./interfaces/IPoolManager.sol";
Expand All @@ -13,67 +15,218 @@ contract SwapRouter is ISwapRouter {
poolManager = IPoolManager(_poolManager);
}

// 确定输入的 token 交易
/// @dev Parses a revert reason that should contain the numeric quote
function parseRevertReason(
bytes memory reason
) private pure returns (uint256) {
if (reason.length != 32) {
if (reason.length < 68) revert("Unexpected error");
assembly {
reason := add(reason, 0x04)
}
revert(abi.decode(reason, (string)));
}
return abi.decode(reason, (uint256));
}

function exactInput(
ExactInputParams calldata params
) external payable override returns (uint256 amountOut) {
// 用 index 记录当前正在读取的 index
uint256 index = 0;
// while 循环遍历 indexPath,获取每个 pool 的价格
while (index < params.indexPath.length) {
address _pool = poolManager.getPool(
// 记录确定的输入 token 的 amount
uint256 amountIn = params.amountIn;

// 根据 tokenIn 和 tokenOut 的大小关系,确定是从 token0 到 token1 还是从 token1 到 token0
bool zeroForOne = params.tokenIn < params.tokenOut;

// 遍历指定的每一个 pool
for (uint256 i = 0; i < params.indexPath.length; i++) {
address poolAddress = poolManager.getPool(
params.tokenIn,
params.tokenOut,
params.indexPath[i]
);

// 如果 pool 不存在,则抛出错误
require(poolAddress != address(0), "Pool not found");

// 获取 pool 实例
IPool pool = IPool(poolAddress);

// 构造 swapCallback 函数需要的参数
bytes memory data = abi.encode(
params.tokenIn,
params.tokenOut,
params.indexPath[index]
params.indexPath[i],
msg.sender
);
IPool pool = IPool(_pool);
// TODO 交易
bytes memory data;
// 交易的钱统一转给本合约,最后都完成之后在 swapCallback 中打给用户
pool.swap(msg.sender, true, 12, 12, data);
amountOut += 2;
index++;

// 调用 pool 的 swap 函数,进行交换,并拿到返回的 token0 和 token1 的数量
(int256 amount0, int256 amount1) = pool.swap(
params.recipient,
zeroForOne,
int256(amountIn),
params.sqrtPriceLimitX96,
data
);

// 更新 amountIn 和 amountOut
amountIn = uint256(zeroForOne ? -amount0 : -amount1);
amountOut += uint256(zeroForOne ? -amount1 : -amount0);

// 如果 amountIn 为 0,表示交换完成,跳出循环
if (amountIn == 0) {
break;
}
}

// 如果交换到的 amountOut 小于指定的最少数量 amountOutMinimum,则抛出错误
require(amountOut >= params.amountOutMinimum, "Slippage exceeded");

// 发射 Swap 事件
emit Swap(msg.sender, zeroForOne, params.amountIn, amountIn, amountOut);

// 返回 amountOut
return amountOut;
}

// 确定输出的 token 交易
function exactOutput(
ExactOutputParams calldata params
) external payable override returns (uint256 amountIn) {}
) external payable override returns (uint256 amountIn) {
// 记录确定的输出 token 的 amount
uint256 amountOut = params.amountOut;

// 确认输入的 token,估算可以获得多少输出的 token
function quoteExactInput(
QuoteExactInputParams memory params
) external view override returns (uint256 amountOut) {
// 用 index 记录当前正在读取的 index
uint256 index = 0;
// while 循环遍历 indexPath,获取每个 pool 的价格
while (index < params.indexPath.length) {
address _pool = poolManager.getPool(
// 根据 tokenIn 和 tokenOut 的大小关系,确定是从 token0 到 token1 还是从 token1 到 token0
bool zeroForOne = params.tokenIn < params.tokenOut;

// 遍历指定的每一个 pool
for (uint256 i = 0; i < params.indexPath.length; i++) {
address poolAddress = poolManager.getPool(
params.tokenIn,
params.tokenOut,
params.indexPath[i]
);

// 如果 pool 不存在,则抛出错误
require(poolAddress != address(0), "Pool not found");

// 获取 pool 实例
IPool pool = IPool(poolAddress);

// 构造 swapCallback 函数需要的参数
bytes memory data = abi.encode(
params.tokenIn,
params.tokenOut,
params.indexPath[index]
params.indexPath[i],
msg.sender
);

// 调用 pool 的 swap 函数,进行交换,并拿到返回的 token0 和 token1 的数量
(int256 amount0, int256 amount1) = pool.swap(
msg.sender,
zeroForOne,
-int256(amountOut),
params.sqrtPriceLimitX96,
data
);
IPool pool = IPool(_pool);
uint160 sqrtPriceX96 = pool.sqrtPriceX96();
// TODO 计算 amountOut
amountOut = sqrtPriceX96;
// 更新 index
index++;

// 更新 amountOut 和 amountIn
amountOut = uint256(zeroForOne ? -amount1 : -amount0);
amountIn += uint256(zeroForOne ? -amount0 : -amount1);

// 如果 amountOut 为 0,表示交换完成,跳出循环
if (amountOut == 0) {
break;
}
}

// 如果交换到指定数量 tokenOut 消耗的 tokenIn 数量超过指定的最大值,报错
require(amountIn <= params.amountInMaximum, "Slippage exceeded");

// 发射 Swap 事件
emit Swap(
msg.sender,
zeroForOne,
params.amountOut,
amountOut,
amountIn
);

// 返回交换后的 amountIn
return amountIn;
}

// 确认输出的 token,估算需要多少输入的 token
// 报价,指定 tokenIn 的数量和 tokenOut 的最小值,返回 tokenOut 的实际数量
function quoteExactInput(
QuoteExactInputParams calldata params
) external override returns (uint256 amountOut) {
// 因为没有实际 approve,所以这里交易会报错,我们捕获错误信息,解析需要多少 token
try
this.exactInput(
ExactInputParams({
tokenIn: params.tokenIn,
tokenOut: params.tokenOut,
indexPath: params.indexPath,
recipient: msg.sender,
deadline: block.timestamp + 1 hours,
amountIn: params.amountIn,
amountOutMinimum: 0,
sqrtPriceLimitX96: params.sqrtPriceLimitX96
})
)
{} catch (bytes memory reason) {
return parseRevertReason(reason);
}
}

// 报价,指定 tokenOut 的数量和 tokenIn 的最大值,返回 tokenIn 的实际数量
function quoteExactOutput(
QuoteExactOutputParams memory params
) external view override returns (uint256 amountIn) {}
QuoteExactOutputParams calldata params
) external override returns (uint256 amountIn) {
try
this.exactOutput(
ExactOutputParams({
tokenIn: params.tokenIn,
tokenOut: params.tokenOut,
indexPath: params.indexPath,
recipient: msg.sender,
deadline: block.timestamp + 1 hours,
amountOut: params.amountOut,
amountInMaximum: type(uint256).max,
sqrtPriceLimitX96: params.sqrtPriceLimitX96
})
)
{} catch (bytes memory reason) {
return parseRevertReason(reason);
}
}

function swapCallback(
uint256 amount0In,
uint256 amount1In,
int256 amount0Delta,
int256 amount1Delta,
bytes calldata data
) external override {
// 每次 swap 后 pool 会调用这个方法
// 最后一次 swap 完成后这里统一把钱打给用户
// transfer token
(address tokenIn, address tokenOut, uint32 index, address payer) = abi
.decode(data, (address, address, uint32, address));
address _pool = poolManager.getPool(tokenIn, tokenOut, index);

// 检查 callback 的合约地址是否是 Pool
require(_pool == msg.sender, "Invalid callback caller");

bool zeroForOne = tokenIn < tokenOut;
if (amount0Delta > 0) {
IERC20(zeroForOne ? tokenIn : tokenOut).transferFrom(
payer,
_pool,
uint(amount0Delta)
);
}
if (amount1Delta > 0) {
IERC20(zeroForOne ? tokenOut : tokenIn).transferFrom(
payer,
_pool,
uint(amount1Delta)
);
}
}
}
24 changes: 14 additions & 10 deletions demo-contract/contracts/wtfswap/interfaces/ISwapRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,17 @@
pragma solidity ^0.8.24;
pragma abicoder v2;

interface ISwapRouter {
import "./IPool.sol";

interface ISwapRouter is ISwapCallback {
event Swap(
address indexed sender,
bool zeroForOne,
uint256 amountIn,
uint256 amountInRemaining,
uint256 amountOut
);

struct ExactInputParams {
address tokenIn;
address tokenOut;
Expand Down Expand Up @@ -42,24 +52,18 @@ interface ISwapRouter {
}

function quoteExactInput(
QuoteExactInputParams memory params
QuoteExactInputParams calldata params
) external returns (uint256 amountOut);

struct QuoteExactOutputParams {
address tokenIn;
address tokenOut;
uint32[] indexPath;
uint256 amount;
uint256 amountOut;
uint160 sqrtPriceLimitX96;
}

function quoteExactOutput(
QuoteExactOutputParams memory params
QuoteExactOutputParams calldata params
) external returns (uint256 amountIn);

function swapCallback(
uint256 amount0In,
uint256 amount1In,
bytes calldata data
) external;
}
Loading

0 comments on commit 0876aac

Please sign in to comment.