审计 Pump.fun 克隆的经验教训

  • mahitman1
  • 发布于 2025-02-04 16:13
  • 阅读 61

本文作者分享了对一个 pump.fun 风格的代码库及其后端的审计经验。重点关注了后端代码的三个主要功能:从 bonding curve 中提取代币、创建 Raydium 池并迁移流动性、以及销毁剩余代币和灰尘代币。文中详细描述了在代币 decimal 处理、后端宕机处理和 JITO 捆绑交易处理中发现的几个关键问题,强调了对后端系统进行安全审计的重要性。

最近我有机会审计了一个 pump.fun 风格的代码库及其后端。对于那些不知道 pump.fun 是什么的人,这里有一个复习。Pump.fun 是 Solana 上的一个启动平台,允许任何人在 Solana 区块链上启动一个代币。它是 Solana 上 meme 币背后的动力。pump.fun 允许公平启动,没有预售,也没有团队分配。它在某种程度上有效地防止了我们过去见过的 rugpull。

pump.fun 如何运作?

部署者只需选择一个名称、代码和 JPG 图像,即可立即开始在 bonding curve 上交易。用户选择一个代币,在 bonding curve 上购买,并在他们喜欢的任何时候出售。

bonding curve 是一条数学曲线,它根据代币的供应量决定代币的价格。通常,购买的代币越多,价格越高。

当足够多的用户购买了代币,并且其市值达到 69,000 美元时,将有 12,000 美元的流动性存入 Solana 去中心化交易所 Raydium 并被销毁。

该项目是为 Solana 开发的,并用 Anchor Framework 编写。我们在 Solana 程序中发现了 1 个严重、1 个高危和 3 个中危漏洞,但在这篇文章中,我将重点关注代码库的后端部分,因为我们通常很少有机会审计链下组件。

后端执行三个主要功能。

  1. 从 bonding curve 中提取代币
  2. 创建 Raydium 池并迁移流动性
  3. 销毁剩余数量和小额代币

现在,所有这些功能都很重要,我们为我们的审计提出了以下非详尽的威胁建模问题列表。

威胁建模问题:

  1. 代币提取机制
  • 未经授权的个人可以触发提取吗?
  • 从池中提取的总金额是否正确?
  • 后端是否保留池中的任何数量的代币?
  • 攻击者可以操纵提款条件或时间吗?
  1. Raydium 流动性池创建
  • 存在哪些验证以确保迁移的流动性金额正确?
  • Raydium 池参数是否配置正确?
  • JITO Bundler 是否正确实施?
  • 当池未创建时,后端如何处理?
  1. 代币销毁过程
  • 有什么机制可以防止不完整或失败的代币销毁?
  • 攻击者可以操纵销毁数量或过程吗?

考虑到上述威胁,我们开始寻找潜在的问题。在审计之前列出威胁对我们非常有帮助,使我们能够超越代码的预期功能进行思考。通过制定项目的高级概述并全面列出可能的威胁,我们为我们的调查创建了一个战略框架。

重要的是要注意,并非所有已识别的威胁最终都适用于特定的代码库。然而,其价值在于全面的初始方法 —— 列出潜在的漏洞提供了一种系统的方法,可以在我们深入审计时有条不紊地评估和消除风险。

现在是有趣的部分,BUG 和 BUG。我将在这里讨论一些。

问题:配置错误的十进制处理导致财务和交易风险

在创建流动性池时,当前实现存在与代币十进制处理相关的重大漏洞。此问题可能导致不正确的代币数量被存入池中,从而可能导致财务差异和交易风险。

根本原因

核心问题在于 createPool 函数,它使用硬编码的 TOKEN_DECIMALS 常量。这种方法未能考虑到不同代币可能具有的不同的小数位数。

createPool = async (connection, payer, baseMint, baseTokenAmount, solAmount)

示例场景

代币 A:6 位小数场景

基础代币规范(Meme 代币)

  • 总供应量:1,000,000,000,000,000 (1 Billion)
  • 十进制格式:6
  • TOKEN_DECIMALS: 6

计算细分

  1. 池分配计算:
  • 池金额:200,000,000 * 10⁶ = 200,000,000,000,000
  • 剩余供应量:1,000,000,000,000,000–200,000,000,000,000
  • 百分比分配:
    • 池分配:总供应量的 20%
    • 剩余待销毁:总供应量的 80%

结果

  • 该计算适用于具有 6 位小数的代币
  • 池收到预期的 20% 的代币供应量

代币 B:7 位小数场景

代币规范

  • 总供应量:10,000,000,000,000,000 (1 Billion)
  • 十进制格式:7
  • TOKEN_DECIMALS: 6 (不匹配导致问题)

计算细分

  1. 池分配计算:
  • 池金额:200,000,000 * 10⁶ = 200,000,000,000,000
  • 剩余供应量:10,000,000,000,000,000 — 200,000,000,000,000
  • 百分比分配:
    • 池分配:总供应量的 2%
    • 剩余待销毁:总供应量的 98%

硬编码的小数处理导致了巨大的分配差异。只有 2% 的代币供应量进入池中,而不是预期的 20%。这表明了使用固定小数常数的关键风险

代码片段

问题:未处理的后端停机威胁事件处理和流动性操作

现在,这个问题更多的是一个架构问题,我们观察到系统假设它将永远运行,并且不考虑任何停机情况。

鉴于链上代码将持续运行,因此链下处理必须强大且能够抵御不可预见的威胁。如果后端基础设施由于任何原因遇到停机,则没有机制可以跟踪已处理/错过的事件。这将导致一个严重的问题,因为停机期间要迁移的池将被错过并且不会被处理。

我们建议后端应使用数据库来存储与 bonding curve 过程相关的事件。该数据库将跟踪每个事件的状态和相应的插槽,直到后端成功处理它们。如果后端停机,系统可以恢复并在恢复在线后处理任何错过的事件,确保不会丢失任何事件。

为了更好的管理,我们建议实施 pagerduty 或 telegram,以便在发生停机时进行通知。

问题:缺少对 JITO 作为区块提议者的检查

我们发现的一个有趣的问题是,只有当 Jito-Solana 领导者生成区块时,才会处理 Jito Bundles。这意味着如果 JITO 不是提议者,则 tx 可能会被恢复,尽管在本次审计时 JITO 的网络权益为 91.69%

根据 JITO 官方 repo 中的示例,使用以下检查。

这将确保每次 JITO 是区块生产者时发送 bundle。

Jito 的 Github

要点:

  • 在去中心化的世界中,后端系统扮演着重要的角色,无论是桥梁还是事件处理系统。应该对这些系统进行适当的审计,以发现逻辑和实现问题。
  • JITO 是 Solana 中的主要 bundler,根据 JITO,可以通过 JITO 为单个执行捆绑 5 个 txs,请注意这一点。
  • 通过 JITO 发送 TX 之前,请查看代码中是否有检查 JITO 是否是下一个提议者。
  • 原文链接: medium.com/@mahitman1/le...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
mahitman1
mahitman1
江湖只有他的大名,没有他的介绍。