c++类的继承学习-去中心化交易所(DEX)的“流动性池初始化与交易指令”设计

基于模板方法模式的去中心化交易所(DEX)协议设计与实现

1. 题目要求

本项目要求开发一个类似 Raydium 的去中心化交易所(DEX)协议。在该协议中,每个流动性池(Pool)的创建和首次交易都必须遵循一套固定流程。该流程不能被子类随意打乱,必须按照预设阶段依次执行。

阶段 方法名 职责
阶段1 pre_initialize() 校验创建者权限、验证代币铸造地址合法性、计算初始价格滑点容忍度
阶段2 initialize() 创建池子的账户空间、初始化代币储备量、设置交易费率
阶段3 initialize2() 启动首次流动性注入(mint LP token)、向交易所注册该池子
阶段4 pre_input() 检查交易是否在允许的时间窗口内、防抢跑(anti-frontrunning)验证
阶段5 process_input() 执行核心交易逻辑(Swap / AddLiquidity / RemoveLiquidity)

从业务角度看,DEX 协议中的池子创建、流动性注入和交易处理都属于高风险操作。如果执行顺序混乱,可能会导致池子未初始化就开始交易、储备量错误、LP Token 铸造异常,甚至出现交易安全问题。因此,本项目选择使用模板方法模式来约束整个执行流程。

2. 设计目标

本项目的核心目标包括以下几个方面:

  1. 固定执行顺序

    将 DEX 指令执行流程固定为五个阶段,保证所有操作都按照统一顺序运行。

  2. 隔离通用流程与具体业务

    抽象父类负责规定流程,具体子类负责实现不同业务逻辑,例如创建池子、兑换交易等。

  3. 提高代码复用性

    通用初始化逻辑和通用交易逻辑可以放在父类中,子类在需要时直接复用。

  4. 提高扩展性

    后续如果需要加入 AddLiquidityInstructionRemoveLiquidityInstruction 等新指令,只需要继承父类并实现对应阶段方法即可。

  5. 模拟链上 DEX 指令执行过程

    通过 Context 模拟区块链上下文,通过不同 Instruction 类模拟链上不同交易指令。

3. 相关知识点

3.1 模板方法模式

模板方法模式是一种行为型设计模式。它的核心思想是:

在父类中定义一个算法的整体骨架,将某些具体步骤延迟到子类中实现。

在本项目中,PoolInstruction::execute() 就是模板方法。它负责规定五个阶段的执行顺序:

text 复制代码
pre_initialize()
        ↓
initialize()
        ↓
initialize2()
        ↓
pre_input()
        ↓
process_input()

具体的创建池子逻辑、兑换逻辑、流动性处理逻辑并不直接写死在 execute() 中,而是由子类分别实现。这样既保证了执行流程统一,又保留了业务扩展能力。

3.2 虚函数

虚函数是 C++ 中实现运行时多态的重要机制。父类中使用 virtual 声明函数后,子类可以通过 override 重写该函数。

在本项目中,pre_initialize()initialize2()pre_input() 等方法都是虚函数。父类只规定这些阶段必须存在,而具体行为由子类决定。

例如:

  • CreatePoolInstruction::pre_initialize() 用于检查管理员权限;
  • SwapInstruction::pre_input() 用于检查储备量和滑点;
  • SwapInstruction::process_input() 用于执行 AMM 兑换。

这体现了"父类定义流程,子类实现细节"的设计思想。

3.3 纯虚函数

纯虚函数使用 = 0 表示,它没有默认实现,子类必须实现该函数,否则子类仍然是抽象类,不能实例化。

在本项目中,部分阶段被设计为纯虚函数,是因为这些阶段与具体业务强相关,父类无法给出统一实现。例如:

cpp 复制代码
virtual bool pre_initialize(Context& ctx) = 0;
virtual bool initialize2(Context& ctx) = 0;
virtual bool pre_input(Context& ctx) = 0;

这样可以强制每个具体指令类对关键阶段作出明确处理。

3.4 final

