Solidity实战:手搓一个Meme币发射平台需要几步?

  • 木西
  • 发布于 7小时前
  • 阅读 35

前言Meme币的爆发式流行催生了新一代去中心化发行平台的需求。本文将深入剖析一个完整的MemePump工厂合约实现,涵盖ERC-20代币发行、联合曲线定价、自动化毕业机制等核心模块,并提供完整的Hardhat测试与部署方案。免责声明:纯技术分享,不构成投资建议。智能合约有风险,上主网前务必审计。

前言

Meme币的爆发式流行催生了新一代去中心化发行平台的需求。本文将深入剖析一个完整的MemePump工厂合约实现,涵盖ERC-20代币发行、联合曲线定价、自动化毕业机制等核心模块,并提供完整的Hardhat测试与部署方案。免责声明:纯技术分享,不构成投资建议。智能合约有风险,上主网前务必审计。操作后果自负。

一、架构概览

本项目采用工厂模式 + 联合曲线的双合约架构:

┌─────────────────┐      铸造全部代币      ┌─────────────────┐
│  MemeToken      │ ◄──────────────────── │ MemePumpFactory │
│  (ERC-20)       │                       │  (工厂/市场)    │
└─────────────────┘      控制流通/定价      └─────────────────┘
                                │
                                ▼
                        达到20ETH募资目标
                                │
                                ▼
                           触发毕业
                      (迁移至Uniswap等DEX)

核心设计哲学:

  • 公平发射:所有代币初始由工厂持有,无预挖、无团队预留
  • 价格发现:线性联合曲线确保早期买家享有价格优势
  • 自动毕业:达到募资目标后自动进入下一阶段

    二、智能合约开发、测试、部署

    2.1 智能合约

    2.1.1 meme币

    
    // SPDX-License-Identifier: MIT
    // Compatible with OpenZeppelin Contracts ^5.5.0
    pragma solidity ^0.8.24;
    import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MemeToken is ERC20 { address public factory;

constructor(
    string memory name, 
    string memory symbol, 
    uint256 totalSupply,
    address _factory
) ERC20(name, symbol) {
    factory = _factory;
    // 初始将所有代币铸造给工厂合约,由工厂控制 Bonding Curve 销售
    _mint(_factory, totalSupply);
}

}

### 2.1.2 平台工厂与联合曲线合约
#### 关键决策:

-   **10亿总量**:Meme币标准配置,便于心理定价(如$0.0001/枚)
-   **20ETH毕业线**:平衡早期流动性需求与DEX上线门槛
-   **1%手续费**:维持平台运营的可持续收入模型
#### 数学模型解析:这是一个线性递增价格曲线: 
$$P(s) = 0.0001 + \frac{s \times 10^{-13}}{10^{18}} = 10^{-4} + 10^{-22}s \text{ (ETH)}$$
#### 曲线特性:

-   **初始低价**:0.0001 ETH(约$0.25)降低参与门槛
-   **温和上涨**:每售出100万枚仅涨价0.01%,抑制早期巨鲸
-   **线性可预测**:买家可精确计算滑点,无突发价格跳跃
#### 安全设计:

-   **重入防护**:先更新状态后转账,符合Checks-Effects-Interactions模式
-   **精确计算**:使用整数运算避免浮点误差,`(amount / 1e18)`确保单位统一
-   **自动触发**:无需手动干预,达到阈值立即执行毕业

// SPDX-License-Identifier: MIT pragma solidity ^0.8.24;

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

contract MemePumpFactory is Ownable { uint256 public constant TOTAL_SUPPLY = 1_000_000_000 * 10**18; // 10亿 uint256 public constant FUNDING_GOAL = 20 ether; // 募资目标 uint256 public constant FEE_PERCENT = 1; // 1% 手续费

struct TokenInfo {
    address tokenAddress;
    uint256 raisedAmount;
    uint256 soldAmount;
    bool isGraduated;
}

// 状态变量
mapping(address => TokenInfo) public tokens;
address[] public allTokens; // 💡 修复点:记录所有创建的代币地址

event TokenCreated(address indexed token, string name, string symbol);
event AssetPurchased(address indexed token, address indexed buyer, uint256 amount, uint256 cost);

constructor() Ownable(msg.sender) {}

// 1. 一键发币
function createMeme(string memory name, string memory symbol) external {
    MemeToken newToken = new MemeToken(name, symbol, TOTAL_SUPPLY, address(this));
    address tokenAddr = address(newToken);

    tokens[tokenAddr] = TokenInfo(tokenAddr, 0, 0, false);
    allTokens.push(tokenAddr); // 💡 修复点:存入数组供查询

    emit TokenCreated(tokenAddr, name, symbol);
}

// 💡 修复点:供测试脚本调用,获取最新创建的代币地址
function getLastCreatedToken() external view returns (address) {
    require(allTokens.length > 0, "No tokens created");
    return allTokens[allTokens.length - 1];
}

// 💡 修复点:供前端/脚本获取代币总数
function getTokenCount() external view returns (uint256) {
    return allTokens.length;
}

// 2. 联合曲线定价逻辑
function getPrice(uint256 currentSupply) public pure returns (uint256) {
    return 0.0001 ether + (currentSupply * 0.0000001 ether / 1e18);
}

// 3. 买入代币
function buy(address tokenAddr, uint256 amount) external payable {
    TokenInfo storage info = tokens[tokenAddr];
    require(!info.isGraduated, "Token already graduated");

    uint256 cost = getPrice(info.soldAmount) * (amount / 1e18);
    uint256 fee = (cost * FEE_PERCENT) / 100;

    require(msg.value >= cost + fee, "Insufficient ETH");

    info.raisedAmount += cost;
    info.soldAmount += amount;

    IERC20(tokenAddr).transfer(msg.sender, amount);

    emit AssetPurchased(tokenAddr, msg.sender, amount, cost);

    if (info.raisedAmount >= FUNDING_GOAL) {
        _graduate(tokenAddr);
    }
}

// 4. 毕业逻辑
function _graduate(address tokenAddr) internal {
    TokenInfo storage info = tokens[tokenAddr];
    info.isGraduated = true;
    // 实际逻辑:迁移至 Uniswap...
}

// 提取平台手续费
function withdrawFees() external onlyOwner {
    uint256 balance = address(this).balance;
    require(balance > 0, "No fees to withdraw");
    payable(owner()).transfer(balance);
}

}

