本节作者:@愚指导
这一讲我们将在本地开发环境中初始化合约,正式启动开发。
Wtfswap 的合约开发我们继续基于之前在《合约本地开发和测试环境》 和《使用 Wagmi CLI 调试本地合约》中搭建的本地开发环境开发,如果你还没有搭建过,请基于那一讲课程搭建。
我们结合在上一讲中接口的设计,我们新增一个 contract/wtfswap
的目录按照如下结构初始化合约:
- contracts
- wtfswap
- interfaces
- IFactory.sol
- IPool.sol
- IPoolManager.sol
- IPositionManager.sol
- ISwapRouter.sol
- Factory.sol
- Pool.sol
- PoolManager.sol
- PositionManager.sol
- SwapRouter.sol
每一个合约文件我们都对应初始化好一个基础的架子,以 Pool.sol
为例:
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.24;
import "./interfaces/IPool.sol";
import "./interfaces/IFactory.sol";
contract Pool is IPool {
/// @inheritdoc IPool
address public immutable override factory;
/// @inheritdoc IPool
address public immutable override token0;
/// @inheritdoc IPool
address public immutable override token1;
/// @inheritdoc IPool
uint24 public immutable override fee;
/// @inheritdoc IPool
int24 public immutable override tickLower;
/// @inheritdoc IPool
int24 public immutable override tickUpper;
/// @inheritdoc IPool
uint160 public override sqrtPriceX96;
/// @inheritdoc IPool
int24 public override tick;
/// @inheritdoc IPool
uint128 public override liquidity;
// 用一个 mapping 来存放所有 Position 的信息
mapping(address => Position) public positions;
constructor() {
// constructor 中初始化 immutable 的常量
// Factory 创建 Pool 时会通 new Pool{salt: salt}() 的方式创建 Pool 合约,通过 salt 指定 Pool 的地址,这样其他地方也可以推算出 Pool 的地址
// 参数通过读取 Factory 合约的 parameters 获取
// 不通过构造函数传入,因为 CREATE2 会根据 initcode 计算出新地址(new_address = hash(0xFF, sender, salt, bytecode)),带上参数就不能计算出稳定的地址了
(factory, token0, token1, tickLower, tickUpper, fee) = IFactory(
msg.sender
).parameters();
}
function initialize(uint160 sqrtPriceX96_) external override {
// 初始化 Pool 的 sqrtPriceX96
sqrtPriceX96 = sqrtPriceX96_;
}
function mint(
address recipient,
uint128 amount,
bytes calldata data
) external override returns (uint256 amount0, uint256 amount1) {
// 基于 amount 计算出当前需要多少 amount0 和 amount1
// TODO 当前先写个假的
(amount0, amount1) = (amount / 2, amount / 2);
// 把流动性记录到对应的 position 中
positions[recipient].liquidity += amount;
// 回调 mintCallback
IMintCallback(recipient).mintCallback(amount0, amount1, data);
// TODO 检查钱到位了没有,如果到位了对应修改相关信息
}
function collect(
address recipient
) external override returns (uint128 amount0, uint128 amount1) {
// 获取当前用户的 position,TODO recipient 应该改为 msg.sender
Position storage position = positions[recipient];
// TODO 把钱退给用户 recipient
// 修改 position 中的信息
position.tokensOwed0 -= amount0;
position.tokensOwed1 -= amount1;
}
function burn(
uint128 amount
) external override returns (uint256 amount0, uint256 amount1) {
// 修改 positions 中的信息
positions[msg.sender].liquidity -= amount;
// 获取燃烧后的 amount0 和 amount1
// TODO 当前先写个假的
(amount0, amount1) = (amount / 2, amount / 2);
positions[msg.sender].tokensOwed0 += amount0;
positions[msg.sender].tokensOwed1 += amount1;
}
function swap(
address recipient,
bool zeroForOne,
int256 amountSpecified,
uint160 sqrtPriceLimitX96,
bytes calldata data
) external override returns (int256 amount0, int256 amount1) {}
}
其它合约对应的代码可以参考 code 查看。
初始化完成后执行 npx hardhat compile
编译合约,合约编译完成后你可以在 demo-contract/artifacts
目录下看到编译后的产物,里面包含了合约的 ABI 等信息。
然后进入到前端项目 demo
目录,执行 npx wagmi generate
生成合约的 React Hooks(具体可以参考《使用 Wagmi CLI 调试本地合约》),这样我们就可以在前端代码中方便的调用合约了。
结合之前《合约本地开发和测试环境》教程的内容,我们新建 ignition/modules/Wtfswap.ts
文件,编写部署脚本:
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
const WtfswapModule = buildModule("Wtfswap", (m) => {
const poolManager = m.contract("PoolManager");
const swapRouter = m.contract("SwapRouter");
const positionManager = m.contract("PositionManager");
return { pool, factory, poolManager, swapRouter, positionManager };
});
export default WtfswapModule;
需要注意的是,Factory
合约和 Pool
合约不需要单独部署,Factory
是由 PoolManager
继承,部署 PoolManager
即可,而 Pool
合约则是应该在链上由 PoolManager
部署。
通过 npx hardhat node
启动本地的测试链。
然后执行 npx hardhat ignition deploy ./ignition/modules/Wtfswap.ts --network localhost
来部署合约到本地的测试链,这个时候你会发现报如下的错误:
[ Wtfswap ] validation failed ⛔
The module contains futures that would fail to execute:
Wtfswap#SwapRouter:
- IGN703: The constructor of the contract 'SwapRouter' expects 1 arguments but 0 were given
Wtfswap#PositionManager:
- IGN703: The constructor of the contract 'PositionManager' expects 1 arguments but 0 were given
Update the invalid futures and rerun the deployment.
这是因为合约 SwapRouter
和 PositionManager
的构造函数需要以 PoolManager
合约地址为参数。我们继续修改 ignition/modules/Wtfswap.ts
,补充相关逻辑。
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
const WtfswapModule = buildModule("Wtfswap", (m) => {
const poolManager = m.contract("PoolManager");
- const swapRouter = m.contract("SwapRouter");
- const positionManager = m.contract("PositionManager");
+ const swapRouter = m.contract("SwapRouter", [poolManager]);
+ const positionManager = m.contract("PositionManager", [poolManager]);
return { poolManager, swapRouter, positionManager };
});
export default WtfswapModule;
如上面代码所示,我们将 PoolManager
合约作为参数来部署 SwapRouter
和 PositionManager
合约,具体可以参考 Hardhat 官方文档。
然后重新执行上面的部署命令,如果顺利你可以看到如下结果:
在开发中,我们需要测试合约的逻辑。
我们可以通过编写单元测试来测试合约,也可以通过运行上面的部署脚本将合约部署到 Hardhat 本地网络或者测试网络进行调试。
下面是一段参考代码,你可以把它放到 demo/pages/test.tsx
下,然后访问 http://localhost:3000/test 来连接 Hardhat 本地网络进行调试。
import { useReadSwapRouterQuoteExactInput } from "@/utils/contracts";
import { hardhat } from "wagmi/chains";
import { WagmiWeb3ConfigProvider, Hardhat } from "@ant-design/web3-wagmi";
import { Button } from "antd";
import { createConfig, http } from "wagmi";
import { Connector, ConnectButton } from "@ant-design/web3";
const config = createConfig({
chains: [hardhat],
transports: {
[hardhat.id]: http("http://127.0.0.1:8545/"),
},
});
const CallTest = () => {
const { data, refetch } = useReadSwapRouterQuoteExactInput({
address: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0",
args: [
{
tokenIn: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0",
tokenOut: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0",
indexPath: [],
amountIn: BigInt(123),
sqrtPriceLimitX96: BigInt(123),
},
],
});
console.log("get data", data);
return (
<>
{data?.toString()}
<Button
onClick={() => {
refetch();
}}
>
refetch
</Button>
</>
);
};
export default function Web3() {
return (
<WagmiWeb3ConfigProvider
config={config}
eip6963={{
autoAddInjectedWallets: true,
}}
chains={[Hardhat]}
>
<Connector>
<ConnectButton />
</Connector>
<CallTest />
</WagmiWeb3ConfigProvider>
);
}
上面的代码中我们调用了 SwapRouter
的 quoteExactInput
方法,你可以在开发过程中按照具体需求修改上述代码进行调试。
接下来,从下一讲开始,我们就可以愉快的进行开发了。🎉