final 用于限制子类继续重写某个虚函数。

本项目中,execute() 被声明为:

cpp 复制代码
virtual bool execute(Context& ctx) final;

这表示子类不能重写 execute()。这样可以防止子类改变五个阶段的执行顺序,从而保证模板方法模式中的"算法骨架"不会被破坏。

3.5 钩子方法

钩子方法是一种给子类提供额外扩展点的方式。父类提供默认实现,子类可以根据自身需要选择是否重写。

本项目中,should_skip_pre_initialize()should_skip_initialize() 等方法就是钩子方法。它们默认返回 false,表示默认不跳过阶段;具体子类可以重写它们,以表达某些阶段在该指令中不需要执行具体业务。

需要注意的是,钩子方法并没有改变 execute() 中的判断顺序。也就是说,整体流程仍然按照五个阶段依次检查,只是某些具体阶段可以根据子类业务选择跳过实际处理。


4. 系统总体设计

本项目主要包含三个核心类:

类名 类型 作用
PoolInstruction 抽象父类 定义 DEX 指令执行流程,是模板方法模式的核心
CreatePoolInstruction 具体子类 实现创建流动性池时的具体逻辑
SwapInstruction 具体子类 实现代币兑换交易的具体逻辑

此外,项目使用 Context 结构体模拟区块链执行上下文。它保存调用者、公钥信息以及池子中的代币储备量。

4.1 Context 上下文

Context 用于模拟链上执行环境。在真实的 Solana 或 Raydium 协议中,交易执行时会涉及账户、公钥、代币 mint、slot、交易签名、手续费账户、程序派生地址等复杂信息。为了突出设计模式,本项目对这些内容进行了简化。

当前 Context 主要包含:

  • caller:模拟当前交易调用者;
  • reserve_a:代币 A 的储备量;
  • reserve_b:代币 B 的储备量。

这些字段足以用于演示创建池子、初始化储备量和执行 AMM 兑换的基本过程。

4.2 PoolInstruction 抽象父类

PoolInstruction 是整个系统的核心。它承担两个作用:

  1. 定义统一执行入口 execute()
  2. 规定五个阶段方法。

其中,execute() 是模板方法。它按照固定顺序依次调用五个阶段方法,并在每个阶段失败时立即返回 false

这种设计的优点是:

  • 调用方只需要调用 execute()
  • 子类不需要关心整体流程;
  • 父类统一控制执行顺序;
  • 子类只负责各阶段的具体业务。

4.3 CreatePoolInstruction 创建池子指令

CreatePoolInstruction 表示创建流动性池的指令。

它主要负责:

  • 检查调用者是否为管理员;
  • 检查代币信息是否符合要求;
  • 使用父类默认初始化逻辑设置初始储备量;
  • 铸造 LP Token;
  • 将池子注册到交易所;
  • 完成创建池子阶段所需的时间窗口检查。

在当前示例中,initialize()process_input() 使用父类默认实现,说明某些通用逻辑可以被多个指令复用。

4.4 SwapInstruction 兑换指令

SwapInstruction 表示代币兑换交易。

它主要负责:

  • 检查池子储备是否充足;
  • 模拟滑点检查;
  • 执行 AMM 交换;
  • 更新池子中代币 A 和代币 B 的储备量。

在当前实现中,兑换指令通过钩子方法跳过创建池子相关阶段,仅保留交易前检查和交易处理阶段。这体现了钩子方法在模板方法模式中的扩展作用。


5. 执行流程分析

整个指令执行流程由 PoolInstruction::execute() 控制。无论具体指令是创建池子还是执行兑换,外部调用者都通过同一个入口执行:

cpp 复制代码
instruction.execute(ctx);

执行过程如下:

text 复制代码
开始执行指令
    ↓
阶段1:pre_initialize()
    ↓
阶段2:initialize()
    ↓
阶段3:initialize2()
    ↓
阶段4:pre_input()
    ↓
阶段5:process_input()
    ↓
执行成功 / 执行失败

