Skip to content

Latest commit

 

History

History
265 lines (211 loc) · 9.36 KB

readme.md

File metadata and controls

265 lines (211 loc) · 9.36 KB

本节作者:@愚指导

这一讲我们将在本地开发环境中初始化合约,正式启动开发。


初始化合约

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.

这是因为合约 SwapRouterPositionManager 的构造函数需要以 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 合约作为参数来部署 SwapRouterPositionManager 合约,具体可以参考 Hardhat 官方文档

然后重新执行上面的部署命令,如果顺利你可以看到如下结果:

deploy

合约调试

在开发中,我们需要测试合约的逻辑。

我们可以通过编写单元测试来测试合约,也可以通过运行上面的部署脚本将合约部署到 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>
  );
}

上面的代码中我们调用了 SwapRouterquoteExactInput 方法,你可以在开发过程中按照具体需求修改上述代码进行调试。

接下来,从下一讲开始,我们就可以愉快的进行开发了。🎉