一、背景:Pump.fun 为什么能成功?
传统 Memecoin 发射的问题很简单:项目方预留大量代币,上线即抛售。社区参与的是一场信息不对称的博弈。
Pump.fun(运行在 Solana 上)的创新在于用机制设计代替了"相信项目方":
- 无预售:没有团队预留份额,所有人从同一起点买入
- Bonding Curve 定价:价格由链上公式决定,不可操纵
- 自动毕业:达到市值阈值后,流动性自动迁移到公开 DEX,LP token 永久销毁,任何人无法撤走流动性
本文用 Solidity + Uniswap V2 复刻这套机制。先看全貌,再逐层拆解。
二、整体架构:一条 Token 的生命线
一个 Memecoin 从诞生到"毕业",经过三个阶段:
创建 → 在 Bonding Curve 上交易 → 积累 24 ETH 后自动毕业到 Uniswap V2
系统由两个合约协作完成:
| 合约 | 职责 |
|---|---|
TokenFactory | 创建 ERC-20 token,铸造总量 10 亿枚,将 80%(8 亿枚)注入交易池 |
BondingCurve | 管理交易池:定价、买卖、收手续费、达到阈值后自动毕业 |
资金流向:
用户调用 Factory.createToken()
→ 部署新 ERC-20,80% token 转入 BondingCurve 池子
→ 用户用 ETH 在池子里买卖 token,价格随买入量上涨
→ 池子积累 ≥ 24 ETH
→ 自动调用 Uniswap Router,注入流动性,销毁 LP token
→ Token 正式在 Uniswap 上自由交易
接下来的问题是:池子里没有对手方提供流动性,价格怎么定?
三、交易机制:Bonding Curve 如何定价
合约就是做市商
传统 DEX(如 Uniswap)需要双边流动性——有人提供 ETH,有人提供 Token,用恒积公式 x * y = k 定价。但在 Memecoin 冷启动场景,Token 刚创建时没有人愿意垫 ETH 流动性——项目方垫付又回到了预售问题。
这套系统选择了不同的路径:合约本身充当做市商,用一条线性公式单边定价,不需要任何外部流动性提供者。
价格公式:
price(n) = INITIAL_PRICE + SLOPE × tokensSold
对应合约常量:
uint256 public constant INITIAL_PRICE = 1e10; // 初始价格 0.00000001 ETH
uint256 public constant SLOPE = 1e4; // 每卖出 1 token,价格上升的幅度
直觉理解:一条从左下到右上的直线。每买一批 token,当前位置往右推,下一个买家面对的价格更高。卖出则反向——价格回落。
这比恒积曲线更可预测:你可以直接计算"再买 X ETH 能得到多少 token,价格会涨到多少",而不需要模拟 AMM 的非线性变化。
买入:花 X ETH 能买多少 token?
不能直接用瞬时价格乘以数量——买入过程中价格在持续上涨。正确做法是对价格曲线求积分(计算曲线下的面积)。
从已卖出 a 个 token 开始,再买 n 个 token 的总花费:
cost = ∫[a to a+n] (INITIAL_PRICE + SLOPE × x) dx
= INITIAL_PRICE × n + SLOPE × (2a×n + n²) / 2
推导:
F(x) = INITIAL_PRICE·x + SLOPE·x²/2
F(a+n) - F(a)
= INITIAL_PRICE·n + SLOPE·((a+n)² - a²)/2
= INITIAL_PRICE·n + SLOPE·(2a·n + n²)/2
对应合约实现:
function _calculateCost(
uint256 currentSold, // 当前已卖出量 a
uint256 tokenAmount // 想买的数量 n
) public pure returns (uint256) {
uint256 linearCost = (INITIAL_PRICE * tokenAmount) / PRECISION;
uint256 quadraticCost = (SLOPE *
(2 * currentSold * tokenAmount + tokenAmount * tokenAmount)) /
(2 * PRECISION * PRECISION);
return linearCost + quadraticCost;
}
关于精度处理: PRECISION = 1e18,是 Solidity 用整数模拟小数的标准做法。tokenAmount 以最小单位传入(实际代币数 × 1e18),因此线性项除以一个 PRECISION,二次项(两个 tokenAmount 相乘)除以两个 PRECISION。
为什么买入要用二分查找?
用户给出的是 ETH 数量,需要反解出能买多少 token。但二次方程在整数域没有精确解——Solidity 没有浮点数,直接解方程会有截断误差。
解决方案:在 [0, 十亿 × PRECISION] 范围内二分查找"花费不超过传入 ETH 的最大 token 数量":
function _calculateBuyTokens(
uint256 currentSold,
uint256 ethAmount
) internal pure returns (uint256) {
uint256 low = 0;
uint256 high = 1_000_000_000 * PRECISION;
uint256 mid;
while (low < high) {
mid = (low + high + 1) / 2;
uint256 cost = _calculateCost(currentSold, mid);
if (cost <= ethAmount) {
low = mid;
} else {
high = mid - 1;
}
}
return low;
}
卖出:直接计算退款
卖出不需要二分查找——数量已知,直接用"卖出前的累计花费 - 卖出后的累计花费"得到退款:
function _calculateSellReturn(
uint256 currentSold,
uint256 tokenAmount
) public pure returns (uint256) {
require(tokenAmount <= currentSold, "Cannot sell more than sold");
uint256 newSold = currentSold - tokenAmount;
uint256 costAtCurrent = _calculateCost(0, currentSold);
uint256 costAtNew = _calculateCost(0, newSold);
return costAtCurrent - costAtNew;
}
买卖的数学对称性成立:买入价格曲线的面积等于卖出返还的面积。由于整数截断,实际存在极微小的舍入误差(dust),但不影响机制的公平性。
四、毕业机制:24 ETH 之后发生了什么
Bonding Curve 是一个"孵化池",不是终点。当池子积累足够 ETH,Token 自动"毕业"——流动性迁移到 Uniswap V2,成为正式的链上交易对。
为什么是 24 ETH?
这是一个工程参数,权衡两件事:太低则流动性不够深、毕业后容易被大单冲击;太高则大多数 Memecoin 永远毕业不了。24 ETH 参考了 Pump.fun 在 Solana 上验证过的市值锚定思路。
触发条件
买入时检查:
if (pool.ethBalance >= GRADUATION_THRESHOLD) {
_graduate(token);
}
毕业流程
_graduate 做了三件事——标记状态、清空池子、注入 Uniswap:
function _graduate(address token) internal {
Pool storage pool = pools[token];
pool.graduated = true; // 1. 标记已毕业,阻止后续交易
uint256 ethForLiquidity = pool.ethBalance;
uint256 tokensForLiquidity = pool.tokenBalance;
pool.ethBalance = 0; // 2. 清空状态(先写状态,防重入)
pool.tokenBalance = 0;
IERC20(token).approve(address(uniswapRouter), tokensForLiquidity);
uniswapRouter.addLiquidityETH{value: ethForLiquidity}( // 3. 注入 Uniswap
token,
tokensForLiquidity,
0, // 接受任意数量的 token
0, // 接受任意数量的 ETH
address(0), // LP token 发给零地址 = 销毁
block.timestamp + 300
);
}
LP 发给 address(0) 是什么意思?
LP token 是注入流动性后拿到的凭证——凭它可以随时按比例赎回池中的 ETH 和 Token。发给零地址意味着没有任何人能取走这些 LP token,等于销毁了赎回流动性的唯一钥匙。
这是机制设计的核心承诺:流动性被永久锁死,没有人能撤走,包括合约开发者。Rug pull 在机制层面被消除,而不是依赖项目方的道德自律。
五、架构设计:为什么拆成两个合约
最直接的实现是把所有逻辑塞进一个合约。但这个项目把"创建"和"交易"分离到了 TokenFactory 和 BondingCurve 两个合约。
拆分的好处:
BondingCurve可以独立迭代(比如换一种曲线),不影响 TokenFactory 的部署- 每个合约更小,更容易审计
- 权限边界清晰:只有 Factory 能创建池子
权限守卫
onlyFactory modifier 确保只有 Factory 能在 BondingCurve 上注册新池子:
modifier onlyFactory() {
require(msg.sender == factory, "Only factory");
_;
}
function createPool(
address token,
address creator,
uint256 tokenAmount
) external onlyFactory { ... }
没有这个限制,任意外部账户都可以调用 createPool,注入伪造的池子——比如伪装成已有 token 的池子覆盖状态,或注册恶意 token 合约。
一次性绑定
function setFactory(address _factory) external onlyOwner {
require(factory == address(0), "Factory already set");
factory = _factory;
}
factory 只能写入一次。部署顺序是:先部署 BondingCurve,再部署 TokenFactory,最后调用 setFactory 绑定。之后无法更改。
这比构造函数传参更灵活(两个合约互相依赖,构造函数无法同时传入对方地址),同时比可随时修改的 setter 更安全。
六、安全考量
重入攻击防护
什么是重入? 合约向外部地址转账时,控制权暂时交给接收方。如果接收方是恶意合约,它可以在状态更新前"回调"原合约,反复提款直到资金耗尽。
本合约用了两层防护:
第一层:nonReentrant 锁(OpenZeppelin ReentrancyGuard),通过状态锁阻止嵌套调用:
function sell(address token, uint256 tokenAmount)
external
nonReentrant
poolExists(token)
notGraduated(token)
{ ... }
第二层:Checks-Effects-Interactions 模式(CEI)。 先更新所有状态变量,再和外部合约交互。以 sell 为例:
// Effects:先更新状态
pool.tokenBalance += tokenAmount;
pool.ethBalance -= ethToReturn;
pool.tokensSold -= tokenAmount;
// Interactions:再向外部地址转账
(bool success, ) = payable(msg.sender).call{value: ethAfterFee}("");
如果顺序反过来,攻击者的恶意合约在收到 ETH 时回调 sell,此时余额还没扣减,可以反复提款。
余额校验兜底
require(ethAfterFee <= pool.ethBalance, "Not enough ETH in pool");
理论上 Bonding Curve 的数学保证了退款不会超过池内 ETH。但整数截断、边界情况、或未来代码修改都可能打破这个不变量。显式校验让合约在最坏情况下安全失败。
approve 顺序
_graduate 里必须先 approve 再调用 addLiquidityETH——后者内部会调用 transferFrom 从合约拉取 token,如果 approve 没有提前执行,整个毕业交易会 revert。
七、总结:三个相互支撑的设计决策
1. 线性 Bonding Curve 而非 AMM
解决了冷启动问题。合约本身作为做市商,无需对手方流动性即可买卖。价格走势透明可预测,数学上可验证。
2. TokenFactory 职责分离
TokenFactory 只负责创建,BondingCurve 只负责交易。onlyFactory 守卫边界,setFactory 一次性绑定防止后期修改。小合约更容易审计,也更容易在不影响存量 token 的情况下迭代。
3. 毕业时销毁 LP
LP token 发给零地址,流动性永久锁死。这把"不 rug pull"从一个承诺变成了一个链上事实。机制信任取代人工信任,这是 DeFi 设计哲学的精髓。
可以继续探索的方向:
- 二次曲线:价格随买入加速上涨,早期参与者获得更高回报,但价格波动更剧烈
- 动态手续费:根据市场活跃度调整手续费,平衡协议收入和用户体验
- 链上元数据:将 token 名称/描述/图片存储到 IPFS,减少对中心化存储的依赖
- 多链部署:相同合约逻辑部署到 Base 等低手续费链
完整代码见 yolo-pump 项目