如果任意阶段返回 false,说明该阶段执行失败,整个流程立即终止。这样可以避免在前置条件不满足的情况下继续执行后续操作。

例如,在创建池子时,如果 pre_initialize() 中发现调用者不是管理员,则直接返回 false,不会继续执行初始化储备量、铸造 LP Token 或注册池子等后续操作。

这种设计符合区块链交易执行中的安全思想:

先校验,再执行;前置条件不满足时立即终止。


实现

PoolInstruction

MyComponent.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include <memory>

// ------------------------------------------------------------
// 模拟区块链上下文(简化版,仅包含必要字段)
// ------------------------------------------------------------
struct Context {
    std::string caller;       // 调用者公钥(模拟)
    int64_t reserve_a = 0;    // 池子中代币A的储备量
    int64_t reserve_b = 0;    // 池子中代币B的储备量
    // 实际项目中还会有 slot、compute_unit 等
};

// ------------------------------------------------------------
// 抽象基类:定义模板方法
// ------------------------------------------------------------
class PoolInstruction {
    public:
        // ~PoolInstruction():析构函数,当一个对象被销毁时(例如离开作用域或被 delete),该函数会自动执行,用于清理资源(如释放内存、关闭文件等)
        // 若未显式定义,编译器会自动生成一个默认的析构函数
        // virtual 虚函数:允许子类重写该函数,确保在删除子类对象时能正确调用子类的析构函数,避免资源泄漏
        // = default:指示编译器使用默认的析构行为,无需手动实现
        virtual ~PoolInstruction() = default;

        virtual bool execute(Context& ctx) final; // 模板方法(虚函数 + final,防止子类覆盖)

        // ---- 纯虚函数(必须由子类实现) ----
        virtual bool pre_initialize(Context& ctx) = 0;   // 阶段1
        virtual bool initialize(Context& ctx);       //虚函数,有模板
        virtual bool initialize2(Context& ctx) = 0;      // 阶段3
        virtual bool pre_input(Context& ctx) = 0;        // 阶段4
        virtual bool process_input(Context& ctx);    // 虚函数,有模板

        // 子类可重写以决定是否跳过某个阶段
        // virtual ------ "留给子类去改写"
        // const { return false; } ------ "默认答案是:不跳过"
        virtual bool should_skip_pre_initialize()   const { return false; }
        virtual bool should_skip_initialize()       const { return false; }
        virtual bool should_skip_initialize2()      const { return false; }
        virtual bool should_skip_pre_input()        const { return false; }
        virtual bool should_skip_process_input()    const { return false; }

};

MyComponent.cpp

cpp 复制代码
#include "MyComponent.hpp"

// ★ 模板方法
bool PoolInstruction::execute(Context& ctx)  {
    std::cout << "=== 开始执行指令 ===" << std::endl;

    if (!should_skip_pre_initialize()) {
        std::cout << "[阶段1] pre_initialize()" << std::endl;
        if (!pre_initialize(ctx)) return false;
    } else {
        std::cout << "[阶段1] 跳过" << std::endl;
    }

    if (!should_skip_initialize()) {
        std::cout << "[阶段2] initialize()" << std::endl;
        if (!initialize(ctx)) return false;
    } else {
        std::cout << "[阶段2] 跳过" << std::endl;
    }

    if (!should_skip_initialize2()) {
        std::cout << "[阶段3] initialize2()" << std::endl;
        if (!initialize2(ctx)) return false;
    } else {
        std::cout << "[阶段3] 跳过" << std::endl;
    }
            if (!should_skip_pre_input()) {
        std::cout << "[阶段4] pre_input()" << std::endl;
        if (!pre_input(ctx)) return false;
    } else {
        std::cout << "[阶段4] 跳过" << std::endl;
    }

    if (!should_skip_process_input()) {
        std::cout << "[阶段5] process_input()" << std::endl;
        if (!process_input(ctx)) return false;
    } else {
        std::cout << "[阶段5] 跳过" << std::endl;
    }

    std::cout << "✅ 执行成功" << std::endl;
    return true;
}