## 2.2 测试脚本
**测试用例**:
* **创建代币 -> 买入 -> 验证状态**
* **达到募资目标并触发毕业逻辑**
* **管理员提取手续费且合约余额清空**

import assert from "node:assert/strict"; import { describe, it, beforeEach } from "node:test"; import { parseEther, formatEther } from 'viem'; import { network } from "hardhat";

describe("MemePump 平台核心逻辑测试", function () { let publicClient: any; let factoryContract: any; let owner: any, user1: any;

beforeEach(async function () { const { viem } = await network.connect(); publicClient = await viem.getPublicClient(); [owner, user1] = await viem.getWalletClients(); factoryContract = await viem.deployContract("MemePumpFactory", []); });

it("应该成功:创建代币 -> 买入 -> 验证状态", async function () { await factoryContract.write.createMeme(["Dogecoin 3.0", "DOGE3"], { account: user1.account }); const tokenAddress = await factoryContract.read.getLastCreatedToken();

// --- 修复点:直接调用合约的 getPrice 获取单价 ---
const buyAmount = parseEther("1000"); 
const unitPrice = await factoryContract.read.getPrice([0n]); // 获取初始价格

// 合约逻辑: cost = price * (amount / 1e18)
const cost = (unitPrice * buyAmount) / parseEther("1");
const fee = (cost * 1n) / 100n; // 1%
const totalRequired = cost + fee + 100n; // 多给 100 wei 容错

const hash = await factoryContract.write.buy([tokenAddress, buyAmount], {
  account: user1.account,
  value: totalRequired
});
await publicClient.waitForTransactionReceipt({ hash });

const tokenInfo = await factoryContract.read.tokens([tokenAddress]);
// index 1 是 raisedAmount, 2 是 soldAmount
assert.equal(tokenInfo[2], buyAmount, "已售数量不匹配");
assert.equal(tokenInfo[1], cost, "筹集金额不匹配");

});

it("应该成功:达到募资目标并触发毕业逻辑", async function () { await factoryContract.write.createMeme(["MoonToken", "MOON"], { account: owner.account }); const tokenAddress = await factoryContract.read.getLastCreatedToken();

// --- 修复点:确保发送的 ETH 确实能覆盖 20 ETH 的目标 ---
// 假设我们直接买入足够大的量来触发 20 ETH
// 由于价格随供应增加,发送 21 ETH 配合一个足够大的 amount 肯定能触发
const buyAmount = parseEther("200000"); // 20万个
const fundingGoalPlusFee = parseEther("21"); 

await factoryContract.write.buy([tokenAddress, buyAmount], {
  account: owner.account,
  value: fundingGoalPlusFee
});

const tokenInfo = await factoryContract.read.tokens([tokenAddress]);
// index 3 是 isGraduated
assert.strictEqual(tokenInfo[3], true, "募集达标后 isGraduated 应为 true");

});

it("应该成功:管理员提取手续费且合约余额清空", async function () { await factoryContract.write.createMeme(["FeeToken", "FEE"], { account: user1.account }); const tokenAddress = await factoryContract.read.getLastCreatedToken();

// 产生手续费
await factoryContract.write.buy([tokenAddress, parseEther("100")], {
  account: user1.account,
  value: parseEther("1") 
});

const contractBalanceBefore = await publicClient.getBalance({ address: factoryContract.address });
assert.ok(contractBalanceBefore > 0n, "合约应当持有手续费");

await factoryContract.write.withdrawFees({ account: owner.account });

const contractBalanceAfter = await publicClient.getBalance({ address: factoryContract.address });
assert.equal(contractBalanceAfter, 0n, "提取后合约余额应归零");

}); });

## 2.3 部署脚本

// scripts/deploy.js import { network, artifacts } from "hardhat"; async function main() { // 连接网络 const { viem } = await network.connect({ network: network.name });//指定网络进行链接

// 获取客户端 const [deployer] = await viem.getWalletClients(); const publicClient = await viem.getPublicClient();

const deployerAddress = deployer.account.address; console.log("部署者的地址:", deployerAddress); // 加载合约 const MemePumpFactoryArtifact = await artifacts.readArtifact("MemePumpFactory");

// 部署(构造函数参数:recipient, initialOwner) const hash = await deployer.deployContract({ abi: MemePumpFactoryArtifact.abi,//获取abi bytecode: MemePumpFactoryArtifact.bytecode,//硬编码 args: [],//process.env.RECIPIENT, process.env.OWNER });

// 等待确认并打印地址 const MemePumpFactoryReceipt = await publicClient.waitForTransactionReceipt({ hash }); console.log("平台工厂与联合曲线合约地址:", MemePumpFactoryReceipt.contractAddress);

} main().catch(console.error);


# 结语
本文展示的MemePump架构提供了一个**最小可行但功能完整**的Meme币发射平台实现。联合曲线机制确保了公平的价格发现,工厂模式实现了代币生命周期的标准化管理,而自动毕业机制则为项目提供了清晰的进阶路径。
点赞 0
收藏 1
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
木西
木西
0x5D5C...2dD7
江湖只有他的大名,没有他的介绍。