// ---- 默认实现(子类可以覆盖) ----
bool PoolInstruction::initialize(Context& ctx) {
    std::cout << "  [默认] 初始化池子账户和储备量" << std::endl;
    // 模拟:设置初始储备
    ctx.reserve_a = 1000;
    ctx.reserve_b = 1000;
    return true;
}

bool PoolInstruction::process_input(Context& ctx) {
    std::cout << "  [默认] 执行通用交易逻辑" << std::endl;
    // 模拟:简单的转移操作
    return true;
}

CreatePoolInstruction

CreatePoolInstruction.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include "MyComponent.hpp"


class CreatePoolInstruction : public PoolInstruction {
public:
    CreatePoolInstruction(const std::string& admin,
                          const std::string& tokenA,
                          const std::string& tokenB,
                          int64_t price)
        : admin_(admin), tokenA_(tokenA), tokenB_(tokenB), price_(price) {}

    bool pre_initialize(Context& ctx) override;

    bool initialize2(Context& ctx) override;

    bool pre_input(Context& ctx) override;
    // initialize() 和 process_input() 方法使用父类的默认实现
private:
    std::string admin_, tokenA_, tokenB_;
    int64_t price_;
};

CreatePoolInstruction.cpp

cpp 复制代码
#include "CreatePoolInstruction.hpp"

bool CreatePoolInstruction::pre_initialize(Context& ctx) {
    std::cout << "  [创建池] 检查管理员权限与代币有效性" << std::endl;
    if (ctx.caller != admin_){
        std::cerr << "  调用者无权创建资金池" << std::endl;
        return false;
    }
    return true;
}

bool CreatePoolInstruction::initialize2(Context& ctx) {
    std::cout << "  [创建池] 铸造 LP Token 并完成注册" << std::endl;
    return true;
}

bool CreatePoolInstruction::pre_input(Context& ctx){
    std::cout << "  [创建池] 检查时间窗口" << std::endl;
    return true;
}

SwapInstruction

SwapInstruction.hpp

cpp 复制代码
#pragma once
#include "MyComponent.hpp"
// 具体子类2:兑换
class SwapInstruction : public PoolInstruction {
public:
    SwapInstruction(int64_t amountIn, int64_t minOut, int64_t slippage)
        : amount_in_(amountIn), min_out_(minOut), slippage_(slippage) {}

    // 必须实现的三个纯虚函数(因相应阶段被跳过,仅提供空实现)
    bool pre_initialize(Context&) override ;

    bool initialize2(Context&) override ;

    bool pre_input(Context& ctx) override ;
    bool process_input(Context& ctx) override ;

    // ---- 钩子方法:跳过不需要的阶段 ----
    bool should_skip_pre_initialize() const override { return true; }
    bool should_skip_initialize()     const override { return true; }
    bool should_skip_initialize2()    const override { return true; }
    // pre_input 和 process_input 不跳过

private:
    int64_t amount_in_, min_out_, slippage_;
};

SwapInstruction.cpp

cpp 复制代码
#include "SwapInstruction.hpp"


bool SwapInstruction::pre_initialize(Context&) {
    // 此阶段被跳过,因此留空
    return true;
}

bool SwapInstruction::initialize2(Context&) {
    return true;  // 此阶段被跳过
}

bool SwapInstruction::pre_input(Context& ctx) {
    std::cout << "  [兑换] 检查储备与滑点" << std::endl;
    if (ctx.reserve_a == 0 || ctx.reserve_b == 0) return false;
    // 模拟滑点检查通过
    return true;
}
bool SwapInstruction::process_input(Context& ctx) {
    std::cout << "  [兑换] 执行AMM交换" << std::endl;
    ctx.reserve_a += amount_in_;
    ctx.reserve_b -= 100;  // 简化
    return true;
}

编译运行

1 命令行

可以在项目目录下使用以下命令进行编译:

bash 复制代码
g++ -std=c++11 main.cpp MyComponent.cpp CreatePoolInstruction.cpp SwapInstruction.cpp -o program
./program

2 配置编译器 JSON

如果使用 VS Code,可以通过 tasks.json 配置自动编译任务,并在 launch.json 中指定调试程序路径。

基本思路如下:

  1. tasks.json 中加入所有需要编译的 .cpp 文件;
  2. 输出文件命名为 program
  3. launch.json 中将 program 设置为 ${workspaceFolder}/program
  4. 使用 preLaunchTask 在调试前自动执行编译任务。

tasks.json 修改为:

bash 复制代码
   "args": [
            "-fdiagnostics-color=always",
            "-g",
            "main.cpp",
            "MyComponent.cpp",
            "CreatePoolInstruction.cpp",
            "SwapInstruction.cpp",
            "-o",
            "${workspaceFolder}/program"
        ],

launch.json 修改为:

bash 复制代码
 "configurations": [
        {
            "name": "(gdb) 启动",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/program",    // 固定指向项目根目录下的 program
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "为 gdb 启用整齐打印",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                },
                {
                    "description": "将反汇编风格设置为 Intel",
                    "text": "-gdb-set disassembly-flavor intel",
                    "ignoreFailures": true
                }
            ],
            "preLaunchTask": "build-all"   // 👈 调试前自动运行 tasks.json 中的编译任务
        }
    ]

3 使用 CMake

如果使用 CMake,可以创建 CMakeLists.txt,将所有 .cpp 文件加入 add_executable() 中。CMake 方式更适合后续项目扩展,因为当源文件数量增加时,项目结构会更加清晰。

::创建 CMakeLists.txt 文件::

bash 复制代码
cmake_minimum_required(VERSION 3.10)
# 项目名称(可自定义))
project(DexProject)
# 指定使用 C++11 标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON))
# 【关键】生成调试信息(等价于 -g)并关闭优化(便于调试))
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0")
# 设置 CMake 构建类型为 Debug(使 -g 调试标志生效))
set(CMAKE_BUILD_TYPE Debug)

# 指定所有源文件(只需列出 .cpp 文件,.hpp 会自动找)
add_executable(program 
    main.cpp
    MyComponent.cpp
    CreatePoolInstruction.cpp
    SwapInstruction.cpp
)

修改 task.json 文件::

bash 复制代码
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "cmake-build",
            "type": "shell",
            "command": "bash",
            "args": [
                "-c",
                "mkdir -p build && cd build && cmake .. && make -j4"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "problemMatcher": ["$gcc"],            "detail": "使用 CMake 配置并编译整个项目。""
        }
    ]
}

修改 launch.json 文件::

bash 复制代码
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(gdb) 启动",
            "type": "cppdbg",
            "request": "launch",            "program": "${workspaceFolder}/build/program",   // 指向 build 文件夹件夹
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "为 gdb 启用整齐打印",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                },
                {
                    "description": "将反汇编风格设置为 Intel",
                    "text": "-gdb-set disassembly-flavor intel",
                    "ignoreFailures": true
                }
            ]            "preLaunchTask": "cmake-build"   // 调试前自动执行 CMake 编译 编译
        }
    ]
}
相关推荐
远离UE41 小时前
UE5 各类型灯光学习
学习·ue5
New农民工1 小时前
射频芯片学习-dBm概念
学习·射频学习
unicrom_深圳市由你创科技5 小时前
数据库用SQLite还是SQL Server?工业数据存储选哪个?
c++
郝学胜_神的一滴5 小时前
CMake 037:宏传递流转机制与C++编译特性跨平台适配指南
c++·cmake
十月的皮皮5 小时前
C语言学习笔记20260703-牛牛与后缀表达式(逆波兰表达式)
c语言·笔记·学习
apocelipes2 天前
常用编程语言和库的正则表达式性能对比
c语言·c++·python·性能优化·golang·开发工具和环境
郝学胜_神的一滴3 天前
CMake 034:生成器表达式:解耦构建时序、精简分支逻辑的终极利器
c++·cmake
见过夏天4 天前
C++ 基础入门完全指南
c++
用户805533698035 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt